@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.
- package/CHANGELOG.md +32 -0
- package/README.md +32 -7
- package/README.zh-CN.md +151 -7
- package/SKILL.md +45 -704
- package/commands/claude/dv/build.md +5 -0
- package/commands/claude/dv/continue.md +4 -0
- package/commands/claude/dv/tasks.md +6 -0
- package/commands/claude/dv/verify.md +2 -0
- package/commands/codex/prompts/dv-build.md +5 -0
- package/commands/codex/prompts/dv-continue.md +4 -0
- package/commands/codex/prompts/dv-tasks.md +6 -0
- package/commands/codex/prompts/dv-verify.md +2 -0
- package/commands/gemini/dv/build.toml +5 -0
- package/commands/gemini/dv/continue.toml +4 -0
- package/commands/gemini/dv/tasks.toml +6 -0
- package/commands/gemini/dv/verify.toml +2 -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 +33 -5
- package/docs/execution-chain-migration.md +23 -0
- package/docs/prompt-entrypoints.md +6 -0
- package/docs/skill-contract-maintenance.md +14 -0
- package/docs/skill-usage.md +16 -0
- package/docs/workflow-overview.md +17 -0
- package/docs/zh-CN/dv-command-reference.md +31 -5
- package/docs/zh-CN/execution-chain-migration.md +23 -0
- package/docs/zh-CN/prompt-entrypoints.md +6 -0
- package/docs/zh-CN/skill-usage.md +16 -0
- package/docs/zh-CN/workflow-overview.md +17 -0
- package/lib/audit-parsers.js +148 -1
- package/lib/cli/helpers.js +43 -0
- package/lib/cli/lint-family.js +56 -0
- package/lib/cli/verify-family.js +79 -0
- package/lib/cli.js +123 -145
- 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 +263 -19
- package/lib/scaffold.js +454 -23
- package/lib/supervisor-review.js +2 -1
- package/lib/task-execution.js +160 -0
- package/lib/task-review.js +197 -0
- package/lib/utils.js +19 -0
- package/lib/verify.js +1308 -85
- package/lib/workflow-state.js +452 -30
- package/lib/worktree-preflight.js +214 -0
- package/package.json +1 -1
- package/references/artifact-templates.md +56 -6
- package/references/skill-workflow-detail.md +66 -0
package/lib/workflow-state.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
1
2
|
const path = require("path");
|
|
2
3
|
const { auditProject } = require("./audit");
|
|
3
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
parseCheckpointStatusMap,
|
|
6
|
+
normalizeCheckpointLabel,
|
|
7
|
+
parseDisciplineMarkers,
|
|
8
|
+
DISCIPLINE_MARKER_NAMES
|
|
9
|
+
} = require("./audit-parsers");
|
|
4
10
|
const { pathExists, readTextIfExists } = require("./utils");
|
|
5
11
|
const {
|
|
6
12
|
parseTasksArtifact,
|
|
@@ -23,7 +29,14 @@ const {
|
|
|
23
29
|
readTaskGroupMetadata,
|
|
24
30
|
sanitizePersistedNotes
|
|
25
31
|
} = require("./workflow-persisted-state");
|
|
26
|
-
const {
|
|
32
|
+
const {
|
|
33
|
+
readExecutionSignals,
|
|
34
|
+
summarizeSignalsBySurface,
|
|
35
|
+
listSignalsBySurfacePrefix
|
|
36
|
+
} = require("./execution-signals");
|
|
37
|
+
const { deriveExecutionProfile } = require("./execution-profile");
|
|
38
|
+
const { collectVerificationFreshness } = require("./verify");
|
|
39
|
+
const { runWorktreePreflight } = require("./worktree-preflight");
|
|
27
40
|
|
|
28
41
|
const MAX_REPORTED_MESSAGES = 3;
|
|
29
42
|
|
|
@@ -50,6 +63,198 @@ function dedupeFindings(findings) {
|
|
|
50
63
|
findings.notes = dedupeMessages(findings.notes);
|
|
51
64
|
}
|
|
52
65
|
|
|
66
|
+
function resolveDisciplineGateMode() {
|
|
67
|
+
const strictEnv = String(process.env.DA_VINCI_DISCIPLINE_REQUIRE_APPROVAL || "").trim();
|
|
68
|
+
const strict = strictEnv === "1" || /^true$/i.test(strictEnv);
|
|
69
|
+
return {
|
|
70
|
+
strict
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function statusTokenMatches(status, acceptedTokens) {
|
|
75
|
+
const normalized = String(status || "").trim().toUpperCase();
|
|
76
|
+
return acceptedTokens.includes(normalized);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inspectDisciplineState(changeDir) {
|
|
80
|
+
const designPath = path.join(changeDir, "design.md");
|
|
81
|
+
const pencilDesignPath = path.join(changeDir, "pencil-design.md");
|
|
82
|
+
const pencilBindingsPath = path.join(changeDir, "pencil-bindings.md");
|
|
83
|
+
const tasksPath = path.join(changeDir, "tasks.md");
|
|
84
|
+
const designText = readTextIfExists(designPath);
|
|
85
|
+
const tasksText = readTextIfExists(tasksPath);
|
|
86
|
+
const designMarkers = parseDisciplineMarkers(designText);
|
|
87
|
+
const taskMarkers = parseDisciplineMarkers(tasksText);
|
|
88
|
+
const gateMode = resolveDisciplineGateMode();
|
|
89
|
+
const hasAnyMarker =
|
|
90
|
+
designMarkers.ordered.length > 0 ||
|
|
91
|
+
taskMarkers.ordered.length > 0 ||
|
|
92
|
+
designMarkers.malformed.length > 0 ||
|
|
93
|
+
taskMarkers.malformed.length > 0;
|
|
94
|
+
|
|
95
|
+
const designApproval =
|
|
96
|
+
designMarkers.markers[DISCIPLINE_MARKER_NAMES.designApproval] ||
|
|
97
|
+
taskMarkers.markers[DISCIPLINE_MARKER_NAMES.designApproval] ||
|
|
98
|
+
null;
|
|
99
|
+
const planSelfReview = taskMarkers.markers[DISCIPLINE_MARKER_NAMES.planSelfReview] || null;
|
|
100
|
+
const operatorReviewAck = taskMarkers.markers[DISCIPLINE_MARKER_NAMES.operatorReviewAck] || null;
|
|
101
|
+
|
|
102
|
+
const blockers = [];
|
|
103
|
+
const warnings = [];
|
|
104
|
+
const notes = [];
|
|
105
|
+
const designApprovalArtifacts = [
|
|
106
|
+
{ label: "design.md", path: designPath },
|
|
107
|
+
{ label: "pencil-design.md", path: pencilDesignPath },
|
|
108
|
+
{ label: "pencil-bindings.md", path: pencilBindingsPath }
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const malformed of [...designMarkers.malformed, ...taskMarkers.malformed]) {
|
|
112
|
+
warnings.push(`Malformed discipline marker at line ${malformed.line}: ${malformed.reason}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let designApprovalState = "missing";
|
|
116
|
+
let designApprovalStale = false;
|
|
117
|
+
if (designApproval) {
|
|
118
|
+
if (!statusTokenMatches(designApproval.status, ["APPROVED", "PASS", "ACCEPTED"])) {
|
|
119
|
+
designApprovalState = "rejected";
|
|
120
|
+
blockers.push(
|
|
121
|
+
`Design approval marker is not approved (${designApproval.status}); keep workflow in tasks/design.`
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
designApprovalState = "approved";
|
|
125
|
+
if (designApproval.time) {
|
|
126
|
+
const approvalMs = Date.parse(designApproval.time);
|
|
127
|
+
const staleArtifacts = [];
|
|
128
|
+
if (Number.isFinite(approvalMs)) {
|
|
129
|
+
for (const artifact of designApprovalArtifacts) {
|
|
130
|
+
let artifactMtimeMs = 0;
|
|
131
|
+
try {
|
|
132
|
+
artifactMtimeMs = fs.statSync(artifact.path).mtimeMs;
|
|
133
|
+
} catch (_error) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (artifactMtimeMs > approvalMs) {
|
|
137
|
+
staleArtifacts.push(artifact.label);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (staleArtifacts.length > 0) {
|
|
142
|
+
designApprovalState = "stale";
|
|
143
|
+
designApprovalStale = true;
|
|
144
|
+
blockers.push(
|
|
145
|
+
`Design approval marker is stale because design artifacts changed after approval timestamp: ${staleArtifacts.join(", ")}.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
warnings.push("Design approval marker is missing timestamp; staleness checks are limited.");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if (hasAnyMarker || gateMode.strict) {
|
|
153
|
+
blockers.push(
|
|
154
|
+
"Missing required design approval marker (`- design approval: APPROVED @ <ISO8601>`) for disciplined handoff."
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
warnings.push(
|
|
158
|
+
"Design approval marker is missing; legacy compatibility mode keeps this advisory. Set DA_VINCI_DISCIPLINE_REQUIRE_APPROVAL=1 to enforce."
|
|
159
|
+
);
|
|
160
|
+
notes.push("Legacy compatibility mode: missing discipline markers are advisory.");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!planSelfReview) {
|
|
164
|
+
warnings.push("Missing plan self-review marker in tasks.md (`- plan self review: PASS @ <ISO8601>`).");
|
|
165
|
+
} else if (!statusTokenMatches(planSelfReview.status, ["PASS", "APPROVED", "DONE"])) {
|
|
166
|
+
warnings.push(`Plan self-review marker is not PASS (${planSelfReview.status}).`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!operatorReviewAck) {
|
|
170
|
+
warnings.push("Missing operator review acknowledgment marker in tasks.md (`- operator review ack: ACKNOWLEDGED @ <ISO8601>`).");
|
|
171
|
+
} else if (!statusTokenMatches(operatorReviewAck.status, ["ACKNOWLEDGED", "ACK", "CONFIRMED", "APPROVED"])) {
|
|
172
|
+
warnings.push(`Operator review acknowledgment marker is not acknowledged (${operatorReviewAck.status}).`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
strictMode: gateMode.strict,
|
|
177
|
+
hasAnyMarker,
|
|
178
|
+
designApproval: {
|
|
179
|
+
state: designApprovalState,
|
|
180
|
+
stale: designApprovalStale,
|
|
181
|
+
marker: designApproval
|
|
182
|
+
},
|
|
183
|
+
planSelfReview: {
|
|
184
|
+
marker: planSelfReview
|
|
185
|
+
},
|
|
186
|
+
operatorReviewAck: {
|
|
187
|
+
marker: operatorReviewAck
|
|
188
|
+
},
|
|
189
|
+
malformed: [...designMarkers.malformed, ...taskMarkers.malformed],
|
|
190
|
+
blockers,
|
|
191
|
+
warnings,
|
|
192
|
+
notes
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mergeSignalBySurface(signals) {
|
|
197
|
+
const summary = {};
|
|
198
|
+
for (const signal of signals || []) {
|
|
199
|
+
const key = String(signal.surface || "").trim();
|
|
200
|
+
if (!key || summary[key]) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
summary[key] = signal;
|
|
204
|
+
}
|
|
205
|
+
return summary;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function applyTaskExecutionAndReviewFindings(findings, signals) {
|
|
209
|
+
const taskExecutionSignals = listSignalsBySurfacePrefix(signals, "task-execution.");
|
|
210
|
+
const taskReviewSignals = listSignalsBySurfacePrefix(signals, "task-review.");
|
|
211
|
+
const latestTaskExecution = mergeSignalBySurface(taskExecutionSignals);
|
|
212
|
+
const latestTaskReview = mergeSignalBySurface(taskReviewSignals);
|
|
213
|
+
|
|
214
|
+
for (const signal of Object.values(latestTaskExecution)) {
|
|
215
|
+
const envelope = signal.details && signal.details.envelope ? signal.details.envelope : null;
|
|
216
|
+
const taskGroupId =
|
|
217
|
+
(envelope && envelope.taskGroupId) ||
|
|
218
|
+
String(signal.surface || "").replace(/^task-execution\./, "") ||
|
|
219
|
+
"unknown";
|
|
220
|
+
if (signal.status === STATUS.BLOCK) {
|
|
221
|
+
findings.blockers.push(`Task group ${taskGroupId} is BLOCKED in implementer status envelope.`);
|
|
222
|
+
} else if (signal.status === STATUS.WARN) {
|
|
223
|
+
findings.warnings.push(`Task group ${taskGroupId} has unresolved implementer concerns/context needs.`);
|
|
224
|
+
}
|
|
225
|
+
if (envelope && envelope.summary) {
|
|
226
|
+
findings.notes.push(`Implementer summary ${taskGroupId}: ${envelope.summary}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const reviewStateByGroup = {};
|
|
231
|
+
for (const signal of Object.values(latestTaskReview)) {
|
|
232
|
+
const envelope = signal.details && signal.details.envelope ? signal.details.envelope : null;
|
|
233
|
+
const taskGroupId =
|
|
234
|
+
(envelope && envelope.taskGroupId) ||
|
|
235
|
+
String(signal.surface || "").replace(/^task-review\./, "").split(".")[0] ||
|
|
236
|
+
"unknown";
|
|
237
|
+
const stage = envelope && envelope.stage ? envelope.stage : String(signal.surface || "").split(".").pop();
|
|
238
|
+
if (!reviewStateByGroup[taskGroupId]) {
|
|
239
|
+
reviewStateByGroup[taskGroupId] = {};
|
|
240
|
+
}
|
|
241
|
+
reviewStateByGroup[taskGroupId][stage] = signal.status;
|
|
242
|
+
if (signal.status === STATUS.BLOCK) {
|
|
243
|
+
findings.blockers.push(`Task review ${taskGroupId}/${stage} is BLOCK.`);
|
|
244
|
+
} else if (signal.status === STATUS.WARN) {
|
|
245
|
+
findings.warnings.push(`Task review ${taskGroupId}/${stage} is WARN and requires follow-up.`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const [taskGroupId, state] of Object.entries(reviewStateByGroup)) {
|
|
250
|
+
if (state.quality && state.quality === STATUS.PASS && state.spec !== STATUS.PASS) {
|
|
251
|
+
findings.blockers.push(
|
|
252
|
+
`Task review ordering violation for ${taskGroupId}: quality review passed before spec review PASS.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
53
258
|
function normalizeCheckpointStatus(status) {
|
|
54
259
|
const normalized = String(status || "").toUpperCase();
|
|
55
260
|
if (normalized === STATUS.PASS || normalized === STATUS.WARN || normalized === STATUS.BLOCK) {
|
|
@@ -130,6 +335,25 @@ function applyExecutionSignalFindings(stageId, findings, signalSummary) {
|
|
|
130
335
|
findings.warnings.push(`Planning diff signal diff-spec reports ${diffSignal.status}.`);
|
|
131
336
|
}
|
|
132
337
|
|
|
338
|
+
const lintTasksSignal = signalSummary["lint-tasks"];
|
|
339
|
+
const strictPromotion =
|
|
340
|
+
String(process.env.DA_VINCI_DISCIPLINE_STRICT_PROMOTION || "").trim() === "1" ||
|
|
341
|
+
/^true$/i.test(String(process.env.DA_VINCI_DISCIPLINE_STRICT_PROMOTION || "").trim());
|
|
342
|
+
if (lintTasksSignal && lintTasksSignal.status === STATUS.BLOCK) {
|
|
343
|
+
findings.blockers.push("lint-tasks signal is BLOCK and prevents promotion into build.");
|
|
344
|
+
if (nextStageId === "build" || nextStageId === "verify" || nextStageId === "complete") {
|
|
345
|
+
nextStageId = "tasks";
|
|
346
|
+
}
|
|
347
|
+
} else if (lintTasksSignal && lintTasksSignal.status === STATUS.WARN) {
|
|
348
|
+
findings.warnings.push("lint-tasks signal is WARN.");
|
|
349
|
+
if (strictPromotion && (nextStageId === "build" || nextStageId === "verify" || nextStageId === "complete")) {
|
|
350
|
+
findings.blockers.push(
|
|
351
|
+
"DA_VINCI_DISCIPLINE_STRICT_PROMOTION is enabled; lint-tasks WARN blocks promotion into build."
|
|
352
|
+
);
|
|
353
|
+
nextStageId = "tasks";
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
133
357
|
const verificationSignal = signalSummary["verify-coverage"];
|
|
134
358
|
if (verificationSignal && verificationSignal.status === STATUS.BLOCK) {
|
|
135
359
|
findings.blockers.push("verify-coverage signal is BLOCK.");
|
|
@@ -362,6 +586,21 @@ function deriveTaskGroupMetadata(tasksMarkdownText, checkpointStatuses) {
|
|
|
362
586
|
}
|
|
363
587
|
|
|
364
588
|
const normalizedTaskCheckpoint = checkpointStatuses[CHECKPOINT_LABELS.TASK] || STATUS.WARN;
|
|
589
|
+
const groupMetadataById = new Map(
|
|
590
|
+
(taskArtifact.taskGroups || []).map((group) => [
|
|
591
|
+
group.id,
|
|
592
|
+
{
|
|
593
|
+
targetFiles: Array.isArray(group.targetFiles) ? group.targetFiles : [],
|
|
594
|
+
fileReferences: Array.isArray(group.fileReferences) ? group.fileReferences : [],
|
|
595
|
+
verificationActions: Array.isArray(group.verificationActions) ? group.verificationActions : [],
|
|
596
|
+
verificationCommands: Array.isArray(group.verificationCommands) ? group.verificationCommands : [],
|
|
597
|
+
executionIntent: Array.isArray(group.executionIntent) ? group.executionIntent : [],
|
|
598
|
+
reviewIntent: group.reviewIntent === true,
|
|
599
|
+
testingIntent: group.testingIntent === true,
|
|
600
|
+
codeChangeLikely: group.codeChangeLikely === true
|
|
601
|
+
}
|
|
602
|
+
])
|
|
603
|
+
);
|
|
365
604
|
const metadata = sections.map((section, index) => {
|
|
366
605
|
const total = section.checklistItems.length;
|
|
367
606
|
const done = section.checklistItems.filter((item) => item.checked).length;
|
|
@@ -372,6 +611,7 @@ function deriveTaskGroupMetadata(tasksMarkdownText, checkpointStatuses) {
|
|
|
372
611
|
sectionStatus === "completed"
|
|
373
612
|
? "advance to next task group"
|
|
374
613
|
: section.checklistItems.find((item) => !item.checked)?.text || "continue group work";
|
|
614
|
+
const groupMetadata = groupMetadataById.get(section.id) || {};
|
|
375
615
|
|
|
376
616
|
return {
|
|
377
617
|
taskGroupId: section.id,
|
|
@@ -381,6 +621,14 @@ function deriveTaskGroupMetadata(tasksMarkdownText, checkpointStatuses) {
|
|
|
381
621
|
checkpointOutcome: normalizedTaskCheckpoint,
|
|
382
622
|
evidence: section.checklistItems.filter((item) => item.checked).map((item) => item.text),
|
|
383
623
|
nextAction,
|
|
624
|
+
targetFiles: groupMetadata.targetFiles || [],
|
|
625
|
+
fileReferences: groupMetadata.fileReferences || [],
|
|
626
|
+
verificationActions: groupMetadata.verificationActions || [],
|
|
627
|
+
verificationCommands: groupMetadata.verificationCommands || [],
|
|
628
|
+
executionIntent: groupMetadata.executionIntent || [],
|
|
629
|
+
reviewIntent: groupMetadata.reviewIntent === true,
|
|
630
|
+
testingIntent: groupMetadata.testingIntent === true,
|
|
631
|
+
codeChangeLikely: groupMetadata.codeChangeLikely === true,
|
|
384
632
|
resumeCursor: {
|
|
385
633
|
groupIndex: index,
|
|
386
634
|
nextUncheckedItem:
|
|
@@ -392,19 +640,30 @@ function deriveTaskGroupMetadata(tasksMarkdownText, checkpointStatuses) {
|
|
|
392
640
|
});
|
|
393
641
|
|
|
394
642
|
if (metadata.length === 0 && taskArtifact.taskGroups.length > 0) {
|
|
395
|
-
return taskArtifact.taskGroups.map((group, index) =>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
643
|
+
return taskArtifact.taskGroups.map((group, index) => {
|
|
644
|
+
const groupMetadata = groupMetadataById.get(group.id) || {};
|
|
645
|
+
return {
|
|
646
|
+
taskGroupId: group.id,
|
|
647
|
+
title: group.title,
|
|
648
|
+
status: "pending",
|
|
649
|
+
completion: 0,
|
|
650
|
+
checkpointOutcome: normalizedTaskCheckpoint,
|
|
651
|
+
evidence: [],
|
|
652
|
+
nextAction: "start task group",
|
|
653
|
+
targetFiles: groupMetadata.targetFiles || [],
|
|
654
|
+
fileReferences: groupMetadata.fileReferences || [],
|
|
655
|
+
verificationActions: groupMetadata.verificationActions || [],
|
|
656
|
+
verificationCommands: groupMetadata.verificationCommands || [],
|
|
657
|
+
executionIntent: groupMetadata.executionIntent || [],
|
|
658
|
+
reviewIntent: groupMetadata.reviewIntent === true,
|
|
659
|
+
testingIntent: groupMetadata.testingIntent === true,
|
|
660
|
+
codeChangeLikely: groupMetadata.codeChangeLikely === true,
|
|
661
|
+
resumeCursor: {
|
|
662
|
+
groupIndex: index,
|
|
663
|
+
nextUncheckedItem: null
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
});
|
|
408
667
|
}
|
|
409
668
|
|
|
410
669
|
return metadata;
|
|
@@ -501,13 +760,22 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
501
760
|
|
|
502
761
|
const tasksArtifactText = activeChangeDir ? readTextIfExists(path.join(activeChangeDir, "tasks.md")) : "";
|
|
503
762
|
const checkpointStatuses = activeChangeDir ? readCheckpointStatuses(activeChangeDir) : {};
|
|
504
|
-
const
|
|
505
|
-
?
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
763
|
+
const changeSignals = activeChangeId
|
|
764
|
+
? readExecutionSignals(projectRoot, {
|
|
765
|
+
changeId: activeChangeId
|
|
766
|
+
})
|
|
767
|
+
: [];
|
|
768
|
+
const signalSummary = summarizeSignalsBySurface(changeSignals);
|
|
769
|
+
const disciplineState = activeChangeDir ? inspectDisciplineState(activeChangeDir) : null;
|
|
770
|
+
const freshnessArtifactPaths = activeChangeDir
|
|
771
|
+
? {
|
|
772
|
+
proposalPath: path.join(activeChangeDir, "proposal.md"),
|
|
773
|
+
tasksPath: path.join(activeChangeDir, "tasks.md"),
|
|
774
|
+
bindingsPath: path.join(activeChangeDir, "pencil-bindings.md"),
|
|
775
|
+
pencilDesignPath: path.join(activeChangeDir, "pencil-design.md"),
|
|
776
|
+
verificationPath: path.join(activeChangeDir, "verification.md")
|
|
777
|
+
}
|
|
778
|
+
: null;
|
|
511
779
|
const integrityAudit = collectIntegrityAudit(projectRoot, workflowRoot, signalSummary);
|
|
512
780
|
const designCheckpointStatus = normalizeCheckpointStatus(checkpointStatuses[CHECKPOINT_LABELS.DESIGN]);
|
|
513
781
|
const designSourceCheckpointStatus = normalizeCheckpointStatus(
|
|
@@ -549,6 +817,33 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
549
817
|
persistedFindings,
|
|
550
818
|
signalSummary
|
|
551
819
|
);
|
|
820
|
+
applyTaskExecutionAndReviewFindings(persistedFindings, changeSignals);
|
|
821
|
+
if (disciplineState) {
|
|
822
|
+
persistedFindings.blockers.push(...disciplineState.blockers);
|
|
823
|
+
persistedFindings.warnings.push(...disciplineState.warnings);
|
|
824
|
+
persistedFindings.notes.push(...disciplineState.notes);
|
|
825
|
+
if (disciplineState.blockers.length > 0 && ["build", "verify", "complete"].includes(persistedStageId)) {
|
|
826
|
+
persistedStageId = artifactState.tasks ? "tasks" : "design";
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const verificationFreshness = activeChangeId
|
|
831
|
+
? collectVerificationFreshness(projectRoot, {
|
|
832
|
+
changeId: activeChangeId,
|
|
833
|
+
resolved: { changeDir: activeChangeDir },
|
|
834
|
+
artifactPaths: freshnessArtifactPaths
|
|
835
|
+
})
|
|
836
|
+
: null;
|
|
837
|
+
if (
|
|
838
|
+
verificationFreshness &&
|
|
839
|
+
!verificationFreshness.fresh &&
|
|
840
|
+
(persistedStageId === "verify" || persistedStageId === "complete")
|
|
841
|
+
) {
|
|
842
|
+
persistedFindings.blockers.push(
|
|
843
|
+
"Completion-facing routing requires fresh verification evidence; stale evidence keeps the route in verify."
|
|
844
|
+
);
|
|
845
|
+
persistedStageId = "verify";
|
|
846
|
+
}
|
|
552
847
|
persistedFindings.notes.push("workflow-status is using trusted persisted workflow state.");
|
|
553
848
|
dedupeFindings(persistedFindings);
|
|
554
849
|
|
|
@@ -560,6 +855,39 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
560
855
|
persistedGates[HANDOFF_GATES.VERIFY_TO_COMPLETE] =
|
|
561
856
|
completionAudit.status === "PASS" ? STATUS.PASS : STATUS.WARN;
|
|
562
857
|
}
|
|
858
|
+
if (disciplineState && disciplineState.blockers.length > 0) {
|
|
859
|
+
persistedGates[HANDOFF_GATES.TASKS_TO_BUILD] = STATUS.BLOCK;
|
|
860
|
+
}
|
|
861
|
+
if (verificationFreshness && !verificationFreshness.fresh) {
|
|
862
|
+
persistedGates[HANDOFF_GATES.VERIFY_TO_COMPLETE] = STATUS.BLOCK;
|
|
863
|
+
}
|
|
864
|
+
const persistedTaskGroups =
|
|
865
|
+
persistedTaskMetadata && Array.isArray(persistedTaskMetadata.taskGroups)
|
|
866
|
+
? persistedTaskMetadata.taskGroups
|
|
867
|
+
: Array.isArray(persistedRecord.taskGroups)
|
|
868
|
+
? persistedRecord.taskGroups
|
|
869
|
+
: [];
|
|
870
|
+
const executionProfile = deriveExecutionProfile({
|
|
871
|
+
stage: persistedStageId,
|
|
872
|
+
taskGroups: persistedTaskGroups
|
|
873
|
+
});
|
|
874
|
+
let worktreePreflight = null;
|
|
875
|
+
if (activeChangeId && (persistedStageId === "build" || persistedStageId === "verify")) {
|
|
876
|
+
worktreePreflight = runWorktreePreflight(projectRoot, {
|
|
877
|
+
parallelPreferred: executionProfile.mode === "bounded_parallel"
|
|
878
|
+
});
|
|
879
|
+
if (
|
|
880
|
+
executionProfile.mode === "bounded_parallel" &&
|
|
881
|
+
worktreePreflight.summary &&
|
|
882
|
+
worktreePreflight.summary.recommendedIsolation
|
|
883
|
+
) {
|
|
884
|
+
executionProfile.effectiveMode = "serial";
|
|
885
|
+
executionProfile.rationale = dedupeMessages([
|
|
886
|
+
...(executionProfile.rationale || []),
|
|
887
|
+
"worktree preflight recommends isolation; effective mode downgraded to serial"
|
|
888
|
+
]);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
563
891
|
|
|
564
892
|
return buildWorkflowResult({
|
|
565
893
|
projectRoot,
|
|
@@ -578,12 +906,11 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
578
906
|
ambiguousChangeSelection
|
|
579
907
|
},
|
|
580
908
|
source: "persisted",
|
|
581
|
-
taskGroups:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
: []
|
|
909
|
+
taskGroups: persistedTaskGroups,
|
|
910
|
+
discipline: disciplineState,
|
|
911
|
+
executionProfile,
|
|
912
|
+
worktreePreflight,
|
|
913
|
+
verificationFreshness
|
|
587
914
|
});
|
|
588
915
|
}
|
|
589
916
|
|
|
@@ -611,6 +938,29 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
611
938
|
: null;
|
|
612
939
|
stageId = applyAuditFindings(stageId, findings, integrityAudit, completionAudit);
|
|
613
940
|
stageId = applyExecutionSignalFindings(stageId, findings, signalSummary);
|
|
941
|
+
applyTaskExecutionAndReviewFindings(findings, changeSignals);
|
|
942
|
+
if (disciplineState) {
|
|
943
|
+
findings.blockers.push(...disciplineState.blockers);
|
|
944
|
+
findings.warnings.push(...disciplineState.warnings);
|
|
945
|
+
findings.notes.push(...disciplineState.notes);
|
|
946
|
+
if (disciplineState.blockers.length > 0 && ["build", "verify", "complete"].includes(stageId)) {
|
|
947
|
+
stageId = artifactState.tasks ? "tasks" : "design";
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const verificationFreshness = activeChangeId
|
|
952
|
+
? collectVerificationFreshness(projectRoot, {
|
|
953
|
+
changeId: activeChangeId,
|
|
954
|
+
resolved: { changeDir: activeChangeDir },
|
|
955
|
+
artifactPaths: freshnessArtifactPaths
|
|
956
|
+
})
|
|
957
|
+
: null;
|
|
958
|
+
if (verificationFreshness && !verificationFreshness.fresh && (stageId === "verify" || stageId === "complete")) {
|
|
959
|
+
findings.blockers.push(
|
|
960
|
+
"Completion-facing routing requires fresh verification evidence; stale evidence keeps the route in verify."
|
|
961
|
+
);
|
|
962
|
+
stageId = "verify";
|
|
963
|
+
}
|
|
614
964
|
|
|
615
965
|
const gates = {
|
|
616
966
|
[HANDOFF_GATES.BREAKDOWN_TO_DESIGN]:
|
|
@@ -636,8 +986,38 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
636
986
|
[HANDOFF_GATES.VERIFY_TO_COMPLETE]:
|
|
637
987
|
completionAudit && completionAudit.status === "PASS" ? STATUS.PASS : STATUS.WARN
|
|
638
988
|
};
|
|
989
|
+
if (disciplineState && disciplineState.blockers.length > 0) {
|
|
990
|
+
gates[HANDOFF_GATES.TASKS_TO_BUILD] = STATUS.BLOCK;
|
|
991
|
+
}
|
|
992
|
+
if (verificationFreshness && !verificationFreshness.fresh) {
|
|
993
|
+
gates[HANDOFF_GATES.VERIFY_TO_COMPLETE] = STATUS.BLOCK;
|
|
994
|
+
}
|
|
639
995
|
|
|
640
996
|
const derivedTaskGroups = deriveTaskGroupMetadata(tasksArtifactText, checkpointStatuses);
|
|
997
|
+
const executionProfile = deriveExecutionProfile({
|
|
998
|
+
stage: stageId,
|
|
999
|
+
taskGroups: derivedTaskGroups
|
|
1000
|
+
});
|
|
1001
|
+
let worktreePreflight = null;
|
|
1002
|
+
if (activeChangeId && (stageId === "build" || stageId === "verify")) {
|
|
1003
|
+
worktreePreflight = runWorktreePreflight(projectRoot, {
|
|
1004
|
+
parallelPreferred: executionProfile.mode === "bounded_parallel"
|
|
1005
|
+
});
|
|
1006
|
+
if (
|
|
1007
|
+
executionProfile.mode === "bounded_parallel" &&
|
|
1008
|
+
worktreePreflight.summary &&
|
|
1009
|
+
worktreePreflight.summary.recommendedIsolation
|
|
1010
|
+
) {
|
|
1011
|
+
executionProfile.effectiveMode = "serial";
|
|
1012
|
+
executionProfile.rationale = dedupeMessages([
|
|
1013
|
+
...(executionProfile.rationale || []),
|
|
1014
|
+
"worktree preflight recommends isolation; effective mode downgraded to serial"
|
|
1015
|
+
]);
|
|
1016
|
+
findings.warnings.push(
|
|
1017
|
+
"Bounded-parallel profile downgraded to serial until worktree isolation is ready or explicitly accepted."
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
641
1021
|
if (activeChangeId && !ambiguousChangeSelection && derivedTaskGroups.length > 0) {
|
|
642
1022
|
const metadataPayload = {
|
|
643
1023
|
version: 1,
|
|
@@ -673,7 +1053,11 @@ function deriveWorkflowStatus(projectPathInput, options = {}) {
|
|
|
673
1053
|
changeId: activeChangeId || requestedChangeId || "change-001",
|
|
674
1054
|
ambiguousChangeSelection
|
|
675
1055
|
},
|
|
676
|
-
taskGroups: derivedTaskGroups
|
|
1056
|
+
taskGroups: derivedTaskGroups,
|
|
1057
|
+
discipline: disciplineState,
|
|
1058
|
+
executionProfile,
|
|
1059
|
+
worktreePreflight,
|
|
1060
|
+
verificationFreshness
|
|
677
1061
|
});
|
|
678
1062
|
|
|
679
1063
|
if (activeChangeId && !ambiguousChangeSelection) {
|
|
@@ -733,7 +1117,11 @@ function buildWorkflowResult(params) {
|
|
|
733
1117
|
audits: params.audits,
|
|
734
1118
|
nextStep,
|
|
735
1119
|
source: params.source || "derived",
|
|
736
|
-
taskGroups: Array.isArray(params.taskGroups) ? params.taskGroups : []
|
|
1120
|
+
taskGroups: Array.isArray(params.taskGroups) ? params.taskGroups : [],
|
|
1121
|
+
discipline: params.discipline || null,
|
|
1122
|
+
executionProfile: params.executionProfile || null,
|
|
1123
|
+
worktreePreflight: params.worktreePreflight || null,
|
|
1124
|
+
verificationFreshness: params.verificationFreshness || null
|
|
737
1125
|
};
|
|
738
1126
|
}
|
|
739
1127
|
|
|
@@ -755,6 +1143,25 @@ function formatWorkflowStatusReport(result) {
|
|
|
755
1143
|
lines.push("Next route: none");
|
|
756
1144
|
}
|
|
757
1145
|
|
|
1146
|
+
if (result.discipline && result.discipline.designApproval) {
|
|
1147
|
+
lines.push(`Discipline design approval: ${result.discipline.designApproval.state}`);
|
|
1148
|
+
}
|
|
1149
|
+
if (result.executionProfile) {
|
|
1150
|
+
lines.push(
|
|
1151
|
+
`Execution profile: ${result.executionProfile.mode}${
|
|
1152
|
+
result.executionProfile.effectiveMode
|
|
1153
|
+
? ` (effective ${result.executionProfile.effectiveMode})`
|
|
1154
|
+
: ""
|
|
1155
|
+
}, max parallel ${result.executionProfile.maxParallel}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
if (result.worktreePreflight) {
|
|
1159
|
+
lines.push(`Worktree preflight: ${result.worktreePreflight.status} (advisory)`);
|
|
1160
|
+
}
|
|
1161
|
+
if (result.verificationFreshness) {
|
|
1162
|
+
lines.push(`Verification freshness: ${result.verificationFreshness.fresh ? "fresh" : "stale"}`);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
758
1165
|
if (result.failures.length > 0) {
|
|
759
1166
|
lines.push("Blockers:");
|
|
760
1167
|
for (const message of result.failures) {
|
|
@@ -815,6 +1222,21 @@ function formatNextStepReport(result) {
|
|
|
815
1222
|
if (result.nextStep.reason) {
|
|
816
1223
|
lines.push(`Reason: ${result.nextStep.reason}`);
|
|
817
1224
|
}
|
|
1225
|
+
if (result.executionProfile) {
|
|
1226
|
+
lines.push(
|
|
1227
|
+
`Execution profile: ${result.executionProfile.mode}${
|
|
1228
|
+
result.executionProfile.effectiveMode
|
|
1229
|
+
? ` (effective ${result.executionProfile.effectiveMode})`
|
|
1230
|
+
: ""
|
|
1231
|
+
}, max parallel ${result.executionProfile.maxParallel}`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
if (result.worktreePreflight) {
|
|
1235
|
+
lines.push(`Worktree preflight: ${result.worktreePreflight.status} (advisory)`);
|
|
1236
|
+
}
|
|
1237
|
+
if (result.verificationFreshness && !result.verificationFreshness.fresh) {
|
|
1238
|
+
lines.push("Completion evidence is stale; stay in verify until fresh evidence is recorded.");
|
|
1239
|
+
}
|
|
818
1240
|
if (Array.isArray(result.taskGroups) && result.taskGroups.length > 0) {
|
|
819
1241
|
const active =
|
|
820
1242
|
result.taskGroups.find((group) => group.status === "in_progress") ||
|