@xenonbyte/da-vinci-workflow 0.2.7 → 0.2.9

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.
@@ -2,6 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const crypto = require("crypto");
4
4
  const { writeFileAtomic, pathExists, readTextIfExists } = require("./utils");
5
+ const { listExecutionSignalPathsForChange } = require("./execution-signals");
5
6
 
6
7
  // Route snapshot schema version; task-group metadata keeps its own version in workflow-state.js.
7
8
  const WORKFLOW_STATE_VERSION = 3;
@@ -64,6 +65,9 @@ function buildWorkflowFingerprint(projectRoot, changeId) {
64
65
  path.join(changeRoot, "tasks.md"),
65
66
  path.join(changeRoot, "verification.md")
66
67
  ];
68
+ if (changeId) {
69
+ candidates.push(...listExecutionSignalPathsForChange(projectRoot, { changeId }));
70
+ }
67
71
 
68
72
  const specRoot = path.join(changeRoot, "specs");
69
73
  if (pathExists(specRoot)) {
@@ -0,0 +1,244 @@
1
+ const path = require("path");
2
+ const {
3
+ parseCheckpointStatusMap,
4
+ normalizeCheckpointLabel
5
+ } = require("./audit-parsers");
6
+ const { readTextIfExists } = require("./utils");
7
+ const {
8
+ STATUS,
9
+ CHECKPOINT_LABELS,
10
+ HANDOFF_GATES,
11
+ mergeStatuses
12
+ } = require("./workflow-contract");
13
+
14
+ /**
15
+ * @typedef {Object} WorkflowArtifactState
16
+ * @property {boolean} workflowRootReady
17
+ * @property {boolean} changeSelected
18
+ * @property {boolean} proposal
19
+ * @property {string[]} specFiles
20
+ * @property {boolean} design
21
+ * @property {boolean} pencilDesign
22
+ * @property {boolean} pencilBindings
23
+ * @property {boolean} tasks
24
+ * @property {boolean} verification
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object.<string, string>} WorkflowCheckpointStatusMap
29
+ */
30
+
31
+ /**
32
+ * @typedef {"bootstrap"|"breakdown"|"design"|"tasks"|"build"|"verify"|"complete"} WorkflowStageId
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} WorkflowFindings
37
+ * @property {string[]} blockers
38
+ * @property {string[]} warnings
39
+ * @property {string[]} notes
40
+ */
41
+
42
+ /**
43
+ * @typedef {Object.<string, string>} WorkflowBaseGateMap
44
+ */
45
+
46
+ /**
47
+ * Read normalized checkpoint statuses from change-local workflow artifacts.
48
+ *
49
+ * @param {string} changeDir
50
+ * @returns {WorkflowCheckpointStatusMap}
51
+ */
52
+ function readCheckpointStatuses(changeDir) {
53
+ const collected = {};
54
+ const files = [
55
+ path.join(changeDir, "design.md"),
56
+ path.join(changeDir, "pencil-design.md"),
57
+ path.join(changeDir, "tasks.md"),
58
+ path.join(changeDir, "verification.md")
59
+ ];
60
+
61
+ for (const artifactPath of files) {
62
+ const text = readTextIfExists(artifactPath);
63
+ if (!text) {
64
+ continue;
65
+ }
66
+ const statuses = parseCheckpointStatusMap(text);
67
+ for (const [label, value] of Object.entries(statuses)) {
68
+ const normalized = normalizeCheckpointLabel(label);
69
+ if (!normalized) {
70
+ continue;
71
+ }
72
+ collected[normalized] = String(value || "").toUpperCase();
73
+ }
74
+ }
75
+
76
+ return collected;
77
+ }
78
+
79
+ /**
80
+ * Normalize a checkpoint status token into PASS/WARN/BLOCK, defaulting to PASS.
81
+ *
82
+ * @param {string} status
83
+ * @returns {string}
84
+ */
85
+ function normalizeCheckpointStatus(status) {
86
+ const normalized = String(status || "").toUpperCase();
87
+ if (normalized === STATUS.PASS || normalized === STATUS.WARN || normalized === STATUS.BLOCK) {
88
+ return normalized;
89
+ }
90
+ return STATUS.PASS;
91
+ }
92
+
93
+ /**
94
+ * Derive the artifact-backed workflow stage before any runtime overlays apply.
95
+ *
96
+ * @param {WorkflowArtifactState} artifactState
97
+ * @param {WorkflowCheckpointStatusMap} checkpointStatuses
98
+ * @param {WorkflowFindings} findings
99
+ * @returns {WorkflowStageId}
100
+ */
101
+ function deriveStageFromArtifacts(artifactState, checkpointStatuses, findings) {
102
+ if (!artifactState.workflowRootReady) {
103
+ findings.blockers.push("Missing `.da-vinci/` directory.");
104
+ return "bootstrap";
105
+ }
106
+
107
+ if (!artifactState.changeSelected) {
108
+ findings.blockers.push("No active change selected under `.da-vinci/changes/`.");
109
+ return "breakdown";
110
+ }
111
+
112
+ if (!artifactState.proposal || artifactState.specFiles.length === 0) {
113
+ if (!artifactState.proposal) {
114
+ findings.blockers.push("Missing `proposal.md` for the active change.");
115
+ }
116
+ if (artifactState.specFiles.length === 0) {
117
+ findings.blockers.push("Missing `specs/*/spec.md` for the active change.");
118
+ }
119
+ return "breakdown";
120
+ }
121
+
122
+ if (!artifactState.design || !artifactState.pencilDesign || !artifactState.pencilBindings) {
123
+ if (!artifactState.design) {
124
+ findings.blockers.push("Missing `design.md` for the active change.");
125
+ }
126
+ if (!artifactState.pencilDesign) {
127
+ findings.blockers.push("Missing `pencil-design.md` for the active change.");
128
+ }
129
+ if (!artifactState.pencilBindings) {
130
+ findings.blockers.push("Missing `pencil-bindings.md` for the active change.");
131
+ }
132
+ return "design";
133
+ }
134
+
135
+ const designCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.DESIGN];
136
+ if (designCheckpoint === STATUS.BLOCK) {
137
+ findings.blockers.push("`design checkpoint` is BLOCK.");
138
+ return "design";
139
+ }
140
+ if (designCheckpoint === STATUS.WARN) {
141
+ findings.warnings.push("`design checkpoint` is WARN.");
142
+ }
143
+
144
+ const designSourceCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.DESIGN_SOURCE];
145
+ if (designSourceCheckpoint === STATUS.BLOCK) {
146
+ findings.blockers.push("`design source checkpoint` is BLOCK.");
147
+ return "design";
148
+ }
149
+ if (designSourceCheckpoint === STATUS.WARN) {
150
+ findings.warnings.push("`design source checkpoint` is WARN.");
151
+ }
152
+
153
+ const runtimeGateCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.RUNTIME_GATE];
154
+ if (runtimeGateCheckpoint === STATUS.BLOCK) {
155
+ findings.blockers.push("`mcp runtime gate` is BLOCK.");
156
+ return "design";
157
+ }
158
+ if (runtimeGateCheckpoint === STATUS.WARN) {
159
+ findings.warnings.push("`mcp runtime gate` is WARN.");
160
+ }
161
+
162
+ const mappingCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.MAPPING];
163
+ if (mappingCheckpoint === STATUS.BLOCK) {
164
+ findings.blockers.push("`mapping checkpoint` is BLOCK.");
165
+ return "design";
166
+ }
167
+ if (mappingCheckpoint === STATUS.WARN) {
168
+ findings.warnings.push("`mapping checkpoint` is WARN.");
169
+ }
170
+
171
+ if (!artifactState.tasks) {
172
+ findings.blockers.push("Missing `tasks.md` for the active change.");
173
+ return "tasks";
174
+ }
175
+
176
+ const taskCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.TASK];
177
+ if (taskCheckpoint === STATUS.BLOCK) {
178
+ findings.blockers.push("`task checkpoint` is BLOCK.");
179
+ return "tasks";
180
+ }
181
+ if (taskCheckpoint === STATUS.WARN) {
182
+ findings.warnings.push("`task checkpoint` is WARN.");
183
+ }
184
+
185
+ if (!artifactState.verification) {
186
+ findings.blockers.push("Missing `verification.md` for the active change.");
187
+ return "build";
188
+ }
189
+
190
+ return "verify";
191
+ }
192
+
193
+ /**
194
+ * Build the handoff-gate baseline that corresponds to artifact/checkpoint truth.
195
+ *
196
+ * @param {WorkflowArtifactState} artifactState
197
+ * @param {WorkflowCheckpointStatusMap} checkpointStatuses
198
+ * @param {{ completionAudit?: { status?: string } | null }} [options]
199
+ * @returns {WorkflowBaseGateMap}
200
+ */
201
+ function buildBaseHandoffGates(artifactState, checkpointStatuses, options = {}) {
202
+ const designCheckpointStatus = normalizeCheckpointStatus(checkpointStatuses[CHECKPOINT_LABELS.DESIGN]);
203
+ const designSourceCheckpointStatus = normalizeCheckpointStatus(
204
+ checkpointStatuses[CHECKPOINT_LABELS.DESIGN_SOURCE]
205
+ );
206
+ const runtimeGateCheckpointStatus = normalizeCheckpointStatus(
207
+ checkpointStatuses[CHECKPOINT_LABELS.RUNTIME_GATE]
208
+ );
209
+ const mappingCheckpointStatus = normalizeCheckpointStatus(checkpointStatuses[CHECKPOINT_LABELS.MAPPING]);
210
+ const taskCheckpointStatus = normalizeCheckpointStatus(checkpointStatuses[CHECKPOINT_LABELS.TASK]);
211
+ const completionAudit = options.completionAudit || null;
212
+
213
+ return {
214
+ [HANDOFF_GATES.BREAKDOWN_TO_DESIGN]:
215
+ artifactState.proposal && artifactState.specFiles.length > 0 ? STATUS.PASS : STATUS.BLOCK,
216
+ [HANDOFF_GATES.DESIGN_TO_TASKS]: mergeStatuses([
217
+ artifactState.design && artifactState.pencilDesign && artifactState.pencilBindings
218
+ ? STATUS.PASS
219
+ : STATUS.BLOCK,
220
+ designCheckpointStatus,
221
+ designSourceCheckpointStatus,
222
+ runtimeGateCheckpointStatus,
223
+ mappingCheckpointStatus
224
+ ]),
225
+ [HANDOFF_GATES.TASKS_TO_BUILD]: mergeStatuses([
226
+ artifactState.tasks ? STATUS.PASS : STATUS.BLOCK,
227
+ designCheckpointStatus,
228
+ designSourceCheckpointStatus,
229
+ runtimeGateCheckpointStatus,
230
+ mappingCheckpointStatus,
231
+ taskCheckpointStatus
232
+ ]),
233
+ [HANDOFF_GATES.BUILD_TO_VERIFY]: artifactState.verification ? STATUS.PASS : STATUS.BLOCK,
234
+ [HANDOFF_GATES.VERIFY_TO_COMPLETE]:
235
+ completionAudit && completionAudit.status === STATUS.PASS ? STATUS.PASS : STATUS.WARN
236
+ };
237
+ }
238
+
239
+ module.exports = {
240
+ readCheckpointStatuses,
241
+ normalizeCheckpointStatus,
242
+ deriveStageFromArtifacts,
243
+ buildBaseHandoffGates
244
+ };