@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
package/lib/cli.js CHANGED
@@ -78,7 +78,21 @@ const {
78
78
  } = require("./verify");
79
79
  const { diffSpec, formatDiffSpecReport } = require("./diff-spec");
80
80
  const { scaffoldFromBindings, formatScaffoldReport } = require("./scaffold");
81
- const { writeExecutionSignal } = require("./execution-signals");
81
+ const { emitOrThrowOnStatus, persistExecutionSignal } = require("./cli/helpers");
82
+ const { handleVerifyFamilyCommand } = require("./cli/verify-family");
83
+ const { handleLintFamilyCommand } = require("./cli/lint-family");
84
+ const {
85
+ writeTaskExecutionEnvelope,
86
+ formatTaskExecutionReport
87
+ } = require("./task-execution");
88
+ const {
89
+ writeTaskReviewEnvelope,
90
+ formatTaskReviewReport
91
+ } = require("./task-review");
92
+ const {
93
+ runWorktreePreflight,
94
+ formatWorktreePreflightReport
95
+ } = require("./worktree-preflight");
82
96
  const { formatTuiHelp, launchTui } = require("../tui");
83
97
 
84
98
  const DEFAULT_MAX_PREFLIGHT_STDIN_BYTES = 1024 * 1024;
@@ -101,6 +115,15 @@ const OPTION_FLAGS_WITH_VALUES = new Set([
101
115
  "--aliases",
102
116
  "--pencil-design",
103
117
  "--status",
118
+ "--stage",
119
+ "--summary",
120
+ "--task-group",
121
+ "--changed-files",
122
+ "--test-evidence",
123
+ "--concerns",
124
+ "--blockers",
125
+ "--issues",
126
+ "--reviewer",
104
127
  "--source",
105
128
  "--executed-reviewers",
106
129
  "--codex-bin",
@@ -163,6 +186,19 @@ const HELP_OPTION_SPECS = [
163
186
  description: "icon-search family filter: all, material, rounded, outlined, sharp, lucide, feather, phosphor"
164
187
  },
165
188
  { flag: "--status <value>", description: "PASS, WARN, or BLOCK for supervisor-review" },
189
+ { flag: "--stage <value>", description: "task-review stage: spec or quality" },
190
+ { flag: "--summary <text>", description: "task-execution/task-review summary text" },
191
+ { flag: "--task-group <id>", description: "task group identifier for task-execution/task-review" },
192
+ {
193
+ flag: "--changed-files <csv>",
194
+ description: "comma-separated changed files for verify-implementation/verify-structure/verify-coverage/task-execution"
195
+ },
196
+ { flag: "--test-evidence <csv>", description: "comma-separated test evidence commands for task-execution" },
197
+ { flag: "--concerns <csv>", description: "comma-separated concern text for task-execution" },
198
+ { flag: "--blockers <csv>", description: "comma-separated blocker text for task-execution" },
199
+ { flag: "--issues <csv>", description: "comma-separated issue text for task-review" },
200
+ { flag: "--reviewer <name>", description: "reviewer identifier for task-review" },
201
+ { flag: "--write-verification", description: "append task-review evidence into verification.md" },
166
202
  { flag: "--source <value>", description: "review source: skill, manual, inferred" },
167
203
  { flag: "--executed-reviewers <csv>", description: "reviewer skills that executed this review" },
168
204
  { flag: "--run-reviewers", description: "execute configured reviewer skills through codex exec" },
@@ -348,17 +384,6 @@ function shouldContinueOnError(args) {
348
384
  return Array.isArray(args) && args.includes("--continue-on-error");
349
385
  }
350
386
 
351
- function emitOrThrowOnStatus(status, blockedStatuses, output, continueOnError) {
352
- if (!Array.isArray(blockedStatuses) || !blockedStatuses.includes(status)) {
353
- return false;
354
- }
355
- if (continueOnError) {
356
- console.log(output);
357
- return true;
358
- }
359
- throw new Error(output);
360
- }
361
-
362
387
  function getIntegerOption(args, name, options = {}) {
363
388
  const raw = getOption(args, name);
364
389
  if (raw === undefined) {
@@ -446,32 +471,6 @@ function appendStatusIssues(lines, label, missing = [], mismatched = [], unreada
446
471
  }
447
472
  }
448
473
 
449
- function persistExecutionSignal(projectPath, changeId, surface, result, strict = false) {
450
- try {
451
- writeExecutionSignal(projectPath, {
452
- changeId: changeId || "global",
453
- surface,
454
- status: result.status,
455
- advisory: strict ? false : true,
456
- strict,
457
- failures: result.failures || [],
458
- warnings: result.warnings || [],
459
- notes: result.notes || []
460
- });
461
- } catch (error) {
462
- // Signals are advisory metadata and should not break command execution.
463
- const code = error && error.code ? String(error.code).toUpperCase() : "";
464
- if (code === "EACCES" || code === "ENOSPC") {
465
- return;
466
- }
467
-
468
- const message = error && error.message ? error.message : String(error);
469
- console.error(
470
- `Warning: failed to persist execution signal (${surface}) for change ${changeId || "global"}: ${message}`
471
- );
472
- }
473
- }
474
-
475
474
  function printHelp() {
476
475
  const optionLines = HELP_OPTION_SPECS.map((optionSpec) => {
477
476
  const paddedFlag = optionSpec.flag.padEnd(30, " ");
@@ -495,9 +494,12 @@ function printHelp() {
495
494
  " da-vinci lint-bindings [--project <path>] [--change <id>] [--strict] [--json]",
496
495
  " da-vinci generate-sidecars [--project <path>] [--change <id>] [--json]",
497
496
  " da-vinci verify-bindings [--project <path>] [--change <id>] [--strict] [--json]",
498
- " da-vinci verify-implementation [--project <path>] [--change <id>] [--strict] [--json]",
499
- " da-vinci verify-structure [--project <path>] [--change <id>] [--strict] [--json]",
500
- " da-vinci verify-coverage [--project <path>] [--change <id>] [--strict] [--json]",
497
+ " da-vinci verify-implementation [--project <path>] [--change <id>] [--changed-files <csv>] [--strict] [--json]",
498
+ " da-vinci verify-structure [--project <path>] [--change <id>] [--changed-files <csv>] [--strict] [--json]",
499
+ " da-vinci verify-coverage [--project <path>] [--change <id>] [--changed-files <csv>] [--strict] [--json]",
500
+ " 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]",
501
+ " 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]",
502
+ " da-vinci worktree-preflight --project <path> [--change <id>] [--json]",
501
503
  " da-vinci diff-spec [--project <path>] [--change <id>] [--from <sidecars-dir>] [--json]",
502
504
  " da-vinci scaffold [--project <path>] [--change <id>] [--output <path>] [--json]",
503
505
  " da-vinci validate-assets",
@@ -1000,7 +1002,21 @@ async function runCli(argv) {
1000
1002
  const result = deriveWorkflowStatus(projectPath, { changeId });
1001
1003
 
1002
1004
  if (argv.includes("--json")) {
1003
- console.log(JSON.stringify(result.nextStep || null, null, 2));
1005
+ console.log(
1006
+ JSON.stringify(
1007
+ {
1008
+ stage: result.stage,
1009
+ checkpointState: result.checkpointState,
1010
+ nextStep: result.nextStep || null,
1011
+ discipline: result.discipline || null,
1012
+ executionProfile: result.executionProfile || null,
1013
+ worktreePreflight: result.worktreePreflight || null,
1014
+ verificationFreshness: result.verificationFreshness || null
1015
+ },
1016
+ null,
1017
+ 2
1018
+ )
1019
+ );
1004
1020
  return;
1005
1021
  }
1006
1022
 
@@ -1008,71 +1024,24 @@ async function runCli(argv) {
1008
1024
  return;
1009
1025
  }
1010
1026
 
1011
- if (command === "lint-spec") {
1012
- const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1013
- const changeId = getOption(argv, "--change");
1014
- const strict = argv.includes("--strict");
1015
- const result = lintRuntimeSpecs(projectPath, { changeId, strict });
1016
- persistExecutionSignal(projectPath, result.changeId || changeId, "lint-spec", result, strict);
1017
- const useJson = argv.includes("--json");
1018
- const output = useJson ? JSON.stringify(result, null, 2) : formatLintSpecReport(result);
1019
-
1020
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1021
- return;
1022
- }
1023
-
1024
- console.log(output);
1025
- return;
1026
- }
1027
-
1028
- if (command === "scope-check") {
1029
- const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1030
- const changeId = getOption(argv, "--change");
1031
- const strict = argv.includes("--strict");
1032
- const result = runScopeCheck(projectPath, { changeId, strict });
1033
- persistExecutionSignal(projectPath, result.changeId || changeId, "scope-check", result, strict);
1034
- const useJson = argv.includes("--json");
1035
- const output = useJson ? JSON.stringify(result, null, 2) : formatScopeCheckReport(result);
1036
-
1037
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1038
- return;
1039
- }
1040
-
1041
- console.log(output);
1042
- return;
1043
- }
1044
-
1045
- if (command === "lint-tasks") {
1046
- const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1047
- const changeId = getOption(argv, "--change");
1048
- const strict = argv.includes("--strict");
1049
- const result = lintTasks(projectPath, { changeId, strict });
1050
- persistExecutionSignal(projectPath, result.changeId || changeId, "lint-tasks", result, strict);
1051
- const useJson = argv.includes("--json");
1052
- const output = useJson ? JSON.stringify(result, null, 2) : formatLintTasksReport(result);
1053
-
1054
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1055
- return;
1056
- }
1057
-
1058
- console.log(output);
1059
- return;
1060
- }
1061
-
1062
- if (command === "lint-bindings") {
1063
- const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1064
- const changeId = getOption(argv, "--change");
1065
- const strict = argv.includes("--strict");
1066
- const result = lintBindings(projectPath, { changeId, strict });
1067
- persistExecutionSignal(projectPath, result.changeId || changeId, "lint-bindings", result, strict);
1068
- const useJson = argv.includes("--json");
1069
- const output = useJson ? JSON.stringify(result, null, 2) : formatLintBindingsReport(result);
1070
-
1071
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1072
- return;
1073
- }
1074
-
1075
- console.log(output);
1027
+ if (
1028
+ handleLintFamilyCommand(command, {
1029
+ argv,
1030
+ positionalArgs,
1031
+ continueOnError,
1032
+ getOption,
1033
+ lintRuntimeSpecs,
1034
+ formatLintSpecReport,
1035
+ runScopeCheck,
1036
+ formatScopeCheckReport,
1037
+ lintTasks,
1038
+ formatLintTasksReport,
1039
+ lintBindings,
1040
+ formatLintBindingsReport,
1041
+ emitOrThrowOnStatus,
1042
+ persistExecutionSignal
1043
+ })
1044
+ ) {
1076
1045
  return;
1077
1046
  }
1078
1047
 
@@ -1092,33 +1061,41 @@ async function runCli(argv) {
1092
1061
  return;
1093
1062
  }
1094
1063
 
1095
- if (command === "verify-bindings") {
1096
- const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1097
- const changeId = getOption(argv, "--change");
1098
- const strict = argv.includes("--strict");
1099
- const result = verifyBindings(projectPath, { changeId, strict });
1100
- persistExecutionSignal(projectPath, result.changeId || changeId, "verify-bindings", result, strict);
1101
- const useJson = argv.includes("--json");
1102
- const output = useJson
1103
- ? JSON.stringify(result, null, 2)
1104
- : formatVerifyReport(result, "Da Vinci verify-bindings");
1105
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1106
- return;
1107
- }
1108
- console.log(output);
1064
+ if (
1065
+ handleVerifyFamilyCommand(command, {
1066
+ argv,
1067
+ positionalArgs,
1068
+ continueOnError,
1069
+ getOption,
1070
+ getCommaSeparatedOptionValues,
1071
+ collectOptionEntries,
1072
+ verifyBindings,
1073
+ verifyImplementation,
1074
+ verifyStructure,
1075
+ verifyCoverage,
1076
+ formatVerifyReport,
1077
+ emitOrThrowOnStatus,
1078
+ persistExecutionSignal
1079
+ })
1080
+ ) {
1109
1081
  return;
1110
1082
  }
1111
1083
 
1112
- if (command === "verify-implementation") {
1084
+ if (command === "task-execution") {
1113
1085
  const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1114
1086
  const changeId = getOption(argv, "--change");
1115
- const strict = argv.includes("--strict");
1116
- const result = verifyImplementation(projectPath, { changeId, strict });
1117
- persistExecutionSignal(projectPath, result.changeId || changeId, "verify-implementation", result, strict);
1087
+ const result = writeTaskExecutionEnvelope(projectPath, {
1088
+ changeId,
1089
+ taskGroupId: getOption(argv, "--task-group"),
1090
+ status: getOption(argv, "--status"),
1091
+ summary: getOption(argv, "--summary"),
1092
+ changedFiles: getCommaSeparatedOptionValues(argv, "--changed-files"),
1093
+ testEvidence: getCommaSeparatedOptionValues(argv, "--test-evidence"),
1094
+ concerns: getCommaSeparatedOptionValues(argv, "--concerns"),
1095
+ blockers: getCommaSeparatedOptionValues(argv, "--blockers")
1096
+ });
1118
1097
  const useJson = argv.includes("--json");
1119
- const output = useJson
1120
- ? JSON.stringify(result, null, 2)
1121
- : formatVerifyReport(result, "Da Vinci verify-implementation");
1098
+ const output = useJson ? JSON.stringify(result, null, 2) : formatTaskExecutionReport(result);
1122
1099
  if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1123
1100
  return;
1124
1101
  }
@@ -1126,16 +1103,21 @@ async function runCli(argv) {
1126
1103
  return;
1127
1104
  }
1128
1105
 
1129
- if (command === "verify-structure") {
1106
+ if (command === "task-review") {
1130
1107
  const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1131
1108
  const changeId = getOption(argv, "--change");
1132
- const strict = argv.includes("--strict");
1133
- const result = verifyStructure(projectPath, { changeId, strict });
1134
- persistExecutionSignal(projectPath, result.changeId || changeId, "verify-structure", result, strict);
1109
+ const result = writeTaskReviewEnvelope(projectPath, {
1110
+ changeId,
1111
+ taskGroupId: getOption(argv, "--task-group"),
1112
+ stage: getOption(argv, "--stage"),
1113
+ status: getOption(argv, "--status"),
1114
+ summary: getOption(argv, "--summary"),
1115
+ issues: getCommaSeparatedOptionValues(argv, "--issues"),
1116
+ reviewer: getOption(argv, "--reviewer"),
1117
+ writeVerification: argv.includes("--write-verification")
1118
+ });
1135
1119
  const useJson = argv.includes("--json");
1136
- const output = useJson
1137
- ? JSON.stringify(result, null, 2)
1138
- : formatVerifyReport(result, "Da Vinci verify-structure");
1120
+ const output = useJson ? JSON.stringify(result, null, 2) : formatTaskReviewReport(result);
1139
1121
  if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1140
1122
  return;
1141
1123
  }
@@ -1143,20 +1125,16 @@ async function runCli(argv) {
1143
1125
  return;
1144
1126
  }
1145
1127
 
1146
- if (command === "verify-coverage") {
1128
+ if (command === "worktree-preflight") {
1147
1129
  const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
1148
1130
  const changeId = getOption(argv, "--change");
1149
- const strict = argv.includes("--strict");
1150
- const result = verifyCoverage(projectPath, { changeId, strict });
1151
- persistExecutionSignal(projectPath, result.changeId || changeId, "verify-coverage", result, strict);
1152
- const useJson = argv.includes("--json");
1153
- const output = useJson
1154
- ? JSON.stringify(result, null, 2)
1155
- : formatVerifyReport(result, "Da Vinci verify-coverage");
1156
- if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
1131
+ const result = runWorktreePreflight(projectPath);
1132
+ persistExecutionSignal(projectPath, changeId || "global", "worktree-preflight", result, false);
1133
+ if (argv.includes("--json")) {
1134
+ console.log(JSON.stringify(result, null, 2));
1157
1135
  return;
1158
1136
  }
1159
- console.log(output);
1137
+ console.log(formatWorktreePreflightReport(result));
1160
1138
  return;
1161
1139
  }
1162
1140
 
@@ -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
+ };
@@ -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((group) => /verify|verification|coverage/i.test(group.title)) ||
113
- parsedTasks.checklistItems.some((item) => /verify|verification|coverage/i.test(item.text));
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