loom-spec 0.2.0 → 0.4.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/dist/cli/exportHtml.d.ts +25 -0
- package/dist/cli/exportHtml.js +201 -0
- package/dist/cli/exportHtml.js.map +1 -0
- package/dist/cli/importTrace.d.ts +15 -0
- package/dist/cli/importTrace.js +188 -0
- package/dist/cli/importTrace.js.map +1 -0
- package/dist/cli/index.js +81 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/server/exportConfig.d.ts +42 -0
- package/dist/server/exportConfig.js +88 -0
- package/dist/server/exportConfig.js.map +1 -0
- package/dist/server/exportFilter.d.ts +59 -0
- package/dist/server/exportFilter.js +172 -0
- package/dist/server/exportFilter.js.map +1 -0
- package/dist/server/otel.d.ts +32 -0
- package/dist/server/otel.js +98 -0
- package/dist/server/otel.js.map +1 -0
- package/dist/view/assets/TimelineView-CsYV15RD.js +16 -0
- package/dist/view/assets/index-CqF2JC9l.js +210 -0
- package/dist/view/assets/index-CvyHnPjR.css +1 -0
- package/dist/view/index.html +2 -2
- package/dist/{view/assets/index-B18EbiQt.css → view-export/assets/bundle.css} +1 -1
- package/dist/view-export/assets/bundle.js +225 -0
- package/dist/view-export/index.html +24 -0
- package/package.json +3 -2
- package/templates/.claude/skills/loom-spec/SKILL.md +111 -0
- package/dist/view/assets/index-DAM9J2qS.js +0 -225
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ExportHtmlArgs {
|
|
2
|
+
/** Output file path. Default: "loom.html" in cwd. */
|
|
3
|
+
out: string;
|
|
4
|
+
/** Working directory root (walked up to find .loom/). */
|
|
5
|
+
root: string;
|
|
6
|
+
/** If set, only this diagram (plus any timelines that reference it). */
|
|
7
|
+
diagram?: string;
|
|
8
|
+
/** Skip all timelines. Useful for manuals where the static topology is
|
|
9
|
+
* the whole story. */
|
|
10
|
+
noTimelines: boolean;
|
|
11
|
+
/** Only export nodes carrying at least one of these tags. */
|
|
12
|
+
includeTags?: string[];
|
|
13
|
+
/** Drop nodes carrying any of these tags. */
|
|
14
|
+
excludeTags?: string[];
|
|
15
|
+
/** Named bundle from .loom/exports.json. Settings from the bundle become
|
|
16
|
+
* defaults; explicit CLI flags override. */
|
|
17
|
+
bundle?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Used by the bundle-merge logic to tell "user passed --out" from "we are
|
|
21
|
+
* using the default". Exported so the CLI dispatcher can default-init the
|
|
22
|
+
* field once and stay consistent with this module's notion of "default".
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_OUT = "loom.html";
|
|
25
|
+
export declare function runExportHtml(args: ExportHtmlArgs): Promise<void>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { readFile, writeFile, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { findLoomRoot } from "../server/findLoomRoot.js";
|
|
5
|
+
import { listDiagrams, readDiagram, listTimelines, readTimeline, readNodeTypes, } from "../server/fileOps.js";
|
|
6
|
+
import { applyFilter } from "../server/exportFilter.js";
|
|
7
|
+
import { loadExportsConfig } from "../server/exportConfig.js";
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
async function fileExists(p) {
|
|
10
|
+
try {
|
|
11
|
+
await stat(p);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Locate the export-mode view bundle. Tries both layouts:
|
|
20
|
+
* - Production: alongside cli/ in dist/ → ../view-export/
|
|
21
|
+
* - Dev (tsx running from src/cli/): ../../dist/view-export/
|
|
22
|
+
*/
|
|
23
|
+
async function findExportBundle() {
|
|
24
|
+
const candidates = [
|
|
25
|
+
resolve(here, "../view-export"),
|
|
26
|
+
resolve(here, "../../dist/view-export"),
|
|
27
|
+
];
|
|
28
|
+
for (const c of candidates) {
|
|
29
|
+
if (await fileExists(resolve(c, "index.html")))
|
|
30
|
+
return c;
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Could not find the export view bundle. Looked in:\n - ${candidates.join("\n - ")}\n` +
|
|
33
|
+
`Did you run 'pnpm build' (or 'pnpm build:export') to produce dist/view-export?`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Used by the bundle-merge logic to tell "user passed --out" from "we are
|
|
37
|
+
* using the default". Exported so the CLI dispatcher can default-init the
|
|
38
|
+
* field once and stay consistent with this module's notion of "default".
|
|
39
|
+
*/
|
|
40
|
+
export const DEFAULT_OUT = "loom.html";
|
|
41
|
+
async function loadAllData(args) {
|
|
42
|
+
const loomRoot = await findLoomRoot(args.root);
|
|
43
|
+
const nodeTypes = await readNodeTypes(loomRoot.loomPath);
|
|
44
|
+
// Diagrams — either one specific or all
|
|
45
|
+
const diagrams = {};
|
|
46
|
+
if (args.diagram) {
|
|
47
|
+
diagrams[args.diagram] = await readDiagram(loomRoot.loomPath, args.diagram);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const summaries = await listDiagrams(loomRoot.loomPath);
|
|
51
|
+
for (const s of summaries) {
|
|
52
|
+
diagrams[s.id] = await readDiagram(loomRoot.loomPath, s.id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Timelines — include those that reference at least one diagram we exported
|
|
56
|
+
const timelines = {};
|
|
57
|
+
if (!args.noTimelines) {
|
|
58
|
+
const summaries = await listTimelines(loomRoot.loomPath);
|
|
59
|
+
for (const s of summaries) {
|
|
60
|
+
if (!(s.diagram in diagrams))
|
|
61
|
+
continue;
|
|
62
|
+
timelines[s.id] = await readTimeline(loomRoot.loomPath, s.id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
generatedAt: new Date().toISOString(),
|
|
67
|
+
diagrams,
|
|
68
|
+
timelines,
|
|
69
|
+
nodeTypes,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Inline the view bundle (HTML + CSS + JS) and the exported data into a
|
|
74
|
+
* single self-contained HTML string.
|
|
75
|
+
*
|
|
76
|
+
* The export bundle's index.html looks roughly like:
|
|
77
|
+
* <html><head><link rel="stylesheet" href="/assets/bundle.css">
|
|
78
|
+
* <script type="module" crossorigin src="/assets/bundle.js"></script>
|
|
79
|
+
* </head><body><div id="root"></div></body></html>
|
|
80
|
+
*
|
|
81
|
+
* We rewrite the link/script tags to inline contents and inject a
|
|
82
|
+
* <script>window.__LOOM_DATA__ = {...}</script> before the bundle.
|
|
83
|
+
*/
|
|
84
|
+
function buildHtml(bundleHtml, bundleCss, bundleJs, data, meta) {
|
|
85
|
+
// Sanitize the JSON for inlining inside a <script> tag.
|
|
86
|
+
// Replace </script> sequences and <!-- inside string values so they can't
|
|
87
|
+
// close the surrounding script element or confuse parsers.
|
|
88
|
+
const safeJson = JSON.stringify(data)
|
|
89
|
+
.replace(/<\/script/gi, "<\\/script")
|
|
90
|
+
.replace(/<!--/g, "<\\!--");
|
|
91
|
+
const inlineDataTag = `<script>window.__LOOM_DATA__ = ${safeJson};</script>`;
|
|
92
|
+
const inlineCssTag = `<style>${bundleCss}</style>`;
|
|
93
|
+
const inlineJsTag = `<script type="module">${bundleJs}</script>`;
|
|
94
|
+
let out = bundleHtml;
|
|
95
|
+
// CRITICAL: pass replacement as a function rather than a string. With a
|
|
96
|
+
// string second arg, JS treats `$$` as a literal `$`, which mangles any
|
|
97
|
+
// `$$typeof` / `$&` / `$'` in the JS bundle (React's reconciler uses
|
|
98
|
+
// `$$typeof` heavily). Function form bypasses the pattern processing.
|
|
99
|
+
out = out.replace(/<link\s+rel=["']stylesheet["'][^>]*>/i, () => inlineCssTag);
|
|
100
|
+
out = out.replace(/<script\s+type=["']module["'][^>]*><\/script>/i, () => inlineDataTag + inlineJsTag);
|
|
101
|
+
out = out.replace(/<title>[^<]*<\/title>/i, () => `<title>loom-spec export · ${escapeHtml(meta.sourceRoot)}</title>`);
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function escapeHtml(s) {
|
|
105
|
+
return s
|
|
106
|
+
.replace(/&/g, "&")
|
|
107
|
+
.replace(/</g, "<")
|
|
108
|
+
.replace(/>/g, ">")
|
|
109
|
+
.replace(/"/g, """);
|
|
110
|
+
}
|
|
111
|
+
export async function runExportHtml(args) {
|
|
112
|
+
// If a bundle name was given, resolve its settings from .loom/exports.json
|
|
113
|
+
// first. Explicit CLI args take precedence (for ad-hoc overrides).
|
|
114
|
+
let effective = args;
|
|
115
|
+
if (args.bundle) {
|
|
116
|
+
const loomRoot = await findLoomRoot(args.root);
|
|
117
|
+
const config = await loadExportsConfig(loomRoot.loomPath);
|
|
118
|
+
if (!config) {
|
|
119
|
+
console.error(`export-html: no .loom/exports.json found, but '${args.bundle}' looks like a named bundle.\n` +
|
|
120
|
+
` Create .loom/exports.json with an 'exports.${args.bundle}' entry, or pass ad-hoc flags instead.`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const bundle = config.exports[args.bundle];
|
|
124
|
+
if (!bundle) {
|
|
125
|
+
const available = Object.keys(config.exports).sort().join(", ") || "(none)";
|
|
126
|
+
console.error(`export-html: bundle '${args.bundle}' not found in .loom/exports.json. ` +
|
|
127
|
+
`Available: ${available}.`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
// Merge: explicit CLI args (in `args`) override config-supplied values.
|
|
131
|
+
effective = {
|
|
132
|
+
...args,
|
|
133
|
+
out: args.out !== DEFAULT_OUT ? args.out : bundle.out ?? args.out,
|
|
134
|
+
diagram: args.diagram ?? bundle.diagram,
|
|
135
|
+
noTimelines: args.noTimelines || (bundle.noTimelines ?? false),
|
|
136
|
+
includeTags: args.includeTags ?? bundle.includeTags,
|
|
137
|
+
excludeTags: args.excludeTags ?? bundle.excludeTags,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const bundleDir = await findExportBundle();
|
|
141
|
+
const [bundleHtml, bundleCss, bundleJs] = await Promise.all([
|
|
142
|
+
readFile(resolve(bundleDir, "index.html"), "utf8"),
|
|
143
|
+
readFile(resolve(bundleDir, "assets/bundle.css"), "utf8"),
|
|
144
|
+
readFile(resolve(bundleDir, "assets/bundle.js"), "utf8"),
|
|
145
|
+
]);
|
|
146
|
+
const data = await loadAllData(effective);
|
|
147
|
+
if (Object.keys(data.diagrams).length === 0) {
|
|
148
|
+
console.error("export-html: no diagrams found — refusing to write an empty export.");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
const filterSpec = {
|
|
152
|
+
includeTags: effective.includeTags,
|
|
153
|
+
excludeTags: effective.excludeTags,
|
|
154
|
+
};
|
|
155
|
+
const { payload: filtered, summary } = applyFilter(data, filterSpec);
|
|
156
|
+
// After filtering, if everything's gone, bail. Better to fail loud than
|
|
157
|
+
// silently emit a blank HTML the user will wonder about.
|
|
158
|
+
const survivingNodes = Object.values(filtered.diagrams).reduce((n, d) => n + d.nodes.length, 0);
|
|
159
|
+
if (survivingNodes === 0) {
|
|
160
|
+
console.error("export-html: filter matched 0 nodes — refusing to write an empty export.");
|
|
161
|
+
if (filterSpec.includeTags?.length || filterSpec.excludeTags?.length) {
|
|
162
|
+
console.error(` filter was: include=[${(filterSpec.includeTags ?? []).join(", ")}], ` +
|
|
163
|
+
`exclude=[${(filterSpec.excludeTags ?? []).join(", ")}]`);
|
|
164
|
+
}
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const finalData = {
|
|
168
|
+
...data,
|
|
169
|
+
diagrams: filtered.diagrams,
|
|
170
|
+
timelines: filtered.timelines,
|
|
171
|
+
};
|
|
172
|
+
const html = buildHtml(bundleHtml, bundleCss, bundleJs, finalData, {
|
|
173
|
+
sourceRoot: effective.root,
|
|
174
|
+
});
|
|
175
|
+
const outPath = resolve(effective.out);
|
|
176
|
+
await writeFile(outPath, html, "utf8");
|
|
177
|
+
const sizeKb = Math.round(Buffer.byteLength(html, "utf8") / 1024);
|
|
178
|
+
const diagramCount = Object.keys(finalData.diagrams).length;
|
|
179
|
+
const timelineCount = Object.keys(finalData.timelines).length;
|
|
180
|
+
console.log(`Wrote ${outPath} (${sizeKb} kB): ` +
|
|
181
|
+
`${diagramCount} diagram${diagramCount === 1 ? "" : "s"}, ` +
|
|
182
|
+
`${timelineCount} timeline${timelineCount === 1 ? "" : "s"}.`);
|
|
183
|
+
const droppedParts = [];
|
|
184
|
+
if (summary.nodesDropped > 0)
|
|
185
|
+
droppedParts.push(`${summary.nodesDropped} nodes`);
|
|
186
|
+
if (summary.edgesDropped > 0)
|
|
187
|
+
droppedParts.push(`${summary.edgesDropped} edges`);
|
|
188
|
+
if (summary.groupsDropped > 0)
|
|
189
|
+
droppedParts.push(`${summary.groupsDropped} groups`);
|
|
190
|
+
if (summary.eventsDropped > 0)
|
|
191
|
+
droppedParts.push(`${summary.eventsDropped} events`);
|
|
192
|
+
if (summary.timelinesDropped > 0)
|
|
193
|
+
droppedParts.push(`${summary.timelinesDropped} timelines`);
|
|
194
|
+
if (summary.drillDownsCleared > 0)
|
|
195
|
+
droppedParts.push(`${summary.drillDownsCleared} drill-down refs`);
|
|
196
|
+
if (droppedParts.length > 0) {
|
|
197
|
+
console.log(`Filter dropped: ${droppedParts.join(", ")}.`);
|
|
198
|
+
}
|
|
199
|
+
console.log(`Open it directly in a browser, or drop it into any docs / wiki / GitHub-Pages site.`);
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=exportHtml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportHtml.js","sourceRoot":"","sources":["../../src/cli/exportHtml.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,8 @@ 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";
|
|
8
|
+
import { runExportHtml, DEFAULT_OUT as EXPORT_DEFAULT_OUT } from "./exportHtml.js";
|
|
7
9
|
const HELP = `loom-spec — node-based architecture spec for your repo
|
|
8
10
|
|
|
9
11
|
Usage:
|
|
@@ -27,17 +29,42 @@ Usage:
|
|
|
27
29
|
pre-commit hook.
|
|
28
30
|
|
|
29
31
|
loom-spec mcp [--root <dir>]
|
|
30
|
-
Start a Model Context Protocol server on stdio. Exposes
|
|
31
|
-
loom_list_diagrams,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
Start a Model Context Protocol server on stdio. Exposes 15 tools
|
|
33
|
+
for diagrams (loom_list_diagrams, loom_add_node, loom_add_edge, …)
|
|
34
|
+
and timelines (loom_list_timelines, loom_add_event, …) — wire it
|
|
35
|
+
into Claude Code's mcp.json (or any MCP-capable client).
|
|
36
|
+
|
|
37
|
+
loom-spec export-html [<bundle-name>] [--out <path>] [--diagram <id>]
|
|
38
|
+
[--no-timelines]
|
|
39
|
+
[--include-tag <comma-list>] [--exclude-tag <comma-list>]
|
|
40
|
+
[--root <dir>]
|
|
41
|
+
Build a standalone interactive HTML file from the spec — pan/zoom,
|
|
42
|
+
drill-down, switch diagrams, play timelines. Single self-contained
|
|
43
|
+
file, no server needed. Drop it into a manual, wiki, GitHub Pages
|
|
44
|
+
site, anywhere. Output defaults to ./loom.html. With --diagram, only
|
|
45
|
+
that diagram (and timelines referencing it) ship. With
|
|
46
|
+
--include-tag / --exclude-tag, only nodes whose 'tags' match
|
|
47
|
+
survive — edges, groups, drill-down chevrons, timeline events that
|
|
48
|
+
point at dropped nodes are cleaned up automatically.
|
|
49
|
+
|
|
50
|
+
Pass a <bundle-name> as the first positional arg to read settings
|
|
51
|
+
from a named bundle in .loom/exports.json. Explicit flags override
|
|
52
|
+
the bundle's values.
|
|
53
|
+
|
|
54
|
+
loom-spec import-trace <trace.json> --as <timeline-id> --diagram <diagram-id>
|
|
55
|
+
[--map <mapping.json>] [--append] [--root <dir>]
|
|
56
|
+
Read an OpenTelemetry OTLP-JSON trace and generate a timeline that
|
|
57
|
+
mirrors the actual spans on the named diagram. Each span becomes
|
|
58
|
+
an event; parent/child relationships preserve as triggered_by;
|
|
59
|
+
service.name becomes the track. Spans whose service or name can't
|
|
60
|
+
be matched to a node are skipped — pass --map to override.
|
|
35
61
|
|
|
36
62
|
loom-spec --help
|
|
37
63
|
Print this help.
|
|
38
64
|
`;
|
|
39
65
|
function parseFlags(argv) {
|
|
40
66
|
const flags = {};
|
|
67
|
+
const positional = [];
|
|
41
68
|
for (let i = 0; i < argv.length; i++) {
|
|
42
69
|
const a = argv[i];
|
|
43
70
|
if (!a)
|
|
@@ -53,12 +80,15 @@ function parseFlags(argv) {
|
|
|
53
80
|
flags[key] = true;
|
|
54
81
|
}
|
|
55
82
|
}
|
|
83
|
+
else {
|
|
84
|
+
positional.push(a);
|
|
85
|
+
}
|
|
56
86
|
}
|
|
57
|
-
return flags;
|
|
87
|
+
return { flags, positional };
|
|
58
88
|
}
|
|
59
89
|
async function main() {
|
|
60
90
|
const [, , subcommand, ...rest] = process.argv;
|
|
61
|
-
const flags = parseFlags(rest);
|
|
91
|
+
const { flags, positional } = parseFlags(rest);
|
|
62
92
|
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
63
93
|
console.log(HELP);
|
|
64
94
|
return;
|
|
@@ -98,6 +128,50 @@ async function main() {
|
|
|
98
128
|
});
|
|
99
129
|
return;
|
|
100
130
|
}
|
|
131
|
+
if (subcommand === "export-html") {
|
|
132
|
+
const splitTags = (v) => {
|
|
133
|
+
if (typeof v !== "string" || !v.trim())
|
|
134
|
+
return undefined;
|
|
135
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
136
|
+
};
|
|
137
|
+
// First positional arg, if any, is a named-bundle key from
|
|
138
|
+
// .loom/exports.json (e.g. `loom-spec export-html user-manual`).
|
|
139
|
+
const bundle = positional[0];
|
|
140
|
+
await runExportHtml({
|
|
141
|
+
out: flags.out ?? EXPORT_DEFAULT_OUT,
|
|
142
|
+
root: flags.root ?? process.cwd(),
|
|
143
|
+
diagram: typeof flags.diagram === "string" ? flags.diagram : undefined,
|
|
144
|
+
noTimelines: Boolean(flags["no-timelines"]),
|
|
145
|
+
includeTags: splitTags(flags["include-tag"]),
|
|
146
|
+
excludeTags: splitTags(flags["exclude-tag"]),
|
|
147
|
+
bundle,
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (subcommand === "import-trace") {
|
|
152
|
+
// Positional arg: the trace file path.
|
|
153
|
+
const trace = positional[0];
|
|
154
|
+
if (!trace) {
|
|
155
|
+
console.error("import-trace: missing trace file path");
|
|
156
|
+
console.log(HELP);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
const asId = flags.as;
|
|
160
|
+
const diagramId = flags.diagram;
|
|
161
|
+
if (!asId || !diagramId) {
|
|
162
|
+
console.error("import-trace: --as <timeline-id> and --diagram <diagram-id> are required");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
await runImportTrace({
|
|
166
|
+
trace,
|
|
167
|
+
asId,
|
|
168
|
+
diagramId,
|
|
169
|
+
map: typeof flags.map === "string" ? flags.map : undefined,
|
|
170
|
+
append: Boolean(flags.append),
|
|
171
|
+
root: flags.root ?? process.cwd(),
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
101
175
|
console.error(`unknown subcommand: ${subcommand}\n`);
|
|
102
176
|
console.log(HELP);
|
|
103
177
|
process.exit(1);
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional `.loom/exports.json` config — defines named export bundles so
|
|
3
|
+
* teams can keep export settings versioned in the repo instead of in shell
|
|
4
|
+
* commands. A new contributor runs `loom-spec export-html user-manual`
|
|
5
|
+
* and gets the same output as everyone else without having to remember
|
|
6
|
+
* which flags to pass.
|
|
7
|
+
*
|
|
8
|
+
* Shape:
|
|
9
|
+
*
|
|
10
|
+
* {
|
|
11
|
+
* "exports": {
|
|
12
|
+
* "user-manual": {
|
|
13
|
+
* "include-tags": ["public"],
|
|
14
|
+
* "exclude-tags": ["wip"],
|
|
15
|
+
* "diagram": "overview", // optional, single-diagram mode
|
|
16
|
+
* "no-timelines": true, // default false
|
|
17
|
+
* "out": "docs/architecture.html" // optional, default loom.html
|
|
18
|
+
* },
|
|
19
|
+
* "ops-runbook": {
|
|
20
|
+
* "include-tags": ["ops"]
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* All fields are optional. Unknown keys are tolerated (forward-compat).
|
|
26
|
+
* CLI flags override config values when both are present.
|
|
27
|
+
*/
|
|
28
|
+
export interface NamedExport {
|
|
29
|
+
includeTags?: string[];
|
|
30
|
+
excludeTags?: string[];
|
|
31
|
+
diagram?: string;
|
|
32
|
+
noTimelines?: boolean;
|
|
33
|
+
out?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ExportsFile {
|
|
36
|
+
exports: Record<string, NamedExport>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Try to read `.loom/exports.json`. Returns null when the file is missing
|
|
40
|
+
* (that's a normal, non-error state — exports config is opt-in).
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadExportsConfig(loomPath: string): Promise<ExportsFile | null>;
|