@xenonbyte/da-vinci-workflow 0.2.3 → 0.2.5

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 (49) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +32 -7
  3. package/README.zh-CN.md +151 -7
  4. package/SKILL.md +45 -704
  5. package/commands/claude/dv/build.md +5 -0
  6. package/commands/claude/dv/continue.md +4 -0
  7. package/commands/claude/dv/tasks.md +6 -0
  8. package/commands/claude/dv/verify.md +2 -0
  9. package/commands/codex/prompts/dv-build.md +5 -0
  10. package/commands/codex/prompts/dv-continue.md +4 -0
  11. package/commands/codex/prompts/dv-tasks.md +6 -0
  12. package/commands/codex/prompts/dv-verify.md +2 -0
  13. package/commands/gemini/dv/build.toml +5 -0
  14. package/commands/gemini/dv/continue.toml +4 -0
  15. package/commands/gemini/dv/tasks.toml +6 -0
  16. package/commands/gemini/dv/verify.toml +2 -0
  17. package/commands/templates/dv-continue.shared.md +4 -0
  18. package/docs/discipline-and-orchestration-upgrade.md +83 -0
  19. package/docs/dv-command-reference.md +33 -5
  20. package/docs/execution-chain-migration.md +23 -0
  21. package/docs/prompt-entrypoints.md +6 -0
  22. package/docs/skill-contract-maintenance.md +14 -0
  23. package/docs/skill-usage.md +16 -0
  24. package/docs/workflow-overview.md +17 -0
  25. package/docs/zh-CN/dv-command-reference.md +31 -5
  26. package/docs/zh-CN/execution-chain-migration.md +23 -0
  27. package/docs/zh-CN/prompt-entrypoints.md +6 -0
  28. package/docs/zh-CN/skill-usage.md +16 -0
  29. package/docs/zh-CN/workflow-overview.md +17 -0
  30. package/lib/audit-parsers.js +148 -1
  31. package/lib/cli/helpers.js +43 -0
  32. package/lib/cli/lint-family.js +56 -0
  33. package/lib/cli/verify-family.js +79 -0
  34. package/lib/cli.js +123 -145
  35. package/lib/execution-profile.js +143 -0
  36. package/lib/execution-signals.js +19 -1
  37. package/lib/lint-tasks.js +86 -2
  38. package/lib/planning-parsers.js +263 -19
  39. package/lib/scaffold.js +454 -23
  40. package/lib/supervisor-review.js +2 -1
  41. package/lib/task-execution.js +160 -0
  42. package/lib/task-review.js +197 -0
  43. package/lib/utils.js +19 -0
  44. package/lib/verify.js +1308 -85
  45. package/lib/workflow-state.js +452 -30
  46. package/lib/worktree-preflight.js +214 -0
  47. package/package.json +1 -1
  48. package/references/artifact-templates.md +56 -6
  49. package/references/skill-workflow-detail.md +66 -0
@@ -0,0 +1,160 @@
1
+ const path = require("path");
2
+ const { resolveChangeDir, unique } = require("./planning-parsers");
3
+ const { writeExecutionSignal } = require("./execution-signals");
4
+
5
+ const VALID_IMPLEMENTER_STATUSES = new Set([
6
+ "DONE",
7
+ "DONE_WITH_CONCERNS",
8
+ "NEEDS_CONTEXT",
9
+ "BLOCKED"
10
+ ]);
11
+
12
+ function normalizeStatus(value) {
13
+ const normalized = String(value || "").trim().toUpperCase();
14
+ if (!VALID_IMPLEMENTER_STATUSES.has(normalized)) {
15
+ throw new Error(
16
+ "`task-execution --status` must be one of DONE, DONE_WITH_CONCERNS, NEEDS_CONTEXT, BLOCKED."
17
+ );
18
+ }
19
+ return normalized;
20
+ }
21
+
22
+ function normalizeList(value) {
23
+ if (Array.isArray(value)) {
24
+ return unique(value.map((item) => String(item || "").trim()).filter(Boolean));
25
+ }
26
+ return unique(
27
+ String(value || "")
28
+ .split(/[,\n;]/)
29
+ .map((item) => item.trim())
30
+ .filter(Boolean)
31
+ );
32
+ }
33
+
34
+ function normalizeTaskGroupId(value) {
35
+ const normalized = String(value || "").trim();
36
+ if (!normalized) {
37
+ throw new Error("`task-execution` requires `--task-group <id>`.");
38
+ }
39
+ return normalized;
40
+ }
41
+
42
+ function mapImplementerStatusToSignal(status) {
43
+ if (status === "DONE") {
44
+ return "PASS";
45
+ }
46
+ if (status === "DONE_WITH_CONCERNS" || status === "NEEDS_CONTEXT") {
47
+ return "WARN";
48
+ }
49
+ return "BLOCK";
50
+ }
51
+
52
+ function buildSurface(taskGroupId) {
53
+ const safeId = String(taskGroupId || "")
54
+ .trim()
55
+ .replace(/[^A-Za-z0-9._-]+/g, "_");
56
+ return `task-execution.${safeId}`;
57
+ }
58
+
59
+ function normalizeTaskExecutionEnvelope(input = {}) {
60
+ const taskGroupId = normalizeTaskGroupId(input.taskGroupId);
61
+ const status = normalizeStatus(input.status);
62
+ const summary = String(input.summary || "").trim();
63
+ if (!summary) {
64
+ throw new Error("`task-execution` requires `--summary <text>`.");
65
+ }
66
+ const changedFiles = normalizeList(input.changedFiles);
67
+ const testEvidence = normalizeList(input.testEvidence);
68
+ const concerns = normalizeList(input.concerns);
69
+ const blockers = normalizeList(input.blockers);
70
+
71
+ return {
72
+ taskGroupId,
73
+ status,
74
+ summary,
75
+ changedFiles,
76
+ testEvidence,
77
+ concerns,
78
+ blockers,
79
+ recordedAt: new Date().toISOString()
80
+ };
81
+ }
82
+
83
+ function writeTaskExecutionEnvelope(projectPathInput, options = {}) {
84
+ const projectRoot = path.resolve(projectPathInput || process.cwd());
85
+ const requestedChangeId = options.changeId ? String(options.changeId).trim() : "";
86
+ const resolved = resolveChangeDir(projectRoot, requestedChangeId);
87
+ if (!resolved.changeDir || !resolved.changeId) {
88
+ const resolveError =
89
+ Array.isArray(resolved.failures) && resolved.failures.length > 0
90
+ ? String(resolved.failures[0] || "").trim()
91
+ : "";
92
+ throw new Error(resolveError || "Unable to resolve active change for task-execution.");
93
+ }
94
+
95
+ const envelope = normalizeTaskExecutionEnvelope(options);
96
+ const signalPath = writeExecutionSignal(projectRoot, {
97
+ changeId: resolved.changeId,
98
+ surface: buildSurface(envelope.taskGroupId),
99
+ status: mapImplementerStatusToSignal(envelope.status),
100
+ advisory: false,
101
+ strict: true,
102
+ failures: envelope.status === "BLOCKED" ? envelope.blockers : [],
103
+ warnings:
104
+ envelope.status === "DONE_WITH_CONCERNS" || envelope.status === "NEEDS_CONTEXT"
105
+ ? envelope.concerns
106
+ : [],
107
+ notes: [envelope.summary, ...envelope.testEvidence.map((item) => `test: ${item}`)],
108
+ details: {
109
+ type: "task_execution",
110
+ envelope
111
+ }
112
+ });
113
+
114
+ return {
115
+ status: mapImplementerStatusToSignal(envelope.status),
116
+ projectRoot,
117
+ changeId: resolved.changeId,
118
+ taskGroupId: envelope.taskGroupId,
119
+ implementerStatus: envelope.status,
120
+ summary: envelope.summary,
121
+ changedFiles: envelope.changedFiles,
122
+ testEvidence: envelope.testEvidence,
123
+ concerns: envelope.concerns,
124
+ blockers: envelope.blockers,
125
+ signalPath
126
+ };
127
+ }
128
+
129
+ function formatTaskExecutionReport(result) {
130
+ const lines = [
131
+ "Task execution envelope",
132
+ `Project: ${result.projectRoot}`,
133
+ `Change: ${result.changeId}`,
134
+ `Task group: ${result.taskGroupId}`,
135
+ `Implementer status: ${result.implementerStatus}`,
136
+ `Signal status: ${result.status}`,
137
+ `Summary: ${result.summary}`,
138
+ `Signal path: ${result.signalPath}`
139
+ ];
140
+ if (result.changedFiles.length > 0) {
141
+ lines.push(`Changed files: ${result.changedFiles.join(", ")}`);
142
+ }
143
+ if (result.testEvidence.length > 0) {
144
+ lines.push(`Test evidence: ${result.testEvidence.join(", ")}`);
145
+ }
146
+ if (result.concerns.length > 0) {
147
+ lines.push(`Concerns: ${result.concerns.join(", ")}`);
148
+ }
149
+ if (result.blockers.length > 0) {
150
+ lines.push(`Blockers: ${result.blockers.join(", ")}`);
151
+ }
152
+ return lines.join("\n");
153
+ }
154
+
155
+ module.exports = {
156
+ VALID_IMPLEMENTER_STATUSES,
157
+ normalizeTaskExecutionEnvelope,
158
+ writeTaskExecutionEnvelope,
159
+ formatTaskExecutionReport
160
+ };
@@ -0,0 +1,197 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { resolveChangeDir, unique } = require("./planning-parsers");
4
+ const { readExecutionSignals, writeExecutionSignal } = require("./execution-signals");
5
+ const { writeFileAtomic, readTextIfExists } = require("./utils");
6
+
7
+ const VALID_REVIEW_STAGES = new Set(["spec", "quality"]);
8
+ const VALID_REVIEW_STATUSES = new Set(["PASS", "WARN", "BLOCK"]);
9
+
10
+ function normalizeReviewStage(value) {
11
+ const normalized = String(value || "").trim().toLowerCase();
12
+ if (!VALID_REVIEW_STAGES.has(normalized)) {
13
+ throw new Error("`task-review --stage` must be `spec` or `quality`.");
14
+ }
15
+ return normalized;
16
+ }
17
+
18
+ function normalizeReviewStatus(value) {
19
+ const normalized = String(value || "").trim().toUpperCase();
20
+ if (!VALID_REVIEW_STATUSES.has(normalized)) {
21
+ throw new Error("`task-review --status` must be PASS, WARN, or BLOCK.");
22
+ }
23
+ return normalized;
24
+ }
25
+
26
+ function normalizeTaskGroupId(value) {
27
+ const normalized = String(value || "").trim();
28
+ if (!normalized) {
29
+ throw new Error("`task-review` requires `--task-group <id>`.");
30
+ }
31
+ return normalized;
32
+ }
33
+
34
+ function normalizeList(value) {
35
+ if (Array.isArray(value)) {
36
+ return unique(value.map((item) => String(item || "").trim()).filter(Boolean));
37
+ }
38
+ return unique(
39
+ String(value || "")
40
+ .split(/[,\n;]/)
41
+ .map((item) => item.trim())
42
+ .filter(Boolean)
43
+ );
44
+ }
45
+
46
+ function buildSafeTaskGroupId(taskGroupId) {
47
+ return String(taskGroupId || "")
48
+ .trim()
49
+ .replace(/[^A-Za-z0-9._-]+/g, "_");
50
+ }
51
+
52
+ function buildTaskReviewSurface(taskGroupId, stage) {
53
+ return `task-review.${buildSafeTaskGroupId(taskGroupId)}.${stage}`;
54
+ }
55
+
56
+ function mapReviewStatusToSignalStatus(status) {
57
+ return status;
58
+ }
59
+
60
+ function normalizeTaskReviewEnvelope(input = {}) {
61
+ const taskGroupId = normalizeTaskGroupId(input.taskGroupId);
62
+ const stage = normalizeReviewStage(input.stage);
63
+ const status = normalizeReviewStatus(input.status);
64
+ const summary = String(input.summary || "").trim();
65
+ if (!summary) {
66
+ throw new Error("`task-review` requires `--summary <text>`.");
67
+ }
68
+ const issues = normalizeList(input.issues);
69
+ const reviewer = String(input.reviewer || "").trim() || "unspecified";
70
+
71
+ return {
72
+ taskGroupId,
73
+ stage,
74
+ status,
75
+ summary,
76
+ issues,
77
+ reviewer,
78
+ recordedAt: new Date().toISOString()
79
+ };
80
+ }
81
+
82
+ function findLatestTaskReviewSignal(signals, taskGroupId, stage) {
83
+ const surface = buildTaskReviewSurface(taskGroupId, stage);
84
+ return (signals || [])
85
+ .filter((signal) => String(signal.surface || "") === surface)
86
+ .sort((left, right) => String(right.timestamp || "").localeCompare(String(left.timestamp || "")))[0] || null;
87
+ }
88
+
89
+ function appendTaskReviewEvidence(verificationText, envelope) {
90
+ const existing = String(verificationText || "").trim();
91
+ const section = [
92
+ `### Task Review ${envelope.taskGroupId} (${envelope.stage})`,
93
+ `- Status: ${envelope.status}`,
94
+ `- Reviewer: ${envelope.reviewer}`,
95
+ `- Summary: ${envelope.summary}`,
96
+ `- Issues: ${envelope.issues.length > 0 ? envelope.issues.join("; ") : "none"}`,
97
+ `- Recorded at: ${envelope.recordedAt}`
98
+ ].join("\n");
99
+
100
+ if (!existing) {
101
+ return `# Verification\n\n## Task Review Evidence\n\n${section}\n`;
102
+ }
103
+ if (!/^\s*##\s+Task Review Evidence\s*$/im.test(existing)) {
104
+ return `${existing}\n\n## Task Review Evidence\n\n${section}\n`;
105
+ }
106
+ return `${existing}\n\n${section}\n`;
107
+ }
108
+
109
+ function writeTaskReviewEnvelope(projectPathInput, options = {}) {
110
+ const projectRoot = path.resolve(projectPathInput || process.cwd());
111
+ const requestedChangeId = options.changeId ? String(options.changeId).trim() : "";
112
+ const resolved = resolveChangeDir(projectRoot, requestedChangeId);
113
+ if (!resolved.changeDir || !resolved.changeId) {
114
+ const resolveError =
115
+ Array.isArray(resolved.failures) && resolved.failures.length > 0
116
+ ? String(resolved.failures[0] || "").trim()
117
+ : "";
118
+ throw new Error(resolveError || "Unable to resolve active change for task-review.");
119
+ }
120
+
121
+ const envelope = normalizeTaskReviewEnvelope(options);
122
+ const existingSignals = readExecutionSignals(projectRoot, { changeId: resolved.changeId });
123
+
124
+ if (envelope.stage === "quality") {
125
+ const specSignal = findLatestTaskReviewSignal(existingSignals, envelope.taskGroupId, "spec");
126
+ if (!specSignal || String(specSignal.status || "").toUpperCase() !== "PASS") {
127
+ throw new Error(
128
+ `Quality review for task group ${envelope.taskGroupId} requires a prior spec review PASS.`
129
+ );
130
+ }
131
+ }
132
+
133
+ const signalPath = writeExecutionSignal(projectRoot, {
134
+ changeId: resolved.changeId,
135
+ surface: buildTaskReviewSurface(envelope.taskGroupId, envelope.stage),
136
+ status: mapReviewStatusToSignalStatus(envelope.status),
137
+ advisory: false,
138
+ strict: true,
139
+ failures: envelope.status === "BLOCK" ? envelope.issues : [],
140
+ warnings: envelope.status === "WARN" ? envelope.issues : [],
141
+ notes: [envelope.summary, `reviewer: ${envelope.reviewer}`],
142
+ details: {
143
+ type: "task_review",
144
+ envelope
145
+ }
146
+ });
147
+
148
+ let verificationPath = null;
149
+ if (options.writeVerification === true) {
150
+ verificationPath = path.join(resolved.changeDir, "verification.md");
151
+ fs.mkdirSync(path.dirname(verificationPath), { recursive: true });
152
+ const nextVerification = appendTaskReviewEvidence(readTextIfExists(verificationPath), envelope);
153
+ writeFileAtomic(verificationPath, nextVerification);
154
+ }
155
+
156
+ return {
157
+ status: envelope.status,
158
+ projectRoot,
159
+ changeId: resolved.changeId,
160
+ taskGroupId: envelope.taskGroupId,
161
+ stage: envelope.stage,
162
+ summary: envelope.summary,
163
+ issues: envelope.issues,
164
+ reviewer: envelope.reviewer,
165
+ signalPath,
166
+ verificationPath
167
+ };
168
+ }
169
+
170
+ function formatTaskReviewReport(result) {
171
+ const lines = [
172
+ "Task review envelope",
173
+ `Project: ${result.projectRoot}`,
174
+ `Change: ${result.changeId}`,
175
+ `Task group: ${result.taskGroupId}`,
176
+ `Stage: ${result.stage}`,
177
+ `Status: ${result.status}`,
178
+ `Reviewer: ${result.reviewer}`,
179
+ `Summary: ${result.summary}`,
180
+ `Signal path: ${result.signalPath}`
181
+ ];
182
+ if (result.issues.length > 0) {
183
+ lines.push(`Issues: ${result.issues.join("; ")}`);
184
+ }
185
+ if (result.verificationPath) {
186
+ lines.push(`Verification write-back: ${result.verificationPath}`);
187
+ }
188
+ return lines.join("\n");
189
+ }
190
+
191
+ module.exports = {
192
+ VALID_REVIEW_STAGES,
193
+ VALID_REVIEW_STATUSES,
194
+ normalizeTaskReviewEnvelope,
195
+ writeTaskReviewEnvelope,
196
+ formatTaskReviewReport
197
+ };
package/lib/utils.js CHANGED
@@ -28,6 +28,23 @@ function readTextIfExists(targetPath, options = {}) {
28
28
  return fs.readFileSync(targetPath, encoding);
29
29
  }
30
30
 
31
+ function normalizeRelativePath(relativePath) {
32
+ return String(relativePath || "")
33
+ .split(path.sep)
34
+ .filter(Boolean)
35
+ .join("/");
36
+ }
37
+
38
+ function pathWithinRoot(projectRoot, candidatePath) {
39
+ const root = path.resolve(projectRoot);
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);
46
+ }
47
+
31
48
  function uniqueValues(values) {
32
49
  return Array.from(new Set((values || []).filter(Boolean)));
33
50
  }
@@ -120,6 +137,8 @@ module.exports = {
120
137
  escapeRegExp,
121
138
  pathExists,
122
139
  readTextIfExists,
140
+ normalizeRelativePath,
141
+ pathWithinRoot,
123
142
  uniqueValues,
124
143
  parseJsonText,
125
144
  readJsonFile,