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.
- package/README.md +207 -0
- package/dist/schema.json +510 -0
- package/dist/src/canonical-json.d.ts +23 -0
- package/dist/src/canonical-json.js +120 -0
- package/dist/src/cli/commands/add.d.ts +22 -0
- package/dist/src/cli/commands/add.js +95 -0
- package/dist/src/cli/commands/check.d.ts +10 -0
- package/dist/src/cli/commands/check.js +33 -0
- package/dist/src/cli/commands/graph.d.ts +15 -0
- package/dist/src/cli/commands/graph.js +32 -0
- package/dist/src/cli/commands/init.d.ts +2 -0
- package/dist/src/cli/commands/init.js +44 -0
- package/dist/src/cli/commands/json2md.d.ts +2 -0
- package/dist/src/cli/commands/json2md.js +60 -0
- package/dist/src/cli/commands/md2json.d.ts +2 -0
- package/dist/src/cli/commands/md2json.js +29 -0
- package/dist/src/cli/commands/plan.d.ts +2 -0
- package/dist/src/cli/commands/plan.js +227 -0
- package/dist/src/cli/commands/query.d.ts +2 -0
- package/dist/src/cli/commands/query.js +275 -0
- package/dist/src/cli/commands/remove.d.ts +13 -0
- package/dist/src/cli/commands/remove.js +50 -0
- package/dist/src/cli/commands/rename.d.ts +14 -0
- package/dist/src/cli/commands/rename.js +34 -0
- package/dist/src/cli/commands/search.d.ts +11 -0
- package/dist/src/cli/commands/search.js +37 -0
- package/dist/src/cli/commands/speckit.d.ts +2 -0
- package/dist/src/cli/commands/speckit.js +318 -0
- package/dist/src/cli/commands/stats.d.ts +10 -0
- package/dist/src/cli/commands/stats.js +51 -0
- package/dist/src/cli/commands/task.d.ts +2 -0
- package/dist/src/cli/commands/task.js +162 -0
- package/dist/src/cli/commands/update.d.ts +2 -0
- package/dist/src/cli/commands/update.js +219 -0
- package/dist/src/cli/commands/validate.d.ts +10 -0
- package/dist/src/cli/commands/validate.js +30 -0
- package/dist/src/cli/define-command.d.ts +34 -0
- package/dist/src/cli/define-command.js +237 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +3 -0
- package/dist/src/cli/program.d.ts +4 -0
- package/dist/src/cli/program.js +46 -0
- package/dist/src/cli/shared.d.ts +26 -0
- package/dist/src/cli/shared.js +41 -0
- package/dist/src/generate-schema.d.ts +1 -0
- package/dist/src/generate-schema.js +9 -0
- package/dist/src/index.d.ts +48 -0
- package/dist/src/index.js +99 -0
- package/dist/src/io.d.ts +22 -0
- package/dist/src/io.js +66 -0
- package/dist/src/json-to-md.d.ts +26 -0
- package/dist/src/json-to-md.js +498 -0
- package/dist/src/md-to-json.d.ts +22 -0
- package/dist/src/md-to-json.js +548 -0
- package/dist/src/operations/add-node.d.ts +887 -0
- package/dist/src/operations/add-node.js +21 -0
- package/dist/src/operations/add-plan-task.d.ts +594 -0
- package/dist/src/operations/add-plan-task.js +25 -0
- package/dist/src/operations/add-relationship.d.ts +635 -0
- package/dist/src/operations/add-relationship.js +25 -0
- package/dist/src/operations/check.d.ts +301 -0
- package/dist/src/operations/check.js +66 -0
- package/dist/src/operations/define-operation.d.ts +14 -0
- package/dist/src/operations/define-operation.js +21 -0
- package/dist/src/operations/graph.d.ts +303 -0
- package/dist/src/operations/graph.js +71 -0
- package/dist/src/operations/index.d.ts +38 -0
- package/dist/src/operations/index.js +45 -0
- package/dist/src/operations/init-document.d.ts +299 -0
- package/dist/src/operations/init-document.js +26 -0
- package/dist/src/operations/json-to-markdown.d.ts +298 -0
- package/dist/src/operations/json-to-markdown.js +13 -0
- package/dist/src/operations/mark-task-done.d.ts +594 -0
- package/dist/src/operations/mark-task-done.js +26 -0
- package/dist/src/operations/mark-task-undone.d.ts +594 -0
- package/dist/src/operations/mark-task-undone.js +26 -0
- package/dist/src/operations/markdown-to-json.d.ts +298 -0
- package/dist/src/operations/markdown-to-json.js +13 -0
- package/dist/src/operations/next-id.d.ts +322 -0
- package/dist/src/operations/next-id.js +29 -0
- package/dist/src/operations/node-history.d.ts +313 -0
- package/dist/src/operations/node-history.js +55 -0
- package/dist/src/operations/plan-add-task.d.ts +595 -0
- package/dist/src/operations/plan-add-task.js +18 -0
- package/dist/src/operations/plan-gate.d.ts +351 -0
- package/dist/src/operations/plan-gate.js +41 -0
- package/dist/src/operations/plan-init.d.ts +299 -0
- package/dist/src/operations/plan-init.js +17 -0
- package/dist/src/operations/plan-progress.d.ts +313 -0
- package/dist/src/operations/plan-progress.js +23 -0
- package/dist/src/operations/plan-status.d.ts +349 -0
- package/dist/src/operations/plan-status.js +41 -0
- package/dist/src/operations/query-node.d.ts +1065 -0
- package/dist/src/operations/query-node.js +27 -0
- package/dist/src/operations/query-nodes.d.ts +594 -0
- package/dist/src/operations/query-nodes.js +23 -0
- package/dist/src/operations/query-relationships.d.ts +343 -0
- package/dist/src/operations/query-relationships.js +27 -0
- package/dist/src/operations/remove-node.d.ts +895 -0
- package/dist/src/operations/remove-node.js +58 -0
- package/dist/src/operations/remove-relationship.d.ts +622 -0
- package/dist/src/operations/remove-relationship.js +26 -0
- package/dist/src/operations/rename.d.ts +594 -0
- package/dist/src/operations/rename.js +113 -0
- package/dist/src/operations/search.d.ts +593 -0
- package/dist/src/operations/search.js +39 -0
- package/dist/src/operations/speckit-diff.d.ts +330 -0
- package/dist/src/operations/speckit-diff.js +89 -0
- package/dist/src/operations/speckit-export.d.ts +300 -0
- package/dist/src/operations/speckit-export.js +17 -0
- package/dist/src/operations/speckit-import.d.ts +299 -0
- package/dist/src/operations/speckit-import.js +39 -0
- package/dist/src/operations/speckit-sync.d.ts +900 -0
- package/dist/src/operations/speckit-sync.js +116 -0
- package/dist/src/operations/state-at.d.ts +309 -0
- package/dist/src/operations/state-at.js +53 -0
- package/dist/src/operations/stats.d.ts +324 -0
- package/dist/src/operations/stats.js +85 -0
- package/dist/src/operations/task-list.d.ts +305 -0
- package/dist/src/operations/task-list.js +44 -0
- package/dist/src/operations/timeline.d.ts +312 -0
- package/dist/src/operations/timeline.js +46 -0
- package/dist/src/operations/trace-from-node.d.ts +1197 -0
- package/dist/src/operations/trace-from-node.js +36 -0
- package/dist/src/operations/update-metadata.d.ts +593 -0
- package/dist/src/operations/update-metadata.js +18 -0
- package/dist/src/operations/update-node.d.ts +957 -0
- package/dist/src/operations/update-node.js +24 -0
- package/dist/src/operations/update-plan-task.d.ts +595 -0
- package/dist/src/operations/update-plan-task.js +31 -0
- package/dist/src/operations/validate.d.ts +310 -0
- package/dist/src/operations/validate.js +82 -0
- package/dist/src/schema.d.ts +891 -0
- package/dist/src/schema.js +356 -0
- package/dist/src/speckit/generate.d.ts +7 -0
- package/dist/src/speckit/generate.js +546 -0
- package/dist/src/speckit/index.d.ts +4 -0
- package/dist/src/speckit/index.js +4 -0
- package/dist/src/speckit/parse.d.ts +11 -0
- package/dist/src/speckit/parse.js +712 -0
- package/dist/src/speckit/plan.d.ts +125 -0
- package/dist/src/speckit/plan.js +636 -0
- package/dist/src/speckit/project.d.ts +39 -0
- package/dist/src/speckit/project.js +141 -0
- package/dist/src/text.d.ts +23 -0
- package/dist/src/text.js +32 -0
- package/package.json +86 -8
- 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,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
|
+
};
|