sysprom 1.3.0 → 1.5.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.
@@ -7,9 +7,9 @@ declare const optsSchema: z.ZodObject<{
7
7
  title: z.ZodOptional<z.ZodString>;
8
8
  scope: z.ZodOptional<z.ZodString>;
9
9
  format: z.ZodOptional<z.ZodEnum<{
10
- dir: "dir";
11
10
  json: "json";
12
11
  md: "md";
12
+ dir: "dir";
13
13
  }>>;
14
14
  }, z.core.$strict>;
15
15
  export declare const initCommand: CommandDef<typeof argsSchema, typeof optsSchema>;
@@ -147,13 +147,13 @@ const removeRelSubcommand = {
147
147
  const opts = removeRelOpts.parse(rawOpts);
148
148
  const loaded = loadDoc(opts.path);
149
149
  const { doc } = loaded;
150
- const newDoc = removeRelationshipOp({
150
+ const result = removeRelationshipOp({
151
151
  doc,
152
152
  from: args.from,
153
153
  type: args.type,
154
154
  to: args.to,
155
155
  });
156
- persistDoc(newDoc, loaded, opts);
156
+ persistDoc(result.doc, loaded, opts);
157
157
  if (opts.json) {
158
158
  console.log(JSON.stringify({ from: args.from, type: args.type, to: args.to }, null, 2));
159
159
  }
@@ -32,6 +32,7 @@ export { graphOp } from "./graph.js";
32
32
  export { renameOp } from "./rename.js";
33
33
  export { jsonToMarkdownOp } from "./json-to-markdown.js";
34
34
  export { markdownToJsonOp } from "./markdown-to-json.js";
35
+ export { syncDocumentsOp, type BidirectionalSyncResult, type ConflictStrategy, } from "./sync.js";
35
36
  export { speckitImportOp } from "./speckit-import.js";
36
37
  export { speckitExportOp } from "./speckit-export.js";
37
38
  export { speckitSyncOp, type SyncResult } from "./speckit-sync.js";
@@ -38,6 +38,8 @@ export { renameOp } from "./rename.js";
38
38
  // Conversion operations
39
39
  export { jsonToMarkdownOp } from "./json-to-markdown.js";
40
40
  export { markdownToJsonOp } from "./markdown-to-json.js";
41
+ // Synchronisation operations
42
+ export { syncDocumentsOp, } from "./sync.js";
41
43
  // Spec-Kit interoperability operations
42
44
  export { speckitImportOp } from "./speckit-import.js";
43
45
  export { speckitExportOp } from "./speckit-export.js";
@@ -602,6 +602,9 @@ export declare const removeNodeOp: import("./define-operation.js").DefinedOperat
602
602
  };
603
603
  };
604
604
  id: z.ZodString;
605
+ hard: z.ZodOptional<z.ZodBoolean>;
606
+ recursive: z.ZodOptional<z.ZodBoolean>;
607
+ repair: z.ZodOptional<z.ZodBoolean>;
605
608
  }, z.core.$strip>, z.ZodObject<{
606
609
  doc: z.ZodObject<{
607
610
  $schema: z.ZodOptional<z.ZodString>;
@@ -17,17 +17,71 @@ export const removeNodeOp = defineOperation({
17
17
  input: z.object({
18
18
  doc: SysProMDocument,
19
19
  id: z.string(),
20
+ hard: z.boolean().optional(),
21
+ recursive: z.boolean().optional(),
22
+ repair: z.boolean().optional(),
20
23
  }),
21
24
  output: RemoveResult,
22
- fn({ doc, id }) {
25
+ fn({ doc, id, hard, recursive, repair }) {
23
26
  const nodeIdx = doc.nodes.findIndex((n) => n.id === id);
24
27
  if (nodeIdx === -1) {
25
28
  throw new Error(`Node not found: ${id}`);
26
29
  }
30
+ const nodeToRemove = doc.nodes[nodeIdx];
27
31
  const warnings = [];
28
- // Remove the node
29
- const newNodes = doc.nodes.filter((n) => n.id !== id);
30
- // Clean up all references to the removed node
32
+ // Check recursive guard for hard delete
33
+ if (hard && nodeToRemove.subsystem) {
34
+ if (!recursive) {
35
+ throw new Error(`Cannot hard delete node ${id} with subsystem without --recursive flag`);
36
+ }
37
+ }
38
+ let newNodes;
39
+ let newRelationships = doc.relationships ?? [];
40
+ if (hard) {
41
+ // Hard delete: physically remove the node
42
+ newNodes = doc.nodes.filter((n) => n.id !== id);
43
+ // Handle must_follow chain repair if requested
44
+ if (repair) {
45
+ const incomingChains = newRelationships.filter((r) => r.to === id && r.type === "must_follow");
46
+ const outgoingChains = newRelationships.filter((r) => r.from === id && r.type === "must_follow");
47
+ // Remove all relationships involving the deleted node
48
+ newRelationships = newRelationships.filter((r) => r.from !== id && r.to !== id);
49
+ // Repair chains by connecting incoming to outgoing
50
+ // Only repair if there are both incoming AND outgoing chains
51
+ if (incomingChains.length > 0 && outgoingChains.length > 0) {
52
+ for (const incoming of incomingChains) {
53
+ for (const outgoing of outgoingChains) {
54
+ // Only add if not already connected
55
+ const exists = newRelationships.some((r) => r.from === incoming.from &&
56
+ r.to === outgoing.to &&
57
+ r.type === "must_follow");
58
+ if (!exists) {
59
+ newRelationships.push({
60
+ from: incoming.from,
61
+ to: outgoing.to,
62
+ type: "must_follow",
63
+ });
64
+ warnings.push(`Repaired chain: ${incoming.from} → ${outgoing.to}`);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ else {
71
+ // Without repair, just remove all relationships
72
+ const oldRelCount = newRelationships.length;
73
+ newRelationships = newRelationships.filter((r) => r.from !== id && r.to !== id);
74
+ if (newRelationships.length < oldRelCount) {
75
+ warnings.push(`Removed relationships involving ${id}`);
76
+ }
77
+ }
78
+ }
79
+ else {
80
+ // Soft delete: mark as retired and preserve relationships
81
+ newNodes = doc.nodes.map((n) => n.id === id ? { ...n, status: "retired" } : n);
82
+ // Don't remove relationships in soft delete
83
+ }
84
+ // Clean up all references to the removed node (both soft and hard)
31
85
  const cleanedNodes = newNodes.map((n) => {
32
86
  let updated = n;
33
87
  // Remove from view includes
@@ -59,12 +113,6 @@ export const removeNodeOp = defineOperation({
59
113
  }
60
114
  return updated;
61
115
  });
62
- // Remove relationships involving this node
63
- const oldRelCount = (doc.relationships ?? []).length;
64
- const newRelationships = (doc.relationships ?? []).filter((r) => r.from !== id && r.to !== id);
65
- if (newRelationships.length < oldRelCount) {
66
- warnings.push(`Removed relationships involving ${id}`);
67
- }
68
116
  // Remove from external references
69
117
  const newExternalRefs = (doc.external_references ?? []).filter((ref) => ref.node_id !== id);
70
118
  return {