sysprom 1.21.2 → 1.22.1
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/dist/schema.json +0 -31
- package/dist/src/cli/commands/plan.js +54 -3
- package/dist/src/cli/commands/speckit.js +0 -1
- package/dist/src/cli/program.js +0 -2
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/json-to-md.js +0 -8
- package/dist/src/mcp/server.js +0 -1
- package/dist/src/md-to-json.js +0 -10
- package/dist/src/operations/add-node.d.ts +0 -51
- package/dist/src/operations/add-relationship.d.ts +0 -34
- package/dist/src/operations/check.d.ts +0 -17
- package/dist/src/operations/graph-decision.d.ts +0 -17
- package/dist/src/operations/graph-dependency.d.ts +0 -17
- package/dist/src/operations/graph-refinement.d.ts +0 -17
- package/dist/src/operations/graph.d.ts +0 -17
- package/dist/src/operations/index.d.ts +3 -5
- package/dist/src/operations/index.js +3 -5
- package/dist/src/operations/infer-completeness.d.ts +0 -17
- package/dist/src/operations/infer-completeness.js +0 -4
- package/dist/src/operations/infer-derived.d.ts +0 -17
- package/dist/src/operations/infer-impact.d.ts +0 -119
- package/dist/src/operations/infer-lifecycle.d.ts +0 -17
- package/dist/src/operations/init-document.d.ts +0 -17
- package/dist/src/operations/json-to-markdown.d.ts +0 -17
- package/dist/src/operations/markdown-to-json.d.ts +0 -17
- package/dist/src/operations/next-id.d.ts +0 -17
- package/dist/src/operations/node-history.d.ts +0 -17
- package/dist/src/operations/plan-add-task.d.ts +0 -34
- package/dist/src/operations/{mark-task-done.d.ts → plan-complete-task.d.ts} +4 -41
- package/dist/src/operations/plan-complete-task.js +18 -0
- package/dist/src/operations/plan-gate.d.ts +0 -17
- package/dist/src/operations/plan-init.d.ts +0 -17
- package/dist/src/operations/plan-progress.d.ts +18 -17
- package/dist/src/operations/plan-progress.js +6 -0
- package/dist/src/operations/{mark-task-undone.d.ts → plan-reopen-task.d.ts} +4 -41
- package/dist/src/operations/plan-reopen-task.js +18 -0
- package/dist/src/operations/{add-plan-task.d.ts → plan-start-task.d.ts} +4 -41
- package/dist/src/operations/plan-start-task.js +18 -0
- package/dist/src/operations/plan-status.d.ts +22 -17
- package/dist/src/operations/plan-status.js +8 -0
- package/dist/src/operations/query-node.d.ts +0 -51
- package/dist/src/operations/query-nodes.d.ts +0 -34
- package/dist/src/operations/query-relationships.d.ts +0 -17
- package/dist/src/operations/remove-node.d.ts +0 -51
- package/dist/src/operations/remove-relationship.d.ts +0 -34
- package/dist/src/operations/rename.d.ts +0 -34
- package/dist/src/operations/search.d.ts +0 -34
- package/dist/src/operations/speckit-diff.d.ts +0 -17
- package/dist/src/operations/speckit-export.d.ts +0 -17
- package/dist/src/operations/speckit-import.d.ts +0 -17
- package/dist/src/operations/speckit-sync.d.ts +0 -51
- package/dist/src/operations/speckit-sync.js +0 -1
- package/dist/src/operations/state-at.d.ts +0 -17
- package/dist/src/operations/stats.d.ts +0 -17
- package/dist/src/operations/sync.d.ts +0 -51
- package/dist/src/operations/timeline.d.ts +0 -17
- package/dist/src/operations/trace-from-node.d.ts +0 -51
- package/dist/src/operations/update-metadata.d.ts +0 -34
- package/dist/src/operations/update-node.d.ts +0 -58
- package/dist/src/operations/validate.d.ts +0 -17
- package/dist/src/operations/validate.js +71 -0
- package/dist/src/schema.d.ts +0 -61
- package/dist/src/schema.js +0 -11
- package/dist/src/speckit/generate.js +17 -20
- package/dist/src/speckit/index.d.ts +1 -1
- package/dist/src/speckit/index.js +1 -1
- package/dist/src/speckit/parse.d.ts +1 -1
- package/dist/src/speckit/parse.js +20 -14
- package/dist/src/speckit/plan.d.ts +29 -7
- package/dist/src/speckit/plan.js +181 -47
- package/package.json +1 -1
- package/schema.json +0 -31
- package/dist/src/cli/commands/task.d.ts +0 -2
- package/dist/src/cli/commands/task.js +0 -157
- package/dist/src/operations/add-plan-task.js +0 -29
- package/dist/src/operations/mark-task-done.js +0 -30
- package/dist/src/operations/mark-task-undone.js +0 -30
- package/dist/src/operations/task-list.d.ts +0 -288
- package/dist/src/operations/task-list.js +0 -49
- package/dist/src/operations/update-plan-task.d.ts +0 -555
- package/dist/src/operations/update-plan-task.js +0 -35
package/dist/src/speckit/plan.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { textToString } from "../text.js";
|
|
2
|
+
import { hasLifecycleState } from "../lifecycle-state.js";
|
|
2
3
|
// ============================================================================
|
|
3
4
|
// Helper functions
|
|
4
5
|
// ============================================================================
|
|
@@ -279,7 +280,7 @@ export function addTask(doc, prefix, name, parentId) {
|
|
|
279
280
|
id: changeId,
|
|
280
281
|
type: "change",
|
|
281
282
|
name: taskName,
|
|
282
|
-
|
|
283
|
+
lifecycle: { proposed: true },
|
|
283
284
|
};
|
|
284
285
|
// Build new relationships
|
|
285
286
|
const newRels = [];
|
|
@@ -353,7 +354,7 @@ function addTaskToParent(doc, protImpl, prefix, parentId, name) {
|
|
|
353
354
|
id: changeId,
|
|
354
355
|
type: "change",
|
|
355
356
|
name: childName,
|
|
356
|
-
|
|
357
|
+
lifecycle: { proposed: true },
|
|
357
358
|
};
|
|
358
359
|
// Build new relationships for child
|
|
359
360
|
const newRels = [];
|
|
@@ -449,41 +450,144 @@ function addTaskToParent(doc, protImpl, prefix, parentId, name) {
|
|
|
449
450
|
nodes: updatedNodes,
|
|
450
451
|
};
|
|
451
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Set lifecycle state on a task (change node) within a plan implementation protocol.
|
|
455
|
+
*
|
|
456
|
+
* - start: marks introduced + in_progress true
|
|
457
|
+
* - complete: marks complete true and clears in_progress
|
|
458
|
+
* - reopen: clears complete and marks in_progress true
|
|
459
|
+
*/
|
|
460
|
+
export function setTaskLifecycle(doc, prefix, taskId, action) {
|
|
461
|
+
const protImpl = findNode(doc, `${prefix}-PROT-IMPL`);
|
|
462
|
+
if (!protImpl?.subsystem) {
|
|
463
|
+
throw new Error(`Node ${prefix}-PROT-IMPL not found`);
|
|
464
|
+
}
|
|
465
|
+
function updateInSubsystem(subsystem) {
|
|
466
|
+
let found = false;
|
|
467
|
+
const updatedNodes = subsystem.nodes.map((node) => {
|
|
468
|
+
if (node.id === taskId) {
|
|
469
|
+
if (node.type !== "change") {
|
|
470
|
+
throw new Error(`Task node ${taskId} is not a change node`);
|
|
471
|
+
}
|
|
472
|
+
found = true;
|
|
473
|
+
const lifecycle = { ...(node.lifecycle ?? {}) };
|
|
474
|
+
if (action === "start") {
|
|
475
|
+
lifecycle.introduced = lifecycle.introduced ?? true;
|
|
476
|
+
lifecycle.in_progress = true;
|
|
477
|
+
}
|
|
478
|
+
else if (action === "complete") {
|
|
479
|
+
lifecycle.complete = true;
|
|
480
|
+
delete lifecycle.in_progress;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
delete lifecycle.complete;
|
|
484
|
+
lifecycle.in_progress = true;
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
...node,
|
|
488
|
+
lifecycle,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (!node.subsystem) {
|
|
492
|
+
return node;
|
|
493
|
+
}
|
|
494
|
+
const child = updateInSubsystem(node.subsystem);
|
|
495
|
+
if (child.found) {
|
|
496
|
+
found = true;
|
|
497
|
+
return {
|
|
498
|
+
...node,
|
|
499
|
+
subsystem: child.updated,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return node;
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
found,
|
|
506
|
+
updated: {
|
|
507
|
+
...(subsystem.metadata ? { metadata: subsystem.metadata } : {}),
|
|
508
|
+
nodes: updatedNodes,
|
|
509
|
+
...(subsystem.relationships
|
|
510
|
+
? { relationships: subsystem.relationships }
|
|
511
|
+
: {}),
|
|
512
|
+
...(subsystem.external_references
|
|
513
|
+
? { external_references: subsystem.external_references }
|
|
514
|
+
: {}),
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const updated = updateInSubsystem(protImpl.subsystem);
|
|
519
|
+
if (!updated.found) {
|
|
520
|
+
throw new Error(`Task ${taskId} not found in ${prefix}-PROT-IMPL`);
|
|
521
|
+
}
|
|
522
|
+
const updatedNodes = doc.nodes.map((node) => node.id === protImpl.id
|
|
523
|
+
? { ...protImpl, subsystem: updated.updated }
|
|
524
|
+
: node);
|
|
525
|
+
return {
|
|
526
|
+
...doc,
|
|
527
|
+
nodes: updatedNodes,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
452
530
|
// ============================================================================
|
|
453
531
|
// isTaskDone
|
|
454
532
|
// ============================================================================
|
|
455
533
|
/**
|
|
456
|
-
* Check if a change node
|
|
457
|
-
*
|
|
458
|
-
* If no subsystem or no change children in subsystem:
|
|
459
|
-
* - All items in node.plan must have done === true AND at least one item must exist.
|
|
460
|
-
* If subsystem has change children:
|
|
461
|
-
* - All children must be recursively done AND own plan items (if any) must be done.
|
|
534
|
+
* Check if a task change node is complete.
|
|
462
535
|
* @param node - The change node to evaluate.
|
|
463
|
-
* @returns Whether
|
|
536
|
+
* @returns Whether the task lifecycle includes complete.
|
|
464
537
|
* @example
|
|
465
538
|
* ```ts
|
|
466
|
-
* isTaskDone(changeNode); // => true
|
|
539
|
+
* isTaskDone(changeNode); // => true when lifecycle.complete is reached
|
|
467
540
|
* ```
|
|
468
541
|
*/
|
|
469
542
|
export function isTaskDone(node) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
543
|
+
return hasLifecycleState(node, "complete");
|
|
544
|
+
}
|
|
545
|
+
function isGateReady(node) {
|
|
546
|
+
if (node.type !== "gate")
|
|
547
|
+
return false;
|
|
548
|
+
const lifecycle = node.lifecycle ?? {};
|
|
549
|
+
const values = Object.values(lifecycle);
|
|
550
|
+
if (values.length === 0)
|
|
551
|
+
return false;
|
|
552
|
+
return values.every((value) => value === true || typeof value === "string");
|
|
553
|
+
}
|
|
554
|
+
function collectNodes(subsystem) {
|
|
555
|
+
if (!subsystem)
|
|
556
|
+
return [];
|
|
557
|
+
const nodes = [];
|
|
558
|
+
for (const node of subsystem.nodes) {
|
|
559
|
+
nodes.push(node);
|
|
560
|
+
if (node.subsystem) {
|
|
561
|
+
nodes.push(...collectNodes(node.subsystem));
|
|
479
562
|
}
|
|
480
563
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
564
|
+
return nodes;
|
|
565
|
+
}
|
|
566
|
+
function taskBlockageReasons(doc, subsystem, taskNode) {
|
|
567
|
+
if (taskNode.type !== "change")
|
|
568
|
+
return [];
|
|
569
|
+
const relationships = subsystem?.relationships ?? [];
|
|
570
|
+
const scopedNodeMap = new Map((subsystem?.nodes ?? []).map((n) => [n.id, n]));
|
|
571
|
+
const globalNodeMap = new Map(doc.nodes.map((n) => [n.id, n]));
|
|
572
|
+
const reasons = [];
|
|
573
|
+
for (const rel of relationships) {
|
|
574
|
+
if (rel.from !== taskNode.id || rel.type !== "depends_on")
|
|
575
|
+
continue;
|
|
576
|
+
const dependency = scopedNodeMap.get(rel.to) ?? globalNodeMap.get(rel.to);
|
|
577
|
+
const dependencyComplete = dependency?.type === "change" && isTaskDone(dependency);
|
|
578
|
+
if (!dependencyComplete) {
|
|
579
|
+
reasons.push({ kind: "dependency_unmet", nodeId: rel.to });
|
|
580
|
+
}
|
|
485
581
|
}
|
|
486
|
-
|
|
582
|
+
for (const rel of relationships) {
|
|
583
|
+
if (rel.from !== taskNode.id || rel.type !== "constrained_by")
|
|
584
|
+
continue;
|
|
585
|
+
const gate = scopedNodeMap.get(rel.to) ?? globalNodeMap.get(rel.to);
|
|
586
|
+
if (!gate || !isGateReady(gate)) {
|
|
587
|
+
reasons.push({ kind: "gate_not_ready", nodeId: rel.to });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return reasons;
|
|
487
591
|
}
|
|
488
592
|
// ============================================================================
|
|
489
593
|
// countTasks
|
|
@@ -501,22 +605,49 @@ export function isTaskDone(node) {
|
|
|
501
605
|
* ```
|
|
502
606
|
*/
|
|
503
607
|
export function countTasks(node) {
|
|
608
|
+
if (node.type !== "change") {
|
|
609
|
+
return { total: 0, done: 0, blocked: 0, blockedTasks: [] };
|
|
610
|
+
}
|
|
611
|
+
const localDoc = {
|
|
612
|
+
nodes: [node, ...collectNodes(node.subsystem)],
|
|
613
|
+
relationships: [],
|
|
614
|
+
};
|
|
615
|
+
return countTasksInSubsystem(localDoc, {
|
|
616
|
+
nodes: [node],
|
|
617
|
+
relationships: [],
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function countTasksInSubsystem(doc, subsystem) {
|
|
504
621
|
let total = 0;
|
|
505
622
|
let done = 0;
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
623
|
+
let blocked = 0;
|
|
624
|
+
const blockedTasks = [];
|
|
625
|
+
if (!subsystem) {
|
|
626
|
+
return { total, done, blocked, blockedTasks };
|
|
627
|
+
}
|
|
628
|
+
for (const node of subsystem.nodes) {
|
|
629
|
+
if (node.type !== "change")
|
|
630
|
+
continue;
|
|
631
|
+
const childChanges = (node.subsystem?.nodes ?? []).filter((child) => child.type === "change");
|
|
632
|
+
if (childChanges.length > 0) {
|
|
633
|
+
const childCounts = countTasksInSubsystem(doc, node.subsystem);
|
|
634
|
+
total += childCounts.total;
|
|
635
|
+
done += childCounts.done;
|
|
636
|
+
blocked += childCounts.blocked;
|
|
637
|
+
blockedTasks.push(...childCounts.blockedTasks);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
total += 1;
|
|
641
|
+
if (isTaskDone(node)) {
|
|
642
|
+
done += 1;
|
|
643
|
+
}
|
|
644
|
+
const reasons = taskBlockageReasons(doc, subsystem, node);
|
|
645
|
+
if (reasons.length > 0) {
|
|
646
|
+
blocked += 1;
|
|
647
|
+
blockedTasks.push({ taskId: node.id, reasons });
|
|
517
648
|
}
|
|
518
649
|
}
|
|
519
|
-
return { total, done };
|
|
650
|
+
return { total, done, blocked, blockedTasks };
|
|
520
651
|
}
|
|
521
652
|
// ============================================================================
|
|
522
653
|
// planStatus
|
|
@@ -543,15 +674,10 @@ export function planStatus(doc, prefix) {
|
|
|
543
674
|
.map((us) => us.id);
|
|
544
675
|
// Count phases (top-level change nodes)
|
|
545
676
|
const phaseCount = (protImpl?.subsystem?.nodes ?? []).filter((n) => n.type === "change").length;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
for (const change of changeNodes) {
|
|
551
|
-
const taskCount = countTasks(change);
|
|
552
|
-
totalTasks += taskCount.total;
|
|
553
|
-
doneTasks += taskCount.done;
|
|
554
|
-
}
|
|
677
|
+
const taskCounts = countTasksInSubsystem(doc, protImpl?.subsystem);
|
|
678
|
+
const totalTasks = taskCounts.total;
|
|
679
|
+
const doneTasks = taskCounts.done;
|
|
680
|
+
const blockedTasks = taskCounts.blocked;
|
|
555
681
|
// Checklist stats
|
|
556
682
|
const checklistLifecycle = checklist?.lifecycle ?? {};
|
|
557
683
|
const checklistItemCount = Object.keys(checklistLifecycle).length;
|
|
@@ -614,6 +740,8 @@ export function planStatus(doc, prefix) {
|
|
|
614
740
|
tasks: {
|
|
615
741
|
total: totalTasks,
|
|
616
742
|
done: doneTasks,
|
|
743
|
+
blocked: blockedTasks,
|
|
744
|
+
blockedTasks: taskCounts.blockedTasks,
|
|
617
745
|
},
|
|
618
746
|
checklist: {
|
|
619
747
|
defined: checklist !== null,
|
|
@@ -649,17 +777,23 @@ export function planProgress(doc, prefix) {
|
|
|
649
777
|
for (let i = 0; i < sortedTasks.length; i++) {
|
|
650
778
|
const task = sortedTasks[i];
|
|
651
779
|
const taskNum = i + 1;
|
|
652
|
-
|
|
653
|
-
|
|
780
|
+
const taskCount = countTasksInSubsystem(doc, {
|
|
781
|
+
nodes: [task],
|
|
782
|
+
relationships: task.subsystem?.relationships ?? [],
|
|
783
|
+
});
|
|
784
|
+
const reasons = taskBlockageReasons(doc, subsystem, task);
|
|
654
785
|
const percent = taskCount.total === 0
|
|
655
786
|
? 0
|
|
656
787
|
: Math.round((taskCount.done / taskCount.total) * 100);
|
|
657
788
|
result.push({
|
|
658
789
|
phase: taskNum,
|
|
790
|
+
id: task.id,
|
|
659
791
|
name: task.name,
|
|
660
792
|
done: taskCount.done,
|
|
661
793
|
total: taskCount.total,
|
|
662
794
|
percent,
|
|
795
|
+
blocked: reasons.length > 0,
|
|
796
|
+
blockageReasons: reasons,
|
|
663
797
|
});
|
|
664
798
|
}
|
|
665
799
|
return result;
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -302,37 +302,6 @@
|
|
|
302
302
|
},
|
|
303
303
|
"type": "array"
|
|
304
304
|
},
|
|
305
|
-
"plan": {
|
|
306
|
-
"description": "Execution plan as a sequence of tasks. Applicable to change nodes.",
|
|
307
|
-
"items": {
|
|
308
|
-
"additionalProperties": {},
|
|
309
|
-
"description": "A single task within a change's execution plan.",
|
|
310
|
-
"properties": {
|
|
311
|
-
"description": {
|
|
312
|
-
"anyOf": [
|
|
313
|
-
{
|
|
314
|
-
"type": "string"
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
"items": {
|
|
318
|
-
"type": "string"
|
|
319
|
-
},
|
|
320
|
-
"type": "array"
|
|
321
|
-
}
|
|
322
|
-
]
|
|
323
|
-
},
|
|
324
|
-
"done": {
|
|
325
|
-
"default": false,
|
|
326
|
-
"type": "boolean"
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
"required": [
|
|
330
|
-
"description"
|
|
331
|
-
],
|
|
332
|
-
"type": "object"
|
|
333
|
-
},
|
|
334
|
-
"type": "array"
|
|
335
|
-
},
|
|
336
305
|
"propagation": {
|
|
337
306
|
"additionalProperties": {
|
|
338
307
|
"type": "boolean"
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
|
-
import { loadDoc, mutationOpts, persistDoc } from "../shared.js";
|
|
3
|
-
import { addPlanTaskOp, markTaskDoneOp, markTaskUndoneOp, taskListOp, } from "../../operations/index.js";
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// Subcommands
|
|
6
|
-
// ============================================================================
|
|
7
|
-
const listOpts = mutationOpts.pick({ path: true, json: true }).extend({
|
|
8
|
-
change: z.string().optional().describe("Filter by change ID"),
|
|
9
|
-
pending: z.boolean().optional().describe("Show only pending tasks"),
|
|
10
|
-
});
|
|
11
|
-
const listSubcommand = {
|
|
12
|
-
name: "list",
|
|
13
|
-
description: taskListOp.def.description,
|
|
14
|
-
apiLink: taskListOp.def.name,
|
|
15
|
-
opts: listOpts,
|
|
16
|
-
action(_args, opts) {
|
|
17
|
-
const { doc } = loadDoc(opts.path);
|
|
18
|
-
try {
|
|
19
|
-
const rows = taskListOp({
|
|
20
|
-
doc,
|
|
21
|
-
changeId: opts.change,
|
|
22
|
-
pendingOnly: opts.pending,
|
|
23
|
-
});
|
|
24
|
-
if (opts.json) {
|
|
25
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const header = `${"Change".padEnd(12)} ${"#".padEnd(4)} ${"Done".padEnd(6)} Description`;
|
|
29
|
-
const divider = `${"-".repeat(12)} ${"-".repeat(4)} ${"-".repeat(6)} ${"-".repeat(30)}`;
|
|
30
|
-
console.log(header);
|
|
31
|
-
console.log(divider);
|
|
32
|
-
for (const row of rows) {
|
|
33
|
-
const doneStr = row.done ? "[x]" : "[ ]";
|
|
34
|
-
console.log(`${row.changeId.padEnd(12)} ${String(row.index).padEnd(4)} ${doneStr.padEnd(6)} ${row.description}`);
|
|
35
|
-
}
|
|
36
|
-
console.log(`\n${String(rows.length)} task(s)`);
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
const addArgs = z.object({
|
|
45
|
-
changeId: z.string().describe("Change node ID"),
|
|
46
|
-
description: z.string().describe("Task description"),
|
|
47
|
-
});
|
|
48
|
-
const addOpts = mutationOpts.pick({ path: true });
|
|
49
|
-
const addSubcommand = {
|
|
50
|
-
name: "add",
|
|
51
|
-
description: addPlanTaskOp.def.description,
|
|
52
|
-
apiLink: addPlanTaskOp.def.name,
|
|
53
|
-
args: addArgs,
|
|
54
|
-
opts: addOpts,
|
|
55
|
-
action(args, opts) {
|
|
56
|
-
const loaded = loadDoc(opts.path);
|
|
57
|
-
const { doc } = loaded;
|
|
58
|
-
try {
|
|
59
|
-
const newDoc = addPlanTaskOp({
|
|
60
|
-
doc,
|
|
61
|
-
changeId: args.changeId,
|
|
62
|
-
description: args.description,
|
|
63
|
-
});
|
|
64
|
-
persistDoc(newDoc, loaded, { ...opts, json: false, dryRun: false });
|
|
65
|
-
const node = newDoc.nodes.find((n) => n.id === args.changeId);
|
|
66
|
-
if (!node)
|
|
67
|
-
throw new Error(`Node ${args.changeId} not found`);
|
|
68
|
-
const newIndex = (node.plan?.length ?? 1) - 1;
|
|
69
|
-
console.log(`Added task ${String(newIndex)} to ${args.changeId}`);
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
const doneArgs = z.object({
|
|
78
|
-
changeId: z.string().describe("Change node ID"),
|
|
79
|
-
taskIndex: z.string().describe("Task index"),
|
|
80
|
-
});
|
|
81
|
-
const doneOpts = mutationOpts.pick({ path: true });
|
|
82
|
-
const doneSubcommand = {
|
|
83
|
-
name: "done",
|
|
84
|
-
description: markTaskDoneOp.def.description,
|
|
85
|
-
apiLink: markTaskDoneOp.def.name,
|
|
86
|
-
args: doneArgs,
|
|
87
|
-
opts: doneOpts,
|
|
88
|
-
action(args, opts) {
|
|
89
|
-
const loaded = loadDoc(opts.path);
|
|
90
|
-
const { doc } = loaded;
|
|
91
|
-
const taskIndex = parseInt(args.taskIndex, 10);
|
|
92
|
-
if (isNaN(taskIndex) || taskIndex < 0) {
|
|
93
|
-
console.error(`Invalid task index: ${args.taskIndex}`);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const newDoc = markTaskDoneOp({
|
|
98
|
-
doc,
|
|
99
|
-
changeId: args.changeId,
|
|
100
|
-
taskIndex,
|
|
101
|
-
});
|
|
102
|
-
persistDoc(newDoc, loaded, { ...opts, json: false, dryRun: false });
|
|
103
|
-
console.log(`Marked task ${String(taskIndex)} done on ${args.changeId}`);
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
const undoneArgs = z.object({
|
|
112
|
-
changeId: z.string().describe("Change node ID"),
|
|
113
|
-
taskIndex: z.string().describe("Task index"),
|
|
114
|
-
});
|
|
115
|
-
const undoneOpts = mutationOpts.pick({ path: true });
|
|
116
|
-
const undoneSubcommand = {
|
|
117
|
-
name: "undone",
|
|
118
|
-
description: markTaskUndoneOp.def.description,
|
|
119
|
-
apiLink: markTaskUndoneOp.def.name,
|
|
120
|
-
args: undoneArgs,
|
|
121
|
-
opts: undoneOpts,
|
|
122
|
-
action(args, opts) {
|
|
123
|
-
const loaded = loadDoc(opts.path);
|
|
124
|
-
const { doc } = loaded;
|
|
125
|
-
const taskIndex = parseInt(args.taskIndex, 10);
|
|
126
|
-
if (isNaN(taskIndex) || taskIndex < 0) {
|
|
127
|
-
console.error(`Invalid task index: ${args.taskIndex}`);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
const newDoc = markTaskUndoneOp({
|
|
132
|
-
doc,
|
|
133
|
-
changeId: args.changeId,
|
|
134
|
-
taskIndex,
|
|
135
|
-
});
|
|
136
|
-
persistDoc(newDoc, loaded, { ...opts, json: false, dryRun: false });
|
|
137
|
-
console.log(`Marked task ${String(taskIndex)} undone on ${args.changeId}`);
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// Main command
|
|
147
|
-
// ============================================================================
|
|
148
|
-
export const taskCommand = {
|
|
149
|
-
name: "task",
|
|
150
|
-
description: "Manage tasks within change nodes",
|
|
151
|
-
subcommands: [
|
|
152
|
-
listSubcommand,
|
|
153
|
-
addSubcommand,
|
|
154
|
-
doneSubcommand,
|
|
155
|
-
undoneSubcommand,
|
|
156
|
-
],
|
|
157
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
|
-
import { defineOperation } from "./define-operation.js";
|
|
3
|
-
import { SysProMDocument } from "../schema.js";
|
|
4
|
-
/**
|
|
5
|
-
* Append a new task to a change node's plan array. Returns a new document with the task added.
|
|
6
|
-
* @throws {Error} If the change node is not found.
|
|
7
|
-
*/
|
|
8
|
-
export const addPlanTaskOp = defineOperation({
|
|
9
|
-
name: "addPlanTask",
|
|
10
|
-
description: "Append a new task to a change node's plan array. Returns a new document.",
|
|
11
|
-
input: z.object({
|
|
12
|
-
doc: SysProMDocument,
|
|
13
|
-
changeId: z.string(),
|
|
14
|
-
description: z.string(),
|
|
15
|
-
}),
|
|
16
|
-
output: SysProMDocument,
|
|
17
|
-
fn({ doc, changeId, description }) {
|
|
18
|
-
const node = doc.nodes.find((n) => n.id === changeId);
|
|
19
|
-
if (!node) {
|
|
20
|
-
throw new Error(`Node not found: ${changeId}`);
|
|
21
|
-
}
|
|
22
|
-
const newTask = { description, done: false };
|
|
23
|
-
const updatedNode = { ...node, plan: [...(node.plan ?? []), newTask] };
|
|
24
|
-
const newNodes = [...doc.nodes];
|
|
25
|
-
const idx = newNodes.findIndex((n) => n.id === changeId);
|
|
26
|
-
newNodes[idx] = updatedNode;
|
|
27
|
-
return { ...doc, nodes: newNodes };
|
|
28
|
-
},
|
|
29
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
|
-
import { defineOperation } from "./define-operation.js";
|
|
3
|
-
import { SysProMDocument } from "../schema.js";
|
|
4
|
-
/**
|
|
5
|
-
* Mark a task as done within a change node's plan.
|
|
6
|
-
* @throws {Error} If the change node is not found or the task index is out of range.
|
|
7
|
-
*/
|
|
8
|
-
export const markTaskDoneOp = defineOperation({
|
|
9
|
-
name: "markTaskDone",
|
|
10
|
-
description: "Mark a task as done",
|
|
11
|
-
input: z.object({
|
|
12
|
-
doc: SysProMDocument,
|
|
13
|
-
changeId: z.string().describe("ID of the change node"),
|
|
14
|
-
taskIndex: z.number().describe("Zero-based index of the task in the plan"),
|
|
15
|
-
}),
|
|
16
|
-
output: SysProMDocument,
|
|
17
|
-
fn: (input) => {
|
|
18
|
-
const node = input.doc.nodes.find((n) => n.id === input.changeId);
|
|
19
|
-
if (!node)
|
|
20
|
-
throw new Error(`Node not found: ${input.changeId}`);
|
|
21
|
-
const plan = node.plan ?? [];
|
|
22
|
-
if (input.taskIndex < 0 || input.taskIndex >= plan.length) {
|
|
23
|
-
throw new Error(`Task index ${String(input.taskIndex)} out of range (plan has ${String(plan.length)} task(s))`);
|
|
24
|
-
}
|
|
25
|
-
const newPlan = [...plan];
|
|
26
|
-
newPlan[input.taskIndex] = { ...newPlan[input.taskIndex], done: true };
|
|
27
|
-
const newNodes = input.doc.nodes.map((n) => n.id === input.changeId ? { ...n, plan: newPlan } : n);
|
|
28
|
-
return { ...input.doc, nodes: newNodes };
|
|
29
|
-
},
|
|
30
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
|
-
import { defineOperation } from "./define-operation.js";
|
|
3
|
-
import { SysProMDocument } from "../schema.js";
|
|
4
|
-
/**
|
|
5
|
-
* Mark a task as undone (incomplete) within a change node's plan.
|
|
6
|
-
* @throws {Error} If the change node is not found or the task index is out of range.
|
|
7
|
-
*/
|
|
8
|
-
export const markTaskUndoneOp = defineOperation({
|
|
9
|
-
name: "markTaskUndone",
|
|
10
|
-
description: "Mark a task as undone",
|
|
11
|
-
input: z.object({
|
|
12
|
-
doc: SysProMDocument,
|
|
13
|
-
changeId: z.string().describe("ID of the change node"),
|
|
14
|
-
taskIndex: z.number().describe("Zero-based index of the task in the plan"),
|
|
15
|
-
}),
|
|
16
|
-
output: SysProMDocument,
|
|
17
|
-
fn: (input) => {
|
|
18
|
-
const node = input.doc.nodes.find((n) => n.id === input.changeId);
|
|
19
|
-
if (!node)
|
|
20
|
-
throw new Error(`Node not found: ${input.changeId}`);
|
|
21
|
-
const plan = node.plan ?? [];
|
|
22
|
-
if (input.taskIndex < 0 || input.taskIndex >= plan.length) {
|
|
23
|
-
throw new Error(`Task index ${String(input.taskIndex)} out of range (plan has ${String(plan.length)} task(s))`);
|
|
24
|
-
}
|
|
25
|
-
const newPlan = [...plan];
|
|
26
|
-
newPlan[input.taskIndex] = { ...newPlan[input.taskIndex], done: false };
|
|
27
|
-
const newNodes = input.doc.nodes.map((n) => n.id === input.changeId ? { ...n, plan: newPlan } : n);
|
|
28
|
-
return { ...input.doc, nodes: newNodes };
|
|
29
|
-
},
|
|
30
|
-
});
|