loom-spec 0.1.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/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +96 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +5 -0
- package/dist/cli/init.js +69 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp.d.ts +4 -0
- package/dist/cli/mcp.js +17 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/validate.d.ts +5 -0
- package/dist/cli/validate.js +77 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/cli/view.d.ts +6 -0
- package/dist/cli/view.js +37 -0
- package/dist/cli/view.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +293 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/server/app.d.ts +9 -0
- package/dist/server/app.js +135 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/drift.d.ts +29 -0
- package/dist/server/drift.js +128 -0
- package/dist/server/drift.js.map +1 -0
- package/dist/server/fileOps.d.ts +13 -0
- package/dist/server/fileOps.js +56 -0
- package/dist/server/fileOps.js.map +1 -0
- package/dist/server/findLoomRoot.d.ts +9 -0
- package/dist/server/findLoomRoot.js +28 -0
- package/dist/server/findLoomRoot.js.map +1 -0
- package/dist/server/watch.d.ts +29 -0
- package/dist/server/watch.js +83 -0
- package/dist/server/watch.js.map +1 -0
- package/dist/types/diagram.d.ts +99 -0
- package/dist/types/diagram.js +7 -0
- package/dist/types/diagram.js.map +1 -0
- package/dist/types/node-types.d.ts +55 -0
- package/dist/types/node-types.js +7 -0
- package/dist/types/node-types.js.map +1 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.js +47 -0
- package/dist/validate.js.map +1 -0
- package/dist/view/assets/index-Cst6HUW5.css +1 -0
- package/dist/view/assets/index-jlp2cU4j.js +205 -0
- package/dist/view/index.html +24 -0
- package/package.json +83 -0
- package/schema/diagram.schema.json +173 -0
- package/schema/node-types.schema.json +116 -0
- package/templates/.claude/skills/loom-spec/SKILL.md +278 -0
- package/templates/.loom/README.md +25 -0
- package/templates/.loom/diagrams/overview.flow.json +8 -0
- package/templates/.loom/node-types.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 René Jesser
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# loom-spec
|
|
2
|
+
|
|
3
|
+
> A node-based architecture spec that lives in your repo. AI-readable, AI-writable, git-diffable.
|
|
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).
|
|
6
|
+
|
|
7
|
+
It's a spec layer, not an execution layer. The nodes don't run — they describe.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# No global install needed — use via npx
|
|
13
|
+
npx loom-spec init
|
|
14
|
+
npx loom-spec view
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or as a dev dependency:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev loom-spec
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then in `package.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"scripts": {
|
|
28
|
+
"loom": "loom-spec view"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `loom-spec init [--path <dir>] [--force]`
|
|
36
|
+
|
|
37
|
+
Scaffolds the spec directory and the agent skill in the target project (defaults to current working directory).
|
|
38
|
+
|
|
39
|
+
Writes:
|
|
40
|
+
|
|
41
|
+
- `.loom/README.md` — explains the directory to humans
|
|
42
|
+
- `.loom/node-types.json` — five default types (`ui`, `service`, `data`, `event`, `external`)
|
|
43
|
+
- `.loom/diagrams/overview.flow.json` — empty starter diagram
|
|
44
|
+
- `.claude/skills/loom-spec/SKILL.md` — tells Claude Code (and any tool following the Agent Skills standard) when and how to maintain the spec
|
|
45
|
+
|
|
46
|
+
Refuses to overwrite an existing `.loom/` unless `--force`.
|
|
47
|
+
|
|
48
|
+
### `loom-spec view [--root <dir>] [--port <n>]`
|
|
49
|
+
|
|
50
|
+
Starts a local browser editor. Walks up from `--root` (default: cwd) to find the nearest `.loom/`. Opens on port 7777 by default.
|
|
51
|
+
|
|
52
|
+
### `loom-spec validate [--root <dir>] [--json]`
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
loom-spec validate
|
|
58
|
+
# ✗ overview.flow.json — Todo App
|
|
59
|
+
# 5 nodes, 5 edges, 3 code refs checked
|
|
60
|
+
# ✗ todo-api → src/server/routes/todos.ts#todoRouter: symbol 'todoRouter' not found
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `loom-spec mcp [--root <dir>]`
|
|
64
|
+
|
|
65
|
+
Starts a **Model Context Protocol** server on stdio. Wire it into Claude Code (or any MCP-capable agent) via the host's `mcp.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"loom-spec": {
|
|
71
|
+
"command": "npx",
|
|
72
|
+
"args": ["loom-spec", "mcp"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The server exposes semantic tools that validate against the schema before writing, more token-efficient than re-reading and re-writing the JSON on every change:
|
|
79
|
+
|
|
80
|
+
- `loom_list_diagrams`, `loom_read_diagram`, `loom_read_node_types`
|
|
81
|
+
- `loom_add_node`, `loom_update_node`, `loom_mark_stale`, `loom_delete_node`
|
|
82
|
+
- `loom_add_edge`, `loom_delete_edge`
|
|
83
|
+
- `loom_validate` (same drift check as the CLI)
|
|
84
|
+
|
|
85
|
+
In the editor you can:
|
|
86
|
+
|
|
87
|
+
- Drag nodes; edits debounce and write to disk within ~500ms
|
|
88
|
+
- Click a node or edge to inspect and edit fields, code refs, tags, type-specific properties
|
|
89
|
+
- Drag from a node's right handle to another node to create an edge
|
|
90
|
+
- Use the "+ Add" menu in the top bar to add a new node by type
|
|
91
|
+
- Use the diagram switcher (top-left dropdown) to navigate between diagrams or create new ones
|
|
92
|
+
- Use the "Drill into" chevron on any node or group with `drill_down` set to jump to a sub-diagram
|
|
93
|
+
- Toggle light/dark theme; preference is persisted
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
## File format
|
|
98
|
+
|
|
99
|
+
### `.loom/node-types.json`
|
|
100
|
+
|
|
101
|
+
Defines the available types for nodes in this project. Each type has a label, color, lucide icon name, optional typed fields (string / number / boolean / enum / markdown / code-ref / array), and optional named ports for typed connections.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"types": {
|
|
106
|
+
"service": {
|
|
107
|
+
"label": "Service",
|
|
108
|
+
"color": "#34d399",
|
|
109
|
+
"icon": "server",
|
|
110
|
+
"fields": [
|
|
111
|
+
{ "name": "language", "type": "string" },
|
|
112
|
+
{ "name": "runtime", "type": "string" }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `.loom/diagrams/*.flow.json`
|
|
120
|
+
|
|
121
|
+
Each diagram is `{ nodes, edges, groups }`. Nodes:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"id": "todo-api",
|
|
126
|
+
"type": "service",
|
|
127
|
+
"label": "Todo API",
|
|
128
|
+
"description": "REST endpoints for todos.",
|
|
129
|
+
"position": { "x": 400, "y": 160 },
|
|
130
|
+
"status": "implemented",
|
|
131
|
+
"code_refs": [
|
|
132
|
+
{ "path": "src/server/routes/todos.ts", "symbol": "todoRouter" }
|
|
133
|
+
],
|
|
134
|
+
"properties": { "language": "typescript", "runtime": "node" },
|
|
135
|
+
"tags": ["public"]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Edges:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"id": "e1",
|
|
144
|
+
"from": "todo-list-view",
|
|
145
|
+
"to": "todo-api",
|
|
146
|
+
"kind": "request",
|
|
147
|
+
"label": "fetch / mutate"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Status enum: `planned`, `implemented`, `stale`, `deprecated`.
|
|
152
|
+
Edge kinds: `request`, `event`, `data-read`, `data-write`, `signal`, `dependency`, `control`.
|
|
153
|
+
|
|
154
|
+
Full JSON Schemas ship with the package — see `schema/diagram.schema.json` and `schema/node-types.schema.json`.
|
|
155
|
+
|
|
156
|
+
## How AI agents use it
|
|
157
|
+
|
|
158
|
+
`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.
|
|
159
|
+
|
|
160
|
+
The skill tells the agent to:
|
|
161
|
+
|
|
162
|
+
1. Read the relevant diagram before implementing.
|
|
163
|
+
2. Add new components as `status: planned`, flip to `implemented` after the code lands.
|
|
164
|
+
3. Always populate `code_refs` — prefer `symbol` over `lines` because symbols survive refactors.
|
|
165
|
+
4. On code deletion, set `status: stale` rather than removing the node — humans review.
|
|
166
|
+
5. Don't invent node types — extend `node-types.json` first.
|
|
167
|
+
|
|
168
|
+
You can extend the skill with project-specific rules; it's committed in your repo.
|
|
169
|
+
|
|
170
|
+
## Tech
|
|
171
|
+
|
|
172
|
+
- TypeScript end-to-end
|
|
173
|
+
- [Hono](https://hono.dev/) server (Node), serves the SPA and the REST/SSE API on a single port
|
|
174
|
+
- [React Flow / xyflow](https://reactflow.dev/) for the canvas
|
|
175
|
+
- [Vite](https://vitejs.dev/) for the SPA build
|
|
176
|
+
- [Ajv](https://ajv.js.org/) for runtime schema validation
|
|
177
|
+
- [Chokidar](https://github.com/paulmillr/chokidar) for filesystem watching
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInit } from "./init.js";
|
|
3
|
+
import { runView } from "./view.js";
|
|
4
|
+
import { runValidate } from "./validate.js";
|
|
5
|
+
import { runMcp } from "./mcp.js";
|
|
6
|
+
const HELP = `loom-spec — node-based architecture spec for your repo
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
loom-spec init [--path <dir>] [--force]
|
|
10
|
+
Scaffold .loom/ and .claude/skills/loom-spec/ in the target directory.
|
|
11
|
+
Defaults to current working directory.
|
|
12
|
+
|
|
13
|
+
loom-spec view [--root <dir>] [--port <n>] [--dev]
|
|
14
|
+
Start the local browser editor. Walks up from --root (default: cwd)
|
|
15
|
+
to find the nearest .loom/ directory.
|
|
16
|
+
|
|
17
|
+
loom-spec validate [--root <dir>] [--json]
|
|
18
|
+
Check every diagram for schema validity and code-ref drift
|
|
19
|
+
(missing files, missing symbols, out-of-range line refs).
|
|
20
|
+
Exits non-zero if any issue is found. Use as a CI step or
|
|
21
|
+
pre-commit hook.
|
|
22
|
+
|
|
23
|
+
loom-spec mcp [--root <dir>]
|
|
24
|
+
Start a Model Context Protocol server on stdio. Exposes
|
|
25
|
+
loom_list_diagrams, loom_read_diagram, loom_add_node,
|
|
26
|
+
loom_update_node, loom_mark_stale, loom_delete_node,
|
|
27
|
+
loom_add_edge, loom_delete_edge, loom_validate as MCP tools.
|
|
28
|
+
Wire it into Claude Code's mcp.json (or any MCP-capable client).
|
|
29
|
+
|
|
30
|
+
loom-spec --help
|
|
31
|
+
Print this help.
|
|
32
|
+
`;
|
|
33
|
+
function parseFlags(argv) {
|
|
34
|
+
const flags = {};
|
|
35
|
+
for (let i = 0; i < argv.length; i++) {
|
|
36
|
+
const a = argv[i];
|
|
37
|
+
if (!a)
|
|
38
|
+
continue;
|
|
39
|
+
if (a.startsWith("--")) {
|
|
40
|
+
const key = a.slice(2);
|
|
41
|
+
const next = argv[i + 1];
|
|
42
|
+
if (next && !next.startsWith("--")) {
|
|
43
|
+
flags[key] = next;
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
flags[key] = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return flags;
|
|
52
|
+
}
|
|
53
|
+
async function main() {
|
|
54
|
+
const [, , subcommand, ...rest] = process.argv;
|
|
55
|
+
const flags = parseFlags(rest);
|
|
56
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
57
|
+
console.log(HELP);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (subcommand === "init") {
|
|
61
|
+
await runInit({
|
|
62
|
+
path: flags.path ?? process.cwd(),
|
|
63
|
+
force: Boolean(flags.force),
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (subcommand === "view") {
|
|
68
|
+
await runView({
|
|
69
|
+
root: flags.root ?? process.cwd(),
|
|
70
|
+
port: flags.port ? Number(flags.port) : 7777,
|
|
71
|
+
dev: Boolean(flags.dev),
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (subcommand === "validate") {
|
|
76
|
+
await runValidate({
|
|
77
|
+
root: flags.root ?? process.cwd(),
|
|
78
|
+
json: Boolean(flags.json),
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (subcommand === "mcp") {
|
|
83
|
+
await runMcp({
|
|
84
|
+
root: flags.root ?? process.cwd(),
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.error(`unknown subcommand: ${subcommand}\n`);
|
|
89
|
+
console.log(HELP);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
main().catch((e) => {
|
|
93
|
+
console.error(e);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, dirname, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
// Templates ship alongside dist/ in the package: dist/cli/init.js → ../../templates/
|
|
6
|
+
const templatesRoot = resolve(here, "../../templates");
|
|
7
|
+
async function copyDir(srcDir, destDir, force) {
|
|
8
|
+
const written = [];
|
|
9
|
+
await mkdir(destDir, { recursive: true });
|
|
10
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const src = join(srcDir, entry.name);
|
|
13
|
+
const dest = join(destDir, entry.name);
|
|
14
|
+
if (entry.isDirectory()) {
|
|
15
|
+
written.push(...(await copyDir(src, dest, force)));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
let exists = false;
|
|
19
|
+
try {
|
|
20
|
+
await stat(dest);
|
|
21
|
+
exists = true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// doesn't exist
|
|
25
|
+
}
|
|
26
|
+
if (exists && !force) {
|
|
27
|
+
console.log(` skip (exists): ${dest}`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const contents = await readFile(src);
|
|
31
|
+
await writeFile(dest, contents);
|
|
32
|
+
written.push(dest);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return written;
|
|
36
|
+
}
|
|
37
|
+
export async function runInit(args) {
|
|
38
|
+
const target = resolve(args.path);
|
|
39
|
+
console.log(`Initializing loom-spec in ${target}`);
|
|
40
|
+
// Pre-flight: warn if .loom already exists
|
|
41
|
+
let existing = false;
|
|
42
|
+
try {
|
|
43
|
+
await stat(resolve(target, ".loom"));
|
|
44
|
+
existing = true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// fresh install
|
|
48
|
+
}
|
|
49
|
+
if (existing && !args.force) {
|
|
50
|
+
console.error("error: .loom/ already exists. Use --force to overwrite specific files (existing files are preserved unless overwritten).");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const written = await copyDir(templatesRoot, target, args.force);
|
|
54
|
+
console.log();
|
|
55
|
+
if (written.length === 0) {
|
|
56
|
+
console.log("Nothing to write. Use --force to overwrite existing files.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log(`Created ${written.length} file(s):`);
|
|
60
|
+
for (const f of written) {
|
|
61
|
+
console.log(` ${relative(target, f)}`);
|
|
62
|
+
}
|
|
63
|
+
console.log();
|
|
64
|
+
console.log("Next steps:");
|
|
65
|
+
console.log(" 1. Run `npx loom-spec view` to open the editor.");
|
|
66
|
+
console.log(" 2. Edit `.loom/node-types.json` to add project-specific node types.");
|
|
67
|
+
console.log(" 3. Start populating `.loom/diagrams/overview.flow.json`.");
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { findLoomRoot } from "../server/findLoomRoot.js";
|
|
2
|
+
import { startMcpServer } from "../mcp/server.js";
|
|
3
|
+
export async function runMcp(args) {
|
|
4
|
+
let loomRoot;
|
|
5
|
+
try {
|
|
6
|
+
loomRoot = await findLoomRoot(args.root);
|
|
7
|
+
}
|
|
8
|
+
catch (e) {
|
|
9
|
+
// MCP servers communicate over stdio — print errors to stderr only.
|
|
10
|
+
console.error(`loom-spec mcp: ${e.message}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// Hint goes to stderr (stdout is the MCP transport)
|
|
14
|
+
console.error(`loom-spec mcp: serving ${loomRoot.loomPath}`);
|
|
15
|
+
await startMcpServer(loomRoot);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/cli/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMlD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAa;IACxC,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,oEAAoE;QACpE,OAAO,CAAC,KAAK,CAAC,kBAAmB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,oDAAoD;IACpD,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7D,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { findLoomRoot } from "../server/findLoomRoot.js";
|
|
2
|
+
import { runDriftCheck } from "../server/drift.js";
|
|
3
|
+
const ICON_OK = "✓";
|
|
4
|
+
const ICON_BAD = "✗";
|
|
5
|
+
const ICON_WARN = "⚠";
|
|
6
|
+
const ICON_INFO = "·";
|
|
7
|
+
function formatIssue(detail, issue) {
|
|
8
|
+
switch (issue) {
|
|
9
|
+
case "missing-file":
|
|
10
|
+
return `file not found`;
|
|
11
|
+
case "missing-symbol":
|
|
12
|
+
return detail ?? "symbol not found";
|
|
13
|
+
case "lines-out-of-range":
|
|
14
|
+
return `lines ${detail}`;
|
|
15
|
+
case "invalid-lines":
|
|
16
|
+
return `invalid line range ${detail}`;
|
|
17
|
+
default:
|
|
18
|
+
return issue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function printDiagram(d) {
|
|
22
|
+
const status = d.schemaErrors.length > 0
|
|
23
|
+
? ICON_BAD
|
|
24
|
+
: d.drift.length > 0
|
|
25
|
+
? ICON_WARN
|
|
26
|
+
: ICON_OK;
|
|
27
|
+
console.log(`${status} ${d.diagramId}.flow.json — ${d.title}`);
|
|
28
|
+
console.log(` ${d.nodeCount} nodes, ${d.edgeCount} edges, ${d.refsChecked} code refs checked${d.staleNodes > 0 ? `, ${d.staleNodes} stale` : ""}`);
|
|
29
|
+
for (const err of d.schemaErrors) {
|
|
30
|
+
console.log(` ${ICON_BAD} schema: ${err}`);
|
|
31
|
+
}
|
|
32
|
+
for (const f of d.drift) {
|
|
33
|
+
console.log(` ${ICON_BAD} ${f.nodeId} → ${f.ref.path}${f.ref.symbol ? `#${f.ref.symbol}` : ""}: ${formatIssue(f.detail, f.issue)}`);
|
|
34
|
+
}
|
|
35
|
+
if (d.schemaErrors.length === 0 && d.drift.length === 0) {
|
|
36
|
+
console.log(` ${ICON_INFO} no issues`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function runValidate(args) {
|
|
40
|
+
let loomRoot;
|
|
41
|
+
try {
|
|
42
|
+
loomRoot = await findLoomRoot(args.root);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.error(`error: ${e.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const report = await runDriftCheck(loomRoot.rootPath, loomRoot.loomPath);
|
|
49
|
+
if (args.json) {
|
|
50
|
+
console.log(JSON.stringify(report, null, 2));
|
|
51
|
+
process.exit(report.totalDrift + report.totalSchemaErrors > 0 ? 1 : 0);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`loom-spec validate`);
|
|
55
|
+
console.log(` root: ${loomRoot.rootPath}`);
|
|
56
|
+
console.log();
|
|
57
|
+
for (const d of report.perDiagram) {
|
|
58
|
+
printDiagram(d);
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
const summary = [];
|
|
62
|
+
if (report.totalSchemaErrors > 0) {
|
|
63
|
+
summary.push(`${report.totalSchemaErrors} schema error(s)`);
|
|
64
|
+
}
|
|
65
|
+
if (report.totalDrift > 0) {
|
|
66
|
+
summary.push(`${report.totalDrift} drift finding(s)`);
|
|
67
|
+
}
|
|
68
|
+
if (summary.length === 0) {
|
|
69
|
+
console.log(`${ICON_OK} All ${report.perDiagram.length} diagram(s) clean.`);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(`${ICON_BAD} ${summary.join(", ")} across ${report.perDiagram.length} diagram(s).`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAsB,MAAM,oBAAoB,CAAC;AAOvE,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,SAAS,WAAW,CAAC,MAA0B,EAAE,KAAa;IAC5D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,cAAc;YACjB,OAAO,gBAAgB,CAAC;QAC1B,KAAK,gBAAgB;YACnB,OAAO,MAAM,IAAI,kBAAkB,CAAC;QACtC,KAAK,oBAAoB;YACvB,OAAO,SAAS,MAAM,EAAE,CAAC;QAC3B,KAAK,eAAe;YAClB,OAAO,sBAAsB,MAAM,EAAE,CAAC;QACxC;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAgB;IACpC,MAAM,MAAM,GACV,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YAClB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,SAAS,gBAAgB,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,WAAW,qBAAqB,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CACvI,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,YAAY,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CACT,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CACxH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,YAAY,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAkB;IAClD,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,UAAW,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,iBAAiB,kBAAkB,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,mBAAmB,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,QAAQ,MAAM,CAAC,UAAU,CAAC,MAAM,oBAAoB,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,UAAU,CAAC,MAAM,cAAc,CAAC,CAAC;QAChG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/cli/view.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { findLoomRoot } from "../server/findLoomRoot.js";
|
|
5
|
+
import { createApp } from "../server/app.js";
|
|
6
|
+
import { LoomWatcher } from "../server/watch.js";
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
export async function runView(args) {
|
|
9
|
+
let loomRoot;
|
|
10
|
+
try {
|
|
11
|
+
loomRoot = await findLoomRoot(args.root);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
console.error(`error: ${e.message}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const watcher = new LoomWatcher(loomRoot.loomPath);
|
|
18
|
+
const app = createApp({
|
|
19
|
+
loomRoot,
|
|
20
|
+
watcher,
|
|
21
|
+
// In dev, Vite serves the SPA. Otherwise we serve the built bundle.
|
|
22
|
+
serveSpaFrom: args.dev ? undefined : resolve(here, "../view"),
|
|
23
|
+
});
|
|
24
|
+
const server = serve({ fetch: app.fetch, port: args.port }, (info) => {
|
|
25
|
+
console.log(`loom-spec: http://localhost:${info.port}`);
|
|
26
|
+
console.log(` root: ${loomRoot.rootPath}`);
|
|
27
|
+
console.log(` .loom: ${loomRoot.loomPath}`);
|
|
28
|
+
});
|
|
29
|
+
const shutdown = async () => {
|
|
30
|
+
await watcher.close();
|
|
31
|
+
server.close();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
};
|
|
34
|
+
process.on("SIGINT", shutdown);
|
|
35
|
+
process.on("SIGTERM", shutdown);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/cli/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAQjD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,UAAW,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEnD,MAAM,GAAG,GAAG,SAAS,CAAC;QACpB,QAAQ;QACR,OAAO;QACP,oEAAoE;QACpE,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;KAC9D,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;QACnE,OAAO,CAAC,GAAG,CAAC,+BAA+B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,YAAY,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,YAAY,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.0.1";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { LoomRoot } from "../server/findLoomRoot.js";
|
|
3
|
+
export declare function createMcpServer(loomRoot: LoomRoot): McpServer;
|
|
4
|
+
export declare function startMcpServer(loomRoot: LoomRoot): Promise<void>;
|