@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/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 {
|
|
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(
|
|
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 (
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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 (
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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 === "
|
|
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
|
|
1116
|
-
|
|
1117
|
-
|
|
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 === "
|
|
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
|
|
1133
|
-
|
|
1134
|
-
|
|
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 === "
|
|
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
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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(
|
|
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
|
+
};
|
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
|
|