sysprom 1.23.1 → 1.25.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.
@@ -51,10 +51,15 @@ const updateNodeArgs = z.object({
51
51
  id: z.string().describe("node ID to update"),
52
52
  });
53
53
  const updateNodeOpts = mutationOpts.extend({
54
+ name: z.string().optional().describe("update node name"),
54
55
  description: z.string().optional().describe("update node description"),
55
56
  status: NodeStatus.optional().describe("set lifecycle state to true"),
56
57
  context: z.string().optional().describe("update node context"),
57
58
  rationale: z.string().optional().describe("update node rationale"),
59
+ selected: z
60
+ .string()
61
+ .optional()
62
+ .describe("set selected option on decision nodes"),
58
63
  lifecycle: z
59
64
  .array(z.string())
60
65
  .optional()
@@ -95,12 +100,16 @@ const nodeSubcommand = {
95
100
  process.exit(1);
96
101
  }
97
102
  const fields = {};
103
+ if (opts.name !== undefined)
104
+ fields.name = opts.name;
98
105
  if (opts.description !== undefined)
99
106
  fields.description = opts.description;
100
107
  if (opts.context !== undefined)
101
108
  fields.context = opts.context;
102
109
  if (opts.rationale !== undefined)
103
110
  fields.rationale = opts.rationale;
111
+ if (opts.selected !== undefined)
112
+ fields.selected = opts.selected;
104
113
  const lifecycleArgs = [...(opts.lifecycle ?? [])];
105
114
  if (opts.status !== undefined) {
106
115
  lifecycleArgs.push(`${opts.status}=true`);
@@ -111,7 +120,7 @@ const nodeSubcommand = {
111
120
  }
112
121
  if (Object.keys(fields).length === 0) {
113
122
  console.error("No fields specified to update.");
114
- console.error("Use --description, --status, --context, --rationale, or --lifecycle.");
123
+ console.error("Use --name, --description, --status, --context, --rationale, --selected, or --lifecycle.");
115
124
  process.exit(1);
116
125
  }
117
126
  const newDoc = updateNodeOp({ doc, id: args.id, fields });
@@ -160,11 +160,11 @@ function registerOption(cmd, flagName, field, desc) {
160
160
  opt.makeOptionMandatory(true);
161
161
  cmd.addOption(opt);
162
162
  }
163
- else if (optional) {
164
- cmd.addOption(new Option(`--${flagName} <value>`).hideHelp());
165
- }
166
163
  else {
167
- cmd.addOption(new Option(`--${flagName} <value>`).makeOptionMandatory(true).hideHelp());
164
+ const opt = new Option(`--${flagName} <value>`, desc);
165
+ if (!optional)
166
+ opt.makeOptionMandatory(true);
167
+ cmd.addOption(opt);
168
168
  }
169
169
  }
170
170
  function registerOptions(cmd, optsSchema) {
@@ -3,6 +3,7 @@ import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { Command } from "commander";
5
5
  import { buildCommander } from "./define-command.js";
6
+ import { formatCliError } from "./shared.js";
6
7
  let cachedVersion;
7
8
  function getVersion() {
8
9
  if (cachedVersion === undefined) {
@@ -81,3 +82,13 @@ program
81
82
  .action(async () => {
82
83
  await import("../mcp/server.js");
83
84
  });
85
+ // Global error handlers for uncaught exceptions
86
+ process.on("uncaughtException", (error) => {
87
+ console.error(formatCliError(error, false));
88
+ process.exit(1);
89
+ });
90
+ process.on("unhandledRejection", (reason) => {
91
+ const error = reason instanceof Error ? reason : new Error(String(reason));
92
+ console.error(formatCliError(error, false));
93
+ process.exit(1);
94
+ });
@@ -72,3 +72,18 @@ export declare function loadDoc(input?: string): LoadedDoc;
72
72
  * ```
73
73
  */
74
74
  export declare function persistDoc(doc: SysProMDocument, loaded: LoadedDoc, opts: MutationOpts): void;
75
+ /**
76
+ * Format an error message for CLI output with issue filing guidance.
77
+ * If the error appears to be unexpected (not a user error), suggests filing an issue.
78
+ * @param error - The error to format
79
+ * @param isUserError - Whether this is a user error (e.g. invalid input); if false, suggests filing an issue
80
+ * @returns Formatted error message
81
+ * @example
82
+ * ```ts
83
+ * try { ... } catch (err: unknown) {
84
+ * console.error(formatCliError(err, false)); // Not a user error - suggest issue
85
+ * process.exit(1);
86
+ * }
87
+ * ```
88
+ */
89
+ export declare function formatCliError(error: unknown, isUserError?: boolean): string;
@@ -207,3 +207,28 @@ export function persistDoc(doc, loaded, opts) {
207
207
  }
208
208
  }
209
209
  }
210
+ // ---------------------------------------------------------------------------
211
+ // Error formatting and reporting
212
+ // ---------------------------------------------------------------------------
213
+ const ISSUE_URL = "https://github.com/ExaDev/SysProM/issues/new";
214
+ /**
215
+ * Format an error message for CLI output with issue filing guidance.
216
+ * If the error appears to be unexpected (not a user error), suggests filing an issue.
217
+ * @param error - The error to format
218
+ * @param isUserError - Whether this is a user error (e.g. invalid input); if false, suggests filing an issue
219
+ * @returns Formatted error message
220
+ * @example
221
+ * ```ts
222
+ * try { ... } catch (err: unknown) {
223
+ * console.error(formatCliError(err, false)); // Not a user error - suggest issue
224
+ * process.exit(1);
225
+ * }
226
+ * ```
227
+ */
228
+ export function formatCliError(error, isUserError = false) {
229
+ const message = error instanceof Error ? error.message : String(error);
230
+ if (isUserError) {
231
+ return message;
232
+ }
233
+ return `${message}\n\nIf this was unexpected or bad UX, please file an issue:\n${ISSUE_URL}`;
234
+ }
@@ -7,19 +7,27 @@ 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
8
  /**
9
9
  * Wrap an error with a descriptive prefix and attach the original as cause.
10
+ * Adds issue filing guidance for unexpected errors.
10
11
  * @param prefix - The error prefix (e.g., "Failed to add node")
11
12
  * @param error - The caught error
13
+ * @param isUserError - Whether this is a user error; if false, suggests filing an issue
12
14
  * @example
13
15
  * ```ts
14
16
  * try {
15
17
  * someOperation();
16
18
  * } catch (error) {
17
- * wrapError("Failed to do X", error);
19
+ * wrapError("Failed to do X", error, false); // Not a user error - suggest issue
18
20
  * }
19
21
  * ```
20
22
  */
21
- function wrapError(prefix, error) {
22
- throw new Error(`${prefix}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
23
+ function wrapError(prefix, error, isUserError = false) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ const fullMessage = `${prefix}: ${message}`;
26
+ const issueUrl = "https://github.com/ExaDev/SysProM/issues/new";
27
+ const errorMsg = isUserError
28
+ ? fullMessage
29
+ : `${fullMessage}\n\nIf this was unexpected or bad UX, please file an issue: ${issueUrl}`;
30
+ throw new Error(errorMsg, { cause: error });
23
31
  }
24
32
  // Create MCP server instance
25
33
  const server = new McpServer({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sysprom",
3
- "version": "1.23.1",
3
+ "version": "1.25.0",
4
4
  "description": "SysProM — System Provenance Model CLI and library",
5
5
  "author": "ExaDev",
6
6
  "homepage": "https://exadev.github.io/SysProM",