@xenonbyte/da-vinci-workflow 0.2.3 → 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 +13 -0
- package/README.md +32 -7
- package/README.zh-CN.md +151 -7
- 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 +18 -2
- package/docs/execution-chain-migration.md +23 -0
- package/docs/prompt-entrypoints.md +5 -0
- package/docs/skill-usage.md +16 -0
- package/docs/workflow-overview.md +17 -0
- package/docs/zh-CN/dv-command-reference.md +16 -2
- package/docs/zh-CN/execution-chain-migration.md +23 -0
- package/docs/zh-CN/prompt-entrypoints.md +5 -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.js +106 -1
- 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/supervisor-review.js +2 -1
- package/lib/task-execution.js +160 -0
- package/lib/task-review.js +197 -0
- package/lib/verify.js +152 -1
- 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/lib/cli.js
CHANGED
|
@@ -79,6 +79,18 @@ const {
|
|
|
79
79
|
const { diffSpec, formatDiffSpecReport } = require("./diff-spec");
|
|
80
80
|
const { scaffoldFromBindings, formatScaffoldReport } = require("./scaffold");
|
|
81
81
|
const { writeExecutionSignal } = require("./execution-signals");
|
|
82
|
+
const {
|
|
83
|
+
writeTaskExecutionEnvelope,
|
|
84
|
+
formatTaskExecutionReport
|
|
85
|
+
} = require("./task-execution");
|
|
86
|
+
const {
|
|
87
|
+
writeTaskReviewEnvelope,
|
|
88
|
+
formatTaskReviewReport
|
|
89
|
+
} = require("./task-review");
|
|
90
|
+
const {
|
|
91
|
+
runWorktreePreflight,
|
|
92
|
+
formatWorktreePreflightReport
|
|
93
|
+
} = require("./worktree-preflight");
|
|
82
94
|
const { formatTuiHelp, launchTui } = require("../tui");
|
|
83
95
|
|
|
84
96
|
const DEFAULT_MAX_PREFLIGHT_STDIN_BYTES = 1024 * 1024;
|
|
@@ -101,6 +113,15 @@ const OPTION_FLAGS_WITH_VALUES = new Set([
|
|
|
101
113
|
"--aliases",
|
|
102
114
|
"--pencil-design",
|
|
103
115
|
"--status",
|
|
116
|
+
"--stage",
|
|
117
|
+
"--summary",
|
|
118
|
+
"--task-group",
|
|
119
|
+
"--changed-files",
|
|
120
|
+
"--test-evidence",
|
|
121
|
+
"--concerns",
|
|
122
|
+
"--blockers",
|
|
123
|
+
"--issues",
|
|
124
|
+
"--reviewer",
|
|
104
125
|
"--source",
|
|
105
126
|
"--executed-reviewers",
|
|
106
127
|
"--codex-bin",
|
|
@@ -163,6 +184,16 @@ const HELP_OPTION_SPECS = [
|
|
|
163
184
|
description: "icon-search family filter: all, material, rounded, outlined, sharp, lucide, feather, phosphor"
|
|
164
185
|
},
|
|
165
186
|
{ flag: "--status <value>", description: "PASS, WARN, or BLOCK for supervisor-review" },
|
|
187
|
+
{ flag: "--stage <value>", description: "task-review stage: spec or quality" },
|
|
188
|
+
{ flag: "--summary <text>", description: "task-execution/task-review summary text" },
|
|
189
|
+
{ flag: "--task-group <id>", description: "task group identifier for task-execution/task-review" },
|
|
190
|
+
{ flag: "--changed-files <csv>", description: "comma-separated changed files for task-execution" },
|
|
191
|
+
{ flag: "--test-evidence <csv>", description: "comma-separated test evidence commands for task-execution" },
|
|
192
|
+
{ flag: "--concerns <csv>", description: "comma-separated concern text for task-execution" },
|
|
193
|
+
{ flag: "--blockers <csv>", description: "comma-separated blocker text for task-execution" },
|
|
194
|
+
{ flag: "--issues <csv>", description: "comma-separated issue text for task-review" },
|
|
195
|
+
{ flag: "--reviewer <name>", description: "reviewer identifier for task-review" },
|
|
196
|
+
{ flag: "--write-verification", description: "append task-review evidence into verification.md" },
|
|
166
197
|
{ flag: "--source <value>", description: "review source: skill, manual, inferred" },
|
|
167
198
|
{ flag: "--executed-reviewers <csv>", description: "reviewer skills that executed this review" },
|
|
168
199
|
{ flag: "--run-reviewers", description: "execute configured reviewer skills through codex exec" },
|
|
@@ -498,6 +529,9 @@ function printHelp() {
|
|
|
498
529
|
" da-vinci verify-implementation [--project <path>] [--change <id>] [--strict] [--json]",
|
|
499
530
|
" da-vinci verify-structure [--project <path>] [--change <id>] [--strict] [--json]",
|
|
500
531
|
" da-vinci verify-coverage [--project <path>] [--change <id>] [--strict] [--json]",
|
|
532
|
+
" da-vinci task-execution --project <path> --change <id> --task-group <id> --status <DONE|DONE_WITH_CONCERNS|NEEDS_CONTEXT|BLOCKED> --summary <text> [--changed-files <csv>] [--test-evidence <csv>] [--concerns <csv>] [--blockers <csv>] [--json]",
|
|
533
|
+
" da-vinci task-review --project <path> --change <id> --task-group <id> --stage <spec|quality> --status <PASS|WARN|BLOCK> --summary <text> [--issues <csv>] [--reviewer <name>] [--write-verification] [--json]",
|
|
534
|
+
" da-vinci worktree-preflight --project <path> [--change <id>] [--json]",
|
|
501
535
|
" da-vinci diff-spec [--project <path>] [--change <id>] [--from <sidecars-dir>] [--json]",
|
|
502
536
|
" da-vinci scaffold [--project <path>] [--change <id>] [--output <path>] [--json]",
|
|
503
537
|
" da-vinci validate-assets",
|
|
@@ -1000,7 +1034,21 @@ async function runCli(argv) {
|
|
|
1000
1034
|
const result = deriveWorkflowStatus(projectPath, { changeId });
|
|
1001
1035
|
|
|
1002
1036
|
if (argv.includes("--json")) {
|
|
1003
|
-
console.log(
|
|
1037
|
+
console.log(
|
|
1038
|
+
JSON.stringify(
|
|
1039
|
+
{
|
|
1040
|
+
stage: result.stage,
|
|
1041
|
+
checkpointState: result.checkpointState,
|
|
1042
|
+
nextStep: result.nextStep || null,
|
|
1043
|
+
discipline: result.discipline || null,
|
|
1044
|
+
executionProfile: result.executionProfile || null,
|
|
1045
|
+
worktreePreflight: result.worktreePreflight || null,
|
|
1046
|
+
verificationFreshness: result.verificationFreshness || null
|
|
1047
|
+
},
|
|
1048
|
+
null,
|
|
1049
|
+
2
|
|
1050
|
+
)
|
|
1051
|
+
);
|
|
1004
1052
|
return;
|
|
1005
1053
|
}
|
|
1006
1054
|
|
|
@@ -1160,6 +1208,63 @@ async function runCli(argv) {
|
|
|
1160
1208
|
return;
|
|
1161
1209
|
}
|
|
1162
1210
|
|
|
1211
|
+
if (command === "task-execution") {
|
|
1212
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1213
|
+
const changeId = getOption(argv, "--change");
|
|
1214
|
+
const result = writeTaskExecutionEnvelope(projectPath, {
|
|
1215
|
+
changeId,
|
|
1216
|
+
taskGroupId: getOption(argv, "--task-group"),
|
|
1217
|
+
status: getOption(argv, "--status"),
|
|
1218
|
+
summary: getOption(argv, "--summary"),
|
|
1219
|
+
changedFiles: getCommaSeparatedOptionValues(argv, "--changed-files"),
|
|
1220
|
+
testEvidence: getCommaSeparatedOptionValues(argv, "--test-evidence"),
|
|
1221
|
+
concerns: getCommaSeparatedOptionValues(argv, "--concerns"),
|
|
1222
|
+
blockers: getCommaSeparatedOptionValues(argv, "--blockers")
|
|
1223
|
+
});
|
|
1224
|
+
const useJson = argv.includes("--json");
|
|
1225
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatTaskExecutionReport(result);
|
|
1226
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
console.log(output);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (command === "task-review") {
|
|
1234
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1235
|
+
const changeId = getOption(argv, "--change");
|
|
1236
|
+
const result = writeTaskReviewEnvelope(projectPath, {
|
|
1237
|
+
changeId,
|
|
1238
|
+
taskGroupId: getOption(argv, "--task-group"),
|
|
1239
|
+
stage: getOption(argv, "--stage"),
|
|
1240
|
+
status: getOption(argv, "--status"),
|
|
1241
|
+
summary: getOption(argv, "--summary"),
|
|
1242
|
+
issues: getCommaSeparatedOptionValues(argv, "--issues"),
|
|
1243
|
+
reviewer: getOption(argv, "--reviewer"),
|
|
1244
|
+
writeVerification: argv.includes("--write-verification")
|
|
1245
|
+
});
|
|
1246
|
+
const useJson = argv.includes("--json");
|
|
1247
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatTaskReviewReport(result);
|
|
1248
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
console.log(output);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (command === "worktree-preflight") {
|
|
1256
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1257
|
+
const changeId = getOption(argv, "--change");
|
|
1258
|
+
const result = runWorktreePreflight(projectPath);
|
|
1259
|
+
persistExecutionSignal(projectPath, changeId || "global", "worktree-preflight", result, false);
|
|
1260
|
+
if (argv.includes("--json")) {
|
|
1261
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
console.log(formatWorktreePreflightReport(result));
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1163
1268
|
if (command === "diff-spec") {
|
|
1164
1269
|
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1165
1270
|
const changeId = getOption(argv, "--change");
|
|
@@ -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
|
|