@xenonbyte/da-vinci-workflow 0.2.2 → 0.2.4
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 +28 -0
- package/README.md +49 -14
- package/README.zh-CN.md +169 -14
- package/commands/claude/dv/breakdown.md +8 -0
- package/commands/claude/dv/build.md +16 -0
- package/commands/claude/dv/continue.md +4 -0
- package/commands/claude/dv/design.md +5 -2
- package/commands/claude/dv/tasks.md +14 -0
- package/commands/claude/dv/verify.md +11 -0
- package/commands/codex/prompts/dv-breakdown.md +8 -0
- package/commands/codex/prompts/dv-build.md +16 -0
- package/commands/codex/prompts/dv-continue.md +4 -0
- package/commands/codex/prompts/dv-design.md +5 -2
- package/commands/codex/prompts/dv-tasks.md +14 -0
- package/commands/codex/prompts/dv-verify.md +10 -0
- package/commands/gemini/dv/breakdown.toml +8 -0
- package/commands/gemini/dv/build.toml +16 -0
- package/commands/gemini/dv/continue.toml +4 -0
- package/commands/gemini/dv/design.toml +5 -2
- package/commands/gemini/dv/tasks.toml +14 -0
- package/commands/gemini/dv/verify.toml +10 -0
- package/commands/templates/dv-continue.shared.md +4 -0
- package/docs/discipline-and-orchestration-upgrade.md +83 -0
- package/docs/dv-command-reference.md +61 -2
- package/docs/execution-chain-migration.md +23 -0
- package/docs/execution-chain-plan.md +10 -3
- package/docs/mode-use-cases.md +2 -1
- package/docs/pencil-rendering-workflow.md +15 -12
- package/docs/prompt-entrypoints.md +5 -0
- package/docs/prompt-presets/README.md +1 -1
- package/docs/prompt-presets/desktop-app.md +3 -3
- package/docs/prompt-presets/mobile-app.md +3 -3
- package/docs/prompt-presets/tablet-app.md +3 -3
- package/docs/prompt-presets/web-app.md +3 -3
- package/docs/skill-usage.md +61 -38
- package/docs/workflow-examples.md +16 -13
- package/docs/workflow-overview.md +19 -0
- package/docs/zh-CN/dv-command-reference.md +59 -2
- package/docs/zh-CN/execution-chain-migration.md +23 -0
- package/docs/zh-CN/mode-use-cases.md +2 -1
- package/docs/zh-CN/pencil-rendering-workflow.md +15 -12
- package/docs/zh-CN/prompt-entrypoints.md +5 -0
- package/docs/zh-CN/prompt-presets/README.md +1 -1
- package/docs/zh-CN/prompt-presets/desktop-app.md +3 -3
- package/docs/zh-CN/prompt-presets/mobile-app.md +3 -3
- package/docs/zh-CN/prompt-presets/tablet-app.md +3 -3
- package/docs/zh-CN/prompt-presets/web-app.md +3 -3
- package/docs/zh-CN/skill-usage.md +61 -38
- package/docs/zh-CN/workflow-examples.md +15 -13
- package/docs/zh-CN/workflow-overview.md +19 -0
- package/examples/greenfield-spec-markupflow/.da-vinci/state/execution-signals/demo__lint-tasks.json +16 -0
- package/lib/audit-parsers.js +166 -10
- package/lib/audit.js +3 -26
- package/lib/cli.js +156 -2
- package/lib/design-source-registry.js +146 -0
- package/lib/execution-profile.js +143 -0
- package/lib/execution-signals.js +19 -1
- package/lib/lint-tasks.js +86 -2
- package/lib/planning-parsers.js +255 -18
- package/lib/save-current-design.js +790 -0
- package/lib/supervisor-review.js +3 -2
- package/lib/task-execution.js +160 -0
- package/lib/task-review.js +197 -0
- package/lib/verify.js +152 -1
- package/lib/workflow-bootstrap.js +2 -13
- package/lib/workflow-persisted-state.js +3 -1
- package/lib/workflow-state.js +503 -33
- package/lib/worktree-preflight.js +214 -0
- package/package.json +1 -1
- package/references/artifact-templates.md +56 -6
- package/tui/catalog.js +103 -0
- package/tui/index.js +2274 -418
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const { unique } = require("./planning-parsers");
|
|
2
|
+
|
|
3
|
+
const ROLE_TAXONOMY = Object.freeze([
|
|
4
|
+
{ id: "coordinator", description: "Own route truth, sequencing, and conflict resolution." },
|
|
5
|
+
{ id: "implementer", description: "Deliver scoped code and docs changes for a task group." },
|
|
6
|
+
{ id: "spec-reviewer", description: "Review task output against spec/plan requirements first." },
|
|
7
|
+
{ id: "quality-reviewer", description: "Review quality, maintainability, and risk after spec pass." },
|
|
8
|
+
{ id: "verifier", description: "Run verification commands and evidence freshness checks." }
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const MODEL_TIER_GUIDANCE = Object.freeze({
|
|
12
|
+
coordinator: "high",
|
|
13
|
+
implementer: "balanced",
|
|
14
|
+
"spec-reviewer": "high",
|
|
15
|
+
"quality-reviewer": "high",
|
|
16
|
+
verifier: "balanced"
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function normalizeOwnershipToken(value) {
|
|
20
|
+
const normalized = String(value || "")
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/\\/g, "/")
|
|
23
|
+
.replace(/^\.?\//, "");
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
28
|
+
if (segments.length === 0) {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
if (segments.length === 1) {
|
|
32
|
+
return segments[0];
|
|
33
|
+
}
|
|
34
|
+
return `${segments[0]}/${segments[1]}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function collectOwnershipKeys(group) {
|
|
38
|
+
const fromTargets = Array.isArray(group.targetFiles) ? group.targetFiles : [];
|
|
39
|
+
const fromRefs = Array.isArray(group.fileReferences) ? group.fileReferences : [];
|
|
40
|
+
return unique([...fromTargets, ...fromRefs].map((item) => normalizeOwnershipToken(item)).filter(Boolean));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildOverlapMatrix(groups) {
|
|
44
|
+
const overlaps = [];
|
|
45
|
+
for (let left = 0; left < groups.length; left += 1) {
|
|
46
|
+
for (let right = left + 1; right < groups.length; right += 1) {
|
|
47
|
+
const leftGroup = groups[left];
|
|
48
|
+
const rightGroup = groups[right];
|
|
49
|
+
const shared = leftGroup.ownershipKeys.filter((token) => rightGroup.ownershipKeys.includes(token));
|
|
50
|
+
if (shared.length === 0) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
overlaps.push({
|
|
54
|
+
leftTaskGroupId: leftGroup.id,
|
|
55
|
+
rightTaskGroupId: rightGroup.id,
|
|
56
|
+
sharedOwnership: shared
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return overlaps;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deriveExecutionMode(stage, groups, overlaps) {
|
|
64
|
+
if (stage !== "build" && stage !== "verify") {
|
|
65
|
+
return "serial";
|
|
66
|
+
}
|
|
67
|
+
if (overlaps.length > 0) {
|
|
68
|
+
return "serial";
|
|
69
|
+
}
|
|
70
|
+
if (groups.some((group) => group.executionIntent.includes("review_required"))) {
|
|
71
|
+
return "review_heavy";
|
|
72
|
+
}
|
|
73
|
+
if (groups.length <= 1) {
|
|
74
|
+
return "serial";
|
|
75
|
+
}
|
|
76
|
+
return "bounded_parallel";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function deriveExecutionProfile(options = {}) {
|
|
80
|
+
const stage = String(options.stage || "");
|
|
81
|
+
const sourceTaskGroups = Array.isArray(options.taskGroups) ? options.taskGroups : [];
|
|
82
|
+
const normalizedGroups = sourceTaskGroups.map((group) => ({
|
|
83
|
+
id: group.taskGroupId || group.id || "",
|
|
84
|
+
title: group.title || "",
|
|
85
|
+
executionIntent: Array.isArray(group.executionIntent) ? group.executionIntent : [],
|
|
86
|
+
ownershipKeys: collectOwnershipKeys(group),
|
|
87
|
+
reviewIntent: group.reviewIntent === true
|
|
88
|
+
}));
|
|
89
|
+
const groups = normalizedGroups.filter((group) => group.id);
|
|
90
|
+
const overlaps = buildOverlapMatrix(groups);
|
|
91
|
+
const mode = deriveExecutionMode(stage, groups, overlaps);
|
|
92
|
+
|
|
93
|
+
let maxParallel = 1;
|
|
94
|
+
if (mode === "bounded_parallel") {
|
|
95
|
+
maxParallel = Math.min(3, Math.max(2, groups.length));
|
|
96
|
+
} else if (mode === "review_heavy") {
|
|
97
|
+
maxParallel = 2;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rationale = [];
|
|
101
|
+
if (mode === "serial") {
|
|
102
|
+
if (overlaps.length > 0) {
|
|
103
|
+
rationale.push("ownership overlap detected across task groups");
|
|
104
|
+
} else if (stage !== "build" && stage !== "verify") {
|
|
105
|
+
rationale.push(`stage ${stage || "unknown"} prefers serial progression`);
|
|
106
|
+
} else {
|
|
107
|
+
rationale.push("single task-group or unspecified execution intent");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (mode === "bounded_parallel") {
|
|
111
|
+
rationale.push("independent ownership detected across task groups");
|
|
112
|
+
}
|
|
113
|
+
if (mode === "review_heavy") {
|
|
114
|
+
rationale.push("review-required execution intent detected");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
advisory: true,
|
|
119
|
+
mode,
|
|
120
|
+
stage,
|
|
121
|
+
maxParallel,
|
|
122
|
+
roles: ROLE_TAXONOMY,
|
|
123
|
+
modelTierGuidance: MODEL_TIER_GUIDANCE,
|
|
124
|
+
overlaps,
|
|
125
|
+
rationale,
|
|
126
|
+
taskGroups: groups.map((group) => ({
|
|
127
|
+
taskGroupId: group.id,
|
|
128
|
+
title: group.title,
|
|
129
|
+
ownershipKeys: group.ownershipKeys,
|
|
130
|
+
executionIntent: group.executionIntent,
|
|
131
|
+
recommendedMode:
|
|
132
|
+
mode === "bounded_parallel" && group.executionIntent.includes("serial")
|
|
133
|
+
? "serial"
|
|
134
|
+
: mode
|
|
135
|
+
}))
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
ROLE_TAXONOMY,
|
|
141
|
+
MODEL_TIER_GUIDANCE,
|
|
142
|
+
deriveExecutionProfile
|
|
143
|
+
};
|
package/lib/execution-signals.js
CHANGED
|
@@ -48,6 +48,7 @@ function writeExecutionSignal(projectRoot, payload) {
|
|
|
48
48
|
failures: Array.isArray(payload.failures) ? payload.failures : [],
|
|
49
49
|
warnings: Array.isArray(payload.warnings) ? payload.warnings : [],
|
|
50
50
|
notes: Array.isArray(payload.notes) ? payload.notes : [],
|
|
51
|
+
details: payload && payload.details !== undefined ? payload.details : null,
|
|
51
52
|
timestamp: new Date().toISOString(),
|
|
52
53
|
changeId
|
|
53
54
|
};
|
|
@@ -129,8 +130,25 @@ function summarizeSignalsBySurface(signals) {
|
|
|
129
130
|
return summary;
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
function listSignalsBySurfacePrefix(signals, prefix) {
|
|
134
|
+
const normalizedPrefix = sanitizeSurfaceName(prefix);
|
|
135
|
+
if (!normalizedPrefix) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
return (signals || [])
|
|
139
|
+
.filter((signal) => sanitizeSurfaceName(signal.surface || "").startsWith(normalizedPrefix))
|
|
140
|
+
.sort((left, right) => String(right.timestamp || "").localeCompare(String(left.timestamp || "")));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getLatestSignalBySurfacePrefix(signals, prefix) {
|
|
144
|
+
const matches = listSignalsBySurfacePrefix(signals, prefix);
|
|
145
|
+
return matches.length > 0 ? matches[0] : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
132
148
|
module.exports = {
|
|
133
149
|
writeExecutionSignal,
|
|
134
150
|
readExecutionSignals,
|
|
135
|
-
summarizeSignalsBySurface
|
|
151
|
+
summarizeSignalsBySurface,
|
|
152
|
+
listSignalsBySurfacePrefix,
|
|
153
|
+
getLatestSignalBySurfacePrefix
|
|
136
154
|
};
|
package/lib/lint-tasks.js
CHANGED
|
@@ -85,6 +85,13 @@ function lintTasks(projectPathInput, options = {}) {
|
|
|
85
85
|
|
|
86
86
|
result.summary.groups = parsedTasks.taskGroups.length;
|
|
87
87
|
result.summary.checklistItems = parsedTasks.checklistItems.length;
|
|
88
|
+
result.summary.discipline = {
|
|
89
|
+
groupsMissingTargets: 0,
|
|
90
|
+
groupsMissingExecutionIntent: 0,
|
|
91
|
+
groupsMissingVerificationCommands: 0,
|
|
92
|
+
groupsWithPlaceholders: 0,
|
|
93
|
+
groupsMissingTestingIntent: 0
|
|
94
|
+
};
|
|
88
95
|
|
|
89
96
|
if (parsedTasks.taskGroups.length === 0) {
|
|
90
97
|
result.failures.push("`tasks.md` is missing top-level numbered task groups (for example `## 1. Setup`).");
|
|
@@ -109,12 +116,86 @@ function lintTasks(projectPathInput, options = {}) {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
const hasVerificationAction =
|
|
112
|
-
parsedTasks.taskGroups.some(
|
|
113
|
-
|
|
119
|
+
parsedTasks.taskGroups.some(
|
|
120
|
+
(group) =>
|
|
121
|
+
/verify|verification|coverage/i.test(group.title) ||
|
|
122
|
+
(Array.isArray(group.verificationActions) && group.verificationActions.length > 0)
|
|
123
|
+
) || parsedTasks.checklistItems.some((item) => /verify|verification|coverage/i.test(item.text));
|
|
114
124
|
if (!hasVerificationAction) {
|
|
115
125
|
result.warnings.push("Missing explicit verification actions in `tasks.md`.");
|
|
116
126
|
}
|
|
117
127
|
|
|
128
|
+
if (parsedTasks.markers && Array.isArray(parsedTasks.markers.malformed) && parsedTasks.markers.malformed.length > 0) {
|
|
129
|
+
for (const malformed of parsedTasks.markers.malformed) {
|
|
130
|
+
result.warnings.push(
|
|
131
|
+
`Malformed discipline marker in tasks at line ${malformed.line}: ${malformed.reason}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const markerSummary = parsedTasks.markerSummary || {};
|
|
137
|
+
if (!markerSummary.hasPlanSelfReview) {
|
|
138
|
+
result.warnings.push(
|
|
139
|
+
"Missing `plan_self_review` discipline marker in `tasks.md` (legacy plans may keep advisory fallback)."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (!markerSummary.hasOperatorReviewAck) {
|
|
143
|
+
result.warnings.push(
|
|
144
|
+
"Missing `operator_review_ack` discipline marker in `tasks.md` (legacy plans may keep advisory fallback)."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const group of parsedTasks.taskGroups) {
|
|
149
|
+
const groupLabel = `${group.id}. ${group.title}`;
|
|
150
|
+
const hasTargets = Array.isArray(group.targetFiles) && group.targetFiles.length > 0;
|
|
151
|
+
const hasFileRefs = Array.isArray(group.fileReferences) && group.fileReferences.length > 0;
|
|
152
|
+
if (!hasTargets && !hasFileRefs) {
|
|
153
|
+
result.summary.discipline.groupsMissingTargets += 1;
|
|
154
|
+
result.warnings.push(
|
|
155
|
+
`Task group ${groupLabel} is missing exact file targets or code-area references.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!Array.isArray(group.executionIntent) || group.executionIntent.length === 0) {
|
|
160
|
+
result.summary.discipline.groupsMissingExecutionIntent += 1;
|
|
161
|
+
result.warnings.push(
|
|
162
|
+
`Task group ${groupLabel} is missing execution-mode hints (serial, bounded parallel, or review-required).`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const hasVerificationCommands =
|
|
167
|
+
Array.isArray(group.verificationCommands) && group.verificationCommands.length > 0;
|
|
168
|
+
const hasVerificationIntent =
|
|
169
|
+
/verify|verification|coverage|test/i.test(group.title) ||
|
|
170
|
+
(Array.isArray(group.verificationActions) && group.verificationActions.length > 0);
|
|
171
|
+
if (hasVerificationIntent && !hasVerificationCommands) {
|
|
172
|
+
result.summary.discipline.groupsMissingVerificationCommands += 1;
|
|
173
|
+
result.warnings.push(
|
|
174
|
+
`Task group ${groupLabel} has verification intent but no explicit verification command (for example \`npm test\`).`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(group.placeholderItems) && group.placeholderItems.length > 0) {
|
|
179
|
+
result.summary.discipline.groupsWithPlaceholders += 1;
|
|
180
|
+
result.warnings.push(
|
|
181
|
+
`Task group ${groupLabel} contains placeholder wording (${group.placeholderItems.length} item(s)).`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (group.codeChangeLikely && !group.testingIntent) {
|
|
186
|
+
result.summary.discipline.groupsMissingTestingIntent += 1;
|
|
187
|
+
result.warnings.push(
|
|
188
|
+
`Task group ${groupLabel} appears to change code behavior but omits testing intent.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (Array.isArray(group.executionIntent) && group.executionIntent.includes("review_required") && !group.reviewIntent) {
|
|
193
|
+
result.warnings.push(
|
|
194
|
+
`Task group ${groupLabel} hints review-required execution but does not declare concrete review intent.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
118
199
|
for (const specRecord of specRecords) {
|
|
119
200
|
const behaviorItems = specRecord.parsed.sections.behavior.items || [];
|
|
120
201
|
for (const behaviorItem of behaviorItems) {
|
|
@@ -135,6 +216,9 @@ function lintTasks(projectPathInput, options = {}) {
|
|
|
135
216
|
}
|
|
136
217
|
|
|
137
218
|
result.notes.push("lint-tasks defaults to advisory mode; pass `--strict` to block on findings.");
|
|
219
|
+
result.notes.push(
|
|
220
|
+
"Strict-promotion guidance: require clean placeholder/file-target/execution-intent/verification-command findings before promoting to build."
|
|
221
|
+
);
|
|
138
222
|
return finalize(result);
|
|
139
223
|
}
|
|
140
224
|
|
package/lib/planning-parsers.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const crypto = require("crypto");
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
getMarkdownSection,
|
|
6
|
+
parseDisciplineMarkers,
|
|
7
|
+
DISCIPLINE_MARKER_NAMES
|
|
8
|
+
} = require("./audit-parsers");
|
|
5
9
|
const { parseRuntimeSpecMarkdown } = require("./artifact-parsers");
|
|
6
10
|
const { pathExists, readTextIfExists } = require("./utils");
|
|
7
11
|
|
|
@@ -350,33 +354,239 @@ function parsePageMapArtifact(text) {
|
|
|
350
354
|
};
|
|
351
355
|
}
|
|
352
356
|
|
|
357
|
+
function parseInlineCodeTokens(line) {
|
|
358
|
+
const tokens = [];
|
|
359
|
+
const matches = String(line || "").matchAll(/`([^`]+)`/g);
|
|
360
|
+
for (const match of matches) {
|
|
361
|
+
const token = String(match[1] || "").trim();
|
|
362
|
+
if (token) {
|
|
363
|
+
tokens.push(token);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return tokens;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function normalizeExecutionIntent(text) {
|
|
370
|
+
const normalized = normalizeText(text);
|
|
371
|
+
if (!normalized) {
|
|
372
|
+
return "";
|
|
373
|
+
}
|
|
374
|
+
if (/bounded parallel|parallel bounded|parallel-safe/.test(normalized)) {
|
|
375
|
+
return "bounded_parallel";
|
|
376
|
+
}
|
|
377
|
+
if (/serial|sequential|one by one/.test(normalized)) {
|
|
378
|
+
return "serial";
|
|
379
|
+
}
|
|
380
|
+
if (/review required|review-required|review heavy|spec review|quality review/.test(normalized)) {
|
|
381
|
+
return "review_required";
|
|
382
|
+
}
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function hasVerificationIntent(text) {
|
|
387
|
+
return /verify|verification|coverage|assert|validate|smoke/i.test(String(text || ""));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function hasTestingIntent(text) {
|
|
391
|
+
return /(?:^|\b)(test|tests|unit test|integration test|e2e|regression|tdd|coverage)(?:\b|$)/i.test(
|
|
392
|
+
String(text || "")
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function hasReviewIntent(text) {
|
|
397
|
+
return /review|reviewer|spec review|quality review|qa|audit/i.test(String(text || ""));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function looksLikeCodeChange(text) {
|
|
401
|
+
return /implement|modify|update|add|create|refactor|fix|remove|rename|rewrite|harden|extend/i.test(
|
|
402
|
+
String(text || "")
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isPlaceholderText(text) {
|
|
407
|
+
return /\b(TBD|TODO|implement later|later fill|to be decided)\b/i.test(String(text || ""));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function extractFileReferences(text) {
|
|
411
|
+
const references = [];
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
const addReference = (value) => {
|
|
414
|
+
const candidate = String(value || "")
|
|
415
|
+
.replace(/[`"'(),]/g, "")
|
|
416
|
+
.trim();
|
|
417
|
+
if (!candidate) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (!/[./]/.test(candidate)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (candidate.length < 3) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (seen.has(candidate)) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
seen.add(candidate);
|
|
430
|
+
references.push(candidate);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
for (const token of parseInlineCodeTokens(text)) {
|
|
434
|
+
addReference(token);
|
|
435
|
+
}
|
|
436
|
+
const plainMatches = String(text || "").matchAll(/(?:^|[\s(])([A-Za-z0-9._/-]+(?:\.[A-Za-z0-9_*.-]+|\/))(?:$|[\s),])/g);
|
|
437
|
+
for (const match of plainMatches) {
|
|
438
|
+
addReference(match[1]);
|
|
439
|
+
}
|
|
440
|
+
return references;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function extractVerificationCommands(text) {
|
|
444
|
+
const commands = [];
|
|
445
|
+
const supportedCommandPattern =
|
|
446
|
+
/^(?:da-vinci\s+verify-(?:bindings|implementation|structure|coverage)|npm|pnpm|yarn|bun|node|npx|pytest|go test|cargo test|dotnet test|mvn test|gradle test|make test|vitest|jest)\b/i;
|
|
447
|
+
const addCommand = (value) => {
|
|
448
|
+
const command = String(value || "").trim();
|
|
449
|
+
if (!command) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (!/\s/.test(command)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (!supportedCommandPattern.test(command)) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
commands.push(command);
|
|
459
|
+
};
|
|
460
|
+
for (const token of parseInlineCodeTokens(text)) {
|
|
461
|
+
addCommand(token);
|
|
462
|
+
}
|
|
463
|
+
return unique(commands);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function analyzeTaskGroup(section) {
|
|
467
|
+
const targetFiles = [];
|
|
468
|
+
const verificationActions = [];
|
|
469
|
+
const verificationCommands = [];
|
|
470
|
+
const placeholderItems = [];
|
|
471
|
+
const rawFileReferences = [];
|
|
472
|
+
const executionHints = [];
|
|
473
|
+
|
|
474
|
+
const lines = Array.isArray(section.lines) ? section.lines : [];
|
|
475
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
476
|
+
const line = String(lines[index] || "");
|
|
477
|
+
rawFileReferences.push(...extractFileReferences(line));
|
|
478
|
+
if (isPlaceholderText(line)) {
|
|
479
|
+
placeholderItems.push(line.trim());
|
|
480
|
+
}
|
|
481
|
+
if (hasVerificationIntent(line)) {
|
|
482
|
+
verificationActions.push(line.trim());
|
|
483
|
+
}
|
|
484
|
+
verificationCommands.push(...extractVerificationCommands(line));
|
|
485
|
+
const executionIntent = normalizeExecutionIntent(line);
|
|
486
|
+
if (executionIntent) {
|
|
487
|
+
executionHints.push(executionIntent);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (/^\s*target files\s*:\s*$/i.test(line)) {
|
|
491
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
492
|
+
const followLine = String(lines[cursor] || "");
|
|
493
|
+
if (/^\s*$/.test(followLine)) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (/^\s{0,3}##\s+/.test(followLine)) {
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (/^\s*[A-Za-z][A-Za-z0-9 _-]{0,80}\s*:\s*$/.test(followLine)) {
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
const itemMatch = followLine.match(/^\s*-\s+(.+)$/);
|
|
503
|
+
if (!itemMatch) {
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
const entry = String(itemMatch[1] || "").trim();
|
|
507
|
+
if (!entry) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
targetFiles.push(...extractFileReferences(entry));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
for (const item of section.checklistItems) {
|
|
516
|
+
rawFileReferences.push(...extractFileReferences(item.text));
|
|
517
|
+
if (isPlaceholderText(item.text)) {
|
|
518
|
+
placeholderItems.push(item.text);
|
|
519
|
+
}
|
|
520
|
+
if (hasVerificationIntent(item.text)) {
|
|
521
|
+
verificationActions.push(item.text);
|
|
522
|
+
}
|
|
523
|
+
verificationCommands.push(...extractVerificationCommands(item.text));
|
|
524
|
+
const executionIntent = normalizeExecutionIntent(item.text);
|
|
525
|
+
if (executionIntent) {
|
|
526
|
+
executionHints.push(executionIntent);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const mergedFileReferences = unique([...targetFiles, ...rawFileReferences]);
|
|
531
|
+
const titleExecutionIntent = normalizeExecutionIntent(section.title);
|
|
532
|
+
if (titleExecutionIntent) {
|
|
533
|
+
executionHints.push(titleExecutionIntent);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const joinedContent = [section.title, ...lines, ...section.checklistItems.map((item) => item.text)].join("\n");
|
|
537
|
+
return {
|
|
538
|
+
...section,
|
|
539
|
+
targetFiles: unique(targetFiles),
|
|
540
|
+
fileReferences: mergedFileReferences,
|
|
541
|
+
verificationActions: unique(verificationActions.filter(Boolean)),
|
|
542
|
+
verificationCommands: unique(verificationCommands),
|
|
543
|
+
executionIntent: unique(executionHints),
|
|
544
|
+
reviewIntent: hasReviewIntent(joinedContent),
|
|
545
|
+
testingIntent: hasTestingIntent(joinedContent),
|
|
546
|
+
codeChangeLikely: looksLikeCodeChange(joinedContent),
|
|
547
|
+
placeholderItems: unique(placeholderItems.filter(Boolean))
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
353
551
|
function parseTasksArtifact(text) {
|
|
354
552
|
const lines = String(text || "").replace(/\r\n?/g, "\n").split("\n");
|
|
355
553
|
const taskGroups = [];
|
|
356
554
|
const checklistItems = [];
|
|
357
555
|
const checkpointItems = [];
|
|
358
556
|
const sections = [];
|
|
557
|
+
const markers = parseDisciplineMarkers(text);
|
|
359
558
|
let currentSection = null;
|
|
360
559
|
|
|
361
|
-
for (
|
|
560
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
561
|
+
const line = lines[lineIndex];
|
|
362
562
|
const groupMatch = line.match(/^\s{0,3}##\s+(\d+(?:\.\d+)*)\.\s+(.+)$/);
|
|
363
563
|
if (groupMatch) {
|
|
364
564
|
const group = {
|
|
365
565
|
id: groupMatch[1],
|
|
366
|
-
title: String(groupMatch[2] || "").trim()
|
|
566
|
+
title: String(groupMatch[2] || "").trim(),
|
|
567
|
+
startLine: lineIndex + 1
|
|
367
568
|
};
|
|
368
569
|
taskGroups.push(group);
|
|
369
570
|
if (currentSection) {
|
|
370
|
-
|
|
571
|
+
currentSection.endLine = lineIndex;
|
|
572
|
+
sections.push(analyzeTaskGroup(currentSection));
|
|
371
573
|
}
|
|
372
574
|
currentSection = {
|
|
373
575
|
id: group.id,
|
|
374
576
|
title: group.title,
|
|
375
|
-
|
|
577
|
+
startLine: lineIndex + 1,
|
|
578
|
+
endLine: lineIndex + 1,
|
|
579
|
+
checklistItems: [],
|
|
580
|
+
lines: []
|
|
376
581
|
};
|
|
377
582
|
continue;
|
|
378
583
|
}
|
|
379
584
|
|
|
585
|
+
if (currentSection) {
|
|
586
|
+
currentSection.lines.push(line);
|
|
587
|
+
currentSection.endLine = lineIndex + 1;
|
|
588
|
+
}
|
|
589
|
+
|
|
380
590
|
const checklistMatch = line.match(/^\s*-\s*\[([ xX])\]\s+(.+)$/);
|
|
381
591
|
if (checklistMatch) {
|
|
382
592
|
const checked = String(checklistMatch[1] || "").toLowerCase() === "x";
|
|
@@ -384,33 +594,60 @@ function parseTasksArtifact(text) {
|
|
|
384
594
|
if (!textValue) {
|
|
385
595
|
continue;
|
|
386
596
|
}
|
|
387
|
-
|
|
597
|
+
const item = {
|
|
388
598
|
checked,
|
|
389
|
-
text: textValue
|
|
390
|
-
|
|
599
|
+
text: textValue,
|
|
600
|
+
line: lineIndex + 1
|
|
601
|
+
};
|
|
602
|
+
checklistItems.push(item);
|
|
391
603
|
if (currentSection) {
|
|
392
|
-
currentSection.checklistItems.push(
|
|
393
|
-
checked,
|
|
394
|
-
text: textValue
|
|
395
|
-
});
|
|
604
|
+
currentSection.checklistItems.push(item);
|
|
396
605
|
}
|
|
397
606
|
if (/checkpoint/i.test(textValue)) {
|
|
398
|
-
checkpointItems.push(
|
|
399
|
-
checked,
|
|
400
|
-
text: textValue
|
|
401
|
-
});
|
|
607
|
+
checkpointItems.push(item);
|
|
402
608
|
}
|
|
403
609
|
}
|
|
404
610
|
}
|
|
405
611
|
if (currentSection) {
|
|
406
|
-
sections.push(currentSection);
|
|
612
|
+
sections.push(analyzeTaskGroup(currentSection));
|
|
407
613
|
}
|
|
408
614
|
|
|
615
|
+
const sectionById = new Map(sections.map((section) => [section.id, section]));
|
|
616
|
+
const normalizedTaskGroups = taskGroups.map((group) => {
|
|
617
|
+
const section = sectionById.get(group.id);
|
|
618
|
+
if (!section) {
|
|
619
|
+
return group;
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
id: group.id,
|
|
623
|
+
title: group.title,
|
|
624
|
+
startLine: group.startLine,
|
|
625
|
+
endLine: section.endLine,
|
|
626
|
+
targetFiles: section.targetFiles,
|
|
627
|
+
fileReferences: section.fileReferences,
|
|
628
|
+
verificationActions: section.verificationActions,
|
|
629
|
+
verificationCommands: section.verificationCommands,
|
|
630
|
+
executionIntent: section.executionIntent,
|
|
631
|
+
reviewIntent: section.reviewIntent,
|
|
632
|
+
testingIntent: section.testingIntent,
|
|
633
|
+
codeChangeLikely: section.codeChangeLikely,
|
|
634
|
+
placeholderItems: section.placeholderItems,
|
|
635
|
+
checklistItems: section.checklistItems
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
|
|
409
639
|
return {
|
|
410
|
-
taskGroups,
|
|
640
|
+
taskGroups: normalizedTaskGroups,
|
|
411
641
|
checklistItems,
|
|
412
642
|
checkpointItems,
|
|
413
643
|
sections,
|
|
644
|
+
markers,
|
|
645
|
+
markerSummary: {
|
|
646
|
+
hasDesignApproval: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.designApproval]),
|
|
647
|
+
hasPlanSelfReview: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.planSelfReview]),
|
|
648
|
+
hasOperatorReviewAck: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.operatorReviewAck]),
|
|
649
|
+
malformedCount: markers.malformed.length
|
|
650
|
+
},
|
|
414
651
|
text: String(text || "")
|
|
415
652
|
};
|
|
416
653
|
}
|