@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,369 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { STATUS } = require("./workflow-contract");
4
+ const { pathExists, writeFileAtomic } = require("./utils");
5
+ const {
6
+ unique,
7
+ resolveChangeDir,
8
+ parseTasksArtifact,
9
+ parseBindingsArtifact,
10
+ parsePageMapArtifact,
11
+ parseRuntimeSpecs,
12
+ digestFile,
13
+ digestObject,
14
+ readChangeArtifacts,
15
+ readArtifactTexts
16
+ } = require("./planning-parsers");
17
+
18
+ const SCHEMA_VERSION = "1.0.0";
19
+
20
+ function buildEnvelope(projectRoot) {
21
+ return {
22
+ status: STATUS.PASS,
23
+ failures: [],
24
+ warnings: [],
25
+ notes: [],
26
+ projectRoot,
27
+ changeId: null,
28
+ sidecarsDir: null,
29
+ sidecars: {},
30
+ freshness: {}
31
+ };
32
+ }
33
+
34
+ function normalizeCollectionItems(values) {
35
+ return unique((values || []).map((value) => String(value || "").trim()).filter(Boolean)).sort((a, b) =>
36
+ a.localeCompare(b)
37
+ );
38
+ }
39
+
40
+ function buildSpecIndex(projectRoot, changeId, specRecords) {
41
+ const behaviors = [];
42
+ const states = [];
43
+ const inputs = [];
44
+ const outputs = [];
45
+ const acceptance = [];
46
+ const sourceRefs = [];
47
+
48
+ for (const record of specRecords) {
49
+ const sourcePath = record.path;
50
+ sourceRefs.push({
51
+ path: sourcePath,
52
+ digest: digestFile(path.join(projectRoot, sourcePath))
53
+ });
54
+
55
+ const parsed = record.parsed;
56
+ for (const item of parsed.sections.behavior.items || []) {
57
+ behaviors.push({ specPath: sourcePath, text: item });
58
+ }
59
+ for (const item of parsed.sections.states.items || []) {
60
+ states.push({ specPath: sourcePath, text: item });
61
+ }
62
+ for (const item of parsed.sections.inputs.items || []) {
63
+ inputs.push({ specPath: sourcePath, text: item });
64
+ }
65
+ for (const item of parsed.sections.outputs.items || []) {
66
+ outputs.push({ specPath: sourcePath, text: item });
67
+ }
68
+ for (const item of parsed.sections.acceptance.items || []) {
69
+ acceptance.push({ specPath: sourcePath, text: item });
70
+ }
71
+ }
72
+
73
+ return {
74
+ schemaVersion: SCHEMA_VERSION,
75
+ sidecar: "spec.index.json",
76
+ changeId,
77
+ sourceRefs: sourceRefs.sort((a, b) => a.path.localeCompare(b.path)),
78
+ collections: {
79
+ behavior: behaviors
80
+ .map((item, index) => ({
81
+ id: `behavior-${String(index + 1).padStart(3, "0")}`,
82
+ specPath: item.specPath,
83
+ text: item.text
84
+ }))
85
+ .sort((a, b) => a.id.localeCompare(b.id)),
86
+ states: states
87
+ .map((item, index) => ({
88
+ id: `state-${String(index + 1).padStart(3, "0")}`,
89
+ specPath: item.specPath,
90
+ text: item.text
91
+ }))
92
+ .sort((a, b) => a.id.localeCompare(b.id)),
93
+ inputs: inputs
94
+ .map((item, index) => ({
95
+ id: `input-${String(index + 1).padStart(3, "0")}`,
96
+ specPath: item.specPath,
97
+ text: item.text
98
+ }))
99
+ .sort((a, b) => a.id.localeCompare(b.id)),
100
+ outputs: outputs
101
+ .map((item, index) => ({
102
+ id: `output-${String(index + 1).padStart(3, "0")}`,
103
+ specPath: item.specPath,
104
+ text: item.text
105
+ }))
106
+ .sort((a, b) => a.id.localeCompare(b.id)),
107
+ acceptance: acceptance
108
+ .map((item, index) => ({
109
+ id: `acceptance-${String(index + 1).padStart(3, "0")}`,
110
+ specPath: item.specPath,
111
+ text: item.text
112
+ }))
113
+ .sort((a, b) => a.id.localeCompare(b.id))
114
+ }
115
+ };
116
+ }
117
+
118
+ function buildTasksIndex(changeId, tasksArtifact, bindingsArtifact, specIndex) {
119
+ const taskSections = Array.isArray(tasksArtifact.sections) ? tasksArtifact.sections : [];
120
+ const groupRecords = tasksArtifact.taskGroups.map((group) => {
121
+ const section =
122
+ taskSections.find((item) => String(item.id || "") === String(group.id || "")) || null;
123
+ const checklistItems = Array.isArray(section && section.checklistItems)
124
+ ? section.checklistItems
125
+ : [];
126
+ return {
127
+ id: String(group.id || ""),
128
+ title: group.title,
129
+ checklistItems: checklistItems.map((item) => ({
130
+ checked: item.checked,
131
+ text: item.text
132
+ }))
133
+ };
134
+ });
135
+
136
+ const specTexts = [
137
+ ...specIndex.collections.behavior.map((item) => item.text),
138
+ ...specIndex.collections.states.map((item) => item.text),
139
+ ...specIndex.collections.acceptance.map((item) => item.text)
140
+ ]
141
+ .join(" ")
142
+ .toLowerCase();
143
+
144
+ const specLinks = groupRecords.map((group) => {
145
+ const tokens = String(group.title || "")
146
+ .toLowerCase()
147
+ .split(/\s+/)
148
+ .filter((token) => token.length >= 4);
149
+ const matchedTokenCount = tokens.filter((token) => specTexts.includes(token)).length;
150
+ return {
151
+ taskGroupId: group.id,
152
+ matchedTokenCount
153
+ };
154
+ });
155
+
156
+ const bindingLinks = bindingsArtifact.mappings.map((mapping, index) => ({
157
+ id: `binding-link-${String(index + 1).padStart(3, "0")}`,
158
+ implementation: mapping.implementation,
159
+ designPage: mapping.designPage,
160
+ designSource: mapping.designSource || ""
161
+ }));
162
+
163
+ const codeAreas = normalizeCollectionItems(
164
+ tasksArtifact.checklistItems
165
+ .map((item) => item.text)
166
+ .filter((text) => /src\/|pages\/|component|module|route|screen/i.test(text))
167
+ );
168
+ const verificationActions = normalizeCollectionItems(
169
+ tasksArtifact.checklistItems
170
+ .map((item) => item.text)
171
+ .filter((text) => /verify|verification|coverage|test/i.test(text))
172
+ );
173
+
174
+ return {
175
+ schemaVersion: SCHEMA_VERSION,
176
+ sidecar: "tasks.index.json",
177
+ changeId,
178
+ sourceRefs: [],
179
+ taskGroups: groupRecords,
180
+ specLinks,
181
+ bindingLinks,
182
+ codeAreas,
183
+ verificationActions
184
+ };
185
+ }
186
+
187
+ function buildPageMapIndex(changeId, pageMapArtifact, pageMapPath, projectRoot) {
188
+ return {
189
+ schemaVersion: SCHEMA_VERSION,
190
+ sidecar: "page-map.index.json",
191
+ changeId,
192
+ sourceRefs: [
193
+ {
194
+ path: path.relative(projectRoot, pageMapPath) || pageMapPath,
195
+ digest: digestFile(pageMapPath)
196
+ }
197
+ ],
198
+ pages: pageMapArtifact.pages,
199
+ statesPerPageItems: pageMapArtifact.statesPerPageItems
200
+ };
201
+ }
202
+
203
+ function buildBindingsIndex(changeId, bindingsArtifact, bindingsPath, projectRoot) {
204
+ return {
205
+ schemaVersion: SCHEMA_VERSION,
206
+ sidecar: "bindings.index.json",
207
+ changeId,
208
+ sourceRefs: [
209
+ {
210
+ path: path.relative(projectRoot, bindingsPath) || bindingsPath,
211
+ digest: digestFile(bindingsPath)
212
+ }
213
+ ],
214
+ mappings: bindingsArtifact.mappings
215
+ .map((mapping, index) => ({
216
+ id: `mapping-${String(index + 1).padStart(3, "0")}`,
217
+ implementation: mapping.implementation,
218
+ designPage: mapping.designPage,
219
+ designSource: mapping.designSource || "",
220
+ screenId: mapping.screenId || ""
221
+ }))
222
+ .sort((a, b) => a.id.localeCompare(b.id)),
223
+ malformed: bindingsArtifact.malformed.sort((a, b) => a.localeCompare(b))
224
+ };
225
+ }
226
+
227
+ function attachTaskSources(tasksIndex, artifactPaths, projectRoot) {
228
+ tasksIndex.sourceRefs = [
229
+ { path: path.relative(projectRoot, artifactPaths.tasksPath), digest: digestFile(artifactPaths.tasksPath) },
230
+ {
231
+ path: path.relative(projectRoot, artifactPaths.bindingsPath),
232
+ digest: digestFile(artifactPaths.bindingsPath)
233
+ }
234
+ ];
235
+ return tasksIndex;
236
+ }
237
+
238
+ function writeSidecarFile(sidecarsDir, fileName, payload) {
239
+ const absolutePath = path.join(sidecarsDir, fileName);
240
+ writeFileAtomic(absolutePath, `${JSON.stringify(payload, null, 2)}\n`);
241
+ return absolutePath;
242
+ }
243
+
244
+ function generatePlanningSidecars(projectPathInput, options = {}) {
245
+ const projectRoot = path.resolve(projectPathInput || process.cwd());
246
+ const write = options.write !== false;
247
+ const requestedChangeId = options.changeId ? String(options.changeId).trim() : "";
248
+ const result = buildEnvelope(projectRoot);
249
+
250
+ const resolved = resolveChangeDir(projectRoot, requestedChangeId);
251
+ result.failures.push(...resolved.failures);
252
+ result.notes.push(...resolved.notes);
253
+ if (!resolved.changeDir) {
254
+ result.status = STATUS.BLOCK;
255
+ return result;
256
+ }
257
+ result.changeId = resolved.changeId;
258
+
259
+ const artifactPaths = readChangeArtifacts(projectRoot, resolved.changeId);
260
+ const artifacts = readArtifactTexts(artifactPaths);
261
+ const specRecords = parseRuntimeSpecs(resolved.changeDir, projectRoot);
262
+ if (!artifacts.tasks || !artifacts.bindings || !artifacts.pageMap || specRecords.length === 0) {
263
+ if (!artifacts.tasks) {
264
+ result.failures.push("Missing `tasks.md` for sidecar generation.");
265
+ }
266
+ if (!artifacts.bindings) {
267
+ result.failures.push("Missing `pencil-bindings.md` for sidecar generation.");
268
+ }
269
+ if (!artifacts.pageMap) {
270
+ result.failures.push("Missing `page-map.md` for sidecar generation.");
271
+ }
272
+ if (specRecords.length === 0) {
273
+ result.failures.push("Missing runtime spec files for sidecar generation.");
274
+ }
275
+ result.status = STATUS.BLOCK;
276
+ return result;
277
+ }
278
+
279
+ const tasksArtifact = parseTasksArtifact(artifacts.tasks);
280
+ const bindingsArtifact = parseBindingsArtifact(artifacts.bindings);
281
+ const pageMapArtifact = parsePageMapArtifact(artifacts.pageMap);
282
+
283
+ const specIndex = buildSpecIndex(projectRoot, resolved.changeId, specRecords);
284
+ const tasksIndex = attachTaskSources(
285
+ buildTasksIndex(resolved.changeId, tasksArtifact, bindingsArtifact, specIndex),
286
+ artifactPaths,
287
+ projectRoot
288
+ );
289
+ const pageMapIndex = buildPageMapIndex(resolved.changeId, pageMapArtifact, artifactPaths.pageMapPath, projectRoot);
290
+ const bindingsIndex = buildBindingsIndex(
291
+ resolved.changeId,
292
+ bindingsArtifact,
293
+ artifactPaths.bindingsPath,
294
+ projectRoot
295
+ );
296
+
297
+ const sidecars = {
298
+ "spec.index.json": specIndex,
299
+ "tasks.index.json": tasksIndex,
300
+ "page-map.index.json": pageMapIndex,
301
+ "bindings.index.json": bindingsIndex
302
+ };
303
+
304
+ result.sidecars = sidecars;
305
+ result.freshness = Object.fromEntries(
306
+ Object.entries(sidecars).map(([name, payload]) => [name, digestObject(payload)])
307
+ );
308
+
309
+ const sidecarsDir = path.join(resolved.changeDir, "sidecars");
310
+ result.sidecarsDir = sidecarsDir;
311
+
312
+ if (write) {
313
+ fs.mkdirSync(sidecarsDir, { recursive: true });
314
+ const written = {};
315
+ for (const [name, payload] of Object.entries(sidecars)) {
316
+ written[name] = writeSidecarFile(sidecarsDir, name, payload);
317
+ }
318
+ result.written = written;
319
+ result.notes.push("Sidecars are generated only through `generate-sidecars`; lint/status/verify do not auto-rewrite them.");
320
+ } else {
321
+ result.notes.push("Dry-run mode: sidecars computed in-memory only.");
322
+ }
323
+
324
+ return result;
325
+ }
326
+
327
+ function formatGenerateSidecarsReport(result) {
328
+ const lines = [
329
+ "Da Vinci generate-sidecars",
330
+ `Project: ${result.projectRoot}`,
331
+ `Change: ${result.changeId || "(not selected)"}`,
332
+ `Status: ${result.status}`
333
+ ];
334
+
335
+ if (result.sidecarsDir) {
336
+ lines.push(`Sidecars dir: ${result.sidecarsDir}`);
337
+ }
338
+ if (result.failures.length > 0) {
339
+ lines.push("", "Failures:");
340
+ for (const failure of result.failures) {
341
+ lines.push(`- ${failure}`);
342
+ }
343
+ }
344
+ if (result.warnings.length > 0) {
345
+ lines.push("", "Warnings:");
346
+ for (const warning of result.warnings) {
347
+ lines.push(`- ${warning}`);
348
+ }
349
+ }
350
+ if (Object.keys(result.freshness || {}).length > 0) {
351
+ lines.push("", "Sidecar digests:");
352
+ for (const [name, digest] of Object.entries(result.freshness)) {
353
+ lines.push(`- ${name}: ${digest}`);
354
+ }
355
+ }
356
+ if (result.notes.length > 0) {
357
+ lines.push("", "Notes:");
358
+ for (const note of result.notes) {
359
+ lines.push(`- ${note}`);
360
+ }
361
+ }
362
+ return lines.join("\n");
363
+ }
364
+
365
+ module.exports = {
366
+ SCHEMA_VERSION,
367
+ generatePlanningSidecars,
368
+ formatGenerateSidecarsReport
369
+ };
@@ -111,13 +111,25 @@ function buildReviewerPrompt({ reviewer, projectRoot, changeId, pencilDesignPath
111
111
  ].join("\n");
112
112
  }
113
113
 
114
+ function truncateForError(value, maxLength = 240) {
115
+ const text = String(value || "").replace(/\s+/g, " ").trim();
116
+ if (text.length <= maxLength) {
117
+ return text;
118
+ }
119
+ return `${text.slice(0, maxLength)}...`;
120
+ }
121
+
114
122
  function parseReviewerPayload(rawText) {
115
123
  const payload = parseJsonText(String(rawText || "").trim(), "reviewer JSON payload");
116
124
  if (!isPlainObject(payload)) {
117
- throw new Error("Invalid reviewer JSON payload: expected a JSON object.");
125
+ throw new Error(
126
+ `Invalid reviewer JSON payload: expected a JSON object. Received: ${truncateForError(rawText)}`
127
+ );
118
128
  }
119
129
  if (!payload.status) {
120
- throw new Error("Invalid reviewer JSON payload: missing required `status`.");
130
+ throw new Error(
131
+ `Invalid reviewer JSON payload: missing required \`status\`. Received: ${truncateForError(rawText)}`
132
+ );
121
133
  }
122
134
  const status = normalizeStatus(payload.status);
123
135
  const issues = Array.isArray(payload.issues)
@@ -241,7 +253,20 @@ async function runReviewerWithCodexOnce(options = {}) {
241
253
  const stdout = String(error.stdout || "").trim();
242
254
  const stderr = String(error.stderr || "").trim();
243
255
  const timeoutHint = error.killed ? " (timed out)" : "";
244
- const message = stderr || stdout || error.message || "unknown error";
256
+ const diagnostics = [];
257
+ if (stderr) {
258
+ diagnostics.push(stderr);
259
+ }
260
+ if (stdout) {
261
+ diagnostics.push(`stdout: ${stdout}`);
262
+ }
263
+ if (error && error.code !== undefined && error.code !== null && !stderr && !stdout) {
264
+ diagnostics.push(`process code: ${String(error.code)}`);
265
+ }
266
+ if (error && error.signal && !stderr && !stdout) {
267
+ diagnostics.push(`signal: ${String(error.signal)}`);
268
+ }
269
+ const message = diagnostics[0] || error.message || "unclassified execution failure";
245
270
  throw new Error(`Reviewer \`${reviewer}\` failed${timeoutHint}: ${message}`);
246
271
  }
247
272
 
package/lib/utils.js CHANGED
@@ -90,8 +90,16 @@ function writeFileAtomic(targetPath, content, options = {}) {
90
90
  const encoding = options.encoding || "utf8";
91
91
  const tempPath = buildAtomicTempPath(resolvedTargetPath);
92
92
  fs.mkdirSync(path.dirname(resolvedTargetPath), { recursive: true });
93
- fs.writeFileSync(tempPath, content, encoding);
94
- fs.renameSync(tempPath, resolvedTargetPath);
93
+ let shouldCleanupTemp = true;
94
+ try {
95
+ fs.writeFileSync(tempPath, content, encoding);
96
+ fs.renameSync(tempPath, resolvedTargetPath);
97
+ shouldCleanupTemp = false;
98
+ } finally {
99
+ if (shouldCleanupTemp) {
100
+ fs.rmSync(tempPath, { force: true });
101
+ }
102
+ }
95
103
  }
96
104
 
97
105
  function writeFileExclusiveAtomic(targetPath, content, options = {}) {