sysprom 1.21.1 → 1.22.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.
Files changed (82) hide show
  1. package/dist/schema.json +0 -31
  2. package/dist/src/cli/commands/plan.js +54 -3
  3. package/dist/src/cli/commands/speckit.js +0 -1
  4. package/dist/src/cli/program.js +0 -2
  5. package/dist/src/index.d.ts +2 -2
  6. package/dist/src/index.js +2 -2
  7. package/dist/src/json-to-md.js +0 -8
  8. package/dist/src/mcp/server.js +0 -1
  9. package/dist/src/md-to-json.js +0 -10
  10. package/dist/src/operations/add-node.d.ts +0 -51
  11. package/dist/src/operations/add-relationship.d.ts +0 -34
  12. package/dist/src/operations/check.d.ts +0 -17
  13. package/dist/src/operations/graph-decision.d.ts +0 -17
  14. package/dist/src/operations/graph-dependency.d.ts +0 -17
  15. package/dist/src/operations/graph-refinement.d.ts +0 -17
  16. package/dist/src/operations/graph.d.ts +0 -17
  17. package/dist/src/operations/index.d.ts +3 -5
  18. package/dist/src/operations/index.js +3 -5
  19. package/dist/src/operations/infer-completeness.d.ts +0 -17
  20. package/dist/src/operations/infer-completeness.js +0 -4
  21. package/dist/src/operations/infer-derived.d.ts +0 -17
  22. package/dist/src/operations/infer-impact.d.ts +0 -119
  23. package/dist/src/operations/infer-lifecycle.d.ts +0 -17
  24. package/dist/src/operations/init-document.d.ts +0 -17
  25. package/dist/src/operations/json-to-markdown.d.ts +0 -17
  26. package/dist/src/operations/markdown-to-json.d.ts +0 -17
  27. package/dist/src/operations/next-id.d.ts +0 -17
  28. package/dist/src/operations/node-history.d.ts +0 -17
  29. package/dist/src/operations/plan-add-task.d.ts +0 -34
  30. package/dist/src/operations/{mark-task-done.d.ts → plan-complete-task.d.ts} +4 -41
  31. package/dist/src/operations/plan-complete-task.js +18 -0
  32. package/dist/src/operations/plan-gate.d.ts +0 -17
  33. package/dist/src/operations/plan-init.d.ts +0 -17
  34. package/dist/src/operations/plan-progress.d.ts +18 -17
  35. package/dist/src/operations/plan-progress.js +6 -0
  36. package/dist/src/operations/{mark-task-undone.d.ts → plan-reopen-task.d.ts} +4 -41
  37. package/dist/src/operations/plan-reopen-task.js +18 -0
  38. package/dist/src/operations/{add-plan-task.d.ts → plan-start-task.d.ts} +4 -41
  39. package/dist/src/operations/plan-start-task.js +18 -0
  40. package/dist/src/operations/plan-status.d.ts +22 -17
  41. package/dist/src/operations/plan-status.js +8 -0
  42. package/dist/src/operations/query-node.d.ts +0 -51
  43. package/dist/src/operations/query-nodes.d.ts +0 -34
  44. package/dist/src/operations/query-relationships.d.ts +0 -17
  45. package/dist/src/operations/remove-node.d.ts +0 -51
  46. package/dist/src/operations/remove-relationship.d.ts +0 -34
  47. package/dist/src/operations/rename.d.ts +0 -34
  48. package/dist/src/operations/search.d.ts +0 -34
  49. package/dist/src/operations/speckit-diff.d.ts +0 -17
  50. package/dist/src/operations/speckit-export.d.ts +0 -17
  51. package/dist/src/operations/speckit-import.d.ts +0 -17
  52. package/dist/src/operations/speckit-sync.d.ts +0 -51
  53. package/dist/src/operations/speckit-sync.js +0 -1
  54. package/dist/src/operations/state-at.d.ts +0 -17
  55. package/dist/src/operations/stats.d.ts +0 -17
  56. package/dist/src/operations/sync.d.ts +0 -51
  57. package/dist/src/operations/timeline.d.ts +0 -17
  58. package/dist/src/operations/trace-from-node.d.ts +0 -51
  59. package/dist/src/operations/update-metadata.d.ts +0 -34
  60. package/dist/src/operations/update-node.d.ts +0 -58
  61. package/dist/src/operations/validate.d.ts +0 -17
  62. package/dist/src/operations/validate.js +71 -0
  63. package/dist/src/schema.d.ts +0 -61
  64. package/dist/src/schema.js +0 -11
  65. package/dist/src/speckit/generate.js +17 -20
  66. package/dist/src/speckit/index.d.ts +1 -1
  67. package/dist/src/speckit/index.js +1 -1
  68. package/dist/src/speckit/parse.d.ts +1 -1
  69. package/dist/src/speckit/parse.js +20 -14
  70. package/dist/src/speckit/plan.d.ts +29 -7
  71. package/dist/src/speckit/plan.js +181 -47
  72. package/package.json +3 -3
  73. package/schema.json +0 -31
  74. package/dist/src/cli/commands/task.d.ts +0 -2
  75. package/dist/src/cli/commands/task.js +0 -157
  76. package/dist/src/operations/add-plan-task.js +0 -29
  77. package/dist/src/operations/mark-task-done.js +0 -30
  78. package/dist/src/operations/mark-task-undone.js +0 -30
  79. package/dist/src/operations/task-list.d.ts +0 -288
  80. package/dist/src/operations/task-list.js +0 -49
  81. package/dist/src/operations/update-plan-task.d.ts +0 -555
  82. package/dist/src/operations/update-plan-task.js +0 -35
@@ -88,18 +88,6 @@ export declare const updateMetadataOp: import("./define-operation.js").DefinedOp
88
88
  description?: string | string[] | undefined;
89
89
  };
90
90
  }>>;
91
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
92
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
93
- is(value: unknown): value is string | string[];
94
- };
95
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
96
- }, z.core.$loose> & {
97
- is(value: unknown): value is {
98
- [x: string]: unknown;
99
- description: string | string[];
100
- done?: boolean | undefined;
101
- };
102
- }>>;
103
91
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
104
92
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
105
93
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -231,11 +219,6 @@ export declare const updateMetadataOp: import("./define-operation.js").DefinedOp
231
219
  target?: string | undefined;
232
220
  description?: string | string[] | undefined;
233
221
  }[] | undefined;
234
- plan?: {
235
- [x: string]: unknown;
236
- description: string | string[];
237
- done?: boolean | undefined;
238
- }[] | undefined;
239
222
  propagation?: Record<string, boolean> | undefined;
240
223
  includes?: string[] | undefined;
241
224
  external_references?: {
@@ -362,18 +345,6 @@ export declare const updateMetadataOp: import("./define-operation.js").DefinedOp
362
345
  description?: string | string[] | undefined;
363
346
  };
364
347
  }>>;
365
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
366
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
367
- is(value: unknown): value is string | string[];
368
- };
369
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
370
- }, z.core.$loose> & {
371
- is(value: unknown): value is {
372
- [x: string]: unknown;
373
- description: string | string[];
374
- done?: boolean | undefined;
375
- };
376
- }>>;
377
348
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
378
349
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
379
350
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -505,11 +476,6 @@ export declare const updateMetadataOp: import("./define-operation.js").DefinedOp
505
476
  target?: string | undefined;
506
477
  description?: string | string[] | undefined;
507
478
  }[] | undefined;
508
- plan?: {
509
- [x: string]: unknown;
510
- description: string | string[];
511
- done?: boolean | undefined;
512
- }[] | undefined;
513
479
  propagation?: Record<string, boolean> | undefined;
514
480
  includes?: string[] | undefined;
515
481
  external_references?: {
@@ -91,18 +91,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
91
91
  description?: string | string[] | undefined;
92
92
  };
93
93
  }>>;
94
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
95
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
96
- is(value: unknown): value is string | string[];
97
- };
98
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
99
- }, z.core.$loose> & {
100
- is(value: unknown): value is {
101
- [x: string]: unknown;
102
- description: string | string[];
103
- done?: boolean | undefined;
104
- };
105
- }>>;
106
94
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
107
95
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
108
96
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -234,11 +222,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
234
222
  target?: string | undefined;
235
223
  description?: string | string[] | undefined;
236
224
  }[] | undefined;
237
- plan?: {
238
- [x: string]: unknown;
239
- description: string | string[];
240
- done?: boolean | undefined;
241
- }[] | undefined;
242
225
  propagation?: Record<string, boolean> | undefined;
243
226
  includes?: string[] | undefined;
244
227
  external_references?: {
@@ -347,18 +330,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
347
330
  description?: string | string[] | undefined;
348
331
  };
349
332
  }>>>;
350
- plan: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
351
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
352
- is(value: unknown): value is string | string[];
353
- };
354
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
355
- }, z.core.$loose> & {
356
- is(value: unknown): value is {
357
- [x: string]: unknown;
358
- description: string | string[];
359
- done?: boolean | undefined;
360
- };
361
- }>>>;
362
333
  propagation: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
363
334
  includes: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
364
335
  external_references: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -477,18 +448,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
477
448
  description?: string | string[] | undefined;
478
449
  };
479
450
  }>>;
480
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
481
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
482
- is(value: unknown): value is string | string[];
483
- };
484
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
485
- }, z.core.$loose> & {
486
- is(value: unknown): value is {
487
- [x: string]: unknown;
488
- description: string | string[];
489
- done?: boolean | undefined;
490
- };
491
- }>>;
492
451
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
493
452
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
494
453
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -684,18 +643,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
684
643
  description?: string | string[] | undefined;
685
644
  };
686
645
  }>>;
687
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
688
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
689
- is(value: unknown): value is string | string[];
690
- };
691
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
692
- }, z.core.$loose> & {
693
- is(value: unknown): value is {
694
- [x: string]: unknown;
695
- description: string | string[];
696
- done?: boolean | undefined;
697
- };
698
- }>>;
699
646
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
700
647
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
701
648
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -827,11 +774,6 @@ export declare const updateNodeOp: import("./define-operation.js").DefinedOperat
827
774
  target?: string | undefined;
828
775
  description?: string | string[] | undefined;
829
776
  }[] | undefined;
830
- plan?: {
831
- [x: string]: unknown;
832
- description: string | string[];
833
- done?: boolean | undefined;
834
- }[] | undefined;
835
777
  propagation?: Record<string, boolean> | undefined;
836
778
  includes?: string[] | undefined;
837
779
  external_references?: {
@@ -104,18 +104,6 @@ export declare const validateOp: import("./define-operation.js").DefinedOperatio
104
104
  description?: string | string[] | undefined;
105
105
  };
106
106
  }>>;
107
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
108
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
109
- is(value: unknown): value is string | string[];
110
- };
111
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
112
- }, z.core.$loose> & {
113
- is(value: unknown): value is {
114
- [x: string]: unknown;
115
- description: string | string[];
116
- done?: boolean | undefined;
117
- };
118
- }>>;
119
107
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
120
108
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
121
109
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -247,11 +235,6 @@ export declare const validateOp: import("./define-operation.js").DefinedOperatio
247
235
  target?: string | undefined;
248
236
  description?: string | string[] | undefined;
249
237
  }[] | undefined;
250
- plan?: {
251
- [x: string]: unknown;
252
- description: string | string[];
253
- done?: boolean | undefined;
254
- }[] | undefined;
255
238
  propagation?: Record<string, boolean> | undefined;
256
239
  includes?: string[] | undefined;
257
240
  external_references?: {
@@ -127,6 +127,77 @@ export const validateOp = defineOperation({
127
127
  issues.push(`${n.id} (${n.name}): decision has no selected option`);
128
128
  }
129
129
  }
130
+ const isLifecycleReached = (value) => value === true || typeof value === "string";
131
+ const isGateReady = (node) => {
132
+ if (node.type !== "gate")
133
+ return false;
134
+ const lifecycle = node.lifecycle ?? {};
135
+ const values = Object.values(lifecycle);
136
+ if (values.length === 0)
137
+ return false;
138
+ return values.every((value) => isLifecycleReached(value));
139
+ };
140
+ const globalNodeMap = new Map(input.doc.nodes.map((node) => [node.id, node]));
141
+ const validatePlanSubsystem = (subsystem, protocolId) => {
142
+ if (!subsystem)
143
+ return;
144
+ const localNodeMap = new Map(subsystem.nodes.map((node) => [node.id, node]));
145
+ for (const node of subsystem.nodes) {
146
+ if (node.type !== "change") {
147
+ issues.push(`${protocolId}: implementation plan contains non-change node ${node.id} (${node.type})`);
148
+ continue;
149
+ }
150
+ if (hasLifecycleState(node, "complete")) {
151
+ const blockers = [];
152
+ for (const rel of subsystem.relationships ?? []) {
153
+ if (rel.from !== node.id || rel.type !== "depends_on")
154
+ continue;
155
+ const dependency = localNodeMap.get(rel.to) ?? globalNodeMap.get(rel.to);
156
+ if (dependency?.type !== "change") {
157
+ blockers.push(`dependency ${rel.to} is not a change task`);
158
+ continue;
159
+ }
160
+ if (!hasLifecycleState(dependency, "complete")) {
161
+ blockers.push(`dependency ${rel.to} is not complete`);
162
+ }
163
+ }
164
+ for (const rel of subsystem.relationships ?? []) {
165
+ if (rel.from !== node.id || rel.type !== "constrained_by")
166
+ continue;
167
+ const gate = localNodeMap.get(rel.to) ?? globalNodeMap.get(rel.to);
168
+ if (!gate || !isGateReady(gate)) {
169
+ blockers.push(`gate ${rel.to} is not ready`);
170
+ }
171
+ }
172
+ if (blockers.length > 0) {
173
+ issues.push(`${node.id} (${node.name}): complete task has unresolved blockers (${blockers.join(", ")})`);
174
+ }
175
+ }
176
+ if (node.subsystem) {
177
+ validatePlanSubsystem(node.subsystem, protocolId);
178
+ }
179
+ }
180
+ for (const rel of subsystem.relationships ?? []) {
181
+ if (rel.type !== "depends_on")
182
+ continue;
183
+ const fromNode = localNodeMap.get(rel.from);
184
+ const toNode = localNodeMap.get(rel.to);
185
+ if (!fromNode || !toNode) {
186
+ issues.push(`${protocolId}: depends_on relationship ${rel.from} -> ${rel.to} must stay within the same plan scope`);
187
+ continue;
188
+ }
189
+ if (fromNode.type !== "change" || toNode.type !== "change") {
190
+ issues.push(`${protocolId}: depends_on scheduling relationship must connect change task nodes (${rel.from} -> ${rel.to})`);
191
+ }
192
+ }
193
+ };
194
+ for (const node of input.doc.nodes) {
195
+ if (node.type !== "protocol")
196
+ continue;
197
+ if (!node.id.endsWith("-PROT-IMPL"))
198
+ continue;
199
+ validatePlanSubsystem(node.subsystem, node.id);
200
+ }
130
201
  return {
131
202
  valid: issues.length === 0,
132
203
  issues,
@@ -202,21 +202,6 @@ export declare const Operation: z.ZodObject<{
202
202
  };
203
203
  /** An atomic operation within a change, targeting a specific node. */
204
204
  export type Operation = z.infer<typeof Operation>;
205
- /** Zod schema for a task within a change's execution plan. */
206
- export declare const Task: z.ZodObject<{
207
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
208
- is(value: unknown): value is string | string[];
209
- };
210
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
211
- }, z.core.$loose> & {
212
- is(value: unknown): value is {
213
- [x: string]: unknown;
214
- description: string | string[];
215
- done?: boolean | undefined;
216
- };
217
- };
218
- /** A single task within a change's execution plan, with a description and done flag. */
219
- export type Task = z.infer<typeof Task>;
220
205
  /** Zod schema for an external reference — a link to a resource outside the SysProM graph. */
221
206
  export declare const ExternalReference: z.ZodObject<{
222
207
  role: z.ZodEnum<{
@@ -478,18 +463,6 @@ export declare const NodeBase: z.ZodObject<{
478
463
  description?: string | string[] | undefined;
479
464
  };
480
465
  }>>;
481
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
482
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
483
- is(value: unknown): value is string | string[];
484
- };
485
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
486
- }, z.core.$loose> & {
487
- is(value: unknown): value is {
488
- [x: string]: unknown;
489
- description: string | string[];
490
- done?: boolean | undefined;
491
- };
492
- }>>;
493
466
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
494
467
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
495
468
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -592,18 +565,6 @@ declare const NodeSchema: z.ZodObject<{
592
565
  description?: string | string[] | undefined;
593
566
  };
594
567
  }>>;
595
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
596
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
597
- is(value: unknown): value is string | string[];
598
- };
599
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
600
- }, z.core.$loose> & {
601
- is(value: unknown): value is {
602
- [x: string]: unknown;
603
- description: string | string[];
604
- done?: boolean | undefined;
605
- };
606
- }>>;
607
568
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
608
569
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
609
570
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -759,11 +720,6 @@ export declare const SysProMDocument: z.ZodObject<{
759
720
  target?: string | undefined;
760
721
  description?: string | string[] | undefined;
761
722
  }[] | undefined;
762
- plan?: {
763
- [x: string]: unknown;
764
- description: string | string[];
765
- done?: boolean | undefined;
766
- }[] | undefined;
767
723
  propagation?: Record<string, boolean> | undefined;
768
724
  includes?: string[] | undefined;
769
725
  external_references?: {
@@ -878,18 +834,6 @@ export declare const Node: z.ZodObject<{
878
834
  description?: string | string[] | undefined;
879
835
  };
880
836
  }>>;
881
- plan: z.ZodOptional<z.ZodArray<z.ZodObject<{
882
- description: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]> & {
883
- is(value: unknown): value is string | string[];
884
- };
885
- done: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
886
- }, z.core.$loose> & {
887
- is(value: unknown): value is {
888
- [x: string]: unknown;
889
- description: string | string[];
890
- done?: boolean | undefined;
891
- };
892
- }>>;
893
837
  propagation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
894
838
  includes: z.ZodOptional<z.ZodArray<z.ZodString>>;
895
839
  external_references: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -946,11 +890,6 @@ export declare const Node: z.ZodObject<{
946
890
  target?: string | undefined;
947
891
  description?: string | string[] | undefined;
948
892
  }[] | undefined;
949
- plan?: {
950
- [x: string]: unknown;
951
- description: string | string[];
952
- done?: boolean | undefined;
953
- }[] | undefined;
954
893
  propagation?: Record<string, boolean> | undefined;
955
894
  includes?: string[] | undefined;
956
895
  external_references?: {
@@ -167,13 +167,6 @@ export const Operation = defineSchema(z
167
167
  description: Text.optional(),
168
168
  })
169
169
  .describe("An atomic operation within a change."));
170
- /** Zod schema for a task within a change's execution plan. */
171
- export const Task = defineSchema(z
172
- .looseObject({
173
- description: Text,
174
- done: z.boolean().default(false).optional(),
175
- })
176
- .describe("A single task within a change's execution plan."));
177
170
  /** Zod schema for an external reference — a link to a resource outside the SysProM graph. */
178
171
  export const ExternalReference = defineSchema(z
179
172
  .object({
@@ -282,10 +275,6 @@ export const NodeBase = z
282
275
  .array(Operation)
283
276
  .describe("Operations performed. Applicable to change nodes.")
284
277
  .optional(),
285
- plan: z
286
- .array(Task)
287
- .describe("Execution plan as a sequence of tasks. Applicable to change nodes.")
288
- .optional(),
289
278
  propagation: z
290
279
  .record(z.string(), z.boolean())
291
280
  .describe("Layer propagation status. Applicable to change nodes.")
@@ -105,20 +105,12 @@ function getIdSuffix(id) {
105
105
  const parts = id.split("-");
106
106
  return parts[parts.length - 1] ?? "000";
107
107
  }
108
- /**
109
- * Parse tasks from a change node's plan array.
110
- * @param node - The change node.
111
- * @returns Array of task descriptions and done flags.
112
- * @example
113
- * ```ts
114
- * const tasks = parseTasks(changeNode);
115
- * ```
116
- */
117
- function parseTasks(node) {
118
- return (node.plan ?? []).map((task) => ({
119
- description: textToString(task.description),
120
- done: task.done ?? false,
121
- }));
108
+ function childTaskNodes(change) {
109
+ return (change.subsystem?.nodes ?? []).filter((n) => n.type === "change");
110
+ }
111
+ function isDone(node) {
112
+ return (node.lifecycle?.complete === true ||
113
+ typeof node.lifecycle?.complete === "string");
122
114
  }
123
115
  /**
124
116
  * Format the lifecycle state for spec output: "proposed" -> "Draft", etc.
@@ -494,10 +486,10 @@ export function generateTasks(doc, prefix) {
494
486
  return output.trim();
495
487
  }
496
488
  // Recursive helper to render a change node and its tasks, including nested child changes.
497
- // Render tasks from a change node's plan array.
489
+ // Render tasks from child change nodes.
498
490
  function renderPlanItems(change, taskCounter) {
499
491
  let result = "";
500
- const tasks = parseTasks(change);
492
+ const tasks = childTaskNodes(change);
501
493
  for (const task of tasks) {
502
494
  // Find capability that this change implements.
503
495
  let usStory = null;
@@ -511,13 +503,13 @@ export function generateTasks(doc, prefix) {
511
503
  usStory = getIdSuffix(implRelsDoc[0].to);
512
504
  }
513
505
  }
514
- const checkbox = task.done ? "[x]" : "[ ]";
506
+ const checkbox = isDone(task) ? "[x]" : "[ ]";
515
507
  const taskNum = String(taskCounter.value).padStart(3, "0");
516
508
  let taskLine = `- ${checkbox} T${taskNum}`;
517
509
  if (usStory && usStory !== "000") {
518
510
  taskLine += ` [US${usStory}]`;
519
511
  }
520
- taskLine += ` ${textToString(task.description)}`;
512
+ taskLine += ` ${task.name}`;
521
513
  result += taskLine + "\n";
522
514
  taskCounter.value++;
523
515
  }
@@ -536,9 +528,14 @@ export function generateTasks(doc, prefix) {
536
528
  result += "\n";
537
529
  else
538
530
  result += "\n\n";
539
- // Recurse into child change nodes.
531
+ // Recurse into child phase nodes only (task nodes are rendered as checkboxes above).
540
532
  if (change.subsystem?.nodes && change.subsystem.nodes.length > 0) {
541
- const childChanges = change.subsystem.nodes.filter((n) => n.type === "change");
533
+ const childChanges = change.subsystem.nodes.filter((n) => {
534
+ if (n.type !== "change")
535
+ return false;
536
+ const children = n.subsystem?.nodes ?? [];
537
+ return children.some((child) => child.type === "change");
538
+ });
542
539
  for (const childChange of childChanges) {
543
540
  result += renderChangeNode(childChange, headingLevel + 1, taskCounter);
544
541
  }
@@ -1,4 +1,4 @@
1
1
  export { detectSpecKitProject, listFeatures, getFeature, resolveConstitution, type SpecKitProject, type SpecKitFeature, } from "./project.js";
2
2
  export { parseConstitution, parseSpec, parsePlan, parseTasks, parseChecklist, parseSpecKitFeature, type ParseResult, } from "./parse.js";
3
3
  export { generateConstitution, generateSpec, generatePlan, generateTasks, generateChecklist, generateSpecKitProject, } from "./generate.js";
4
- export { initDocument, addTask, planStatus, planProgress, checkGate, isTaskDone, countTasks, type PlanStatus, type PhaseProgress, type GateIssue, type GateResult, } from "./plan.js";
4
+ export { initDocument, addTask, setTaskLifecycle, planStatus, planProgress, checkGate, isTaskDone, countTasks, type PlanStatus, type PhaseProgress, type GateIssue, type GateResult, type BlockageReason, type TaskBlockage, } from "./plan.js";
@@ -1,4 +1,4 @@
1
1
  export { detectSpecKitProject, listFeatures, getFeature, resolveConstitution, } from "./project.js";
2
2
  export { parseConstitution, parseSpec, parsePlan, parseTasks, parseChecklist, parseSpecKitFeature, } from "./parse.js";
3
3
  export { generateConstitution, generateSpec, generatePlan, generateTasks, generateChecklist, generateSpecKitProject, } from "./generate.js";
4
- export { initDocument, addTask, planStatus, planProgress, checkGate, isTaskDone, countTasks, } from "./plan.js";
4
+ export { initDocument, addTask, setTaskLifecycle, planStatus, planProgress, checkGate, isTaskDone, countTasks, } from "./plan.js";
@@ -40,7 +40,7 @@ export declare function parseSpec(content: string, idPrefix: string): ParseResul
40
40
  */
41
41
  export declare function parsePlan(content: string, idPrefix: string): ParseResult;
42
42
  /**
43
- * Parse a Spec-Kit tasks file into SysProM change nodes with task plans.
43
+ * Parse a Spec-Kit tasks file into SysProM change nodes with nested task nodes.
44
44
  * @param content - Markdown file content.
45
45
  * @param idPrefix - ID prefix for generated nodes.
46
46
  * @returns The result.
@@ -580,7 +580,7 @@ export function parsePlan(content, idPrefix) {
580
580
  // tasks.md parser
581
581
  // ---------------------------------------------------------------------------
582
582
  /**
583
- * Parse a Spec-Kit tasks file into SysProM change nodes with task plans.
583
+ * Parse a Spec-Kit tasks file into SysProM change nodes with nested task nodes.
584
584
  * @param content - Markdown file content.
585
585
  * @param idPrefix - ID prefix for generated nodes.
586
586
  * @returns The result.
@@ -631,21 +631,27 @@ export function parseTasks(content, idPrefix) {
631
631
  }
632
632
  }
633
633
  }
634
- // Create change nodes for each phase (with LOCAL IDs in subsystem)
635
- // Use numeric indices (CHG-1, CHG-2, etc.) for phase changes
634
+ const toTaskNode = (parentId, index, task) => ({
635
+ id: `${parentId}-${String(index + 1)}`,
636
+ type: "change",
637
+ name: task.text,
638
+ lifecycle: task.done ? { complete: true } : { proposed: true },
639
+ });
640
+ // Create change nodes for each phase (with local IDs in subsystem)
641
+ // Use numeric indices (CHG-1, CHG-2, etc.) for phase changes.
636
642
  for (let i = 0; i < phases.length; i++) {
637
643
  const phase = phases[i];
638
644
  const tasks = changesByPhase[phase.phaseNum] ?? [];
639
- const plan = tasks.map((t) => ({
640
- description: t.text,
641
- done: t.done,
642
- }));
643
645
  const changeLocalId = `CHG-${String(phase.phaseNum)}`;
644
646
  subsystemNodes.push({
645
647
  id: changeLocalId,
646
648
  type: "change",
647
649
  name: phase.title,
648
- plan,
650
+ lifecycle: { introduced: true },
651
+ subsystem: {
652
+ nodes: tasks.map((task, index) => toTaskNode(changeLocalId, index, task)),
653
+ relationships: [],
654
+ },
649
655
  });
650
656
  // Wire must_follow between consecutive phase changes
651
657
  if (i > 0) {
@@ -657,18 +663,18 @@ export function parseTasks(content, idPrefix) {
657
663
  });
658
664
  }
659
665
  }
660
- // Create change nodes for user stories (with LOCAL IDs in subsystem)
666
+ // Create change nodes for user stories (with local IDs in subsystem)
661
667
  for (const [storyKey, tasks] of Object.entries(changesByStory)) {
662
- const plan = tasks.map((t) => ({
663
- description: t.text,
664
- done: t.done,
665
- }));
666
668
  const changeLocalId = `CHG-${storyKey}`;
667
669
  subsystemNodes.push({
668
670
  id: changeLocalId,
669
671
  type: "change",
670
672
  name: storyKey,
671
- plan,
673
+ lifecycle: { introduced: true },
674
+ subsystem: {
675
+ nodes: tasks.map((task, index) => toTaskNode(changeLocalId, index, task)),
676
+ relationships: [],
677
+ },
672
678
  });
673
679
  // Link to the capability at the top level (using GLOBAL ID format)
674
680
  const changeGlobalId = `${idPrefix}-CHG-${storyKey}`;
@@ -17,6 +17,8 @@ export interface PlanStatus {
17
17
  tasks: {
18
18
  total: number;
19
19
  done: number;
20
+ blocked: number;
21
+ blockedTasks: TaskBlockage[];
20
22
  };
21
23
  checklist: {
22
24
  defined: boolean;
@@ -28,10 +30,13 @@ export interface PlanStatus {
28
30
  /** Per-phase progress metrics — task counts and completion percentage. */
29
31
  export interface PhaseProgress {
30
32
  phase: number;
33
+ id: string;
31
34
  name: string;
32
35
  done: number;
33
36
  total: number;
34
37
  percent: number;
38
+ blocked: boolean;
39
+ blockageReasons: BlockageReason[];
35
40
  }
36
41
  /** A specific issue preventing gate entry — incomplete tasks, missing acceptance criteria, or unlinked requirements. */
37
42
  export type GateIssue = {
@@ -58,6 +63,18 @@ export interface GateResult {
58
63
  export interface TaskCount {
59
64
  total: number;
60
65
  done: number;
66
+ blocked: number;
67
+ blockedTasks: TaskBlockage[];
68
+ }
69
+ /** Why a task is blocked. */
70
+ export interface BlockageReason {
71
+ kind: "dependency_unmet" | "gate_not_ready";
72
+ nodeId: string;
73
+ }
74
+ /** Blockage detail for a specific task. */
75
+ export interface TaskBlockage {
76
+ taskId: string;
77
+ reasons: BlockageReason[];
61
78
  }
62
79
  /**
63
80
  * Scaffold a new SysProMDocument with the standard spec-kit-compatible node
@@ -106,18 +123,22 @@ export declare function initDocument(prefix: string, name: string): SysProMDocum
106
123
  * ```
107
124
  */
108
125
  export declare function addTask(doc: SysProMDocument, prefix: string, name?: string, parentId?: string): SysProMDocument;
126
+ type TaskLifecycleAction = "start" | "complete" | "reopen";
109
127
  /**
110
- * Check if a change node's task is complete.
128
+ * Set lifecycle state on a task (change node) within a plan implementation protocol.
111
129
  *
112
- * If no subsystem or no change children in subsystem:
113
- * - All items in node.plan must have done === true AND at least one item must exist.
114
- * If subsystem has change children:
115
- * - All children must be recursively done AND own plan items (if any) must be done.
130
+ * - start: marks introduced + in_progress true
131
+ * - complete: marks complete true and clears in_progress
132
+ * - reopen: clears complete and marks in_progress true
133
+ */
134
+ export declare function setTaskLifecycle(doc: SysProMDocument, prefix: string, taskId: string, action: TaskLifecycleAction): SysProMDocument;
135
+ /**
136
+ * Check if a task change node is complete.
116
137
  * @param node - The change node to evaluate.
117
- * @returns Whether all tasks in the node's plan are complete.
138
+ * @returns Whether the task lifecycle includes complete.
118
139
  * @example
119
140
  * ```ts
120
- * isTaskDone(changeNode); // => true if all plan items done
141
+ * isTaskDone(changeNode); // => true when lifecycle.complete is reached
121
142
  * ```
122
143
  */
123
144
  export declare function isTaskDone(node: Node): boolean;
@@ -178,3 +199,4 @@ export declare function planProgress(doc: SysProMDocument, prefix: string): Phas
178
199
  * ```
179
200
  */
180
201
  export declare function checkGate(doc: SysProMDocument, prefix: string, phase: number): GateResult;
202
+ export {};