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,318 @@
1
+ import * as z from "zod";
2
+ import { resolve, dirname } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { loadDocument, saveDocument } from "../../io.js";
5
+ import { parseSpecKitFeature } from "../../speckit/parse.js";
6
+ import { generateSpecKitProject } from "../../speckit/generate.js";
7
+ import { detectSpecKitProject } from "../../speckit/project.js";
8
+ import { speckitImportOp, speckitExportOp, speckitSyncOp, speckitDiffOp, } from "../../operations/index.js";
9
+ // ============================================================================
10
+ // Helper: Format detection
11
+ // ============================================================================
12
+ function detectFormat(outputPath) {
13
+ if (outputPath.endsWith(".json") || outputPath.endsWith(".spm.json")) {
14
+ return "json";
15
+ }
16
+ if (outputPath.endsWith(".md") || outputPath.endsWith(".spm.md")) {
17
+ return "single-md";
18
+ }
19
+ return "json"; // default
20
+ }
21
+ function compareDocuments(oldDoc, newDoc) {
22
+ const oldNodes = new Map(oldDoc.nodes.map((n) => [n.id, n]));
23
+ const newNodes = new Map(newDoc.nodes.map((n) => [n.id, n]));
24
+ const added = [];
25
+ const modified = [];
26
+ const removed = [];
27
+ // Find added and modified
28
+ for (const [id, newNode] of newNodes) {
29
+ const oldNode = oldNodes.get(id);
30
+ if (!oldNode) {
31
+ added.push(newNode);
32
+ }
33
+ else if (JSON.stringify(oldNode) !== JSON.stringify(newNode)) {
34
+ modified.push({ old: oldNode, new: newNode });
35
+ }
36
+ }
37
+ // Find removed
38
+ for (const [id, oldNode] of oldNodes) {
39
+ if (!newNodes.has(id)) {
40
+ removed.push(oldNode);
41
+ }
42
+ }
43
+ return { added, modified, removed };
44
+ }
45
+ // ============================================================================
46
+ // Subcommands
47
+ // ============================================================================
48
+ const importArgs = z.object({
49
+ speckitDir: z.string().describe("Path to Spec-Kit feature directory"),
50
+ output: z.string().describe("Path to output SysProM file"),
51
+ });
52
+ const importOpts = z.object({
53
+ prefix: z
54
+ .string()
55
+ .optional()
56
+ .describe("ID prefix (defaults to directory name)"),
57
+ });
58
+ const importSubcommand = {
59
+ name: "import",
60
+ description: speckitImportOp.def.description,
61
+ args: importArgs,
62
+ opts: importOpts,
63
+ action(args, opts) {
64
+ const specKitDir = resolve(args.speckitDir);
65
+ const outputPath = resolve(args.output);
66
+ if (!existsSync(specKitDir)) {
67
+ console.error(`Error: Spec-Kit directory does not exist: ${specKitDir}`);
68
+ process.exit(1);
69
+ }
70
+ // Determine the prefix: use flag if provided, otherwise use directory name
71
+ const idPrefix = opts.prefix ?? specKitDir.split("/").pop() ?? "FEAT";
72
+ // Find constitution file by detecting the project from parent directories
73
+ let constitutionPath;
74
+ let searchDir = dirname(specKitDir);
75
+ for (let i = 0; i < 5; i++) {
76
+ // Search up to 5 levels
77
+ const project = detectSpecKitProject(searchDir);
78
+ if (project.constitutionPath) {
79
+ constitutionPath = project.constitutionPath;
80
+ break;
81
+ }
82
+ const parent = dirname(searchDir);
83
+ if (parent === searchDir)
84
+ break; // reached root
85
+ searchDir = parent;
86
+ }
87
+ // Parse Spec-Kit feature
88
+ const doc = parseSpecKitFeature(specKitDir, idPrefix, constitutionPath);
89
+ // Determine output format
90
+ const format = detectFormat(outputPath);
91
+ // Save document
92
+ saveDocument(doc, format, outputPath);
93
+ // Print summary
94
+ const nodeCount = doc.nodes.length;
95
+ const relationshipCount = doc.relationships?.length ?? 0;
96
+ console.log(`Imported Spec-Kit from ${specKitDir} to ${outputPath}`);
97
+ console.log(` ${String(nodeCount)} nodes, ${String(relationshipCount)} relationships`);
98
+ },
99
+ };
100
+ const exportArgs = z.object({
101
+ input: z.string().describe("Path to SysProM document"),
102
+ speckitDir: z.string().describe("Path to Spec-Kit output directory"),
103
+ });
104
+ const exportOpts = z.object({
105
+ prefix: z.string().describe("ID prefix identifying nodes to export"),
106
+ });
107
+ const exportSubcommand = {
108
+ name: "export",
109
+ description: speckitExportOp.def.description,
110
+ args: exportArgs,
111
+ opts: exportOpts,
112
+ action(args, opts) {
113
+ const inputPath = resolve(args.input);
114
+ const specKitDir = resolve(args.speckitDir);
115
+ if (!opts.prefix) {
116
+ console.error("Error: --prefix flag is required for export (identifies which nodes to export)");
117
+ process.exit(1);
118
+ }
119
+ // Load SysProM document
120
+ const { doc } = loadDocument(inputPath);
121
+ // Generate Spec-Kit project
122
+ generateSpecKitProject(doc, specKitDir, opts.prefix);
123
+ // Print summary
124
+ console.log(`Exported SysProM document from ${inputPath} to ${specKitDir}`);
125
+ console.log(` Generated Spec-Kit files with prefix: ${opts.prefix}`);
126
+ },
127
+ };
128
+ const syncArgs = z.object({
129
+ input: z.string().describe("Path to SysProM document"),
130
+ speckitDir: z.string().describe("Path to Spec-Kit directory"),
131
+ });
132
+ const syncOpts = z.object({
133
+ prefix: z
134
+ .string()
135
+ .optional()
136
+ .describe("ID prefix (defaults to directory name)"),
137
+ });
138
+ const syncSubcommand = {
139
+ name: "sync",
140
+ description: speckitSyncOp.def.description,
141
+ args: syncArgs,
142
+ opts: syncOpts,
143
+ action(args, opts) {
144
+ const inputPath = resolve(args.input);
145
+ const specKitDir = resolve(args.speckitDir);
146
+ if (!existsSync(inputPath)) {
147
+ console.error(`Error: Input file does not exist: ${inputPath}`);
148
+ process.exit(1);
149
+ }
150
+ if (!existsSync(specKitDir)) {
151
+ console.error(`Error: Spec-Kit directory does not exist: ${specKitDir}`);
152
+ process.exit(1);
153
+ }
154
+ // Determine the prefix: use flag if provided, otherwise use directory name
155
+ const idPrefix = opts.prefix ?? specKitDir.split("/").pop() ?? "FEAT";
156
+ // Load SysProM document
157
+ const { doc: syspromDoc, format } = loadDocument(inputPath);
158
+ // Find constitution file
159
+ let constitutionPath;
160
+ let searchDir = dirname(specKitDir);
161
+ for (let i = 0; i < 5; i++) {
162
+ const project = detectSpecKitProject(searchDir);
163
+ if (project.constitutionPath) {
164
+ constitutionPath = project.constitutionPath;
165
+ break;
166
+ }
167
+ const parent = dirname(searchDir);
168
+ if (parent === searchDir)
169
+ break;
170
+ searchDir = parent;
171
+ }
172
+ // Parse Spec-Kit feature
173
+ const specKitDoc = parseSpecKitFeature(specKitDir, idPrefix, constitutionPath);
174
+ // Compare documents
175
+ const diff = compareDocuments(syspromDoc, specKitDoc);
176
+ // Merge: Spec-Kit wins for content (description, status), SysProM wins for structure
177
+ const mergedNodes = new Map(syspromDoc.nodes.map((n) => [n.id, n]));
178
+ const specKitNodes = new Map(specKitDoc.nodes.map((n) => [n.id, n]));
179
+ for (const [id, specKitNode] of specKitNodes) {
180
+ const syspromNode = mergedNodes.get(id);
181
+ if (!syspromNode) {
182
+ // Add new node from Spec-Kit
183
+ mergedNodes.set(id, specKitNode);
184
+ }
185
+ else {
186
+ // Merge: Spec-Kit content wins, SysProM structure wins
187
+ const merged = {
188
+ ...syspromNode,
189
+ description: specKitNode.description ?? syspromNode.description,
190
+ status: specKitNode.status ?? syspromNode.status,
191
+ context: specKitNode.context ?? syspromNode.context,
192
+ options: specKitNode.options ?? syspromNode.options,
193
+ selected: specKitNode.selected ?? syspromNode.selected,
194
+ rationale: specKitNode.rationale ?? syspromNode.rationale,
195
+ scope: specKitNode.scope ?? syspromNode.scope,
196
+ operations: specKitNode.operations ?? syspromNode.operations,
197
+ plan: specKitNode.plan ?? syspromNode.plan,
198
+ };
199
+ mergedNodes.set(id, merged);
200
+ }
201
+ }
202
+ // Remove nodes that were deleted in Spec-Kit
203
+ for (const node of diff.removed) {
204
+ mergedNodes.delete(node.id);
205
+ }
206
+ // Update merged document
207
+ const mergedDoc = {
208
+ ...syspromDoc,
209
+ nodes: Array.from(mergedNodes.values()),
210
+ relationships: syspromDoc.relationships, // Keep original relationships
211
+ };
212
+ // Save updated SysProM document
213
+ saveDocument(mergedDoc, format, inputPath);
214
+ // Re-generate Spec-Kit files from merged document
215
+ generateSpecKitProject(mergedDoc, specKitDir, idPrefix);
216
+ // Print what changed
217
+ console.log(`Synced SysProM document with Spec-Kit directory`);
218
+ if (diff.added.length > 0) {
219
+ console.log(` Added: ${String(diff.added.length)} node(s)`);
220
+ }
221
+ if (diff.modified.length > 0) {
222
+ console.log(` Modified: ${String(diff.modified.length)} node(s)`);
223
+ }
224
+ if (diff.removed.length > 0) {
225
+ console.log(` Removed: ${String(diff.removed.length)} node(s)`);
226
+ }
227
+ },
228
+ };
229
+ const diffArgs = z.object({
230
+ input: z.string().describe("Path to SysProM document"),
231
+ speckitDir: z.string().describe("Path to Spec-Kit directory"),
232
+ });
233
+ const diffOpts = z.object({
234
+ prefix: z
235
+ .string()
236
+ .optional()
237
+ .describe("ID prefix (defaults to directory name)"),
238
+ });
239
+ const diffSubcommand = {
240
+ name: "diff",
241
+ description: speckitDiffOp.def.description,
242
+ args: diffArgs,
243
+ opts: diffOpts,
244
+ action(args, opts) {
245
+ const inputPath = resolve(args.input);
246
+ const specKitDir = resolve(args.speckitDir);
247
+ if (!existsSync(inputPath)) {
248
+ console.error(`Error: Input file does not exist: ${inputPath}`);
249
+ process.exit(1);
250
+ }
251
+ if (!existsSync(specKitDir)) {
252
+ console.error(`Error: Spec-Kit directory does not exist: ${specKitDir}`);
253
+ process.exit(1);
254
+ }
255
+ // Determine the prefix: use flag if provided, otherwise use directory name
256
+ const idPrefix = opts.prefix ?? specKitDir.split("/").pop() ?? "FEAT";
257
+ // Load SysProM document
258
+ const { doc: syspromDoc } = loadDocument(inputPath);
259
+ // Find constitution file
260
+ let constitutionPath;
261
+ let searchDir = dirname(specKitDir);
262
+ for (let i = 0; i < 5; i++) {
263
+ const project = detectSpecKitProject(searchDir);
264
+ if (project.constitutionPath) {
265
+ constitutionPath = project.constitutionPath;
266
+ break;
267
+ }
268
+ const parent = dirname(searchDir);
269
+ if (parent === searchDir)
270
+ break;
271
+ searchDir = parent;
272
+ }
273
+ // Parse Spec-Kit feature
274
+ const specKitDoc = parseSpecKitFeature(specKitDir, idPrefix, constitutionPath);
275
+ // Compare documents
276
+ const diff = compareDocuments(syspromDoc, specKitDoc);
277
+ // Print what would change (read-only)
278
+ console.log(`Diff between SysProM document and Spec-Kit directory:`);
279
+ if (diff.added.length === 0 &&
280
+ diff.modified.length === 0 &&
281
+ diff.removed.length === 0) {
282
+ console.log(` (no changes)`);
283
+ }
284
+ else {
285
+ if (diff.added.length > 0) {
286
+ console.log(` Added: ${String(diff.added.length)} node(s)`);
287
+ for (const node of diff.added) {
288
+ console.log(` - ${node.id}: ${node.name}`);
289
+ }
290
+ }
291
+ if (diff.modified.length > 0) {
292
+ console.log(` Modified: ${String(diff.modified.length)} node(s)`);
293
+ for (const { old } of diff.modified) {
294
+ console.log(` - ${old.id}: ${old.name}`);
295
+ }
296
+ }
297
+ if (diff.removed.length > 0) {
298
+ console.log(` Removed: ${String(diff.removed.length)} node(s)`);
299
+ for (const node of diff.removed) {
300
+ console.log(` - ${node.id}: ${node.name}`);
301
+ }
302
+ }
303
+ }
304
+ },
305
+ };
306
+ // ============================================================================
307
+ // Main command
308
+ // ============================================================================
309
+ export const speckitCommand = {
310
+ name: "speckit",
311
+ description: "Spec-Kit interoperability — import, export, sync, and diff",
312
+ subcommands: [
313
+ importSubcommand,
314
+ exportSubcommand,
315
+ syncSubcommand,
316
+ diffSubcommand,
317
+ ],
318
+ };
@@ -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 statsCommand: CommandDef<typeof argsSchema, typeof optsSchema>;
10
+ export {};
@@ -0,0 +1,51 @@
1
+ import * as z from "zod";
2
+ import pc from "picocolors";
3
+ import { statsOp } 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 statsCommand = {
10
+ name: "stats",
11
+ description: statsOp.def.description,
12
+ apiLink: statsOp.def.name,
13
+ args: argsSchema,
14
+ opts: optsSchema,
15
+ action(args) {
16
+ const { doc } = loadDoc(args.input);
17
+ const s = statsOp({ doc });
18
+ console.log(`${pc.bold("SysProM Document")}: ${s.title}`);
19
+ console.log("");
20
+ console.log(pc.bold("Nodes by type:"));
21
+ for (const [type, count] of Object.entries(s.nodesByType).sort()) {
22
+ console.log(` ${type.padEnd(20)} ${pc.cyan(String(count))}`);
23
+ }
24
+ console.log(` ${"TOTAL".padEnd(20)} ${pc.cyan(String(s.totalNodes))}`);
25
+ console.log("");
26
+ console.log(pc.bold("Relationships by type:"));
27
+ for (const [type, count] of Object.entries(s.relationshipsByType).sort()) {
28
+ console.log(` ${type.padEnd(20)} ${pc.cyan(String(count))}`);
29
+ }
30
+ console.log(` ${"TOTAL".padEnd(20)} ${pc.cyan(String(s.totalRelationships))}`);
31
+ console.log("");
32
+ console.log(`${pc.dim("Subsystems")}: ${pc.cyan(String(s.subsystemCount))}`);
33
+ console.log(`${pc.dim("Max subsystem depth")}: ${pc.cyan(String(s.maxSubsystemDepth))}`);
34
+ console.log(`${pc.dim("Views")}: ${pc.cyan(String(s.viewCount))}`);
35
+ console.log(`${pc.dim("External references")}: ${pc.cyan(String(s.externalReferenceCount))}`);
36
+ if (Object.keys(s.decisionLifecycle).length > 0) {
37
+ console.log("");
38
+ console.log(pc.bold("Decision lifecycle:"));
39
+ for (const [state, count] of Object.entries(s.decisionLifecycle).sort()) {
40
+ console.log(` ${state.padEnd(20)} ${pc.cyan(String(count))}`);
41
+ }
42
+ }
43
+ if (Object.keys(s.changeLifecycle).length > 0) {
44
+ console.log("");
45
+ console.log(pc.bold("Change lifecycle:"));
46
+ for (const [state, count] of Object.entries(s.changeLifecycle).sort()) {
47
+ console.log(` ${state.padEnd(20)} ${pc.cyan(String(count))}`);
48
+ }
49
+ }
50
+ },
51
+ };
@@ -0,0 +1,2 @@
1
+ import type { CommandDef } from "../define-command.js";
2
+ export declare const taskCommand: CommandDef;
@@ -0,0 +1,162 @@
1
+ import * as z from "zod";
2
+ import { loadDocument, saveDocument } from "../../io.js";
3
+ import { addPlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, } from "../../operations/index.js";
4
+ // ============================================================================
5
+ // Subcommands
6
+ // ============================================================================
7
+ const listArgs = z.object({
8
+ input: z.string().describe("Path to SysProM document"),
9
+ });
10
+ const listOpts = z.object({
11
+ change: z.string().optional().describe("Filter by change ID"),
12
+ pending: z.boolean().optional().describe("Show only pending tasks"),
13
+ json: z.boolean().optional().describe("Output as JSON"),
14
+ });
15
+ const listSubcommand = {
16
+ name: "list",
17
+ description: taskListOp.def.description,
18
+ apiLink: taskListOp.def.name,
19
+ args: listArgs,
20
+ opts: listOpts,
21
+ action(args, opts) {
22
+ const { doc } = loadDocument(args.input);
23
+ try {
24
+ const rows = taskListOp({
25
+ doc,
26
+ changeId: opts.change,
27
+ pendingOnly: opts.pending,
28
+ });
29
+ if (opts.json) {
30
+ console.log(JSON.stringify(rows, null, 2));
31
+ return;
32
+ }
33
+ const header = `${"Change".padEnd(12)} ${"#".padEnd(4)} ${"Done".padEnd(6)} Description`;
34
+ const divider = `${"-".repeat(12)} ${"-".repeat(4)} ${"-".repeat(6)} ${"-".repeat(30)}`;
35
+ console.log(header);
36
+ console.log(divider);
37
+ for (const row of rows) {
38
+ const doneStr = row.done ? "[x]" : "[ ]";
39
+ console.log(`${row.changeId.padEnd(12)} ${String(row.index).padEnd(4)} ${doneStr.padEnd(6)} ${row.description}`);
40
+ }
41
+ console.log(`\n${String(rows.length)} task(s)`);
42
+ }
43
+ catch (err) {
44
+ console.error(err instanceof Error ? err.message : String(err));
45
+ process.exit(1);
46
+ }
47
+ },
48
+ };
49
+ const addArgs = z.object({
50
+ input: z.string().describe("Path to SysProM document"),
51
+ changeId: z.string().describe("Change node ID"),
52
+ description: z.string().describe("Task description"),
53
+ });
54
+ const addOpts = z.object({});
55
+ const addSubcommand = {
56
+ name: "add",
57
+ description: addPlanTaskOp.def.description,
58
+ apiLink: addPlanTaskOp.def.name,
59
+ args: addArgs,
60
+ opts: addOpts,
61
+ action(args) {
62
+ const { doc, format, path } = loadDocument(args.input);
63
+ try {
64
+ const newDoc = addPlanTaskOp({
65
+ doc,
66
+ changeId: args.changeId,
67
+ description: args.description,
68
+ });
69
+ saveDocument(newDoc, format, path);
70
+ const node = newDoc.nodes.find((n) => n.id === args.changeId);
71
+ if (!node)
72
+ throw new Error(`Node ${args.changeId} not found`);
73
+ const newIndex = (node.plan?.length ?? 1) - 1;
74
+ console.log(`Added task ${String(newIndex)} to ${args.changeId}`);
75
+ }
76
+ catch (err) {
77
+ console.error(err instanceof Error ? err.message : String(err));
78
+ process.exit(1);
79
+ }
80
+ },
81
+ };
82
+ const doneArgs = z.object({
83
+ input: z.string().describe("Path to SysProM document"),
84
+ changeId: z.string().describe("Change node ID"),
85
+ taskIndex: z.string().describe("Task index"),
86
+ });
87
+ const doneOpts = z.object({});
88
+ const doneSubcommand = {
89
+ name: "done",
90
+ description: markTaskDoneOp.def.description,
91
+ apiLink: markTaskDoneOp.def.name,
92
+ args: doneArgs,
93
+ opts: doneOpts,
94
+ action(args) {
95
+ const { doc, format, path } = loadDocument(args.input);
96
+ const taskIndex = parseInt(args.taskIndex, 10);
97
+ if (isNaN(taskIndex) || taskIndex < 0) {
98
+ console.error(`Invalid task index: ${args.taskIndex}`);
99
+ process.exit(1);
100
+ }
101
+ try {
102
+ const newDoc = markTaskDoneOp({
103
+ doc,
104
+ changeId: args.changeId,
105
+ taskIndex,
106
+ });
107
+ saveDocument(newDoc, format, path);
108
+ console.log(`Marked task ${String(taskIndex)} done on ${args.changeId}`);
109
+ }
110
+ catch (err) {
111
+ console.error(err instanceof Error ? err.message : String(err));
112
+ process.exit(1);
113
+ }
114
+ },
115
+ };
116
+ const undoneArgs = z.object({
117
+ input: z.string().describe("Path to SysProM document"),
118
+ changeId: z.string().describe("Change node ID"),
119
+ taskIndex: z.string().describe("Task index"),
120
+ });
121
+ const undoneOpts = z.object({});
122
+ const undoneSubcommand = {
123
+ name: "undone",
124
+ description: markTaskUndoneOp.def.description,
125
+ apiLink: markTaskUndoneOp.def.name,
126
+ args: undoneArgs,
127
+ opts: undoneOpts,
128
+ action(args) {
129
+ const { doc, format, path } = loadDocument(args.input);
130
+ const taskIndex = parseInt(args.taskIndex, 10);
131
+ if (isNaN(taskIndex) || taskIndex < 0) {
132
+ console.error(`Invalid task index: ${args.taskIndex}`);
133
+ process.exit(1);
134
+ }
135
+ try {
136
+ const newDoc = markTaskUndoneOp({
137
+ doc,
138
+ changeId: args.changeId,
139
+ taskIndex,
140
+ });
141
+ saveDocument(newDoc, format, path);
142
+ console.log(`Marked task ${String(taskIndex)} undone on ${args.changeId}`);
143
+ }
144
+ catch (err) {
145
+ console.error(err instanceof Error ? err.message : String(err));
146
+ process.exit(1);
147
+ }
148
+ },
149
+ };
150
+ // ============================================================================
151
+ // Main command
152
+ // ============================================================================
153
+ export const taskCommand = {
154
+ name: "task",
155
+ description: "Manage tasks within change nodes",
156
+ subcommands: [
157
+ listSubcommand,
158
+ addSubcommand,
159
+ doneSubcommand,
160
+ undoneSubcommand,
161
+ ],
162
+ };
@@ -0,0 +1,2 @@
1
+ import type { CommandDef } from "../define-command.js";
2
+ export declare const updateCommand: CommandDef;