@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +28 -65
  3. package/README.zh-CN.md +28 -65
  4. package/bin/da-vinci-tui.js +8 -0
  5. package/commands/claude/dv/continue.md +5 -0
  6. package/commands/codex/prompts/dv-continue.md +6 -1
  7. package/commands/gemini/dv/continue.toml +5 -0
  8. package/commands/templates/dv-continue.shared.md +33 -0
  9. package/docs/dv-command-reference.md +35 -0
  10. package/docs/execution-chain-migration.md +46 -0
  11. package/docs/execution-chain-plan.md +125 -0
  12. package/docs/prompt-entrypoints.md +8 -0
  13. package/docs/skill-usage.md +217 -0
  14. package/docs/workflow-examples.md +10 -0
  15. package/docs/workflow-overview.md +26 -0
  16. package/docs/zh-CN/dv-command-reference.md +35 -0
  17. package/docs/zh-CN/execution-chain-migration.md +46 -0
  18. package/docs/zh-CN/prompt-entrypoints.md +8 -0
  19. package/docs/zh-CN/skill-usage.md +217 -0
  20. package/docs/zh-CN/workflow-examples.md +10 -0
  21. package/docs/zh-CN/workflow-overview.md +26 -0
  22. package/lib/artifact-parsers.js +120 -0
  23. package/lib/audit.js +61 -0
  24. package/lib/cli.js +351 -13
  25. package/lib/diff-spec.js +242 -0
  26. package/lib/execution-signals.js +136 -0
  27. package/lib/lint-bindings.js +143 -0
  28. package/lib/lint-spec.js +408 -0
  29. package/lib/lint-tasks.js +176 -0
  30. package/lib/planning-parsers.js +567 -0
  31. package/lib/scaffold.js +193 -0
  32. package/lib/scope-check.js +603 -0
  33. package/lib/sidecars.js +369 -0
  34. package/lib/supervisor-review.js +28 -3
  35. package/lib/utils.js +10 -2
  36. package/lib/verify.js +652 -0
  37. package/lib/workflow-contract.js +107 -0
  38. package/lib/workflow-persisted-state.js +297 -0
  39. package/lib/workflow-state.js +785 -0
  40. package/package.json +13 -3
  41. package/references/artifact-templates.md +26 -0
  42. package/references/checkpoints.md +14 -0
  43. package/references/modes.md +10 -0
  44. package/tui/catalog.js +1190 -0
  45. 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
+ };