@xenonbyte/da-vinci-workflow 0.1.26 → 0.2.2
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 +31 -0
- package/README.md +28 -65
- package/README.zh-CN.md +28 -65
- package/bin/da-vinci-tui.js +8 -0
- package/commands/claude/dv/continue.md +5 -0
- package/commands/codex/prompts/dv-continue.md +6 -1
- package/commands/gemini/dv/continue.toml +5 -0
- package/commands/templates/dv-continue.shared.md +33 -0
- package/docs/dv-command-reference.md +35 -0
- package/docs/execution-chain-migration.md +46 -0
- package/docs/execution-chain-plan.md +125 -0
- package/docs/prompt-entrypoints.md +8 -0
- package/docs/skill-usage.md +217 -0
- package/docs/workflow-examples.md +10 -0
- package/docs/workflow-overview.md +26 -0
- package/docs/zh-CN/dv-command-reference.md +35 -0
- package/docs/zh-CN/execution-chain-migration.md +46 -0
- package/docs/zh-CN/prompt-entrypoints.md +8 -0
- package/docs/zh-CN/skill-usage.md +217 -0
- package/docs/zh-CN/workflow-examples.md +10 -0
- package/docs/zh-CN/workflow-overview.md +26 -0
- package/lib/artifact-parsers.js +120 -0
- package/lib/audit.js +61 -0
- package/lib/cli.js +351 -13
- package/lib/diff-spec.js +242 -0
- package/lib/execution-signals.js +136 -0
- package/lib/lint-bindings.js +143 -0
- package/lib/lint-spec.js +408 -0
- package/lib/lint-tasks.js +176 -0
- package/lib/planning-parsers.js +567 -0
- package/lib/scaffold.js +193 -0
- package/lib/scope-check.js +603 -0
- package/lib/sidecars.js +369 -0
- package/lib/supervisor-review.js +28 -3
- package/lib/utils.js +10 -2
- package/lib/verify.js +652 -0
- package/lib/workflow-contract.js +107 -0
- package/lib/workflow-persisted-state.js +297 -0
- package/lib/workflow-state.js +785 -0
- package/package.json +13 -3
- package/references/artifact-templates.md +26 -0
- package/references/checkpoints.md +14 -0
- package/references/modes.md +10 -0
- package/tui/catalog.js +1190 -0
- package/tui/index.js +727 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const STATUS = Object.freeze({
|
|
2
|
+
PASS: "PASS",
|
|
3
|
+
WARN: "WARN",
|
|
4
|
+
BLOCK: "BLOCK"
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const STATUS_PRIORITY = Object.freeze({
|
|
8
|
+
PASS: 0,
|
|
9
|
+
WARN: 1,
|
|
10
|
+
BLOCK: 2
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const STAGES = Object.freeze([
|
|
14
|
+
Object.freeze({
|
|
15
|
+
id: "bootstrap",
|
|
16
|
+
label: "Bootstrap",
|
|
17
|
+
defaultRoute: "da-vinci bootstrap-project --project <project-path> --change <change-id>"
|
|
18
|
+
}),
|
|
19
|
+
Object.freeze({
|
|
20
|
+
id: "breakdown",
|
|
21
|
+
label: "Breakdown",
|
|
22
|
+
defaultRoute: "/dv:breakdown"
|
|
23
|
+
}),
|
|
24
|
+
Object.freeze({
|
|
25
|
+
id: "design",
|
|
26
|
+
label: "Design",
|
|
27
|
+
defaultRoute: "/dv:design"
|
|
28
|
+
}),
|
|
29
|
+
Object.freeze({
|
|
30
|
+
id: "tasks",
|
|
31
|
+
label: "Tasks",
|
|
32
|
+
defaultRoute: "/dv:tasks"
|
|
33
|
+
}),
|
|
34
|
+
Object.freeze({
|
|
35
|
+
id: "build",
|
|
36
|
+
label: "Build",
|
|
37
|
+
defaultRoute: "/dv:build"
|
|
38
|
+
}),
|
|
39
|
+
Object.freeze({
|
|
40
|
+
id: "verify",
|
|
41
|
+
label: "Verify",
|
|
42
|
+
defaultRoute: "/dv:verify"
|
|
43
|
+
}),
|
|
44
|
+
Object.freeze({
|
|
45
|
+
id: "complete",
|
|
46
|
+
label: "Complete",
|
|
47
|
+
defaultRoute: null
|
|
48
|
+
})
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const STAGE_INDEX = Object.freeze(
|
|
52
|
+
STAGES.reduce((acc, stage, index) => {
|
|
53
|
+
acc[stage.id] = {
|
|
54
|
+
...stage,
|
|
55
|
+
order: index + 1
|
|
56
|
+
};
|
|
57
|
+
return acc;
|
|
58
|
+
}, {})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const CHECKPOINT_LABELS = Object.freeze({
|
|
62
|
+
DESIGN: "design checkpoint",
|
|
63
|
+
MAPPING: "mapping checkpoint",
|
|
64
|
+
TASK: "task checkpoint",
|
|
65
|
+
DESIGN_SOURCE: "design source checkpoint",
|
|
66
|
+
RUNTIME_GATE: "mcp runtime gate"
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const HANDOFF_GATES = Object.freeze({
|
|
70
|
+
BREAKDOWN_TO_DESIGN: "breakdown_to_design",
|
|
71
|
+
DESIGN_TO_TASKS: "design_to_tasks",
|
|
72
|
+
TASKS_TO_BUILD: "tasks_to_build",
|
|
73
|
+
BUILD_TO_VERIFY: "build_to_verify",
|
|
74
|
+
VERIFY_TO_COMPLETE: "verify_to_complete"
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function getStageById(stageId) {
|
|
78
|
+
return STAGE_INDEX[stageId] || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mergeStatuses(statuses) {
|
|
82
|
+
const normalized = (statuses || [])
|
|
83
|
+
.map((value) => String(value || "").toUpperCase())
|
|
84
|
+
.filter((value) => STATUS_PRIORITY[value] !== undefined);
|
|
85
|
+
if (normalized.length === 0) {
|
|
86
|
+
return STATUS.PASS;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let selected = STATUS.PASS;
|
|
90
|
+
for (const status of normalized) {
|
|
91
|
+
if (STATUS_PRIORITY[status] > STATUS_PRIORITY[selected]) {
|
|
92
|
+
selected = status;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return selected;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
STATUS,
|
|
100
|
+
STATUS_PRIORITY,
|
|
101
|
+
STAGES,
|
|
102
|
+
STAGE_INDEX,
|
|
103
|
+
CHECKPOINT_LABELS,
|
|
104
|
+
HANDOFF_GATES,
|
|
105
|
+
getStageById,
|
|
106
|
+
mergeStatuses
|
|
107
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { writeFileAtomic, pathExists, readTextIfExists } = require("./utils");
|
|
4
|
+
|
|
5
|
+
const WORKFLOW_STATE_VERSION = 1;
|
|
6
|
+
const DEFAULT_STALE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const PERSISTED_NOTE_EXCLUDE_PATTERNS = [
|
|
8
|
+
/^No persisted workflow state found for this change; deriving from artifacts\.$/i,
|
|
9
|
+
/^Persisted workflow state .* deriving from artifacts\.$/i,
|
|
10
|
+
/^workflow-status persisted a fresh derived workflow snapshot\.$/i,
|
|
11
|
+
/^Task-group metadata refreshed:/i
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function resolveWorkflowStatePath(projectRoot) {
|
|
15
|
+
return path.join(projectRoot, ".da-vinci", "state", "workflow.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveTaskGroupMetadataPath(projectRoot, changeId) {
|
|
19
|
+
return path.join(projectRoot, ".da-vinci", "state", "task-groups", `${changeId}.json`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fingerprintForPath(targetPath) {
|
|
23
|
+
if (!pathExists(targetPath)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs.statSync(targetPath);
|
|
28
|
+
return {
|
|
29
|
+
path: targetPath,
|
|
30
|
+
mtimeMs: stat.mtimeMs,
|
|
31
|
+
size: stat.size
|
|
32
|
+
};
|
|
33
|
+
} catch (_error) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildWorkflowFingerprint(projectRoot, changeId) {
|
|
39
|
+
const changeRoot = changeId ? path.join(projectRoot, ".da-vinci", "changes", changeId) : "";
|
|
40
|
+
const candidates = [
|
|
41
|
+
path.join(projectRoot, ".da-vinci", "page-map.md"),
|
|
42
|
+
path.join(changeRoot, "proposal.md"),
|
|
43
|
+
path.join(changeRoot, "design.md"),
|
|
44
|
+
path.join(changeRoot, "pencil-design.md"),
|
|
45
|
+
path.join(changeRoot, "pencil-bindings.md"),
|
|
46
|
+
path.join(changeRoot, "tasks.md"),
|
|
47
|
+
path.join(changeRoot, "verification.md"),
|
|
48
|
+
path.join(changeRoot, "workflow.json")
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const specRoot = path.join(changeRoot, "specs");
|
|
52
|
+
if (pathExists(specRoot)) {
|
|
53
|
+
const stack = [specRoot];
|
|
54
|
+
const visited = new Set();
|
|
55
|
+
while (stack.length > 0) {
|
|
56
|
+
const current = stack.pop();
|
|
57
|
+
let resolvedCurrent;
|
|
58
|
+
try {
|
|
59
|
+
resolvedCurrent = fs.realpathSync(current);
|
|
60
|
+
} catch (_error) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (visited.has(resolvedCurrent)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
visited.add(resolvedCurrent);
|
|
67
|
+
|
|
68
|
+
let entries = [];
|
|
69
|
+
try {
|
|
70
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
71
|
+
} catch (_error) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const absolutePath = path.join(current, entry.name);
|
|
76
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
77
|
+
stack.push(absolutePath);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (entry.isFile() && entry.name === "spec.md") {
|
|
81
|
+
candidates.push(absolutePath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return candidates
|
|
88
|
+
.map((candidate) => fingerprintForPath(candidate))
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function stableFingerprintHash(fingerprint) {
|
|
94
|
+
return JSON.stringify(
|
|
95
|
+
(fingerprint || []).map((entry) => ({
|
|
96
|
+
path: entry.path,
|
|
97
|
+
mtimeMs: entry.mtimeMs,
|
|
98
|
+
size: entry.size
|
|
99
|
+
}))
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sanitizePersistedNotes(notes) {
|
|
104
|
+
const filtered = [];
|
|
105
|
+
for (const note of Array.isArray(notes) ? notes : []) {
|
|
106
|
+
const text = String(note || "").trim();
|
|
107
|
+
if (!text) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (PERSISTED_NOTE_EXCLUDE_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
filtered.push(text);
|
|
114
|
+
}
|
|
115
|
+
return Array.from(new Set(filtered));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readPersistedWorkflowState(projectRoot) {
|
|
119
|
+
const statePath = resolveWorkflowStatePath(projectRoot);
|
|
120
|
+
if (!pathExists(statePath)) {
|
|
121
|
+
return {
|
|
122
|
+
statePath,
|
|
123
|
+
state: null
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const payload = JSON.parse(readTextIfExists(statePath));
|
|
129
|
+
return {
|
|
130
|
+
statePath,
|
|
131
|
+
state: payload
|
|
132
|
+
};
|
|
133
|
+
} catch (_error) {
|
|
134
|
+
return {
|
|
135
|
+
statePath,
|
|
136
|
+
state: null,
|
|
137
|
+
parseError: true
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function writePersistedWorkflowState(projectRoot, payload) {
|
|
143
|
+
const statePath = resolveWorkflowStatePath(projectRoot);
|
|
144
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
145
|
+
writeFileAtomic(statePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
146
|
+
return statePath;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function selectPersistedStateForChange(projectRoot, changeId, options = {}) {
|
|
150
|
+
const staleWindowMs = Number.isFinite(Number(options.staleWindowMs))
|
|
151
|
+
? Number(options.staleWindowMs)
|
|
152
|
+
: DEFAULT_STALE_WINDOW_MS;
|
|
153
|
+
|
|
154
|
+
const loaded = readPersistedWorkflowState(projectRoot);
|
|
155
|
+
if (!loaded.state) {
|
|
156
|
+
return {
|
|
157
|
+
usable: false,
|
|
158
|
+
reason: loaded.parseError ? "parse-error" : "missing",
|
|
159
|
+
statePath: loaded.statePath,
|
|
160
|
+
persisted: null
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (loaded.state.version !== WORKFLOW_STATE_VERSION) {
|
|
165
|
+
return {
|
|
166
|
+
usable: false,
|
|
167
|
+
reason: "version-mismatch",
|
|
168
|
+
statePath: loaded.statePath,
|
|
169
|
+
persisted: loaded.state
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const byChange = loaded.state.changes || {};
|
|
174
|
+
const changeRecord = changeId ? byChange[changeId] : null;
|
|
175
|
+
if (!changeRecord) {
|
|
176
|
+
return {
|
|
177
|
+
usable: false,
|
|
178
|
+
reason: "change-missing",
|
|
179
|
+
statePath: loaded.statePath,
|
|
180
|
+
persisted: loaded.state
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const persistedAt = Date.parse(String(changeRecord.persistedAt || ""));
|
|
185
|
+
if (!Number.isFinite(persistedAt)) {
|
|
186
|
+
return {
|
|
187
|
+
usable: false,
|
|
188
|
+
reason: "invalid-timestamp",
|
|
189
|
+
statePath: loaded.statePath,
|
|
190
|
+
persisted: loaded.state
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (Date.now() - persistedAt > staleWindowMs) {
|
|
195
|
+
return {
|
|
196
|
+
usable: false,
|
|
197
|
+
reason: "time-stale",
|
|
198
|
+
statePath: loaded.statePath,
|
|
199
|
+
persisted: loaded.state
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const currentFingerprint = buildWorkflowFingerprint(projectRoot, changeId);
|
|
204
|
+
const currentHash = stableFingerprintHash(currentFingerprint);
|
|
205
|
+
const persistedHash = String(changeRecord.fingerprintHash || "");
|
|
206
|
+
if (!persistedHash || persistedHash !== currentHash) {
|
|
207
|
+
return {
|
|
208
|
+
usable: false,
|
|
209
|
+
reason: "fingerprint-mismatch",
|
|
210
|
+
statePath: loaded.statePath,
|
|
211
|
+
persisted: loaded.state
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
usable: true,
|
|
217
|
+
reason: null,
|
|
218
|
+
statePath: loaded.statePath,
|
|
219
|
+
persisted: loaded.state,
|
|
220
|
+
changeRecord
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function persistDerivedWorkflowResult(projectRoot, changeId, workflowResult, options = {}) {
|
|
225
|
+
const loaded = readPersistedWorkflowState(projectRoot);
|
|
226
|
+
const current = loaded.state && typeof loaded.state === "object" ? loaded.state : {};
|
|
227
|
+
const changes = current.changes && typeof current.changes === "object" ? current.changes : {};
|
|
228
|
+
const metadataRefs = options.metadataRefs || {};
|
|
229
|
+
|
|
230
|
+
if (!changeId) {
|
|
231
|
+
return resolveWorkflowStatePath(projectRoot);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const fingerprint = buildWorkflowFingerprint(projectRoot, changeId);
|
|
235
|
+
changes[changeId] = {
|
|
236
|
+
stage: workflowResult.stage,
|
|
237
|
+
checkpointState: workflowResult.checkpointState,
|
|
238
|
+
gates: workflowResult.gates,
|
|
239
|
+
nextStep: workflowResult.nextStep,
|
|
240
|
+
status: workflowResult.status,
|
|
241
|
+
failures: workflowResult.failures,
|
|
242
|
+
warnings: workflowResult.warnings,
|
|
243
|
+
notes: sanitizePersistedNotes(workflowResult.notes),
|
|
244
|
+
taskGroups: workflowResult.taskGroups || null,
|
|
245
|
+
metadataRefs,
|
|
246
|
+
fingerprintHash: stableFingerprintHash(fingerprint),
|
|
247
|
+
persistedAt: new Date().toISOString()
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const payload = {
|
|
251
|
+
version: WORKFLOW_STATE_VERSION,
|
|
252
|
+
updatedAt: new Date().toISOString(),
|
|
253
|
+
changes
|
|
254
|
+
};
|
|
255
|
+
return writePersistedWorkflowState(projectRoot, payload);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function writeTaskGroupMetadata(projectRoot, changeId, metadataPayload) {
|
|
259
|
+
if (!changeId) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const targetPath = resolveTaskGroupMetadataPath(projectRoot, changeId);
|
|
263
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
264
|
+
writeFileAtomic(targetPath, `${JSON.stringify(metadataPayload, null, 2)}\n`);
|
|
265
|
+
return targetPath;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function readTaskGroupMetadata(projectRoot, changeId) {
|
|
269
|
+
if (!changeId) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const targetPath = resolveTaskGroupMetadataPath(projectRoot, changeId);
|
|
273
|
+
if (!pathExists(targetPath)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
return JSON.parse(readTextIfExists(targetPath));
|
|
278
|
+
} catch (_error) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = {
|
|
284
|
+
WORKFLOW_STATE_VERSION,
|
|
285
|
+
DEFAULT_STALE_WINDOW_MS,
|
|
286
|
+
resolveWorkflowStatePath,
|
|
287
|
+
resolveTaskGroupMetadataPath,
|
|
288
|
+
buildWorkflowFingerprint,
|
|
289
|
+
stableFingerprintHash,
|
|
290
|
+
sanitizePersistedNotes,
|
|
291
|
+
readPersistedWorkflowState,
|
|
292
|
+
writePersistedWorkflowState,
|
|
293
|
+
selectPersistedStateForChange,
|
|
294
|
+
persistDerivedWorkflowResult,
|
|
295
|
+
writeTaskGroupMetadata,
|
|
296
|
+
readTaskGroupMetadata
|
|
297
|
+
};
|