@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
package/lib/execution-signals.js
CHANGED
|
@@ -118,6 +118,38 @@ function readExecutionSignals(projectRoot, options = {}) {
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function listExecutionSignalPathsForChange(projectRoot, options = {}) {
|
|
122
|
+
const changeId = options.changeId ? String(options.changeId).trim() : "";
|
|
123
|
+
const signalsDir = resolveSignalsDir(projectRoot);
|
|
124
|
+
if (!changeId || !pathExists(signalsDir)) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const entries = fs
|
|
129
|
+
.readdirSync(signalsDir, { withFileTypes: true })
|
|
130
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"));
|
|
131
|
+
const paths = [];
|
|
132
|
+
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const absolutePath = path.join(signalsDir, entry.name);
|
|
135
|
+
const parsedName = parseSignalFileName(entry.name);
|
|
136
|
+
let include = false;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const payload = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
140
|
+
include = String(payload.changeId || "") === changeId;
|
|
141
|
+
} catch (_error) {
|
|
142
|
+
include = String(parsedName.changeId || "").trim() === changeId;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (include) {
|
|
146
|
+
paths.push(absolutePath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return paths.sort((left, right) => left.localeCompare(right));
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
function summarizeSignalsBySurface(signals) {
|
|
122
154
|
const summary = {};
|
|
123
155
|
for (const signal of signals || []) {
|
|
@@ -148,6 +180,7 @@ function getLatestSignalBySurfacePrefix(signals, prefix) {
|
|
|
148
180
|
module.exports = {
|
|
149
181
|
writeExecutionSignal,
|
|
150
182
|
readExecutionSignals,
|
|
183
|
+
listExecutionSignalPathsForChange,
|
|
151
184
|
summarizeSignalsBySurface,
|
|
152
185
|
listSignalsBySurfacePrefix,
|
|
153
186
|
getLatestSignalBySurfacePrefix
|
package/lib/fs-safety.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { pathExists } = require("./utils");
|
|
4
|
+
const { isPathInside } = require("./path-inside");
|
|
4
5
|
|
|
5
6
|
const DEFAULT_MAX_DEPTH = 32;
|
|
6
7
|
const DEFAULT_MAX_ENTRIES = 20000;
|
|
@@ -19,18 +20,6 @@ function toPositiveInteger(value, fallback) {
|
|
|
19
20
|
return normalized;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function isPathInside(basePath, targetPath) {
|
|
23
|
-
const resolvedBase = path.resolve(basePath);
|
|
24
|
-
const resolvedTarget = path.resolve(targetPath);
|
|
25
|
-
|
|
26
|
-
if (resolvedBase === resolvedTarget) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const relative = path.relative(resolvedBase, resolvedTarget);
|
|
31
|
-
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
23
|
function listFilesRecursiveSafe(rootDir, options = {}) {
|
|
35
24
|
const maxDepth = toPositiveInteger(options.maxDepth, DEFAULT_MAX_DEPTH);
|
|
36
25
|
const maxEntries = toPositiveInteger(options.maxEntries, DEFAULT_MAX_ENTRIES);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
function isPathInside(basePath, targetPath) {
|
|
4
|
+
const resolvedBase = path.resolve(basePath);
|
|
5
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
6
|
+
|
|
7
|
+
if (resolvedBase === resolvedTarget) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const relative = path.relative(resolvedBase, resolvedTarget);
|
|
12
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
isPathInside
|
|
17
|
+
};
|
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
+
const { isPathInside } = require("./path-inside");
|
|
3
4
|
|
|
4
5
|
const HAS_SYNC_WAIT =
|
|
5
6
|
typeof SharedArrayBuffer === "function" &&
|
|
@@ -36,13 +37,7 @@ function normalizeRelativePath(relativePath) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function pathWithinRoot(projectRoot, candidatePath) {
|
|
39
|
-
|
|
40
|
-
const candidate = path.resolve(candidatePath);
|
|
41
|
-
if (candidate === root) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
45
|
-
return candidate.startsWith(prefix);
|
|
40
|
+
return isPathInside(projectRoot, candidatePath);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
function uniqueValues(values) {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const { getStageById } = require("./workflow-contract");
|
|
2
|
+
const {
|
|
3
|
+
deriveStageFromArtifacts,
|
|
4
|
+
buildBaseHandoffGates
|
|
5
|
+
} = require("./workflow-stage");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} WorkflowFindings
|
|
9
|
+
* @property {string[]} blockers
|
|
10
|
+
* @property {string[]} warnings
|
|
11
|
+
* @property {string[]} notes
|
|
12
|
+
*/
|
|
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 {Object} WorkflowCompletionAudit
|
|
33
|
+
* @property {string} [status]
|
|
34
|
+
* @property {string[]} [failures]
|
|
35
|
+
* @property {string[]} [warnings]
|
|
36
|
+
* @property {string[]} [notes]
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} PersistedWorkflowRecord
|
|
41
|
+
* @property {string} [stage]
|
|
42
|
+
* @property {Object.<string, string>} [gates]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} WorkflowBaseView
|
|
47
|
+
* @property {"persisted"|"derived"} source
|
|
48
|
+
* @property {string} stageId
|
|
49
|
+
* @property {WorkflowFindings} findings
|
|
50
|
+
* @property {Object.<string, string>} baseGates
|
|
51
|
+
* @property {WorkflowCompletionAudit | null} completionAudit
|
|
52
|
+
* @property {Array<object>} taskGroupSeed
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
function cloneFindings(findings) {
|
|
56
|
+
return {
|
|
57
|
+
blockers: Array.isArray(findings && findings.blockers) ? findings.blockers.slice() : [],
|
|
58
|
+
warnings: Array.isArray(findings && findings.warnings) ? findings.warnings.slice() : [],
|
|
59
|
+
notes: Array.isArray(findings && findings.notes) ? findings.notes.slice() : []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveCompletionAudit(stageId, resolver) {
|
|
64
|
+
if (typeof resolver !== "function") {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return resolver(stageId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalize a trusted persisted workflow snapshot into the shared base-view shape.
|
|
72
|
+
*
|
|
73
|
+
* @param {{
|
|
74
|
+
* persistedRecord?: PersistedWorkflowRecord,
|
|
75
|
+
* findings?: WorkflowFindings,
|
|
76
|
+
* taskGroupSeed?: Array<object>,
|
|
77
|
+
* resolveCompletionAudit?: (stageId: string) => (WorkflowCompletionAudit | null)
|
|
78
|
+
* }} [options]
|
|
79
|
+
* @returns {WorkflowBaseView}
|
|
80
|
+
*/
|
|
81
|
+
function buildPersistedWorkflowBaseView(options = {}) {
|
|
82
|
+
const persistedRecord = options.persistedRecord || {};
|
|
83
|
+
const stage = getStageById(persistedRecord.stage) || getStageById("bootstrap");
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
source: "persisted",
|
|
87
|
+
stageId: stage.id,
|
|
88
|
+
findings: cloneFindings(options.findings),
|
|
89
|
+
baseGates:
|
|
90
|
+
persistedRecord && persistedRecord.gates && typeof persistedRecord.gates === "object"
|
|
91
|
+
? { ...persistedRecord.gates }
|
|
92
|
+
: {},
|
|
93
|
+
completionAudit: resolveCompletionAudit(stage.id, options.resolveCompletionAudit),
|
|
94
|
+
taskGroupSeed: Array.isArray(options.taskGroupSeed) ? options.taskGroupSeed : []
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build the artifact/checkpoint-backed base view before runtime overlays apply.
|
|
100
|
+
*
|
|
101
|
+
* @param {{
|
|
102
|
+
* artifactState?: WorkflowArtifactState,
|
|
103
|
+
* checkpointStatuses?: WorkflowCheckpointStatusMap,
|
|
104
|
+
* findings?: WorkflowFindings,
|
|
105
|
+
* taskGroupSeed?: Array<object>,
|
|
106
|
+
* resolveCompletionAudit?: (stageId: string) => (WorkflowCompletionAudit | null)
|
|
107
|
+
* }} [options]
|
|
108
|
+
* @returns {WorkflowBaseView}
|
|
109
|
+
*/
|
|
110
|
+
function buildDerivedWorkflowBaseView(options = {}) {
|
|
111
|
+
const findings = cloneFindings(options.findings);
|
|
112
|
+
const stageId = deriveStageFromArtifacts(
|
|
113
|
+
options.artifactState || {},
|
|
114
|
+
options.checkpointStatuses || {},
|
|
115
|
+
findings
|
|
116
|
+
);
|
|
117
|
+
const completionAudit = resolveCompletionAudit(stageId, options.resolveCompletionAudit);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
source: "derived",
|
|
121
|
+
stageId,
|
|
122
|
+
findings,
|
|
123
|
+
baseGates: buildBaseHandoffGates(options.artifactState || {}, options.checkpointStatuses || {}, {
|
|
124
|
+
completionAudit
|
|
125
|
+
}),
|
|
126
|
+
completionAudit,
|
|
127
|
+
taskGroupSeed: Array.isArray(options.taskGroupSeed) ? options.taskGroupSeed : []
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
buildPersistedWorkflowBaseView,
|
|
133
|
+
buildDerivedWorkflowBaseView
|
|
134
|
+
};
|