sysprom 1.13.2 → 1.15.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 +91 -31
- package/dist/src/cli/commands/infer.d.ts +2 -0
- package/dist/src/cli/commands/infer.js +206 -0
- package/dist/src/cli/commands/init.js +12 -4
- package/dist/src/cli/program.js +2 -0
- package/dist/src/cli/shared.d.ts +12 -7
- package/dist/src/cli/shared.js +60 -12
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/json-to-md.js +71 -16
- package/dist/src/mcp/server.js +91 -13
- package/dist/src/md-to-json.js +8 -4
- package/dist/src/operations/index.d.ts +4 -0
- package/dist/src/operations/index.js +5 -0
- package/dist/src/operations/infer-completeness.d.ts +414 -0
- package/dist/src/operations/infer-completeness.js +131 -0
- package/dist/src/operations/infer-derived.d.ts +375 -0
- package/dist/src/operations/infer-derived.js +158 -0
- package/dist/src/operations/infer-impact.d.ts +1246 -0
- package/dist/src/operations/infer-impact.js +144 -0
- package/dist/src/operations/infer-lifecycle.d.ts +421 -0
- package/dist/src/operations/infer-lifecycle.js +119 -0
- package/package.json +1 -1
package/dist/src/json-to-md.js
CHANGED
|
@@ -22,6 +22,58 @@ function renderFrontMatter(fields) {
|
|
|
22
22
|
lines.push("---");
|
|
23
23
|
return lines.join("\n");
|
|
24
24
|
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Node location map (for hyperlinking)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/** GitHub-compatible heading anchor slug. */
|
|
29
|
+
function slugify(text) {
|
|
30
|
+
return text
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^\w\s-]/g, "")
|
|
33
|
+
.replace(/\s/g, "-");
|
|
34
|
+
}
|
|
35
|
+
/** Heading anchor for a node: `id--name` slugified from `### ID — Name`. */
|
|
36
|
+
function nodeAnchor(n) {
|
|
37
|
+
return slugify(`${n.id} — ${n.name}`);
|
|
38
|
+
}
|
|
39
|
+
/** Build a map from node ID to its markdown file and heading anchor. */
|
|
40
|
+
function buildNodeLocationMap(nodes, mode) {
|
|
41
|
+
const map = new Map();
|
|
42
|
+
for (const n of nodes) {
|
|
43
|
+
const anchor = nodeAnchor(n);
|
|
44
|
+
if (mode === "single-file") {
|
|
45
|
+
map.set(n.id, { file: "", anchor });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const file = fileForNodeType(n.type);
|
|
49
|
+
map.set(n.id, { file, anchor });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return map;
|
|
53
|
+
}
|
|
54
|
+
/** Determine the markdown file a node type belongs to in multi-doc mode. */
|
|
55
|
+
function fileForNodeType(type) {
|
|
56
|
+
for (const [fileName, types] of Object.entries(NODE_FILE_MAP)) {
|
|
57
|
+
if (types.includes(type))
|
|
58
|
+
return `${fileName}.md`;
|
|
59
|
+
}
|
|
60
|
+
return "README.md";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format a node ID as a markdown hyperlink.
|
|
64
|
+
* In single-file mode: `[ID](#anchor)`
|
|
65
|
+
* In multi-doc mode: `[ID](./FILE.md#anchor)`
|
|
66
|
+
* Falls back to plain ID if the node isn't in the map.
|
|
67
|
+
*/
|
|
68
|
+
function linkNodeId(id, nodeMap, currentFile) {
|
|
69
|
+
const loc = nodeMap.get(id);
|
|
70
|
+
if (!loc)
|
|
71
|
+
return id;
|
|
72
|
+
if (loc.file === "" || loc.file === currentFile) {
|
|
73
|
+
return `[${id}](#${loc.anchor})`;
|
|
74
|
+
}
|
|
75
|
+
return `[${id}](./${loc.file}#${loc.anchor})`;
|
|
76
|
+
}
|
|
25
77
|
function indexRelationshipsFrom(rels) {
|
|
26
78
|
const idx = new Map();
|
|
27
79
|
for (const r of rels) {
|
|
@@ -79,7 +131,7 @@ function renderLifecycle(lifecycle) {
|
|
|
79
131
|
return `- [${checkbox}] ${label}`;
|
|
80
132
|
});
|
|
81
133
|
}
|
|
82
|
-
function renderNodeRelationships(nodeId, fromIdx) {
|
|
134
|
+
function renderNodeRelationships(nodeId, fromIdx, nodeMap, currentFile) {
|
|
83
135
|
const rels = fromIdx.get(nodeId);
|
|
84
136
|
if (!rels || rels.length === 0)
|
|
85
137
|
return [];
|
|
@@ -97,12 +149,12 @@ function renderNodeRelationships(nodeId, fromIdx) {
|
|
|
97
149
|
? RELATIONSHIP_TYPE_LABELS[type]
|
|
98
150
|
: type;
|
|
99
151
|
if (targets.length === 1) {
|
|
100
|
-
lines.push(`- ${label}: ${targets[0]}`);
|
|
152
|
+
lines.push(`- ${label}: ${linkNodeId(targets[0], nodeMap, currentFile)}`);
|
|
101
153
|
}
|
|
102
154
|
else {
|
|
103
155
|
lines.push(`- ${label}:`);
|
|
104
156
|
for (const t of targets) {
|
|
105
|
-
lines.push(` - ${t}`);
|
|
157
|
+
lines.push(` - ${linkNodeId(t, nodeMap, currentFile)}`);
|
|
106
158
|
}
|
|
107
159
|
}
|
|
108
160
|
}
|
|
@@ -123,7 +175,7 @@ function renderExternalReferences(refs) {
|
|
|
123
175
|
}
|
|
124
176
|
return lines;
|
|
125
177
|
}
|
|
126
|
-
function renderNode(n, headingLevel, fromIdx) {
|
|
178
|
+
function renderNode(n, headingLevel, fromIdx, nodeMap, currentFile) {
|
|
127
179
|
const prefix = "#".repeat(headingLevel);
|
|
128
180
|
const lines = [];
|
|
129
181
|
lines.push(`${prefix} ${n.id} — ${n.name}`);
|
|
@@ -132,7 +184,7 @@ function renderNode(n, headingLevel, fromIdx) {
|
|
|
132
184
|
lines.push(renderText(n.description));
|
|
133
185
|
lines.push("");
|
|
134
186
|
}
|
|
135
|
-
const rels = renderNodeRelationships(n.id, fromIdx);
|
|
187
|
+
const rels = renderNodeRelationships(n.id, fromIdx, nodeMap, currentFile);
|
|
136
188
|
if (rels.length > 0) {
|
|
137
189
|
lines.push(...rels);
|
|
138
190
|
lines.push("");
|
|
@@ -207,7 +259,7 @@ function renderNode(n, headingLevel, fromIdx) {
|
|
|
207
259
|
if (n.includes && n.includes.length > 0) {
|
|
208
260
|
lines.push("Includes:");
|
|
209
261
|
for (const inc of n.includes) {
|
|
210
|
-
lines.push(`- ${inc}`);
|
|
262
|
+
lines.push(`- ${linkNodeId(inc, nodeMap, currentFile)}`);
|
|
211
263
|
}
|
|
212
264
|
lines.push("");
|
|
213
265
|
}
|
|
@@ -233,8 +285,9 @@ function renderNode(n, headingLevel, fromIdx) {
|
|
|
233
285
|
const subNodes = n.subsystem.nodes;
|
|
234
286
|
const subRels = n.subsystem.relationships ?? [];
|
|
235
287
|
const subIdx = indexRelationshipsFrom(subRels);
|
|
288
|
+
const subMap = buildNodeLocationMap(subNodes, "single-file");
|
|
236
289
|
for (const sub of subNodes) {
|
|
237
|
-
lines.push(...renderNode(sub, headingLevel + 2, subIdx));
|
|
290
|
+
lines.push(...renderNode(sub, headingLevel + 2, subIdx, subMap));
|
|
238
291
|
}
|
|
239
292
|
}
|
|
240
293
|
return lines;
|
|
@@ -242,7 +295,7 @@ function renderNode(n, headingLevel, fromIdx) {
|
|
|
242
295
|
// ---------------------------------------------------------------------------
|
|
243
296
|
// File generators
|
|
244
297
|
// ---------------------------------------------------------------------------
|
|
245
|
-
function renderNodesGrouped(nodes, types, fromIdx, headingLevel) {
|
|
298
|
+
function renderNodesGrouped(nodes, types, fromIdx, headingLevel, nodeMap, currentFile) {
|
|
246
299
|
const lines = [];
|
|
247
300
|
for (const type of types) {
|
|
248
301
|
const matching = nodes.filter((n) => n.type === type);
|
|
@@ -252,12 +305,12 @@ function renderNodesGrouped(nodes, types, fromIdx, headingLevel) {
|
|
|
252
305
|
lines.push(`${"#".repeat(headingLevel)} ${label}`);
|
|
253
306
|
lines.push("");
|
|
254
307
|
for (const n of matching) {
|
|
255
|
-
lines.push(...renderNode(n, headingLevel + 1, fromIdx));
|
|
308
|
+
lines.push(...renderNode(n, headingLevel + 1, fromIdx, nodeMap, currentFile));
|
|
256
309
|
}
|
|
257
310
|
}
|
|
258
311
|
return lines;
|
|
259
312
|
}
|
|
260
|
-
function generateReadme(doc, fromIdx) {
|
|
313
|
+
function generateReadme(doc, fromIdx, nodeMap) {
|
|
261
314
|
const lines = [];
|
|
262
315
|
const title = doc.metadata?.title ?? "SysProM";
|
|
263
316
|
lines.push(renderFrontMatter({
|
|
@@ -329,7 +382,7 @@ function generateReadme(doc, fromIdx) {
|
|
|
329
382
|
// Views
|
|
330
383
|
const views = doc.nodes.filter((n) => n.type === "view");
|
|
331
384
|
if (views.length > 0) {
|
|
332
|
-
lines.push(...renderNodesGrouped(doc.nodes, ["view"], fromIdx, 2));
|
|
385
|
+
lines.push(...renderNodesGrouped(doc.nodes, ["view"], fromIdx, 2, nodeMap, "README.md"));
|
|
333
386
|
}
|
|
334
387
|
// Graph-level external references
|
|
335
388
|
if (doc.external_references && doc.external_references.length > 0) {
|
|
@@ -347,7 +400,7 @@ function generateReadme(doc, fromIdx) {
|
|
|
347
400
|
}
|
|
348
401
|
return lines.join("\n") + "\n";
|
|
349
402
|
}
|
|
350
|
-
function generateDocFile(doc, fileName, types, fromIdx) {
|
|
403
|
+
function generateDocFile(doc, fileName, types, fromIdx, nodeMap) {
|
|
351
404
|
const lines = [];
|
|
352
405
|
lines.push(renderFrontMatter({
|
|
353
406
|
title: fileName.replace(".md", ""),
|
|
@@ -356,7 +409,7 @@ function generateDocFile(doc, fileName, types, fromIdx) {
|
|
|
356
409
|
lines.push("");
|
|
357
410
|
lines.push(`# ${fileName.replace(".md", "")}`);
|
|
358
411
|
lines.push("");
|
|
359
|
-
lines.push(...renderNodesGrouped(doc.nodes, types, fromIdx, 2));
|
|
412
|
+
lines.push(...renderNodesGrouped(doc.nodes, types, fromIdx, 2, nodeMap, `${fileName}.md`));
|
|
360
413
|
return lines.join("\n") + "\n";
|
|
361
414
|
}
|
|
362
415
|
/**
|
|
@@ -371,6 +424,7 @@ function generateDocFile(doc, fileName, types, fromIdx) {
|
|
|
371
424
|
*/
|
|
372
425
|
export function jsonToMarkdownSingle(doc) {
|
|
373
426
|
const fromIdx = indexRelationshipsFrom(doc.relationships ?? []);
|
|
427
|
+
const nodeMap = buildNodeLocationMap(doc.nodes, "single-file");
|
|
374
428
|
const lines = [];
|
|
375
429
|
const title = doc.metadata?.title ?? "SysProM";
|
|
376
430
|
lines.push(renderFrontMatter({
|
|
@@ -394,7 +448,7 @@ export function jsonToMarkdownSingle(doc) {
|
|
|
394
448
|
"milestone",
|
|
395
449
|
"version",
|
|
396
450
|
];
|
|
397
|
-
lines.push(...renderNodesGrouped(doc.nodes, allTypes, fromIdx, 2));
|
|
451
|
+
lines.push(...renderNodesGrouped(doc.nodes, allTypes, fromIdx, 2, nodeMap));
|
|
398
452
|
// Relationships summary
|
|
399
453
|
if (doc.relationships && doc.relationships.length > 0) {
|
|
400
454
|
lines.push("## Relationships");
|
|
@@ -433,12 +487,13 @@ export function jsonToMarkdownSingle(doc) {
|
|
|
433
487
|
export function jsonToMarkdownMultiDoc(doc, outDir) {
|
|
434
488
|
mkdirSync(outDir, { recursive: true });
|
|
435
489
|
const fromIdx = indexRelationshipsFrom(doc.relationships ?? []);
|
|
436
|
-
|
|
490
|
+
const nodeMap = buildNodeLocationMap(doc.nodes, "multi-doc");
|
|
491
|
+
writeFileSync(join(outDir, "README.md"), generateReadme(doc, fromIdx, nodeMap));
|
|
437
492
|
for (const [fileName, types] of Object.entries(NODE_FILE_MAP)) {
|
|
438
493
|
const hasNodes = doc.nodes.some((n) => types.includes(n.type));
|
|
439
494
|
if (!hasNodes)
|
|
440
495
|
continue;
|
|
441
|
-
writeFileSync(join(outDir, `${fileName}.md`), generateDocFile(doc, fileName, types, fromIdx));
|
|
496
|
+
writeFileSync(join(outDir, `${fileName}.md`), generateDocFile(doc, fileName, types, fromIdx, nodeMap));
|
|
442
497
|
}
|
|
443
498
|
// Subsystem folders or single files
|
|
444
499
|
const subsystemNodes = doc.nodes.filter((n) => n.subsystem);
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import * as z from "zod";
|
|
5
|
-
import { loadDocument } from "../io.js";
|
|
5
|
+
import { loadDocument, saveDocument } from "../io.js";
|
|
6
6
|
import { NodeType, RelationshipType } from "../schema.js";
|
|
7
|
-
import { validateOp, statsOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, nextIdOp, } from "../operations/index.js";
|
|
7
|
+
import { validateOp, statsOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, nextIdOp, inferCompletenessOp, inferLifecycleOp, inferImpactOp, inferDerivedOp, } from "../operations/index.js";
|
|
8
8
|
// Create MCP server instance
|
|
9
9
|
const server = new McpServer({
|
|
10
10
|
name: "sysprom-mcp",
|
|
@@ -145,14 +145,14 @@ server.registerTool("add-node", {
|
|
|
145
145
|
description: z.string().optional().describe("Node description"),
|
|
146
146
|
}),
|
|
147
147
|
}, ({ path, type, id, name, description }) => {
|
|
148
|
-
const
|
|
148
|
+
const loaded = loadDocument(path);
|
|
149
149
|
const nodeType = NodeType.safeParse(type);
|
|
150
150
|
if (!nodeType.success) {
|
|
151
151
|
throw new Error(`Invalid node type: "${type}". Valid types: ${NodeType.options.join(", ")}`);
|
|
152
152
|
}
|
|
153
|
-
const nodeId = id ?? nextIdOp({ doc, type: nodeType.data });
|
|
153
|
+
const nodeId = id ?? nextIdOp({ doc: loaded.doc, type: nodeType.data });
|
|
154
154
|
const updated = addNodeOp({
|
|
155
|
-
doc,
|
|
155
|
+
doc: loaded.doc,
|
|
156
156
|
node: {
|
|
157
157
|
id: nodeId,
|
|
158
158
|
type: nodeType.data,
|
|
@@ -160,6 +160,7 @@ server.registerTool("add-node", {
|
|
|
160
160
|
...(description && { description }),
|
|
161
161
|
},
|
|
162
162
|
});
|
|
163
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
163
164
|
return {
|
|
164
165
|
content: [
|
|
165
166
|
{
|
|
@@ -181,8 +182,9 @@ server.registerTool("remove-node", {
|
|
|
181
182
|
id: z.string().describe("Node ID"),
|
|
182
183
|
}),
|
|
183
184
|
}, ({ path, id }) => {
|
|
184
|
-
const
|
|
185
|
-
const result = removeNodeOp({ doc, id });
|
|
185
|
+
const loaded = loadDocument(path);
|
|
186
|
+
const result = removeNodeOp({ doc: loaded.doc, id });
|
|
187
|
+
saveDocument(result.doc, loaded.format, loaded.path);
|
|
186
188
|
return {
|
|
187
189
|
content: [
|
|
188
190
|
{
|
|
@@ -205,7 +207,7 @@ server.registerTool("update-node", {
|
|
|
205
207
|
fields: z.record(z.string(), z.unknown()).describe("Fields to update"),
|
|
206
208
|
}),
|
|
207
209
|
}, ({ path, id, fields }) => {
|
|
208
|
-
const
|
|
210
|
+
const loaded = loadDocument(path);
|
|
209
211
|
// Validate fields are valid node property updates
|
|
210
212
|
const validFields = Object.entries(fields).reduce((acc, [key, value]) => {
|
|
211
213
|
// Allow common node fields; unknown fields are silently ignored
|
|
@@ -231,10 +233,11 @@ server.registerTool("update-node", {
|
|
|
231
233
|
return acc;
|
|
232
234
|
}, {});
|
|
233
235
|
const updated = updateNodeOp({
|
|
234
|
-
doc,
|
|
236
|
+
doc: loaded.doc,
|
|
235
237
|
id,
|
|
236
238
|
fields: validFields,
|
|
237
239
|
});
|
|
240
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
238
241
|
const node = updated.nodes.find((n) => n.id === id);
|
|
239
242
|
return {
|
|
240
243
|
content: [
|
|
@@ -255,19 +258,20 @@ server.registerTool("add-relationship", {
|
|
|
255
258
|
type: z.string().describe("Relationship type"),
|
|
256
259
|
}),
|
|
257
260
|
}, ({ path, from, to, type }) => {
|
|
258
|
-
const
|
|
261
|
+
const loaded = loadDocument(path);
|
|
259
262
|
const relType = RelationshipType.safeParse(type);
|
|
260
263
|
if (!relType.success) {
|
|
261
264
|
throw new Error(`Invalid relationship type: "${type}". Valid types: ${RelationshipType.options.join(", ")}`);
|
|
262
265
|
}
|
|
263
266
|
const updated = addRelationshipOp({
|
|
264
|
-
doc,
|
|
267
|
+
doc: loaded.doc,
|
|
265
268
|
rel: {
|
|
266
269
|
from,
|
|
267
270
|
to,
|
|
268
271
|
type: relType.data,
|
|
269
272
|
},
|
|
270
273
|
});
|
|
274
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
271
275
|
return {
|
|
272
276
|
content: [
|
|
273
277
|
{
|
|
@@ -290,17 +294,18 @@ server.registerTool("remove-relationship", {
|
|
|
290
294
|
type: z.string().describe("Relationship type"),
|
|
291
295
|
}),
|
|
292
296
|
}, ({ path, from, to, type }) => {
|
|
293
|
-
const
|
|
297
|
+
const loaded = loadDocument(path);
|
|
294
298
|
const relType = RelationshipType.safeParse(type);
|
|
295
299
|
if (!relType.success) {
|
|
296
300
|
throw new Error(`Invalid relationship type: "${type}". Valid types: ${RelationshipType.options.join(", ")}`);
|
|
297
301
|
}
|
|
298
302
|
const result = removeRelationshipOp({
|
|
299
|
-
doc,
|
|
303
|
+
doc: loaded.doc,
|
|
300
304
|
from,
|
|
301
305
|
to,
|
|
302
306
|
type: relType.data,
|
|
303
307
|
});
|
|
308
|
+
saveDocument(result.doc, loaded.format, loaded.path);
|
|
304
309
|
return {
|
|
305
310
|
content: [
|
|
306
311
|
{
|
|
@@ -313,6 +318,79 @@ server.registerTool("remove-relationship", {
|
|
|
313
318
|
],
|
|
314
319
|
};
|
|
315
320
|
});
|
|
321
|
+
// Register infer-completeness tool
|
|
322
|
+
server.registerTool("infer-completeness", {
|
|
323
|
+
description: "Infer completeness of nodes based on expected refinement relationships",
|
|
324
|
+
inputSchema: z.object({
|
|
325
|
+
path: z.string().describe("Path to SysProM file"),
|
|
326
|
+
}),
|
|
327
|
+
}, ({ path }) => {
|
|
328
|
+
const { doc } = loadDocument(path);
|
|
329
|
+
const result = inferCompletenessOp({ doc });
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify(result, null, 2),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
// Register infer-lifecycle tool
|
|
340
|
+
server.registerTool("infer-lifecycle", {
|
|
341
|
+
description: "Infer lifecycle state for nodes based on status and lifecycle fields",
|
|
342
|
+
inputSchema: z.object({
|
|
343
|
+
path: z.string().describe("Path to SysProM file"),
|
|
344
|
+
}),
|
|
345
|
+
}, ({ path }) => {
|
|
346
|
+
const { doc } = loadDocument(path);
|
|
347
|
+
const result = inferLifecycleOp({ doc });
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: JSON.stringify(result, null, 2),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
// Register infer-impact tool
|
|
358
|
+
server.registerTool("infer-impact", {
|
|
359
|
+
description: "Infer impact from a node through the graph following impact relationships",
|
|
360
|
+
inputSchema: z.object({
|
|
361
|
+
path: z.string().describe("Path to SysProM file"),
|
|
362
|
+
startId: z.string().describe("Node ID to start impact analysis from"),
|
|
363
|
+
}),
|
|
364
|
+
}, ({ path, startId }) => {
|
|
365
|
+
const { doc } = loadDocument(path);
|
|
366
|
+
const result = inferImpactOp({ doc, startId });
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: JSON.stringify(result, null, 2),
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
// Register infer-derived tool
|
|
377
|
+
server.registerTool("infer-derived", {
|
|
378
|
+
description: "Infer derived relationships from transitive closure, inverses, and composites",
|
|
379
|
+
inputSchema: z.object({
|
|
380
|
+
path: z.string().describe("Path to SysProM file"),
|
|
381
|
+
}),
|
|
382
|
+
}, ({ path }) => {
|
|
383
|
+
const { doc } = loadDocument(path);
|
|
384
|
+
const result = inferDerivedOp({ doc });
|
|
385
|
+
return {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: JSON.stringify(result, null, 2),
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
});
|
|
316
394
|
// Start server
|
|
317
395
|
async function main() {
|
|
318
396
|
const transport = new StdioServerTransport();
|
package/dist/src/md-to-json.js
CHANGED
|
@@ -2,6 +2,10 @@ import * as z from "zod";
|
|
|
2
2
|
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join, basename } from "node:path";
|
|
4
4
|
import { NODE_FILE_MAP, NODE_LABEL_TO_TYPE, RELATIONSHIP_TYPE_LABELS, RELATIONSHIP_LABEL_TO_TYPE, NodeType, RelationshipType, NodeStatus, ExternalReferenceRole, } from "./schema.js";
|
|
5
|
+
/** Strip markdown link syntax `[text](url)` → `text`. */
|
|
6
|
+
function stripMarkdownLink(s) {
|
|
7
|
+
return s.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
8
|
+
}
|
|
5
9
|
const LABEL_TO_TYPE = Object.fromEntries(Object.entries(NODE_LABEL_TO_TYPE).map(([k, v]) => [k.toLowerCase(), v]));
|
|
6
10
|
const operationType = z.enum(["add", "update", "remove", "link"]);
|
|
7
11
|
function parseNodeType(s) {
|
|
@@ -162,18 +166,18 @@ function parseListItems(body, prefix) {
|
|
|
162
166
|
collecting = true;
|
|
163
167
|
const inline = line.slice(prefix.length + 1).trim();
|
|
164
168
|
if (inline) {
|
|
165
|
-
items.push(inline);
|
|
169
|
+
items.push(stripMarkdownLink(inline));
|
|
166
170
|
collecting = false;
|
|
167
171
|
}
|
|
168
172
|
continue;
|
|
169
173
|
}
|
|
170
174
|
if (collecting && line.startsWith(" - ")) {
|
|
171
|
-
items.push(line.slice(4));
|
|
175
|
+
items.push(stripMarkdownLink(line.slice(4)));
|
|
172
176
|
}
|
|
173
177
|
else if (collecting &&
|
|
174
178
|
line.startsWith("- ") &&
|
|
175
179
|
!isRelationshipLabel(line)) {
|
|
176
|
-
items.push(line.slice(2));
|
|
180
|
+
items.push(stripMarkdownLink(line.slice(2)));
|
|
177
181
|
}
|
|
178
182
|
else if (collecting) {
|
|
179
183
|
collecting = false;
|
|
@@ -210,7 +214,7 @@ function parseRelationshipsFromBody(body, nodeId) {
|
|
|
210
214
|
if (items.length === 0) {
|
|
211
215
|
const val = parseSingleValue(body, `- ${label}`);
|
|
212
216
|
if (val) {
|
|
213
|
-
rels.push({ from: nodeId, to: val, type: relType });
|
|
217
|
+
rels.push({ from: nodeId, to: stripMarkdownLink(val), type: relType });
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
else {
|
|
@@ -37,3 +37,7 @@ export { speckitImportOp } from "./speckit-import.js";
|
|
|
37
37
|
export { speckitExportOp } from "./speckit-export.js";
|
|
38
38
|
export { speckitSyncOp, type SyncResult } from "./speckit-sync.js";
|
|
39
39
|
export { speckitDiffOp, type DiffResult } from "./speckit-diff.js";
|
|
40
|
+
export { inferCompletenessOp, type CompletenessResult, type CompletenessOutput, } from "./infer-completeness.js";
|
|
41
|
+
export { inferLifecycleOp, type LifecycleResult, type LifecycleOutput, } from "./infer-lifecycle.js";
|
|
42
|
+
export { inferImpactOp, type ImpactNode, type ImpactOutput, } from "./infer-impact.js";
|
|
43
|
+
export { inferDerivedOp, type DerivedRelationship, type DerivedOutput, } from "./infer-derived.js";
|
|
@@ -45,3 +45,8 @@ export { speckitImportOp } from "./speckit-import.js";
|
|
|
45
45
|
export { speckitExportOp } from "./speckit-export.js";
|
|
46
46
|
export { speckitSyncOp } from "./speckit-sync.js";
|
|
47
47
|
export { speckitDiffOp } from "./speckit-diff.js";
|
|
48
|
+
// Inference operations
|
|
49
|
+
export { inferCompletenessOp, } from "./infer-completeness.js";
|
|
50
|
+
export { inferLifecycleOp, } from "./infer-lifecycle.js";
|
|
51
|
+
export { inferImpactOp, } from "./infer-impact.js";
|
|
52
|
+
export { inferDerivedOp, } from "./infer-derived.js";
|