@xenonbyte/da-vinci-workflow 0.2.5 → 0.2.6
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 +16 -0
- package/README.md +15 -9
- package/README.zh-CN.md +16 -9
- package/docs/dv-command-reference.md +18 -2
- package/docs/execution-chain-migration.md +14 -3
- package/docs/maintainer-bootstrap.md +102 -0
- package/docs/pencil-rendering-workflow.md +1 -1
- package/docs/skill-usage.md +31 -0
- package/docs/workflow-overview.md +40 -5
- package/docs/zh-CN/dv-command-reference.md +16 -2
- package/docs/zh-CN/maintainer-bootstrap.md +101 -0
- package/docs/zh-CN/pencil-rendering-workflow.md +1 -1
- package/docs/zh-CN/skill-usage.md +30 -0
- package/docs/zh-CN/workflow-overview.md +38 -5
- package/lib/audit.js +19 -0
- package/lib/cli/helpers.js +63 -2
- package/lib/cli.js +98 -0
- package/lib/gate-utils.js +56 -0
- package/lib/install.js +134 -6
- package/lib/lint-bindings.js +41 -28
- package/lib/lint-spec.js +403 -109
- package/lib/lint-tasks.js +571 -21
- package/lib/maintainer-readiness.js +317 -0
- package/lib/planning-parsers.js +190 -1
- package/lib/planning-quality-utils.js +81 -0
- package/lib/planning-signal-freshness.js +205 -0
- package/lib/scope-check.js +751 -82
- package/lib/sidecars.js +396 -1
- package/lib/task-review.js +2 -1
- package/lib/utils.js +15 -0
- package/lib/workflow-persisted-state.js +52 -32
- package/lib/workflow-state.js +1187 -249
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { pathExists } = require("./utils");
|
|
4
|
+
|
|
5
|
+
function normalizeSurface(surface) {
|
|
6
|
+
return String(surface || "")
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function collectMarkdownFilesRecursive(rootPath) {
|
|
14
|
+
if (!rootPath || !pathExists(rootPath)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const pending = [rootPath];
|
|
19
|
+
const seenDirs = new Set();
|
|
20
|
+
const files = [];
|
|
21
|
+
|
|
22
|
+
while (pending.length > 0) {
|
|
23
|
+
const current = pending.pop();
|
|
24
|
+
let realDir = null;
|
|
25
|
+
try {
|
|
26
|
+
realDir = fs.realpathSync(current);
|
|
27
|
+
} catch (_error) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (seenDirs.has(realDir)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
seenDirs.add(realDir);
|
|
34
|
+
|
|
35
|
+
let entries = [];
|
|
36
|
+
try {
|
|
37
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
38
|
+
} catch (_error) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const absolutePath = path.join(current, entry.name);
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
pending.push(absolutePath);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (entry.isSymbolicLink()) {
|
|
49
|
+
let stat = null;
|
|
50
|
+
try {
|
|
51
|
+
stat = fs.statSync(absolutePath);
|
|
52
|
+
} catch (_error) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (stat.isDirectory()) {
|
|
56
|
+
pending.push(absolutePath);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (stat.isFile() && /\.md$/i.test(entry.name)) {
|
|
60
|
+
files.push(absolutePath);
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (entry.isFile() && /\.md$/i.test(entry.name)) {
|
|
65
|
+
files.push(absolutePath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return files;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectSurfaceArtifacts(projectRoot, changeId, surface) {
|
|
74
|
+
const workflowRoot = path.join(projectRoot, ".da-vinci");
|
|
75
|
+
const changeDir = path.join(workflowRoot, "changes", String(changeId || "").trim());
|
|
76
|
+
const normalizedSurface = normalizeSurface(surface);
|
|
77
|
+
if (!normalizedSurface || !pathExists(changeDir)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const artifactPaths = new Set();
|
|
82
|
+
const addIfExists = (targetPath) => {
|
|
83
|
+
if (targetPath && pathExists(targetPath)) {
|
|
84
|
+
artifactPaths.add(path.resolve(targetPath));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const addCommonSpecArtifacts = () => {
|
|
89
|
+
const specsRoot = path.join(changeDir, "specs");
|
|
90
|
+
for (const filePath of collectMarkdownFilesRecursive(specsRoot)) {
|
|
91
|
+
addIfExists(filePath);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (normalizedSurface === "lint-spec") {
|
|
96
|
+
addCommonSpecArtifacts();
|
|
97
|
+
addIfExists(path.join(changeDir, "proposal.md"));
|
|
98
|
+
addIfExists(path.join(changeDir, "design-brief.md"));
|
|
99
|
+
addIfExists(path.join(changeDir, "design.md"));
|
|
100
|
+
addIfExists(path.join(changeDir, "tasks.md"));
|
|
101
|
+
addIfExists(path.join(changeDir, "verification.md"));
|
|
102
|
+
addIfExists(path.join(changeDir, "pencil-design.md"));
|
|
103
|
+
addIfExists(path.join(changeDir, "pencil-bindings.md"));
|
|
104
|
+
addIfExists(path.join(projectRoot, "DA-VINCI.md"));
|
|
105
|
+
addIfExists(path.join(projectRoot, "openspec", "config.yaml"));
|
|
106
|
+
return Array.from(artifactPaths);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (normalizedSurface === "scope-check") {
|
|
110
|
+
addCommonSpecArtifacts();
|
|
111
|
+
addIfExists(path.join(changeDir, "proposal.md"));
|
|
112
|
+
addIfExists(path.join(changeDir, "tasks.md"));
|
|
113
|
+
addIfExists(path.join(changeDir, "verification.md"));
|
|
114
|
+
addIfExists(path.join(changeDir, "pencil-design.md"));
|
|
115
|
+
addIfExists(path.join(changeDir, "pencil-bindings.md"));
|
|
116
|
+
addIfExists(path.join(workflowRoot, "page-map.md"));
|
|
117
|
+
addIfExists(path.join(changeDir, "page-map.md"));
|
|
118
|
+
return Array.from(artifactPaths);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (normalizedSurface === "lint-tasks") {
|
|
122
|
+
addCommonSpecArtifacts();
|
|
123
|
+
addIfExists(path.join(changeDir, "proposal.md"));
|
|
124
|
+
addIfExists(path.join(changeDir, "design-brief.md"));
|
|
125
|
+
addIfExists(path.join(changeDir, "design.md"));
|
|
126
|
+
addIfExists(path.join(changeDir, "tasks.md"));
|
|
127
|
+
addIfExists(path.join(changeDir, "pencil-bindings.md"));
|
|
128
|
+
addIfExists(path.join(changeDir, "pencil-design.md"));
|
|
129
|
+
addIfExists(path.join(changeDir, "verification.md"));
|
|
130
|
+
addIfExists(path.join(workflowRoot, "page-map.md"));
|
|
131
|
+
addIfExists(path.join(changeDir, "page-map.md"));
|
|
132
|
+
return Array.from(artifactPaths);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function latestMtimeMs(paths) {
|
|
139
|
+
let latest = 0;
|
|
140
|
+
for (const targetPath of Array.isArray(paths) ? paths : []) {
|
|
141
|
+
let stat = null;
|
|
142
|
+
try {
|
|
143
|
+
stat = fs.statSync(targetPath);
|
|
144
|
+
} catch (_error) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (Number.isFinite(stat.mtimeMs) && stat.mtimeMs > latest) {
|
|
148
|
+
latest = stat.mtimeMs;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return latest;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function evaluatePlanningSignalFreshness(projectRoot, options = {}) {
|
|
155
|
+
const changeId = String(options.changeId || "").trim();
|
|
156
|
+
const surface = normalizeSurface(options.surface);
|
|
157
|
+
const signal = options.signal || null;
|
|
158
|
+
const artifactPaths = collectSurfaceArtifacts(projectRoot, changeId, surface);
|
|
159
|
+
const latestArtifactMtime = latestMtimeMs(artifactPaths);
|
|
160
|
+
const toleranceMs = Number.isFinite(Number(options.toleranceMs))
|
|
161
|
+
? Math.max(0, Number(options.toleranceMs))
|
|
162
|
+
: 1000;
|
|
163
|
+
|
|
164
|
+
if (!changeId || !surface) {
|
|
165
|
+
return {
|
|
166
|
+
applicable: false,
|
|
167
|
+
fresh: true,
|
|
168
|
+
reasons: [],
|
|
169
|
+
artifactCount: artifactPaths.length,
|
|
170
|
+
latestArtifactMtimeMs: latestArtifactMtime,
|
|
171
|
+
signalTimestampMs: 0,
|
|
172
|
+
staleByMs: 0
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const signalTimestampMs = Date.parse(String(signal && signal.timestamp ? signal.timestamp : ""));
|
|
177
|
+
const reasons = [];
|
|
178
|
+
if (!Number.isFinite(signalTimestampMs)) {
|
|
179
|
+
reasons.push("missing_or_invalid_signal_timestamp");
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
latestArtifactMtime > 0 &&
|
|
183
|
+
Number.isFinite(signalTimestampMs) &&
|
|
184
|
+
signalTimestampMs + toleranceMs < latestArtifactMtime
|
|
185
|
+
) {
|
|
186
|
+
reasons.push("artifact_newer_than_signal");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
applicable: true,
|
|
191
|
+
fresh: reasons.length === 0,
|
|
192
|
+
reasons,
|
|
193
|
+
artifactCount: artifactPaths.length,
|
|
194
|
+
latestArtifactMtimeMs: latestArtifactMtime,
|
|
195
|
+
signalTimestampMs: Number.isFinite(signalTimestampMs) ? signalTimestampMs : 0,
|
|
196
|
+
staleByMs:
|
|
197
|
+
Number.isFinite(signalTimestampMs) && latestArtifactMtime > signalTimestampMs + toleranceMs
|
|
198
|
+
? latestArtifactMtime - (signalTimestampMs + toleranceMs)
|
|
199
|
+
: 0
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
evaluatePlanningSignalFreshness
|
|
205
|
+
};
|