sysprom 1.0.0 → 1.0.6

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.
Files changed (148) hide show
  1. package/README.md +207 -0
  2. package/dist/schema.json +510 -0
  3. package/dist/src/canonical-json.d.ts +23 -0
  4. package/dist/src/canonical-json.js +120 -0
  5. package/dist/src/cli/commands/add.d.ts +22 -0
  6. package/dist/src/cli/commands/add.js +95 -0
  7. package/dist/src/cli/commands/check.d.ts +10 -0
  8. package/dist/src/cli/commands/check.js +33 -0
  9. package/dist/src/cli/commands/graph.d.ts +15 -0
  10. package/dist/src/cli/commands/graph.js +32 -0
  11. package/dist/src/cli/commands/init.d.ts +2 -0
  12. package/dist/src/cli/commands/init.js +44 -0
  13. package/dist/src/cli/commands/json2md.d.ts +2 -0
  14. package/dist/src/cli/commands/json2md.js +60 -0
  15. package/dist/src/cli/commands/md2json.d.ts +2 -0
  16. package/dist/src/cli/commands/md2json.js +29 -0
  17. package/dist/src/cli/commands/plan.d.ts +2 -0
  18. package/dist/src/cli/commands/plan.js +227 -0
  19. package/dist/src/cli/commands/query.d.ts +2 -0
  20. package/dist/src/cli/commands/query.js +275 -0
  21. package/dist/src/cli/commands/remove.d.ts +13 -0
  22. package/dist/src/cli/commands/remove.js +50 -0
  23. package/dist/src/cli/commands/rename.d.ts +14 -0
  24. package/dist/src/cli/commands/rename.js +34 -0
  25. package/dist/src/cli/commands/search.d.ts +11 -0
  26. package/dist/src/cli/commands/search.js +37 -0
  27. package/dist/src/cli/commands/speckit.d.ts +2 -0
  28. package/dist/src/cli/commands/speckit.js +318 -0
  29. package/dist/src/cli/commands/stats.d.ts +10 -0
  30. package/dist/src/cli/commands/stats.js +51 -0
  31. package/dist/src/cli/commands/task.d.ts +2 -0
  32. package/dist/src/cli/commands/task.js +162 -0
  33. package/dist/src/cli/commands/update.d.ts +2 -0
  34. package/dist/src/cli/commands/update.js +219 -0
  35. package/dist/src/cli/commands/validate.d.ts +10 -0
  36. package/dist/src/cli/commands/validate.js +30 -0
  37. package/dist/src/cli/define-command.d.ts +34 -0
  38. package/dist/src/cli/define-command.js +237 -0
  39. package/dist/src/cli/index.d.ts +2 -0
  40. package/dist/src/cli/index.js +3 -0
  41. package/dist/src/cli/program.d.ts +4 -0
  42. package/dist/src/cli/program.js +46 -0
  43. package/dist/src/cli/shared.d.ts +26 -0
  44. package/dist/src/cli/shared.js +41 -0
  45. package/dist/src/generate-schema.d.ts +1 -0
  46. package/dist/src/generate-schema.js +9 -0
  47. package/dist/src/index.d.ts +48 -0
  48. package/dist/src/index.js +99 -0
  49. package/dist/src/io.d.ts +22 -0
  50. package/dist/src/io.js +66 -0
  51. package/dist/src/json-to-md.d.ts +26 -0
  52. package/dist/src/json-to-md.js +498 -0
  53. package/dist/src/md-to-json.d.ts +22 -0
  54. package/dist/src/md-to-json.js +548 -0
  55. package/dist/src/operations/add-node.d.ts +887 -0
  56. package/dist/src/operations/add-node.js +21 -0
  57. package/dist/src/operations/add-plan-task.d.ts +594 -0
  58. package/dist/src/operations/add-plan-task.js +25 -0
  59. package/dist/src/operations/add-relationship.d.ts +635 -0
  60. package/dist/src/operations/add-relationship.js +25 -0
  61. package/dist/src/operations/check.d.ts +301 -0
  62. package/dist/src/operations/check.js +66 -0
  63. package/dist/src/operations/define-operation.d.ts +14 -0
  64. package/dist/src/operations/define-operation.js +21 -0
  65. package/dist/src/operations/graph.d.ts +303 -0
  66. package/dist/src/operations/graph.js +71 -0
  67. package/dist/src/operations/index.d.ts +38 -0
  68. package/dist/src/operations/index.js +45 -0
  69. package/dist/src/operations/init-document.d.ts +299 -0
  70. package/dist/src/operations/init-document.js +26 -0
  71. package/dist/src/operations/json-to-markdown.d.ts +298 -0
  72. package/dist/src/operations/json-to-markdown.js +13 -0
  73. package/dist/src/operations/mark-task-done.d.ts +594 -0
  74. package/dist/src/operations/mark-task-done.js +26 -0
  75. package/dist/src/operations/mark-task-undone.d.ts +594 -0
  76. package/dist/src/operations/mark-task-undone.js +26 -0
  77. package/dist/src/operations/markdown-to-json.d.ts +298 -0
  78. package/dist/src/operations/markdown-to-json.js +13 -0
  79. package/dist/src/operations/next-id.d.ts +322 -0
  80. package/dist/src/operations/next-id.js +29 -0
  81. package/dist/src/operations/node-history.d.ts +313 -0
  82. package/dist/src/operations/node-history.js +55 -0
  83. package/dist/src/operations/plan-add-task.d.ts +595 -0
  84. package/dist/src/operations/plan-add-task.js +18 -0
  85. package/dist/src/operations/plan-gate.d.ts +351 -0
  86. package/dist/src/operations/plan-gate.js +41 -0
  87. package/dist/src/operations/plan-init.d.ts +299 -0
  88. package/dist/src/operations/plan-init.js +17 -0
  89. package/dist/src/operations/plan-progress.d.ts +313 -0
  90. package/dist/src/operations/plan-progress.js +23 -0
  91. package/dist/src/operations/plan-status.d.ts +349 -0
  92. package/dist/src/operations/plan-status.js +41 -0
  93. package/dist/src/operations/query-node.d.ts +1065 -0
  94. package/dist/src/operations/query-node.js +27 -0
  95. package/dist/src/operations/query-nodes.d.ts +594 -0
  96. package/dist/src/operations/query-nodes.js +23 -0
  97. package/dist/src/operations/query-relationships.d.ts +343 -0
  98. package/dist/src/operations/query-relationships.js +27 -0
  99. package/dist/src/operations/remove-node.d.ts +895 -0
  100. package/dist/src/operations/remove-node.js +58 -0
  101. package/dist/src/operations/remove-relationship.d.ts +622 -0
  102. package/dist/src/operations/remove-relationship.js +26 -0
  103. package/dist/src/operations/rename.d.ts +594 -0
  104. package/dist/src/operations/rename.js +113 -0
  105. package/dist/src/operations/search.d.ts +593 -0
  106. package/dist/src/operations/search.js +39 -0
  107. package/dist/src/operations/speckit-diff.d.ts +330 -0
  108. package/dist/src/operations/speckit-diff.js +89 -0
  109. package/dist/src/operations/speckit-export.d.ts +300 -0
  110. package/dist/src/operations/speckit-export.js +17 -0
  111. package/dist/src/operations/speckit-import.d.ts +299 -0
  112. package/dist/src/operations/speckit-import.js +39 -0
  113. package/dist/src/operations/speckit-sync.d.ts +900 -0
  114. package/dist/src/operations/speckit-sync.js +116 -0
  115. package/dist/src/operations/state-at.d.ts +309 -0
  116. package/dist/src/operations/state-at.js +53 -0
  117. package/dist/src/operations/stats.d.ts +324 -0
  118. package/dist/src/operations/stats.js +85 -0
  119. package/dist/src/operations/task-list.d.ts +305 -0
  120. package/dist/src/operations/task-list.js +44 -0
  121. package/dist/src/operations/timeline.d.ts +312 -0
  122. package/dist/src/operations/timeline.js +46 -0
  123. package/dist/src/operations/trace-from-node.d.ts +1197 -0
  124. package/dist/src/operations/trace-from-node.js +36 -0
  125. package/dist/src/operations/update-metadata.d.ts +593 -0
  126. package/dist/src/operations/update-metadata.js +18 -0
  127. package/dist/src/operations/update-node.d.ts +957 -0
  128. package/dist/src/operations/update-node.js +24 -0
  129. package/dist/src/operations/update-plan-task.d.ts +595 -0
  130. package/dist/src/operations/update-plan-task.js +31 -0
  131. package/dist/src/operations/validate.d.ts +310 -0
  132. package/dist/src/operations/validate.js +82 -0
  133. package/dist/src/schema.d.ts +891 -0
  134. package/dist/src/schema.js +356 -0
  135. package/dist/src/speckit/generate.d.ts +7 -0
  136. package/dist/src/speckit/generate.js +546 -0
  137. package/dist/src/speckit/index.d.ts +4 -0
  138. package/dist/src/speckit/index.js +4 -0
  139. package/dist/src/speckit/parse.d.ts +11 -0
  140. package/dist/src/speckit/parse.js +712 -0
  141. package/dist/src/speckit/plan.d.ts +125 -0
  142. package/dist/src/speckit/plan.js +636 -0
  143. package/dist/src/speckit/project.d.ts +39 -0
  144. package/dist/src/speckit/project.js +141 -0
  145. package/dist/src/text.d.ts +23 -0
  146. package/dist/src/text.js +32 -0
  147. package/package.json +86 -8
  148. package/schema.json +510 -0
@@ -0,0 +1,219 @@
1
+ import * as z from "zod";
2
+ import { RelationshipType, NodeStatus } from "../../schema.js";
3
+ import { updateNodeOp, addRelationshipOp, removeRelationshipOp, updateMetadataOp, } from "../../operations/index.js";
4
+ import { inputArg, mutationOpts, loadDoc, persistDoc } from "../shared.js";
5
+ // ---------------------------------------------------------------------------
6
+ // CLI helper functions
7
+ // ---------------------------------------------------------------------------
8
+ function parseLifecycleValue(rawVal) {
9
+ if (rawVal === "true") {
10
+ return true;
11
+ }
12
+ if (rawVal === "false") {
13
+ return false;
14
+ }
15
+ if (/^\d{4}-\d{2}-\d{2}/.test(rawVal)) {
16
+ return rawVal; // ISO date string
17
+ }
18
+ return rawVal;
19
+ }
20
+ function parseMetaValue(val) {
21
+ const numVal = Number(val);
22
+ return Number.isFinite(numVal) && val === String(numVal) ? numVal : val;
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Arg/opt schemas
26
+ // ---------------------------------------------------------------------------
27
+ const updateNodeArgs = z.object({
28
+ input: inputArg,
29
+ id: z.string().describe("node ID to update"),
30
+ });
31
+ const updateNodeOpts = mutationOpts.extend({
32
+ description: z.string().optional().describe("update node description"),
33
+ status: NodeStatus.optional().describe("update node status"),
34
+ context: z.string().optional().describe("update node context"),
35
+ rationale: z.string().optional().describe("update node rationale"),
36
+ lifecycle: z
37
+ .array(z.string())
38
+ .optional()
39
+ .describe("set lifecycle state (key=value format)"),
40
+ });
41
+ const addRelArgs = z.object({
42
+ input: inputArg,
43
+ from: z.string().describe("source node ID"),
44
+ type: RelationshipType.describe("relationship type"),
45
+ to: z.string().describe("target node ID"),
46
+ });
47
+ const addRelOpts = mutationOpts;
48
+ const removeRelArgs = z.object({
49
+ input: inputArg,
50
+ from: z.string().describe("source node ID"),
51
+ type: RelationshipType.describe("relationship type"),
52
+ to: z.string().describe("target node ID"),
53
+ });
54
+ const removeRelOpts = mutationOpts;
55
+ const metaArgs = z.object({
56
+ input: inputArg,
57
+ });
58
+ const metaOpts = mutationOpts.extend({
59
+ fields: z
60
+ .array(z.string())
61
+ .describe("metadata field updates (key=value format)"),
62
+ });
63
+ // ---------------------------------------------------------------------------
64
+ // Subcommands
65
+ // ---------------------------------------------------------------------------
66
+ const nodeSubcommand = {
67
+ name: "node",
68
+ description: updateNodeOp.def.description,
69
+ apiLink: updateNodeOp.def.name,
70
+ args: updateNodeArgs,
71
+ opts: updateNodeOpts,
72
+ action(rawArgs, rawOpts) {
73
+ const args = updateNodeArgs.parse(rawArgs);
74
+ const opts = updateNodeOpts.parse(rawOpts);
75
+ const loaded = loadDoc(args.input);
76
+ const { doc } = loaded;
77
+ const node = doc.nodes.find((n) => n.id === args.id);
78
+ if (!node) {
79
+ console.error(`Node not found: ${args.id}`);
80
+ process.exit(1);
81
+ }
82
+ const fields = {};
83
+ if (opts.description !== undefined)
84
+ fields.description = opts.description;
85
+ if (opts.status !== undefined)
86
+ fields.status = opts.status;
87
+ if (opts.context !== undefined)
88
+ fields.context = opts.context;
89
+ if (opts.rationale !== undefined)
90
+ fields.rationale = opts.rationale;
91
+ if (opts.lifecycle && opts.lifecycle.length > 0) {
92
+ const lifecycle = { ...node.lifecycle };
93
+ for (const kv of opts.lifecycle) {
94
+ const eqIdx = kv.indexOf("=");
95
+ if (eqIdx < 0) {
96
+ console.error(`Invalid --lifecycle format: ${kv} (expected key=value)`);
97
+ process.exit(1);
98
+ }
99
+ const key = kv.slice(0, eqIdx);
100
+ const rawVal = kv.slice(eqIdx + 1);
101
+ lifecycle[key] = parseLifecycleValue(rawVal);
102
+ }
103
+ fields.lifecycle = lifecycle;
104
+ }
105
+ if (Object.keys(fields).length === 0) {
106
+ console.error("No fields specified to update.");
107
+ console.error("Use --description, --status, --context, --rationale, or --lifecycle.");
108
+ process.exit(1);
109
+ }
110
+ const newDoc = updateNodeOp({ doc, id: args.id, fields });
111
+ persistDoc(newDoc, loaded, opts);
112
+ if (opts.json) {
113
+ console.log(JSON.stringify(fields, null, 2));
114
+ }
115
+ else {
116
+ console.log(`${opts.dryRun ? "[dry-run] Would update" : "Updated"} ${node.type} ${args.id} — ${node.name}`);
117
+ }
118
+ },
119
+ };
120
+ const addRelSubcommand = {
121
+ name: "add-rel",
122
+ description: addRelationshipOp.def.description,
123
+ apiLink: addRelationshipOp.def.name,
124
+ args: addRelArgs,
125
+ opts: addRelOpts,
126
+ action(rawArgs, rawOpts) {
127
+ const args = addRelArgs.parse(rawArgs);
128
+ const opts = addRelOpts.parse(rawOpts);
129
+ const loaded = loadDoc(args.input);
130
+ const { doc } = loaded;
131
+ const newDoc = addRelationshipOp({
132
+ doc,
133
+ rel: { from: args.from, to: args.to, type: args.type },
134
+ });
135
+ persistDoc(newDoc, loaded, opts);
136
+ if (opts.json) {
137
+ const rel = newDoc.relationships?.find((r) => r.from === args.from && r.type === args.type && r.to === args.to);
138
+ console.log(JSON.stringify(rel, null, 2));
139
+ }
140
+ else {
141
+ console.log(`${opts.dryRun ? "[dry-run] Would add" : "Added"} relationship: ${args.from} ${args.type} ${args.to}`);
142
+ }
143
+ },
144
+ };
145
+ const removeRelSubcommand = {
146
+ name: "remove-rel",
147
+ description: removeRelationshipOp.def.description,
148
+ apiLink: removeRelationshipOp.def.name,
149
+ args: removeRelArgs,
150
+ opts: removeRelOpts,
151
+ action(rawArgs, rawOpts) {
152
+ const args = removeRelArgs.parse(rawArgs);
153
+ const opts = removeRelOpts.parse(rawOpts);
154
+ const loaded = loadDoc(args.input);
155
+ const { doc } = loaded;
156
+ const newDoc = removeRelationshipOp({
157
+ doc,
158
+ from: args.from,
159
+ type: args.type,
160
+ to: args.to,
161
+ });
162
+ persistDoc(newDoc, loaded, opts);
163
+ if (opts.json) {
164
+ console.log(JSON.stringify({ from: args.from, type: args.type, to: args.to }, null, 2));
165
+ }
166
+ else {
167
+ console.log(`${opts.dryRun ? "[dry-run] Would remove" : "Removed"} relationship: ${args.from} ${args.type} ${args.to}`);
168
+ }
169
+ },
170
+ };
171
+ const metaSubcommand = {
172
+ name: "meta",
173
+ description: updateMetadataOp.def.description,
174
+ apiLink: updateMetadataOp.def.name,
175
+ args: metaArgs,
176
+ opts: metaOpts,
177
+ action(rawArgs, rawOpts) {
178
+ const args = metaArgs.parse(rawArgs);
179
+ const opts = metaOpts.parse(rawOpts);
180
+ if (opts.fields.length === 0) {
181
+ console.error("No metadata fields specified. Use --fields key=value");
182
+ process.exit(1);
183
+ }
184
+ const loaded = loadDoc(args.input);
185
+ const { doc } = loaded;
186
+ const fields = {};
187
+ for (const kv of opts.fields) {
188
+ const eqIdx = kv.indexOf("=");
189
+ if (eqIdx < 0) {
190
+ console.error(`Invalid --fields format: ${kv} (expected key=value)`);
191
+ process.exit(1);
192
+ }
193
+ const key = kv.slice(0, eqIdx);
194
+ const val = kv.slice(eqIdx + 1);
195
+ fields[key] = parseMetaValue(val);
196
+ }
197
+ const newDoc = updateMetadataOp({ doc, fields });
198
+ persistDoc(newDoc, loaded, opts);
199
+ if (opts.json) {
200
+ console.log(JSON.stringify(fields, null, 2));
201
+ }
202
+ else {
203
+ console.log(`${opts.dryRun ? "[dry-run] Would update" : "Updated"} metadata: ${opts.fields.join(", ")}`);
204
+ }
205
+ },
206
+ };
207
+ // ---------------------------------------------------------------------------
208
+ // Main command
209
+ // ---------------------------------------------------------------------------
210
+ export const updateCommand = {
211
+ name: "update",
212
+ description: "Update nodes, relationships, and document metadata",
213
+ subcommands: [
214
+ nodeSubcommand,
215
+ addRelSubcommand,
216
+ removeRelSubcommand,
217
+ metaSubcommand,
218
+ ],
219
+ };
@@ -0,0 +1,10 @@
1
+ import * as z from "zod";
2
+ import type { CommandDef } from "../define-command.js";
3
+ declare const argsSchema: z.ZodObject<{
4
+ input: z.ZodString;
5
+ }, z.core.$strip>;
6
+ declare const optsSchema: z.ZodObject<{
7
+ json: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
8
+ }, z.core.$strip>;
9
+ export declare const validateCommand: CommandDef<typeof argsSchema, typeof optsSchema>;
10
+ export {};
@@ -0,0 +1,30 @@
1
+ import * as z from "zod";
2
+ import pc from "picocolors";
3
+ import { validateOp } from "../../operations/index.js";
4
+ import { inputArg, readOpts, loadDoc } from "../shared.js";
5
+ const argsSchema = z.object({
6
+ input: inputArg,
7
+ });
8
+ const optsSchema = readOpts;
9
+ export const validateCommand = {
10
+ name: "validate",
11
+ description: validateOp.def.description,
12
+ apiLink: validateOp.def.name,
13
+ args: argsSchema,
14
+ opts: optsSchema,
15
+ action(args) {
16
+ const { doc } = loadDoc(args.input);
17
+ const result = validateOp({ doc });
18
+ if (result.valid) {
19
+ console.log(pc.green("Valid SysProM document."));
20
+ console.log(` ${pc.cyan(String(result.nodeCount))} nodes, ${pc.cyan(String(result.relationshipCount))} relationships`);
21
+ }
22
+ else {
23
+ console.error(pc.red(`Found ${String(result.issues.length)} issue(s):`));
24
+ for (const issue of result.issues) {
25
+ console.error(` - ${pc.red(issue)}`);
26
+ }
27
+ process.exit(1);
28
+ }
29
+ },
30
+ };
@@ -0,0 +1,34 @@
1
+ import * as z from "zod";
2
+ import { Command } from "commander";
3
+ export interface CommandDef<TArgs extends z.ZodObject<z.ZodRawShape> = z.ZodObject<z.ZodRawShape>, TOpts extends z.ZodObject<z.ZodRawShape> = z.ZodObject<z.ZodRawShape>> {
4
+ name: string;
5
+ description: string;
6
+ args?: TArgs;
7
+ opts?: TOpts;
8
+ subcommands?: CommandDef[];
9
+ action?: (args: z.infer<TArgs>, opts: z.infer<TOpts>) => void;
10
+ apiLink?: string;
11
+ }
12
+ export declare function buildCommander(def: CommandDef, parent: Command): Command;
13
+ export interface ArgDoc {
14
+ name: string;
15
+ description: string;
16
+ required: boolean;
17
+ choices?: string[];
18
+ }
19
+ export interface OptDoc {
20
+ flag: string;
21
+ description: string;
22
+ required: boolean;
23
+ repeatable: boolean;
24
+ choices?: string[];
25
+ }
26
+ export interface CommandDoc {
27
+ name: string;
28
+ description: string;
29
+ args: ArgDoc[];
30
+ opts: OptDoc[];
31
+ subcommands?: CommandDoc[];
32
+ apiLink?: string;
33
+ }
34
+ export declare function extractDocs(def: CommandDef): CommandDoc;
@@ -0,0 +1,237 @@
1
+ import { Argument, Option } from "commander";
2
+ function unwrapField(field) {
3
+ if (typeof field.removeDefault === "function")
4
+ return unwrapField(field.removeDefault());
5
+ if (fieldIsOptional(field) && typeof field.unwrap === "function")
6
+ return unwrapField(field.unwrap());
7
+ return field;
8
+ }
9
+ function fieldIsOptional(field) {
10
+ return (typeof field.safeParse === "function" && field.safeParse(undefined).success);
11
+ }
12
+ function fieldIsArray(field) {
13
+ const inner = unwrapField(field);
14
+ return "element" in inner;
15
+ }
16
+ function fieldIsBoolean(field) {
17
+ const inner = unwrapField(field);
18
+ // ZodBoolean has no unique property, but we can check safeParse
19
+ const trueResult = inner.safeParse(true);
20
+ const strResult = inner.safeParse("test");
21
+ return trueResult.success && !strResult.success;
22
+ }
23
+ function fieldChoices(field) {
24
+ const inner = unwrapField(field);
25
+ if ("options" in inner && Array.isArray(inner.options)) {
26
+ return inner.options.map(String);
27
+ }
28
+ if ("element" in inner && inner.element) {
29
+ const el = inner.element;
30
+ if ("options" in el && Array.isArray(el.options)) {
31
+ return el.options.map(String);
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+ function isZodField(obj) {
37
+ if (typeof obj !== "object" || obj === null)
38
+ return false;
39
+ // Check if it has methods/properties characteristic of Zod fields
40
+ const maybeField = toRecord(obj);
41
+ return (maybeField !== undefined &&
42
+ (typeof maybeField.safeParse === "function" ||
43
+ "description" in maybeField ||
44
+ "options" in maybeField ||
45
+ "element" in maybeField));
46
+ }
47
+ function isRecord(obj) {
48
+ return typeof obj === "object" && obj !== null;
49
+ }
50
+ function toRecord(obj) {
51
+ if (isRecord(obj)) {
52
+ return obj;
53
+ }
54
+ return undefined;
55
+ }
56
+ function fieldDescription(field) {
57
+ return field.description ?? "";
58
+ }
59
+ function camelToKebab(name) {
60
+ return name.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
61
+ }
62
+ function isZodLikeSchema(obj) {
63
+ if (typeof obj !== "object" || obj === null)
64
+ return false;
65
+ const maybeShape = toRecord(obj)?.shape;
66
+ return typeof maybeShape === "object" && maybeShape !== null;
67
+ }
68
+ function getShape(schema) {
69
+ if (isZodLikeSchema(schema)) {
70
+ return schema.shape;
71
+ }
72
+ return undefined;
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Build Commander from CommandDef
76
+ // ---------------------------------------------------------------------------
77
+ function collect(value, previous) {
78
+ return [...previous, value];
79
+ }
80
+ export function buildCommander(def, parent) {
81
+ const cmd = parent.command(def.name);
82
+ cmd.description(def.description);
83
+ {
84
+ const argsShape = getShape(def.args);
85
+ if (argsShape) {
86
+ for (const key of Object.keys(argsShape)) {
87
+ const field = argsShape[key];
88
+ if (!field || !isZodField(field))
89
+ continue;
90
+ const desc = fieldDescription(field);
91
+ const choices = fieldChoices(field);
92
+ const flagName = camelToKebab(key);
93
+ if (choices) {
94
+ cmd.addArgument(new Argument(`<${flagName}>`, desc).choices(choices));
95
+ }
96
+ else {
97
+ cmd.argument(`<${flagName}>`, desc);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ {
103
+ const optsShape = getShape(def.opts);
104
+ if (optsShape) {
105
+ for (const key of Object.keys(optsShape)) {
106
+ const field = optsShape[key];
107
+ if (!field || !isZodField(field))
108
+ continue;
109
+ const desc = fieldDescription(field);
110
+ const choices = fieldChoices(field);
111
+ const flagName = camelToKebab(key);
112
+ const optional = fieldIsOptional(field);
113
+ const isArr = fieldIsArray(field);
114
+ const isBool = fieldIsBoolean(field);
115
+ if (isBool) {
116
+ cmd.option(`--${flagName}`, desc);
117
+ }
118
+ else if (isArr) {
119
+ if (optional) {
120
+ cmd.option(`--${flagName} <value>`, desc, collect, []);
121
+ }
122
+ else {
123
+ cmd.requiredOption(`--${flagName} <value>`, desc, collect, []);
124
+ }
125
+ }
126
+ else if (choices) {
127
+ const opt = new Option(`--${flagName} <value>`, desc).choices(choices);
128
+ if (!optional)
129
+ opt.makeOptionMandatory();
130
+ cmd.addOption(opt);
131
+ }
132
+ else if (optional) {
133
+ cmd.option(`--${flagName} <value>`, desc);
134
+ }
135
+ else {
136
+ cmd.requiredOption(`--${flagName} <value>`, desc);
137
+ }
138
+ }
139
+ }
140
+ }
141
+ if (def.action) {
142
+ const action = def.action;
143
+ const argsSchema = def.args;
144
+ const optsSchema = def.opts;
145
+ cmd.action((...commanderArgs) => {
146
+ let argsKeys = [];
147
+ const argsShape = getShape(argsSchema);
148
+ if (argsShape) {
149
+ argsKeys = Object.keys(argsShape);
150
+ }
151
+ const argsObj = {};
152
+ for (let i = 0; i < argsKeys.length; i++) {
153
+ argsObj[argsKeys[i]] = commanderArgs[i];
154
+ }
155
+ const optsObjValue = commanderArgs[argsKeys.length];
156
+ const optsObj = toRecord(optsObjValue) ?? {};
157
+ const parsedArgs = argsSchema
158
+ ? argsSchema.safeParse(argsObj)
159
+ : { data: {} };
160
+ const parsedOpts = optsSchema
161
+ ? optsSchema.safeParse(optsObj)
162
+ : { data: {} };
163
+ if ("success" in parsedArgs && !parsedArgs.success) {
164
+ for (const issue of parsedArgs.error.issues) {
165
+ console.error(`${issue.path.join(".")}: ${issue.message}`);
166
+ }
167
+ process.exit(1);
168
+ }
169
+ if ("success" in parsedOpts && !parsedOpts.success) {
170
+ for (const issue of parsedOpts.error.issues) {
171
+ console.error(`${issue.path.join(".")}: ${issue.message}`);
172
+ }
173
+ process.exit(1);
174
+ }
175
+ try {
176
+ const argsData = "data" in parsedArgs ? parsedArgs.data : {};
177
+ const optsData = "data" in parsedOpts ? parsedOpts.data : {};
178
+ action(argsData, optsData);
179
+ }
180
+ catch (err) {
181
+ console.error(err instanceof Error ? err.message : String(err));
182
+ process.exit(1);
183
+ }
184
+ });
185
+ }
186
+ if (def.subcommands) {
187
+ for (const sub of def.subcommands) {
188
+ buildCommander(sub, cmd);
189
+ }
190
+ }
191
+ return cmd;
192
+ }
193
+ export function extractDocs(def) {
194
+ const args = [];
195
+ {
196
+ const argsShape = getShape(def.args);
197
+ if (argsShape) {
198
+ for (const key of Object.keys(argsShape)) {
199
+ const field = argsShape[key];
200
+ if (!field || !isZodField(field))
201
+ continue;
202
+ args.push({
203
+ name: camelToKebab(key),
204
+ description: fieldDescription(field),
205
+ required: !fieldIsOptional(field),
206
+ choices: fieldChoices(field),
207
+ });
208
+ }
209
+ }
210
+ }
211
+ const opts = [];
212
+ {
213
+ const optsShape = getShape(def.opts);
214
+ if (optsShape) {
215
+ for (const key of Object.keys(optsShape)) {
216
+ const field = optsShape[key];
217
+ if (!field || !isZodField(field))
218
+ continue;
219
+ opts.push({
220
+ flag: `--${camelToKebab(key)}`,
221
+ description: fieldDescription(field),
222
+ required: !fieldIsOptional(field),
223
+ repeatable: fieldIsArray(field),
224
+ choices: fieldChoices(field),
225
+ });
226
+ }
227
+ }
228
+ }
229
+ return {
230
+ name: def.name,
231
+ description: def.description,
232
+ args,
233
+ opts,
234
+ subcommands: def.subcommands?.map(extractDocs),
235
+ apiLink: def.apiLink,
236
+ };
237
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "./program.js";
3
+ program.parse();
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import type { CommandDef } from "./define-command.js";
3
+ export declare const program: Command;
4
+ export declare const commands: CommandDef[];
@@ -0,0 +1,46 @@
1
+ import { Command } from "commander";
2
+ import { buildCommander } from "./define-command.js";
3
+ import { validateCommand } from "./commands/validate.js";
4
+ import { statsCommand } from "./commands/stats.js";
5
+ import { json2mdCommand } from "./commands/json2md.js";
6
+ import { md2jsonCommand } from "./commands/md2json.js";
7
+ import { removeCommand } from "./commands/remove.js";
8
+ import { addCommand } from "./commands/add.js";
9
+ import { initCommand } from "./commands/init.js";
10
+ import { searchCommand } from "./commands/search.js";
11
+ import { checkCommand } from "./commands/check.js";
12
+ import { graphCommand } from "./commands/graph.js";
13
+ import { renameCommand } from "./commands/rename.js";
14
+ import { queryCommand } from "./commands/query.js";
15
+ import { updateCommand } from "./commands/update.js";
16
+ import { speckitCommand } from "./commands/speckit.js";
17
+ import { taskCommand } from "./commands/task.js";
18
+ import { planCommand } from "./commands/plan.js";
19
+ const VERSION = "1.0.0";
20
+ export const program = new Command();
21
+ program
22
+ .name("sysprom")
23
+ .description("System Provenance Model CLI — record where every part of a system came from")
24
+ .version(VERSION)
25
+ .showHelpAfterError(true);
26
+ export const commands = [
27
+ validateCommand,
28
+ statsCommand,
29
+ json2mdCommand,
30
+ md2jsonCommand,
31
+ queryCommand,
32
+ addCommand,
33
+ removeCommand,
34
+ updateCommand,
35
+ initCommand,
36
+ searchCommand,
37
+ checkCommand,
38
+ graphCommand,
39
+ renameCommand,
40
+ speckitCommand,
41
+ taskCommand,
42
+ planCommand,
43
+ ];
44
+ for (const cmd of commands) {
45
+ buildCommander(cmd, program);
46
+ }
@@ -0,0 +1,26 @@
1
+ import * as z from "zod";
2
+ import { type Format } from "../io.js";
3
+ import type { SysProMDocument } from "../schema.js";
4
+ /** Positional argument for a SysProM document path. */
5
+ export declare const inputArg: z.ZodString;
6
+ /** Common output/persistence options for read-only commands. */
7
+ export declare const readOpts: z.ZodObject<{
8
+ json: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
9
+ }, z.core.$strip>;
10
+ /** Common output/persistence options for mutation commands. */
11
+ export declare const mutationOpts: z.ZodObject<{
12
+ json: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
+ dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
14
+ sync: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>;
16
+ export type ReadOpts = z.infer<typeof readOpts>;
17
+ export type MutationOpts = z.infer<typeof mutationOpts>;
18
+ export interface LoadedDoc {
19
+ doc: SysProMDocument;
20
+ format: Format;
21
+ path: string;
22
+ }
23
+ /** Load a document from a CLI input path. */
24
+ export declare function loadDoc(input: string): LoadedDoc;
25
+ /** Persist a document and optionally sync to markdown. */
26
+ export declare function persistDoc(doc: SysProMDocument, loaded: LoadedDoc, opts: MutationOpts): void;
@@ -0,0 +1,41 @@
1
+ import * as z from "zod";
2
+ import { loadDocument, saveDocument } from "../io.js";
3
+ import { jsonToMarkdownMultiDoc } from "../json-to-md.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Reusable CLI schemas — shared across all commands
6
+ // ---------------------------------------------------------------------------
7
+ /** Positional argument for a SysProM document path. */
8
+ export const inputArg = z
9
+ .string()
10
+ .describe("SysProM document path (JSON, .md, or directory)");
11
+ /** Common output/persistence options for read-only commands. */
12
+ export const readOpts = z.object({
13
+ json: z.boolean().optional().default(false).describe("output as JSON"),
14
+ });
15
+ /** Common output/persistence options for mutation commands. */
16
+ export const mutationOpts = z.object({
17
+ json: z.boolean().optional().default(false).describe("output as JSON"),
18
+ dryRun: z
19
+ .boolean()
20
+ .optional()
21
+ .default(false)
22
+ .describe("preview changes without saving"),
23
+ sync: z
24
+ .string()
25
+ .optional()
26
+ .describe("sync to markdown directory after saving"),
27
+ });
28
+ /** Load a document from a CLI input path. */
29
+ export function loadDoc(input) {
30
+ return loadDocument(input);
31
+ }
32
+ /** Persist a document and optionally sync to markdown. */
33
+ export function persistDoc(doc, loaded, opts) {
34
+ if (!opts.dryRun) {
35
+ saveDocument(doc, loaded.format, loaded.path);
36
+ if (opts.sync) {
37
+ jsonToMarkdownMultiDoc(doc, opts.sync);
38
+ console.log(`Synced to ${opts.sync}`);
39
+ }
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { resolve, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { canonicalise } from "./canonical-json.js";
5
+ import { toJSONSchema } from "./schema.js";
6
+ const schema = toJSONSchema();
7
+ const outPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "schema.json");
8
+ writeFileSync(outPath, canonicalise(schema, { indent: "\t" }) + "\n");
9
+ console.log(`Written to ${outPath}`);