@xenonbyte/da-vinci-workflow 0.2.8 → 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.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/lib/async-offload.js +39 -2
- package/lib/cli/command-handlers-core.js +132 -0
- package/lib/cli/command-handlers-design.js +129 -0
- package/lib/cli/command-handlers-pen.js +231 -0
- package/lib/cli/command-handlers-workflow.js +221 -0
- package/lib/cli/command-handlers.js +49 -0
- package/lib/cli/helpers.js +62 -0
- package/lib/cli.js +98 -542
- package/lib/execution-signals.js +33 -0
- package/lib/fs-safety.js +1 -12
- package/lib/path-inside.js +17 -0
- package/lib/utils.js +2 -7
- package/lib/workflow-base-view.js +134 -0
- package/lib/workflow-overlay.js +1033 -0
- package/lib/workflow-persisted-state.js +4 -0
- package/lib/workflow-stage.js +244 -0
- package/lib/workflow-state.js +359 -1998
- package/lib/workflow-task-groups.js +881 -0
- package/lib/worktree-preflight.js +31 -11
- package/package.json +1 -1
|
@@ -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
|
+
};
|