sysprom 1.16.1 → 1.18.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 +147 -75
- package/dist/schema.json +2 -1
- package/dist/src/cli/commands/add.js +52 -36
- package/dist/src/cli/commands/graph.d.ts +15 -0
- package/dist/src/cli/commands/graph.js +51 -2
- package/dist/src/cli/commands/infer.js +44 -25
- package/dist/src/cli/commands/init.d.ts +1 -1
- package/dist/src/cli/commands/json2md.d.ts +30 -1
- package/dist/src/cli/commands/json2md.js +42 -1
- package/dist/src/cli/commands/md2json.d.ts +1 -1
- package/dist/src/cli/commands/query.js +35 -37
- package/dist/src/cli/commands/speckit.js +81 -77
- package/dist/src/cli/commands/stats.js +4 -4
- package/dist/src/cli/commands/sync.d.ts +1 -1
- package/dist/src/cli/commands/update.js +33 -20
- package/dist/src/cli/define-command.d.ts +1 -1
- package/dist/src/cli/define-command.js +176 -156
- package/dist/src/endpoint-types.js +13 -6
- package/dist/src/io.js +59 -8
- package/dist/src/json-to-md.d.ts +32 -2
- package/dist/src/json-to-md.js +145 -5
- package/dist/src/mcp/server.js +269 -112
- package/dist/src/md-to-json.js +7 -0
- package/dist/src/operations/add-node.d.ts +12 -9
- package/dist/src/operations/add-plan-task.d.ts +8 -6
- package/dist/src/operations/add-relationship.d.ts +11 -8
- package/dist/src/operations/check.d.ts +4 -3
- package/dist/src/operations/define-operation.d.ts +1 -1
- package/dist/src/operations/graph-decision.d.ts +329 -0
- package/dist/src/operations/graph-decision.js +96 -0
- package/dist/src/operations/graph-dependency.d.ts +329 -0
- package/dist/src/operations/graph-dependency.js +121 -0
- package/dist/src/operations/graph-refinement.d.ts +329 -0
- package/dist/src/operations/graph-refinement.js +97 -0
- package/dist/src/operations/graph-shared.d.ts +116 -0
- package/dist/src/operations/graph-shared.js +257 -0
- package/dist/src/operations/graph.d.ts +20 -4
- package/dist/src/operations/graph.js +129 -36
- package/dist/src/operations/index.d.ts +3 -0
- package/dist/src/operations/index.js +3 -0
- package/dist/src/operations/infer-completeness.d.ts +4 -3
- package/dist/src/operations/infer-derived.d.ts +4 -3
- package/dist/src/operations/infer-impact.d.ts +28 -21
- package/dist/src/operations/infer-lifecycle.d.ts +4 -3
- package/dist/src/operations/init-document.d.ts +4 -3
- package/dist/src/operations/json-to-markdown.d.ts +28 -3
- package/dist/src/operations/json-to-markdown.js +11 -1
- package/dist/src/operations/mark-task-done.d.ts +8 -6
- package/dist/src/operations/mark-task-undone.d.ts +8 -6
- package/dist/src/operations/markdown-to-json.d.ts +4 -3
- package/dist/src/operations/next-id.d.ts +4 -3
- package/dist/src/operations/node-history.d.ts +4 -3
- package/dist/src/operations/plan-add-task.d.ts +8 -6
- package/dist/src/operations/plan-gate.d.ts +4 -3
- package/dist/src/operations/plan-init.d.ts +4 -3
- package/dist/src/operations/plan-progress.d.ts +4 -3
- package/dist/src/operations/plan-status.d.ts +4 -3
- package/dist/src/operations/query-node.d.ts +24 -17
- package/dist/src/operations/query-nodes.d.ts +8 -6
- package/dist/src/operations/query-relationships.d.ts +7 -5
- package/dist/src/operations/remove-node.d.ts +12 -9
- package/dist/src/operations/remove-relationship.d.ts +10 -7
- package/dist/src/operations/rename.d.ts +8 -6
- package/dist/src/operations/search.d.ts +8 -6
- package/dist/src/operations/speckit-diff.d.ts +4 -3
- package/dist/src/operations/speckit-export.d.ts +4 -3
- package/dist/src/operations/speckit-import.d.ts +4 -3
- package/dist/src/operations/speckit-sync.d.ts +12 -9
- package/dist/src/operations/state-at.d.ts +4 -3
- package/dist/src/operations/stats.d.ts +4 -3
- package/dist/src/operations/sync.d.ts +12 -9
- package/dist/src/operations/task-list.d.ts +4 -3
- package/dist/src/operations/timeline.d.ts +4 -3
- package/dist/src/operations/trace-from-node.d.ts +12 -9
- package/dist/src/operations/update-metadata.d.ts +8 -6
- package/dist/src/operations/update-node.d.ts +11 -8
- package/dist/src/operations/update-plan-task.d.ts +8 -6
- package/dist/src/operations/validate.d.ts +4 -3
- package/dist/src/schema.d.ts +15 -10
- package/dist/src/schema.js +3 -11
- package/dist/src/utils/define-schema.d.ts +17 -0
- package/dist/src/utils/define-schema.js +21 -0
- package/package.json +98 -93
- package/schema.json +2 -1
package/dist/src/json-to-md.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { NODE_FILE_MAP, NODE_TYPE_LABELS, NodeType, RelationshipType, RELATIONSHIP_TYPE_LABELS, } from "./schema.js";
|
|
4
|
+
import { graphOp } from "./operations/graph.js";
|
|
5
|
+
import { graphRefinementOp } from "./operations/graph-refinement.js";
|
|
6
|
+
import { graphDecisionOp } from "./operations/graph-decision.js";
|
|
7
|
+
import { graphDependencyOp } from "./operations/graph-dependency.js";
|
|
4
8
|
// ---------------------------------------------------------------------------
|
|
5
9
|
// Text helpers
|
|
6
10
|
// ---------------------------------------------------------------------------
|
|
@@ -25,7 +29,13 @@ function renderFrontMatter(fields) {
|
|
|
25
29
|
// ---------------------------------------------------------------------------
|
|
26
30
|
// Node location map (for hyperlinking)
|
|
27
31
|
// ---------------------------------------------------------------------------
|
|
28
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* GitHub-compatible heading anchor slug.
|
|
34
|
+
* @param text
|
|
35
|
+
* @example
|
|
36
|
+
* // Convert a heading into a GitHub-style slug
|
|
37
|
+
* // slugify('ID — Name') // 'id---name'
|
|
38
|
+
*/
|
|
29
39
|
function slugify(text) {
|
|
30
40
|
return text
|
|
31
41
|
.toLowerCase()
|
|
@@ -64,6 +74,10 @@ function fileForNodeType(type) {
|
|
|
64
74
|
* In single-file mode: `[ID](#anchor)`
|
|
65
75
|
* In multi-doc mode: `[ID](./FILE.md#anchor)`
|
|
66
76
|
* Falls back to plain ID if the node isn't in the map.
|
|
77
|
+
* @param id
|
|
78
|
+
* @param nodeMap
|
|
79
|
+
* @param currentFile
|
|
80
|
+
* @example
|
|
67
81
|
*/
|
|
68
82
|
function linkNodeId(id, nodeMap, currentFile) {
|
|
69
83
|
const loc = nodeMap.get(id);
|
|
@@ -412,9 +426,82 @@ function generateDocFile(doc, fileName, types, fromIdx, nodeMap) {
|
|
|
412
426
|
lines.push(...renderNodesGrouped(doc.nodes, types, fromIdx, 2, nodeMap, `${fileName}.md`));
|
|
413
427
|
return lines.join("\n") + "\n";
|
|
414
428
|
}
|
|
429
|
+
function generateDiagramsFile(doc, opts) {
|
|
430
|
+
const lines = [];
|
|
431
|
+
lines.push(renderFrontMatter({
|
|
432
|
+
title: "Diagrams",
|
|
433
|
+
doc_type: "diagrams",
|
|
434
|
+
}));
|
|
435
|
+
lines.push("");
|
|
436
|
+
lines.push("# Diagrams");
|
|
437
|
+
lines.push("");
|
|
438
|
+
lines.push("## Relationship Graph");
|
|
439
|
+
lines.push("");
|
|
440
|
+
lines.push("```mermaid");
|
|
441
|
+
lines.push(graphOp({
|
|
442
|
+
doc,
|
|
443
|
+
format: "mermaid",
|
|
444
|
+
layout: opts?.relationshipLayout ?? "TD",
|
|
445
|
+
labelMode: opts?.labelMode ?? "friendly",
|
|
446
|
+
cluster: true,
|
|
447
|
+
connectedOnly: false,
|
|
448
|
+
}));
|
|
449
|
+
lines.push("```");
|
|
450
|
+
lines.push("");
|
|
451
|
+
const refinement = graphRefinementOp({
|
|
452
|
+
doc,
|
|
453
|
+
format: "mermaid",
|
|
454
|
+
layout: opts?.refinementLayout ?? "TD",
|
|
455
|
+
labelMode: opts?.labelMode ?? "friendly",
|
|
456
|
+
});
|
|
457
|
+
if (refinement.includes("-->")) {
|
|
458
|
+
lines.push("## Refinement Chain");
|
|
459
|
+
lines.push("");
|
|
460
|
+
lines.push("```mermaid");
|
|
461
|
+
lines.push(refinement);
|
|
462
|
+
lines.push("```");
|
|
463
|
+
lines.push("");
|
|
464
|
+
}
|
|
465
|
+
const decisions = graphDecisionOp({
|
|
466
|
+
doc,
|
|
467
|
+
format: "mermaid",
|
|
468
|
+
layout: opts?.decisionLayout ?? "TD",
|
|
469
|
+
labelMode: opts?.labelMode ?? "friendly",
|
|
470
|
+
});
|
|
471
|
+
if (decisions.includes("-->")) {
|
|
472
|
+
lines.push("## Decision Map");
|
|
473
|
+
lines.push("");
|
|
474
|
+
lines.push("```mermaid");
|
|
475
|
+
lines.push(decisions);
|
|
476
|
+
lines.push("```");
|
|
477
|
+
lines.push("");
|
|
478
|
+
}
|
|
479
|
+
const dependencies = graphDependencyOp({
|
|
480
|
+
doc,
|
|
481
|
+
format: "mermaid",
|
|
482
|
+
layout: opts?.dependencyLayout ?? "LR",
|
|
483
|
+
labelMode: opts?.labelMode ?? "friendly",
|
|
484
|
+
});
|
|
485
|
+
if (dependencies.includes("-->") || dependencies.includes("-.->")) {
|
|
486
|
+
lines.push("## Dependency Graph");
|
|
487
|
+
lines.push("");
|
|
488
|
+
lines.push("```mermaid");
|
|
489
|
+
lines.push(dependencies);
|
|
490
|
+
lines.push("```");
|
|
491
|
+
lines.push("");
|
|
492
|
+
}
|
|
493
|
+
return lines.join("\n") + "\n";
|
|
494
|
+
}
|
|
415
495
|
/**
|
|
416
496
|
* Convert a SysProM document to a single Markdown string.
|
|
417
497
|
* @param doc - The SysProM document to convert.
|
|
498
|
+
* @param options
|
|
499
|
+
* @param options.embedDiagrams
|
|
500
|
+
* @param options.labelMode
|
|
501
|
+
* @param options.relationshipLayout
|
|
502
|
+
* @param options.refinementLayout
|
|
503
|
+
* @param options.decisionLayout
|
|
504
|
+
* @param options.dependencyLayout
|
|
418
505
|
* @returns The Markdown representation.
|
|
419
506
|
* @example
|
|
420
507
|
* ```ts
|
|
@@ -422,7 +509,7 @@ function generateDocFile(doc, fileName, types, fromIdx, nodeMap) {
|
|
|
422
509
|
* writeFileSync("output.spm.md", md);
|
|
423
510
|
* ```
|
|
424
511
|
*/
|
|
425
|
-
export function jsonToMarkdownSingle(doc) {
|
|
512
|
+
export function jsonToMarkdownSingle(doc, options) {
|
|
426
513
|
const fromIdx = indexRelationshipsFrom(doc.relationships ?? []);
|
|
427
514
|
const nodeMap = buildNodeLocationMap(doc.nodes, "single-file");
|
|
428
515
|
const lines = [];
|
|
@@ -449,6 +536,26 @@ export function jsonToMarkdownSingle(doc) {
|
|
|
449
536
|
"version",
|
|
450
537
|
];
|
|
451
538
|
lines.push(...renderNodesGrouped(doc.nodes, allTypes, fromIdx, 2, nodeMap));
|
|
539
|
+
// Diagrams section
|
|
540
|
+
if (options?.embedDiagrams &&
|
|
541
|
+
doc.relationships &&
|
|
542
|
+
doc.relationships.length > 0) {
|
|
543
|
+
lines.push("## Diagrams");
|
|
544
|
+
lines.push("");
|
|
545
|
+
lines.push("### Relationship Graph");
|
|
546
|
+
lines.push("");
|
|
547
|
+
lines.push("```mermaid");
|
|
548
|
+
lines.push(graphOp({
|
|
549
|
+
doc,
|
|
550
|
+
format: "mermaid",
|
|
551
|
+
layout: options?.relationshipLayout ?? "TD",
|
|
552
|
+
labelMode: options?.labelMode ?? "friendly",
|
|
553
|
+
cluster: true,
|
|
554
|
+
connectedOnly: false,
|
|
555
|
+
}));
|
|
556
|
+
lines.push("```");
|
|
557
|
+
lines.push("");
|
|
558
|
+
}
|
|
452
559
|
// Relationships summary
|
|
453
560
|
if (doc.relationships && doc.relationships.length > 0) {
|
|
454
561
|
lines.push("## Relationships");
|
|
@@ -479,12 +586,19 @@ export function jsonToMarkdownSingle(doc) {
|
|
|
479
586
|
* Convert a SysProM document to a multi-document Markdown folder.
|
|
480
587
|
* @param doc - The SysProM document to convert.
|
|
481
588
|
* @param outDir - Output directory path.
|
|
589
|
+
* @param options
|
|
590
|
+
* @param options.embedDiagrams
|
|
591
|
+
* @param options.labelMode
|
|
592
|
+
* @param options.relationshipLayout
|
|
593
|
+
* @param options.refinementLayout
|
|
594
|
+
* @param options.decisionLayout
|
|
595
|
+
* @param options.dependencyLayout
|
|
482
596
|
* @example
|
|
483
597
|
* ```ts
|
|
484
598
|
* jsonToMarkdownMultiDoc(doc, "./SysProM");
|
|
485
599
|
* ```
|
|
486
600
|
*/
|
|
487
|
-
export function jsonToMarkdownMultiDoc(doc, outDir) {
|
|
601
|
+
export function jsonToMarkdownMultiDoc(doc, outDir, options) {
|
|
488
602
|
mkdirSync(outDir, { recursive: true });
|
|
489
603
|
const fromIdx = indexRelationshipsFrom(doc.relationships ?? []);
|
|
490
604
|
const nodeMap = buildNodeLocationMap(doc.nodes, "multi-doc");
|
|
@@ -495,6 +609,18 @@ export function jsonToMarkdownMultiDoc(doc, outDir) {
|
|
|
495
609
|
continue;
|
|
496
610
|
writeFileSync(join(outDir, `${fileName}.md`), generateDocFile(doc, fileName, types, fromIdx, nodeMap));
|
|
497
611
|
}
|
|
612
|
+
// Diagrams file
|
|
613
|
+
if (options?.embedDiagrams &&
|
|
614
|
+
doc.relationships &&
|
|
615
|
+
doc.relationships.length > 0) {
|
|
616
|
+
writeFileSync(join(outDir, "DIAGRAMS.md"), generateDiagramsFile(doc, {
|
|
617
|
+
labelMode: options?.labelMode ?? "friendly",
|
|
618
|
+
relationshipLayout: options?.relationshipLayout,
|
|
619
|
+
refinementLayout: options?.refinementLayout,
|
|
620
|
+
decisionLayout: options?.decisionLayout,
|
|
621
|
+
dependencyLayout: options?.dependencyLayout,
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
498
624
|
// Subsystem folders or single files
|
|
499
625
|
const subsystemNodes = doc.nodes.filter((n) => n.subsystem);
|
|
500
626
|
// Count subsystems per type to decide automatic grouping
|
|
@@ -556,9 +682,23 @@ export function jsonToMarkdownMultiDoc(doc, outDir) {
|
|
|
556
682
|
*/
|
|
557
683
|
export function jsonToMarkdown(doc, output, options) {
|
|
558
684
|
if (options.form === "single-file") {
|
|
559
|
-
writeFileSync(output, jsonToMarkdownSingle(doc
|
|
685
|
+
writeFileSync(output, jsonToMarkdownSingle(doc, {
|
|
686
|
+
embedDiagrams: options.embedDiagrams,
|
|
687
|
+
labelMode: options.labelMode,
|
|
688
|
+
relationshipLayout: options.relationshipLayout,
|
|
689
|
+
refinementLayout: options.refinementLayout,
|
|
690
|
+
decisionLayout: options.decisionLayout,
|
|
691
|
+
dependencyLayout: options.dependencyLayout,
|
|
692
|
+
}));
|
|
560
693
|
}
|
|
561
694
|
else {
|
|
562
|
-
jsonToMarkdownMultiDoc(doc, output
|
|
695
|
+
jsonToMarkdownMultiDoc(doc, output, {
|
|
696
|
+
embedDiagrams: options.embedDiagrams,
|
|
697
|
+
labelMode: options.labelMode,
|
|
698
|
+
relationshipLayout: options.relationshipLayout,
|
|
699
|
+
refinementLayout: options.refinementLayout,
|
|
700
|
+
decisionLayout: options.decisionLayout,
|
|
701
|
+
dependencyLayout: options.dependencyLayout,
|
|
702
|
+
});
|
|
563
703
|
}
|
|
564
704
|
}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -5,11 +5,76 @@ import * as z from "zod";
|
|
|
5
5
|
import { loadDocument, saveDocument } from "../io.js";
|
|
6
6
|
import { NodeType, RelationshipType } from "../schema.js";
|
|
7
7
|
import { validateOp, statsOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, nextIdOp, inferCompletenessOp, inferLifecycleOp, inferImpactOp, impactSummaryOp, inferDerivedOp, } from "../operations/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Wrap an error with a descriptive prefix and attach the original as cause.
|
|
10
|
+
* @param prefix - The error prefix (e.g., "Failed to add node")
|
|
11
|
+
* @param error - The caught error
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* try {
|
|
15
|
+
* someOperation();
|
|
16
|
+
* } catch (error) {
|
|
17
|
+
* wrapError("Failed to do X", error);
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function wrapError(prefix, error) {
|
|
22
|
+
throw new Error(`${prefix}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
23
|
+
}
|
|
8
24
|
// Create MCP server instance
|
|
9
25
|
const server = new McpServer({
|
|
10
26
|
name: "sysprom-mcp",
|
|
11
27
|
version: "1.0.0",
|
|
12
28
|
});
|
|
29
|
+
// Register init-document tool
|
|
30
|
+
server.registerTool("init-document", {
|
|
31
|
+
description: "Initialise a new SysProM document with metadata and empty structure",
|
|
32
|
+
inputSchema: z.object({
|
|
33
|
+
path: z
|
|
34
|
+
.string()
|
|
35
|
+
.describe("Output path for the document (must end in .json)"),
|
|
36
|
+
title: z.string().describe("Document title"),
|
|
37
|
+
description: z.string().optional().describe("Document description"),
|
|
38
|
+
scope: z.string().optional().describe("Document scope"),
|
|
39
|
+
}),
|
|
40
|
+
}, ({ path, title, description, scope }) => {
|
|
41
|
+
try {
|
|
42
|
+
const now = new Date().toISOString().split("T")[0];
|
|
43
|
+
const doc = {
|
|
44
|
+
$schema: "https://sysprom.org/schema.json",
|
|
45
|
+
metadata: {
|
|
46
|
+
title,
|
|
47
|
+
...(description && { description }),
|
|
48
|
+
...(scope && { scope }),
|
|
49
|
+
version: "1.0.0",
|
|
50
|
+
created: now,
|
|
51
|
+
},
|
|
52
|
+
nodes: [],
|
|
53
|
+
relationships: [],
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
saveDocument(doc, "json", path);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
wrapError("Failed to save document", error);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: JSON.stringify({
|
|
66
|
+
message: "Document initialised",
|
|
67
|
+
path,
|
|
68
|
+
title,
|
|
69
|
+
}, null, 2),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
wrapError("init-document", error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
13
78
|
// Register validate tool
|
|
14
79
|
server.registerTool("validate", {
|
|
15
80
|
description: "Validate a SysProM document and return any validation issues",
|
|
@@ -145,34 +210,50 @@ server.registerTool("add-node", {
|
|
|
145
210
|
description: z.string().optional().describe("Node description"),
|
|
146
211
|
}),
|
|
147
212
|
}, ({ path, type, id, name, description }) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
...(description && { description }),
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
saveDocument(updated, loaded.format, loaded.path);
|
|
164
|
-
return {
|
|
165
|
-
content: [
|
|
166
|
-
{
|
|
167
|
-
type: "text",
|
|
168
|
-
text: JSON.stringify({
|
|
169
|
-
message: "Node added",
|
|
213
|
+
try {
|
|
214
|
+
const loaded = loadDocument(path);
|
|
215
|
+
const nodeType = NodeType.safeParse(type);
|
|
216
|
+
if (!nodeType.success) {
|
|
217
|
+
throw new Error(`Invalid node type: "${type}". Valid types: ${NodeType.options.join(", ")}`);
|
|
218
|
+
}
|
|
219
|
+
const nodeId = id ?? nextIdOp({ doc: loaded.doc, type: nodeType.data });
|
|
220
|
+
let updated;
|
|
221
|
+
try {
|
|
222
|
+
updated = addNodeOp({
|
|
223
|
+
doc: loaded.doc,
|
|
224
|
+
node: {
|
|
170
225
|
id: nodeId,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
226
|
+
type: nodeType.data,
|
|
227
|
+
name,
|
|
228
|
+
...(description && { description }),
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
wrapError("Failed to add node", error);
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
throw new Error(`Failed to save document: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: JSON.stringify({
|
|
246
|
+
message: "Node added",
|
|
247
|
+
id: nodeId,
|
|
248
|
+
nodeCount: updated.nodes.length,
|
|
249
|
+
}, null, 2),
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
wrapError("add-node", error);
|
|
256
|
+
}
|
|
176
257
|
});
|
|
177
258
|
// Register remove-node tool
|
|
178
259
|
server.registerTool("remove-node", {
|
|
@@ -182,21 +263,37 @@ server.registerTool("remove-node", {
|
|
|
182
263
|
id: z.string().describe("Node ID"),
|
|
183
264
|
}),
|
|
184
265
|
}, ({ path, id }) => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
266
|
+
try {
|
|
267
|
+
const loaded = loadDocument(path);
|
|
268
|
+
let result;
|
|
269
|
+
try {
|
|
270
|
+
result = removeNodeOp({ doc: loaded.doc, id });
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
wrapError("Failed to remove node", error);
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
saveDocument(result.doc, loaded.format, loaded.path);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
throw new Error(`Failed to save document: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: JSON.stringify({
|
|
286
|
+
message: `Node ${id} removed`,
|
|
287
|
+
nodeCount: result.doc.nodes.length,
|
|
288
|
+
warnings: result.warnings,
|
|
289
|
+
}, null, 2),
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
wrapError("remove-node", error);
|
|
296
|
+
}
|
|
200
297
|
});
|
|
201
298
|
// Register update-node tool
|
|
202
299
|
server.registerTool("update-node", {
|
|
@@ -207,11 +304,10 @@ server.registerTool("update-node", {
|
|
|
207
304
|
fields: z.record(z.string(), z.unknown()).describe("Fields to update"),
|
|
208
305
|
}),
|
|
209
306
|
}, ({ path, id, fields }) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if ([
|
|
307
|
+
try {
|
|
308
|
+
const loaded = loadDocument(path);
|
|
309
|
+
// Track which fields are valid
|
|
310
|
+
const allowedFields = [
|
|
215
311
|
"name",
|
|
216
312
|
"description",
|
|
217
313
|
"status",
|
|
@@ -227,26 +323,55 @@ server.registerTool("update-node", {
|
|
|
227
323
|
"input",
|
|
228
324
|
"output",
|
|
229
325
|
"external_references",
|
|
230
|
-
]
|
|
231
|
-
|
|
326
|
+
];
|
|
327
|
+
const droppedFields = [];
|
|
328
|
+
const validFields = Object.entries(fields).reduce((acc, [key, value]) => {
|
|
329
|
+
if (allowedFields.includes(key)) {
|
|
330
|
+
acc[key] = value;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
droppedFields.push(key);
|
|
334
|
+
}
|
|
335
|
+
return acc;
|
|
336
|
+
}, {});
|
|
337
|
+
let updated;
|
|
338
|
+
try {
|
|
339
|
+
updated = updateNodeOp({
|
|
340
|
+
doc: loaded.doc,
|
|
341
|
+
id,
|
|
342
|
+
fields: validFields,
|
|
343
|
+
});
|
|
232
344
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
345
|
+
catch (error) {
|
|
346
|
+
wrapError("Failed to update node", error);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
throw new Error(`Failed to save document: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
353
|
+
}
|
|
354
|
+
const node = updated.nodes.find((n) => n.id === id);
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify({
|
|
360
|
+
message: "Node updated",
|
|
361
|
+
node,
|
|
362
|
+
...(droppedFields.length > 0 && {
|
|
363
|
+
warnings: [
|
|
364
|
+
`These fields are not updateable and were ignored: ${droppedFields.join(", ")}`,
|
|
365
|
+
],
|
|
366
|
+
}),
|
|
367
|
+
}, null, 2),
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
wrapError("update-node", error);
|
|
374
|
+
}
|
|
250
375
|
});
|
|
251
376
|
// Register add-relationship tool
|
|
252
377
|
server.registerTool("add-relationship", {
|
|
@@ -258,31 +383,47 @@ server.registerTool("add-relationship", {
|
|
|
258
383
|
type: z.string().describe("Relationship type"),
|
|
259
384
|
}),
|
|
260
385
|
}, ({ path, from, to, type }) => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
386
|
+
try {
|
|
387
|
+
const loaded = loadDocument(path);
|
|
388
|
+
const relType = RelationshipType.safeParse(type);
|
|
389
|
+
if (!relType.success) {
|
|
390
|
+
throw new Error(`Invalid relationship type: "${type}". Valid types: ${RelationshipType.options.join(", ")}`);
|
|
391
|
+
}
|
|
392
|
+
let updated;
|
|
393
|
+
try {
|
|
394
|
+
updated = addRelationshipOp({
|
|
395
|
+
doc: loaded.doc,
|
|
396
|
+
rel: {
|
|
397
|
+
from,
|
|
398
|
+
to,
|
|
399
|
+
type: relType.data,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
wrapError("Failed to add relationship", error);
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
saveDocument(updated, loaded.format, loaded.path);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
throw new Error(`Failed to save document: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
content: [
|
|
414
|
+
{
|
|
415
|
+
type: "text",
|
|
416
|
+
text: JSON.stringify({
|
|
417
|
+
message: "Relationship added",
|
|
418
|
+
relationshipCount: (updated.relationships ?? []).length,
|
|
419
|
+
}, null, 2),
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
wrapError("add-relationship", error);
|
|
265
426
|
}
|
|
266
|
-
const updated = addRelationshipOp({
|
|
267
|
-
doc: loaded.doc,
|
|
268
|
-
rel: {
|
|
269
|
-
from,
|
|
270
|
-
to,
|
|
271
|
-
type: relType.data,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
saveDocument(updated, loaded.format, loaded.path);
|
|
275
|
-
return {
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: "text",
|
|
279
|
-
text: JSON.stringify({
|
|
280
|
-
message: "Relationship added",
|
|
281
|
-
relationshipCount: (updated.relationships ?? []).length,
|
|
282
|
-
}, null, 2),
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
};
|
|
286
427
|
});
|
|
287
428
|
// Register remove-relationship tool
|
|
288
429
|
server.registerTool("remove-relationship", {
|
|
@@ -294,29 +435,45 @@ server.registerTool("remove-relationship", {
|
|
|
294
435
|
type: z.string().describe("Relationship type"),
|
|
295
436
|
}),
|
|
296
437
|
}, ({ path, from, to, type }) => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
438
|
+
try {
|
|
439
|
+
const loaded = loadDocument(path);
|
|
440
|
+
const relType = RelationshipType.safeParse(type);
|
|
441
|
+
if (!relType.success) {
|
|
442
|
+
throw new Error(`Invalid relationship type: "${type}". Valid types: ${RelationshipType.options.join(", ")}`);
|
|
443
|
+
}
|
|
444
|
+
let result;
|
|
445
|
+
try {
|
|
446
|
+
result = removeRelationshipOp({
|
|
447
|
+
doc: loaded.doc,
|
|
448
|
+
from,
|
|
449
|
+
to,
|
|
450
|
+
type: relType.data,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
wrapError("Failed to remove relationship", error);
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
saveDocument(result.doc, loaded.format, loaded.path);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
throw new Error(`Failed to save document: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
content: [
|
|
464
|
+
{
|
|
465
|
+
type: "text",
|
|
466
|
+
text: JSON.stringify({
|
|
467
|
+
message: "Relationship removed",
|
|
468
|
+
relationshipCount: (result.doc.relationships ?? []).length,
|
|
469
|
+
}, null, 2),
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
wrapError("remove-relationship", error);
|
|
301
476
|
}
|
|
302
|
-
const result = removeRelationshipOp({
|
|
303
|
-
doc: loaded.doc,
|
|
304
|
-
from,
|
|
305
|
-
to,
|
|
306
|
-
type: relType.data,
|
|
307
|
-
});
|
|
308
|
-
saveDocument(result.doc, loaded.format, loaded.path);
|
|
309
|
-
return {
|
|
310
|
-
content: [
|
|
311
|
-
{
|
|
312
|
-
type: "text",
|
|
313
|
-
text: JSON.stringify({
|
|
314
|
-
message: "Relationship removed",
|
|
315
|
-
relationshipCount: (result.doc.relationships ?? []).length,
|
|
316
|
-
}, null, 2),
|
|
317
|
-
},
|
|
318
|
-
],
|
|
319
|
-
};
|
|
320
477
|
});
|
|
321
478
|
// Register infer-completeness tool
|
|
322
479
|
server.registerTool("infer-completeness", {
|
package/dist/src/md-to-json.js
CHANGED
|
@@ -3,6 +3,13 @@ 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
5
|
/** Strip markdown link syntax `[text](url)` → `text`. */
|
|
6
|
+
/**
|
|
7
|
+
* Strip markdown link syntax `[text](url)` → `text`.
|
|
8
|
+
* @param s - Markdown text potentially containing links
|
|
9
|
+
* @returns Text with markdown links removed
|
|
10
|
+
* @example
|
|
11
|
+
* // stripMarkdownLink('[Hello](https://example.com)') // 'Hello'
|
|
12
|
+
*/
|
|
6
13
|
function stripMarkdownLink(s) {
|
|
7
14
|
return s.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
8
15
|
}
|