sysprom 1.14.0 → 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 CHANGED
@@ -52,13 +52,20 @@ sysprom update node D1 --status deprecated
52
52
  sysprom update add-rel D1 affects EL5
53
53
  sysprom update remove-rel D1 affects EL5
54
54
  sysprom update meta --fields version=2
55
+
56
+ # Inference operations (deterministic graph analysis)
57
+ sysprom infer completeness # Score node completeness (0-1)
58
+ sysprom infer lifecycle # Infer lifecycle phases
59
+ sysprom infer impact I1 # Trace impact from node
60
+ sysprom infer derived # Compute transitive closure
61
+ sysprom infer all # Run all analyses
55
62
  ```
56
63
 
57
64
  All commands auto-detect the document — they search the current directory for `.SysProM.json`, `.SysProM.md`, or `.SysProM/` (in that priority order), then fall back to `.spm.json`, `.spm.md`, or `.spm/`. Use `--path` to specify an explicit path. Note: `spm` is an alias for `sysprom` for backwards compatibility.
58
65
 
59
66
  ## MCP Server
60
67
 
61
- SysProM includes an MCP (Model Context Protocol) server exposing 11 tools over stdio transport. Any MCP-compatible agent — Cursor, Windsurf, VS Code Copilot, Cline, or custom clients — can use it.
68
+ SysProM includes an MCP (Model Context Protocol) server exposing 15 tools over stdio transport. Any MCP-compatible agent — Cursor, Windsurf, VS Code Copilot, Cline, or custom clients — can use it.
62
69
 
63
70
  ### Configuration
64
71
 
@@ -96,6 +103,10 @@ sysprom mcp # starts the MCP server on stdio
96
103
  | `update-node` | Update fields on an existing node |
97
104
  | `add-relationship` | Add a relationship between nodes |
98
105
  | `remove-relationship` | Remove a relationship |
106
+ | `infer-completeness` | Score node completeness (0-1) based on refinement relationships |
107
+ | `infer-lifecycle` | Infer lifecycle phase from status and lifecycle fields |
108
+ | `infer-impact` | Trace impact propagation from a starting node |
109
+ | `infer-derived` | Compute transitive closure and inverse relationships |
99
110
 
100
111
  All tools accept a `path` parameter to specify the SysProM document location.
101
112
 
@@ -134,6 +145,12 @@ import {
134
145
  removeRelationship,
135
146
  updateMetadata,
136
147
 
148
+ // Inference
149
+ inferCompletenessOp,
150
+ inferLifecycleOp,
151
+ inferImpactOp,
152
+ inferDerivedOp,
153
+
137
154
  // File I/O
138
155
  loadDocument,
139
156
  saveDocument,
@@ -212,7 +229,7 @@ SysProM models systems as directed graphs across abstraction layers — intent,
212
229
  <tr><td><a href="https://github.com/Priivacy-ai/spec-kitty">Spec Kitty</a></td><td>✅</td><td>🔶</td><td>✅</td><td>🔶</td><td></td><td>🔶</td><td>🔶</td><td>🔶</td><td></td><td></td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td></tr>
213
230
  <tr><td><a href="https://github.com/shotgun-sh/shotgun">Shotgun</a></td><td>✅</td><td>🔶</td><td>🔶</td><td></td><td></td><td>🔶</td><td></td><td>🔶</td><td></td><td></td><td>🔶</td><td>🔶</td><td>✅</td><td>🔶</td></tr>
214
231
  <tr><td><a href="https://github.com/obra/superpowers">Superpowers</a></td><td>✅</td><td>🔶</td><td>🔶</td><td>🔶</td><td></td><td>🔶</td><td>✅</td><td>🔶</td><td></td><td>✅</td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td></tr>
215
- <tr><td><strong>SysProM</strong></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td></td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td></tr>
232
+ <tr><td><strong>SysProM</strong></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>🔶</td><td>✅</td><td>✅</td><td>✅</td></tr>
216
233
  </tbody>
217
234
  </table>
218
235
 
@@ -0,0 +1,2 @@
1
+ import type { CommandDef } from "../define-command.js";
2
+ export declare const inferCommand: CommandDef;
@@ -0,0 +1,206 @@
1
+ import pc from "picocolors";
2
+ import * as z from "zod";
3
+ import { readOpts, loadDoc } from "../shared.js";
4
+ import { inferCompletenessOp, inferLifecycleOp, inferImpactOp, inferDerivedOp, } from "../../operations/index.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Presentation helpers
7
+ // ---------------------------------------------------------------------------
8
+ function printCompletenessNode(r) {
9
+ const scoreColour = r.score === 1 ? pc.green : r.score >= 0.5 ? pc.yellow : pc.red;
10
+ console.log(`${pc.cyan(r.id.padEnd(12))} ${pc.dim(r.type.padEnd(16))} ${pc.bold(r.name)} ${scoreColour(`[${(r.score * 100).toFixed(0)}%]`)}`);
11
+ for (const issue of r.issues) {
12
+ console.log(` ${pc.dim("•")} ${pc.red(issue)}`);
13
+ }
14
+ }
15
+ function printLifecycleNode(r) {
16
+ const phaseColours = {
17
+ early: pc.blue,
18
+ middle: pc.yellow,
19
+ late: pc.green,
20
+ terminal: pc.red,
21
+ unknown: pc.dim,
22
+ };
23
+ const colour = phaseColours[r.inferredPhase] ?? pc.dim;
24
+ console.log(`${pc.cyan(r.id.padEnd(12))} ${pc.dim(r.type.padEnd(16))} ${pc.bold(r.name)} ${colour(`[${r.inferredPhase}]`)} ${pc.dim(r.inferredState)}`);
25
+ }
26
+ function printImpactNode(r) {
27
+ const typeColours = {
28
+ direct: pc.red,
29
+ transitive: pc.yellow,
30
+ potential: pc.blue,
31
+ };
32
+ const colour = typeColours[r.impactType] ?? pc.dim;
33
+ const nodeName = r.node ? r.node.name : "(unknown)";
34
+ const indent = " ".repeat(r.distance);
35
+ console.log(`${indent}${pc.cyan(r.id)} ${pc.dim(`(${String(r.distance)})`)} ${colour(`[${r.impactType}]`)} ${pc.bold(nodeName)}`);
36
+ }
37
+ function printDerivedRelationship(r) {
38
+ const typeColours = {
39
+ transitive: pc.yellow,
40
+ composite: pc.blue,
41
+ inverse: pc.green,
42
+ };
43
+ const colour = typeColours[r.derivationType] ?? pc.dim;
44
+ console.log(`${pc.cyan(r.from.padEnd(12))} ${colour(r.type.padEnd(20))} ${pc.cyan(r.to)} ${pc.dim(`[${r.derivationType}]`)}`);
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Arg/opt schemas
48
+ // ---------------------------------------------------------------------------
49
+ const impactArgs = z.object({
50
+ id: z.string().describe("node ID to start impact analysis from"),
51
+ });
52
+ // ---------------------------------------------------------------------------
53
+ // Subcommands
54
+ // ---------------------------------------------------------------------------
55
+ const completenessSubcommand = {
56
+ name: "completeness",
57
+ description: inferCompletenessOp.def.description,
58
+ apiLink: inferCompletenessOp.def.name,
59
+ opts: readOpts,
60
+ action(_rawArgs, rawOpts) {
61
+ const opts = readOpts.parse(rawOpts);
62
+ const { doc } = loadDoc(opts.path);
63
+ const result = inferCompletenessOp({ doc });
64
+ if (opts.json) {
65
+ console.log(JSON.stringify(result, null, 2));
66
+ }
67
+ else {
68
+ console.log(pc.bold("\nCompleteness Analysis\n") +
69
+ pc.dim(`Average score: ${(result.averageScore * 100).toFixed(1)}% | `) +
70
+ pc.green(`${String(result.completeNodes)} complete`) +
71
+ pc.dim(" | ") +
72
+ pc.red(`${String(result.incompleteNodes)} incomplete`) +
73
+ "\n");
74
+ // Show incomplete nodes first
75
+ const incomplete = result.nodes.filter((n) => n.score < 1);
76
+ const complete = result.nodes.filter((n) => n.score === 1);
77
+ if (incomplete.length > 0) {
78
+ console.log(pc.dim("Incomplete nodes:"));
79
+ for (const n of incomplete)
80
+ printCompletenessNode(n);
81
+ console.log();
82
+ }
83
+ console.log(pc.dim(`${String(complete.length)} fully complete nodes`));
84
+ }
85
+ },
86
+ };
87
+ const lifecycleSubcommand = {
88
+ name: "lifecycle",
89
+ description: inferLifecycleOp.def.description,
90
+ apiLink: inferLifecycleOp.def.name,
91
+ opts: readOpts,
92
+ action(_rawArgs, rawOpts) {
93
+ const opts = readOpts.parse(rawOpts);
94
+ const { doc } = loadDoc(opts.path);
95
+ const result = inferLifecycleOp({ doc });
96
+ if (opts.json) {
97
+ console.log(JSON.stringify(result, null, 2));
98
+ }
99
+ else {
100
+ console.log(pc.bold("\nLifecycle Analysis\n") +
101
+ pc.dim(`Early: ${String(result.summary.early)} | Middle: ${String(result.summary.middle)} | Late: ${String(result.summary.late)} | Terminal: ${String(result.summary.terminal)} | Unknown: ${String(result.summary.unknown)}`) +
102
+ "\n");
103
+ for (const n of result.nodes)
104
+ printLifecycleNode(n);
105
+ }
106
+ },
107
+ };
108
+ const impactSubcommand = {
109
+ name: "impact",
110
+ description: inferImpactOp.def.description,
111
+ apiLink: inferImpactOp.def.name,
112
+ args: impactArgs,
113
+ opts: readOpts,
114
+ action(rawArgs, rawOpts) {
115
+ const args = impactArgs.parse(rawArgs);
116
+ const opts = readOpts.parse(rawOpts);
117
+ const { doc } = loadDoc(opts.path);
118
+ const result = inferImpactOp({ doc, startId: args.id });
119
+ if (opts.json) {
120
+ console.log(JSON.stringify(result, null, 2));
121
+ }
122
+ else {
123
+ console.log(pc.bold(`\nImpact Analysis from ${args.id}\n`) +
124
+ pc.dim(`Direct: ${String(result.summary.direct)} | Transitive: ${String(result.summary.transitive)} | Potential: ${String(result.summary.potential)} | Total: ${String(result.summary.total)}`) +
125
+ "\n");
126
+ if (result.impactedNodes.length === 0) {
127
+ console.log(pc.dim("No impacted nodes found"));
128
+ }
129
+ else {
130
+ for (const n of result.impactedNodes)
131
+ printImpactNode(n);
132
+ }
133
+ }
134
+ },
135
+ };
136
+ const derivedSubcommand = {
137
+ name: "derived",
138
+ description: inferDerivedOp.def.description,
139
+ apiLink: inferDerivedOp.def.name,
140
+ opts: readOpts,
141
+ action(_rawArgs, rawOpts) {
142
+ const opts = readOpts.parse(rawOpts);
143
+ const { doc } = loadDoc(opts.path);
144
+ const result = inferDerivedOp({ doc });
145
+ if (opts.json) {
146
+ console.log(JSON.stringify(result, null, 2));
147
+ }
148
+ else {
149
+ console.log(pc.bold("\nDerived Relationships\n") +
150
+ pc.dim(`Transitive: ${String(result.summary.transitive)} | Composite: ${String(result.summary.composite)} | Inverse: ${String(result.summary.inverse)} | Total: ${String(result.summary.total)}`) +
151
+ "\n");
152
+ if (result.derivedRelationships.length === 0) {
153
+ console.log(pc.dim("No derived relationships found"));
154
+ }
155
+ else {
156
+ for (const r of result.derivedRelationships)
157
+ printDerivedRelationship(r);
158
+ }
159
+ }
160
+ },
161
+ };
162
+ const allSubcommand = {
163
+ name: "all",
164
+ description: "Run all inference analyses",
165
+ opts: readOpts,
166
+ action(_rawArgs, rawOpts) {
167
+ const opts = readOpts.parse(rawOpts);
168
+ const { doc } = loadDoc(opts.path);
169
+ // Completeness
170
+ console.log(pc.bold("\n=== Completeness ===\n"));
171
+ const completeness = inferCompletenessOp({ doc });
172
+ console.log(pc.dim(`Average score: ${(completeness.averageScore * 100).toFixed(1)}% | `) +
173
+ pc.green(`${String(completeness.completeNodes)} complete`) +
174
+ pc.dim(" | ") +
175
+ pc.red(`${String(completeness.incompleteNodes)} incomplete`));
176
+ // Lifecycle
177
+ console.log(pc.bold("\n=== Lifecycle ===\n"));
178
+ const lifecycle = inferLifecycleOp({ doc });
179
+ console.log(pc.dim(`Early: ${String(lifecycle.summary.early)} | Middle: ${String(lifecycle.summary.middle)} | Late: ${String(lifecycle.summary.late)} | Terminal: ${String(lifecycle.summary.terminal)} | Unknown: ${String(lifecycle.summary.unknown)}`));
180
+ // Derived
181
+ console.log(pc.bold("\n=== Derived Relationships ===\n"));
182
+ const derived = inferDerivedOp({ doc });
183
+ console.log(pc.dim(`Transitive: ${String(derived.summary.transitive)} | Composite: ${String(derived.summary.composite)} | Inverse: ${String(derived.summary.inverse)} | Total: ${String(derived.summary.total)}`));
184
+ if (opts.json) {
185
+ console.log(JSON.stringify({
186
+ completeness,
187
+ lifecycle,
188
+ derived,
189
+ }, null, 2));
190
+ }
191
+ },
192
+ };
193
+ // ---------------------------------------------------------------------------
194
+ // Main command
195
+ // ---------------------------------------------------------------------------
196
+ export const inferCommand = {
197
+ name: "infer",
198
+ description: "Infer completeness, lifecycle, impact, and derived relationships",
199
+ subcommands: [
200
+ completenessSubcommand,
201
+ lifecycleSubcommand,
202
+ impactSubcommand,
203
+ derivedSubcommand,
204
+ allSubcommand,
205
+ ],
206
+ };
@@ -43,6 +43,7 @@ import { speckitCommand } from "./commands/speckit.js";
43
43
  import { taskCommand } from "./commands/task.js";
44
44
  import { planCommand } from "./commands/plan.js";
45
45
  import { syncCommandDef } from "./commands/sync.js";
46
+ import { inferCommand } from "./commands/infer.js";
46
47
  export const program = new Command();
47
48
  program
48
49
  .name("sysprom")
@@ -71,6 +72,7 @@ export const commands = [
71
72
  taskCommand,
72
73
  planCommand,
73
74
  syncCommandDef,
75
+ inferCommand,
74
76
  ];
75
77
  for (const cmd of commands) {
76
78
  buildCommander(cmd, program);
@@ -6,7 +6,7 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
  export { SysProMDocument, Node, Relationship, NodeType, NodeStatus, RelationshipType, Text, Option, Operation, Task, ExternalReference, ExternalReferenceRole, Metadata, NODE_TYPE_LABELS, NODE_LABEL_TO_TYPE, RELATIONSHIP_TYPE_LABELS, RELATIONSHIP_LABEL_TO_TYPE, EXTERNAL_REFERENCE_ROLE_LABELS, EXTERNAL_REFERENCE_LABEL_TO_ROLE, NODE_STATUSES, NODE_FILE_MAP, NODE_ID_PREFIX, toJSONSchema, } from "./schema.js";
9
- export { defineOperation, type OperationDef, type DefinedOperation, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, updateMetadataOp, nextIdOp, initDocumentOp, addPlanTaskOp, updatePlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, planInitOp, planAddTaskOp, planStatusOp, planProgressOp, planGateOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, timelineOp, nodeHistoryOp, stateAtOp, validateOp, statsOp, searchOp, checkOp, graphOp, renameOp, jsonToMarkdownOp, markdownToJsonOp, speckitImportOp, speckitExportOp, speckitSyncOp, speckitDiffOp, type RemoveResult, type ValidationResult, type DocumentStats, type NodeDetail, type TraceNode, type TimelineEvent, type NodeState, type PlanStatusResult, type PhaseProgressResult, type GateResultOutput, type SyncResult, type DiffResult, } from "./operations/index.js";
9
+ export { defineOperation, type OperationDef, type DefinedOperation, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, updateMetadataOp, nextIdOp, initDocumentOp, addPlanTaskOp, updatePlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, planInitOp, planAddTaskOp, planStatusOp, planProgressOp, planGateOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, timelineOp, nodeHistoryOp, stateAtOp, validateOp, statsOp, searchOp, checkOp, graphOp, renameOp, jsonToMarkdownOp, markdownToJsonOp, speckitImportOp, speckitExportOp, speckitSyncOp, speckitDiffOp, inferCompletenessOp, inferLifecycleOp, inferImpactOp, inferDerivedOp, type RemoveResult, type ValidationResult, type DocumentStats, type NodeDetail, type TraceNode, type TimelineEvent, type NodeState, type PlanStatusResult, type PhaseProgressResult, type GateResultOutput, type SyncResult, type DiffResult, type CompletenessOutput, type LifecycleOutput, type ImpactOutput, type DerivedOutput, } from "./operations/index.js";
10
10
  export { jsonToMarkdownSingle, jsonToMarkdownMultiDoc, jsonToMarkdown, type ConvertOptions, } from "./json-to-md.js";
11
11
  export { markdownSingleToJson, markdownMultiDocToJson, markdownToJson, } from "./md-to-json.js";
12
12
  export { RELATIONSHIP_ENDPOINT_TYPES, isValidEndpointPair, } from "./endpoint-types.js";
package/dist/src/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // Schema types and validators
9
9
  export { SysProMDocument, Node, Relationship, NodeType, NodeStatus, RelationshipType, Text, Option, Operation, Task, ExternalReference, ExternalReferenceRole, Metadata, NODE_TYPE_LABELS, NODE_LABEL_TO_TYPE, RELATIONSHIP_TYPE_LABELS, RELATIONSHIP_LABEL_TO_TYPE, EXTERNAL_REFERENCE_ROLE_LABELS, EXTERNAL_REFERENCE_LABEL_TO_ROLE, NODE_STATUSES, NODE_FILE_MAP, NODE_ID_PREFIX, toJSONSchema, } from "./schema.js";
10
10
  // Operations (single source of truth for domain logic + metadata)
11
- export { defineOperation, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, updateMetadataOp, nextIdOp, initDocumentOp, addPlanTaskOp, updatePlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, planInitOp, planAddTaskOp, planStatusOp, planProgressOp, planGateOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, timelineOp, nodeHistoryOp, stateAtOp, validateOp, statsOp, searchOp, checkOp, graphOp, renameOp, jsonToMarkdownOp, markdownToJsonOp, speckitImportOp, speckitExportOp, speckitSyncOp, speckitDiffOp, } from "./operations/index.js";
11
+ export { defineOperation, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, updateMetadataOp, nextIdOp, initDocumentOp, addPlanTaskOp, updatePlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, planInitOp, planAddTaskOp, planStatusOp, planProgressOp, planGateOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, timelineOp, nodeHistoryOp, stateAtOp, validateOp, statsOp, searchOp, checkOp, graphOp, renameOp, jsonToMarkdownOp, markdownToJsonOp, speckitImportOp, speckitExportOp, speckitSyncOp, speckitDiffOp, inferCompletenessOp, inferLifecycleOp, inferImpactOp, inferDerivedOp, } from "./operations/index.js";
12
12
  // Conversion
13
13
  export { jsonToMarkdownSingle, jsonToMarkdownMultiDoc, jsonToMarkdown, } from "./json-to-md.js";
14
14
  export { markdownSingleToJson, markdownMultiDocToJson, markdownToJson, } from "./md-to-json.js";
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import * as z from "zod";
5
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",
@@ -318,6 +318,79 @@ server.registerTool("remove-relationship", {
318
318
  ],
319
319
  };
320
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
+ });
321
394
  // Start server
322
395
  async function main() {
323
396
  const transport = new StdioServerTransport();
@@ -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";