lee-spec-kit 0.9.4 → 0.9.6
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/dist/index.js +198 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +4 -2
- package/templates/en/common/agents/agents.md +4 -3
- package/templates/en/common/agents/skills/create-feature.md +2 -1
- package/templates/en/common/agents/skills/create-pr.md +4 -2
- package/templates/en/common/agents/skills/execute-task.md +2 -1
- package/templates/en/common/features/feature-base/plan.md +2 -0
- package/templates/en/common/features/feature-base/tasks.md +5 -1
- package/templates/ko/common/README.md +4 -2
- package/templates/ko/common/agents/agents.md +4 -3
- package/templates/ko/common/agents/skills/create-feature.md +2 -1
- package/templates/ko/common/agents/skills/create-pr.md +4 -2
- package/templates/ko/common/agents/skills/execute-task.md +2 -1
- package/templates/ko/common/features/feature-base/plan.md +2 -0
- package/templates/ko/common/features/feature-base/tasks.md +5 -1
package/dist/index.js
CHANGED
|
@@ -213,6 +213,7 @@ var koCli = {
|
|
|
213
213
|
"init.choice.taskAgent.on": "\uC0AC\uC6A9 - \uAC01 Task \uAD6C\uD604\uC744 \uC704\uC784",
|
|
214
214
|
"init.choice.taskAgent.off": "\uC0AC\uC6A9 \uC548 \uD568 - \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC9C1\uC811 \uAD6C\uD604",
|
|
215
215
|
"init.prompt.reviews": "\uB3C5\uB9BD \uAC80\uC218 \uB2E8\uACC4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
216
|
+
"init.prompt.maxReviewRounds": "\uC794\uC5EC \uC704\uD5D8\uC744 \uAE30\uB85D\uD558\uACE0 \uACC4\uC18D\uD558\uAE30 \uC804 \uB9AC\uBDF0 \uC9C0\uC801 \uC790\uB3D9 \uBC18\uC601 \uCD5C\uB300 \uD69F\uC218:",
|
|
216
217
|
"init.choice.review.plan": "Plan",
|
|
217
218
|
"init.choice.review.task": "Task",
|
|
218
219
|
"init.choice.review.feature": "Feature",
|
|
@@ -238,6 +239,7 @@ var koCli = {
|
|
|
238
239
|
"init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
239
240
|
"init.log.taskAgentLabel": "Task \uAD6C\uD604",
|
|
240
241
|
"init.log.reviewsLabel": "\uAC80\uC218",
|
|
242
|
+
"init.log.maxReviewRoundsLabel": "\uB9AC\uBDF0 \uC9C0\uC801 \uC790\uB3D9 \uBC18\uC601 \uD69F\uC218",
|
|
241
243
|
"init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
|
|
242
244
|
"init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
|
|
243
245
|
"init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
|
|
@@ -525,6 +527,7 @@ var enCli = {
|
|
|
525
527
|
"init.choice.taskAgent.on": "On - delegate each task implementation",
|
|
526
528
|
"init.choice.taskAgent.off": "Off - implement tasks in the main agent",
|
|
527
529
|
"init.prompt.reviews": "Select independent review gates:",
|
|
530
|
+
"init.prompt.maxReviewRounds": "Maximum automatic finding remediation rounds before continuing with residual risks:",
|
|
528
531
|
"init.choice.review.plan": "Plan",
|
|
529
532
|
"init.choice.review.task": "Task",
|
|
530
533
|
"init.choice.review.feature": "Feature",
|
|
@@ -550,6 +553,7 @@ var enCli = {
|
|
|
550
553
|
"init.log.workflowLabel": "Workflow",
|
|
551
554
|
"init.log.taskAgentLabel": "Task implementation",
|
|
552
555
|
"init.log.reviewsLabel": "Reviews",
|
|
556
|
+
"init.log.maxReviewRoundsLabel": "Review remediation rounds",
|
|
553
557
|
"init.log.completionStrategyLabel": "Local integration",
|
|
554
558
|
"init.summary.taskAgent.on": "subagent",
|
|
555
559
|
"init.summary.taskAgent.off": "main agent",
|
|
@@ -1364,14 +1368,15 @@ Before taking the next workflow step:
|
|
|
1364
1368
|
10. In standalone mode, do not hand-write \`git worktree add\`; run the exact \`nextAction.command\` from \`workflow-stage\` so the managed workspace path, stale directory cleanup, and \`.env\` / \`.env.*\` copy step stay consistent
|
|
1365
1369
|
11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
|
|
1366
1370
|
12. When docs are synced to code, keep exactly one explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in a single active feature doc (prefer \`tasks.md\` or \`decisions.md\`): replace an existing marker timestamp or remove duplicates instead of appending another marker, so \`workflow-audit\` can prove the sync happened after the latest code change
|
|
1367
|
-
13. When \`workflow-stage --json\` returns \`nextAction.category === "plan_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review of \`spec.md\` and \`plan.md\` using the returned model settings and exact \`specHash\` / \`planHash\`; the main agent records evidence, decision, reviewer metadata, and both hashes, and any later spec/plan content change requires a fresh review
|
|
1371
|
+
13. When \`workflow-stage --json\` returns \`nextAction.category === "plan_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review of \`spec.md\` and \`plan.md\` using the returned model settings and exact \`specHash\` / \`planHash\`; the main agent records the returned reviewRound, evidence, decision, reviewer metadata, and both hashes, and any later spec/plan content change requires a fresh review
|
|
1368
1372
|
14. When \`workflow-stage --json\` returns \`nextAction.category === "task_execute"\` with \`executor === "subagent"\`, mark exactly the returned \`taskId\` as active and delegate its implementation plus task-scoped verification to a fresh subagent in the returned \`workingDirectory\`, using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, and exact \`workerContract\`; no named execution skill is required
|
|
1369
1373
|
15. The task implementation worker executes directly: it must follow the approved Verification Contract, must not add unplanned durable tests, and must not run \`workflow-stage\`, spawn another subagent, edit lee-spec-kit docs, change task state, commit, request approvals, or perform remote/destructive actions. It may modify project code and run task-scoped checks only. The main agent inspects the result, synchronizes docs and task state, and owns every commit and workflow transition; official hooks block commits while \`task_execute\` is still active
|
|
1370
|
-
16. When \`workflow-stage --json\` returns \`nextAction.category === "task_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review for the returned \`taskId\`, \`baseSha\`, \`targetSha\`, and \`targetTree\`; the main agent records the evidence and moves the task from \`REVIEW\` to \`DONE\` only after an approve decision
|
|
1371
|
-
17. When \`workflow-stage --json\` returns \`nextAction.category === "pre_pr_review"\` and \`nextAction.executor === "subagent"\`, delegate a fresh read-only Feature review using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, and review target metadata; do not select or require a named review skill
|
|
1374
|
+
16. When \`workflow-stage --json\` returns \`nextAction.category === "task_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review for the returned \`taskId\`, \`baseSha\`, \`targetSha\`, and \`targetTree\`; the main agent records the returned reviewRound and evidence and moves the task from \`REVIEW\` to \`DONE\` only after an approve decision
|
|
1375
|
+
17. When \`workflow-stage --json\` returns \`nextAction.category === "pre_pr_review"\` and \`nextAction.executor === "subagent"\`, delegate a fresh read-only Feature review using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, reviewRound, and review target metadata; do not select or require a named review skill
|
|
1372
1376
|
18. Review subagents return findings without modifying code; the main agent remediates findings and records the actual reviewer metadata, reviewed scope, evidence, decision, and exact hash/SHA/tree target metadata
|
|
1373
|
-
19.
|
|
1374
|
-
20.
|
|
1377
|
+
19. workflow.agentReview.maxRounds counts automatic finding-remediation passes, not the initial review. After those passes are exhausted, keep the latest changes_requested findings as residual risks and automatically complete that Plan/task/Feature review gate without asking the user for a review-approval token; blocked decisions never auto-complete
|
|
1378
|
+
20. For a local workflow, do not report completion directly after implementation approval; follow the exact returned \`local verify\` / \`local merge\` / \`local cleanup\` commands until \`workflow-stage\` proves verification, integration, and cleanup and returns \`done\`; review-fix and \`feature_remediation\` stages explicitly permit scoped fixes
|
|
1379
|
+
21. In a \`local-ff\` or \`local-squash\` workflow, keep implementation approval and local merge approval distinct when \`local_merge\` is required: the first accepts the implementation, and the second authorizes the configured integration strategy, post-merge checks, and local cleanup
|
|
1375
1380
|
|
|
1376
1381
|
Approval and remote actions:
|
|
1377
1382
|
|
|
@@ -2104,6 +2109,15 @@ function assertValidInitWorkflowOptions(options) {
|
|
|
2104
2109
|
"`--completion-strategy` must be `local-ff`, `local-squash`, or `none`."
|
|
2105
2110
|
);
|
|
2106
2111
|
}
|
|
2112
|
+
if (options.maxReviewRounds !== void 0) {
|
|
2113
|
+
const parsed = Number(options.maxReviewRounds);
|
|
2114
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
2115
|
+
throw createCliError(
|
|
2116
|
+
"INVALID_ARGUMENT",
|
|
2117
|
+
"`--max-review-rounds` must be a positive integer."
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2107
2121
|
}
|
|
2108
2122
|
function initCommand(program2) {
|
|
2109
2123
|
program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option(
|
|
@@ -2115,6 +2129,9 @@ function initCommand(program2) {
|
|
|
2115
2129
|
).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("--task-agent <mode>", "Task implementation agent: on | off").option(
|
|
2116
2130
|
"--reviews <list>",
|
|
2117
2131
|
"Review gates: comma-separated plan,task,feature | none"
|
|
2132
|
+
).option(
|
|
2133
|
+
"--max-review-rounds <count>",
|
|
2134
|
+
"Automatic review finding remediation rounds before continuing with residual risks (default: 1)"
|
|
2118
2135
|
).option(
|
|
2119
2136
|
"--completion-strategy <strategy>",
|
|
2120
2137
|
"Local completion: local-ff | local-squash | none"
|
|
@@ -2158,6 +2175,7 @@ async function runInit(options) {
|
|
|
2158
2175
|
let workflowMode = options.workflow || "github";
|
|
2159
2176
|
let taskAgentEnabled = options.taskAgent !== "off";
|
|
2160
2177
|
let enabledReviews = parseInitReviews(options.reviews) ?? ["plan", "feature"];
|
|
2178
|
+
let maxReviewRounds = Number(options.maxReviewRounds || 1);
|
|
2161
2179
|
let completionStrategy = options.completionStrategy || "local-ff";
|
|
2162
2180
|
let docsRepo = options.docsRepo || "embedded";
|
|
2163
2181
|
let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
|
|
@@ -2419,7 +2437,7 @@ async function runInit(options) {
|
|
|
2419
2437
|
docsRemote = remoteResponse.docsRemote || docsRemote;
|
|
2420
2438
|
}
|
|
2421
2439
|
}
|
|
2422
|
-
const hasExplicitWorkflowSetup = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.completionStrategy !== "undefined";
|
|
2440
|
+
const hasExplicitWorkflowSetup = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.maxReviewRounds !== "undefined" || typeof options.completionStrategy !== "undefined";
|
|
2423
2441
|
let customizeWorkflow = hasExplicitWorkflowSetup;
|
|
2424
2442
|
if (!hasExplicitWorkflowSetup) {
|
|
2425
2443
|
const setupResponse = await prompts(
|
|
@@ -2496,6 +2514,13 @@ async function runInit(options) {
|
|
|
2496
2514
|
selected: phase !== "task"
|
|
2497
2515
|
}))
|
|
2498
2516
|
},
|
|
2517
|
+
{
|
|
2518
|
+
type: typeof options.maxReviewRounds === "undefined" ? "number" : null,
|
|
2519
|
+
name: "maxReviewRounds",
|
|
2520
|
+
message: tr(lang, "cli", "init.prompt.maxReviewRounds"),
|
|
2521
|
+
initial: 1,
|
|
2522
|
+
min: 1
|
|
2523
|
+
},
|
|
2499
2524
|
{
|
|
2500
2525
|
type: workflowMode === "local" && typeof options.completionStrategy === "undefined" ? "select" : null,
|
|
2501
2526
|
name: "completionStrategy",
|
|
@@ -2539,6 +2564,9 @@ async function runInit(options) {
|
|
|
2539
2564
|
(phase) => workflowResponse.reviews.includes(phase)
|
|
2540
2565
|
);
|
|
2541
2566
|
}
|
|
2567
|
+
if (Number.isInteger(workflowResponse.maxReviewRounds) && workflowResponse.maxReviewRounds > 0) {
|
|
2568
|
+
maxReviewRounds = workflowResponse.maxReviewRounds;
|
|
2569
|
+
}
|
|
2542
2570
|
completionStrategy = workflowResponse.completionStrategy || completionStrategy;
|
|
2543
2571
|
}
|
|
2544
2572
|
}
|
|
@@ -2770,6 +2798,11 @@ async function runInit(options) {
|
|
|
2770
2798
|
` ${tr(lang, "cli", "init.log.reviewsLabel")}: ${reviewSummary}`
|
|
2771
2799
|
)
|
|
2772
2800
|
);
|
|
2801
|
+
console.log(
|
|
2802
|
+
chalk.gray(
|
|
2803
|
+
` ${tr(lang, "cli", "init.log.maxReviewRoundsLabel")}: ${maxReviewRounds}`
|
|
2804
|
+
)
|
|
2805
|
+
);
|
|
2773
2806
|
if (workflowMode === "local") {
|
|
2774
2807
|
console.log(
|
|
2775
2808
|
chalk.gray(
|
|
@@ -2839,6 +2872,7 @@ async function runInit(options) {
|
|
|
2839
2872
|
}
|
|
2840
2873
|
},
|
|
2841
2874
|
agentReview: {
|
|
2875
|
+
maxRounds: maxReviewRounds,
|
|
2842
2876
|
plan: {
|
|
2843
2877
|
enabled: enabledReviews.includes("plan"),
|
|
2844
2878
|
evidenceMode: "path_required",
|
|
@@ -3099,7 +3133,7 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
3099
3133
|
filtered.push(line);
|
|
3100
3134
|
}
|
|
3101
3135
|
next = filtered.join("\n");
|
|
3102
|
-
next = lang === "ko" ? next.replaceAll("**PR \uC804 \uB9AC\uBDF0**", "**Feature \uB9AC\uBDF0**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Evidence**", "**Feature \uB9AC\uBDF0 Evidence**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Decision**", "**Feature \uB9AC\uBDF0 Decision**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Head**", "**Feature \uB9AC\uBDF0 Head**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Tree**", "**Feature \uB9AC\uBDF0 Tree**").replaceAll("pre-PR \uB9AC\uBDF0 handoff", "Feature \uB9AC\uBDF0 handoff") : next.replaceAll("**Pre-PR Review**", "**Feature Review**").replaceAll("**Pre-PR Evidence**", "**Feature Review Evidence**").replaceAll("**Pre-PR Decision**", "**Feature Review Decision**").replaceAll("**Pre-PR Reviewed Head**", "**Feature Reviewed Head**").replaceAll("**Pre-PR Reviewed Tree**", "**Feature Reviewed Tree**").replaceAll("pre-PR review handoff", "Feature review handoff");
|
|
3136
|
+
next = lang === "ko" ? next.replaceAll("**PR \uC804 \uB9AC\uBDF0**", "**Feature \uB9AC\uBDF0**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Evidence**", "**Feature \uB9AC\uBDF0 Evidence**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Decision**", "**Feature \uB9AC\uBDF0 Decision**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Round**", "**Feature \uB9AC\uBDF0 Round**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Head**", "**Feature \uB9AC\uBDF0 Head**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Tree**", "**Feature \uB9AC\uBDF0 Tree**").replaceAll("pre-PR \uB9AC\uBDF0 handoff", "Feature \uB9AC\uBDF0 handoff") : next.replaceAll("**Pre-PR Review**", "**Feature Review**").replaceAll("**Pre-PR Evidence**", "**Feature Review Evidence**").replaceAll("**Pre-PR Decision**", "**Feature Review Decision**").replaceAll("**Pre-PR Review Round**", "**Feature Review Round**").replaceAll("**Pre-PR Reviewed Head**", "**Feature Reviewed Head**").replaceAll("**Pre-PR Reviewed Tree**", "**Feature Reviewed Tree**").replaceAll("pre-PR review handoff", "Feature review handoff");
|
|
3103
3137
|
next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
|
|
3104
3138
|
return normalizeTrailingBlankLines(next);
|
|
3105
3139
|
}
|
|
@@ -4154,6 +4188,11 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
4154
4188
|
changedPaths.push("workflow.agentReview");
|
|
4155
4189
|
}
|
|
4156
4190
|
const agentReview = workflow.agentReview;
|
|
4191
|
+
setIfMissing(agentReview, "maxRounds", 1, "workflow.agentReview.maxRounds");
|
|
4192
|
+
if (typeof agentReview.maxRounds !== "number" || !Number.isInteger(agentReview.maxRounds) || agentReview.maxRounds < 1) {
|
|
4193
|
+
agentReview.maxRounds = 1;
|
|
4194
|
+
changedPaths.push("workflow.agentReview.maxRounds");
|
|
4195
|
+
}
|
|
4157
4196
|
const normalizeAgentReviewPhase = (key, enabledDefault, legacySeed = null) => {
|
|
4158
4197
|
const phasePath = `workflow.agentReview.${key}`;
|
|
4159
4198
|
if (!isPlainObject(agentReview[key])) {
|
|
@@ -8351,6 +8390,12 @@ var PRE_PR_DECISION_LABELS = [
|
|
|
8351
8390
|
"Feature Review Decision",
|
|
8352
8391
|
"Feature \uB9AC\uBDF0 Decision"
|
|
8353
8392
|
];
|
|
8393
|
+
var PRE_PR_REVIEW_ROUND_LABELS = [
|
|
8394
|
+
"Pre-PR Review Round",
|
|
8395
|
+
"PR \uC804 \uB9AC\uBDF0 Round",
|
|
8396
|
+
"Feature Review Round",
|
|
8397
|
+
"Feature \uB9AC\uBDF0 Round"
|
|
8398
|
+
];
|
|
8354
8399
|
var PRE_PR_REVIEWED_HEAD_LABELS = [
|
|
8355
8400
|
"Pre-PR Reviewed Head",
|
|
8356
8401
|
"PR \uC804 \uB9AC\uBDF0 Head",
|
|
@@ -8374,6 +8419,11 @@ var PLAN_REVIEW_DECISION_LABELS = [
|
|
|
8374
8419
|
"Plan \uAC80\uC218 Decision",
|
|
8375
8420
|
"Plan \uB9AC\uBDF0 Decision"
|
|
8376
8421
|
];
|
|
8422
|
+
var PLAN_REVIEW_ROUND_LABELS = [
|
|
8423
|
+
"Plan Review Round",
|
|
8424
|
+
"Plan \uAC80\uC218 Round",
|
|
8425
|
+
"Plan \uB9AC\uBDF0 Round"
|
|
8426
|
+
];
|
|
8377
8427
|
var PLAN_REVIEWED_SPEC_HASH_LABELS = [
|
|
8378
8428
|
"Plan Reviewed Spec Hash",
|
|
8379
8429
|
"Plan \uAC80\uC218 Spec Hash",
|
|
@@ -8460,6 +8510,17 @@ function parseReviewDecisionOutcome(value) {
|
|
|
8460
8510
|
const match = (value || "").trim().toLowerCase().match(/\b(approve|changes_requested|blocked)\b/);
|
|
8461
8511
|
return match?.[1] || null;
|
|
8462
8512
|
}
|
|
8513
|
+
function parseReviewRound(value) {
|
|
8514
|
+
const parsed = Number((value || "").trim());
|
|
8515
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
8516
|
+
}
|
|
8517
|
+
function resolveMaxReviewRounds(config) {
|
|
8518
|
+
const configured = config.workflow?.agentReview?.maxRounds;
|
|
8519
|
+
return typeof configured === "number" && Number.isInteger(configured) && configured > 0 ? configured : 1;
|
|
8520
|
+
}
|
|
8521
|
+
function reviewRemediationLimitReached(config, reviewRound, decisionOutcome) {
|
|
8522
|
+
return decisionOutcome === "changes_requested" && (reviewRound || 1) > resolveMaxReviewRounds(config);
|
|
8523
|
+
}
|
|
8463
8524
|
function parseWorkflowTaskLine(line, index = -1) {
|
|
8464
8525
|
const canonical = parseTaskLine(line, index);
|
|
8465
8526
|
if (canonical) {
|
|
@@ -8530,6 +8591,13 @@ function parseTasksDoc(content, feature) {
|
|
|
8530
8591
|
index,
|
|
8531
8592
|
["Review Decision", "Task Review Decision", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Decision"]
|
|
8532
8593
|
);
|
|
8594
|
+
const reviewRound = parseReviewRound(
|
|
8595
|
+
extractTaskReviewValue(nonCodeLines, index, [
|
|
8596
|
+
"Review Round",
|
|
8597
|
+
"Task Review Round",
|
|
8598
|
+
"\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Round"
|
|
8599
|
+
])
|
|
8600
|
+
);
|
|
8533
8601
|
const legacyTitleKey = normalizeCommitTopicText(parsed.title).toLowerCase();
|
|
8534
8602
|
const sameTitleOccurrence = (legacyTitleOccurrences.get(legacyTitleKey) || 0) + 1;
|
|
8535
8603
|
legacyTitleOccurrences.set(legacyTitleKey, sameTitleOccurrence);
|
|
@@ -8547,6 +8615,7 @@ function parseTasksDoc(content, feature) {
|
|
|
8547
8615
|
),
|
|
8548
8616
|
reviewDecision,
|
|
8549
8617
|
reviewDecisionOutcome: parseReviewDecisionOutcome(reviewDecision),
|
|
8618
|
+
reviewRound,
|
|
8550
8619
|
reviewedHead: extractTaskReviewValue(
|
|
8551
8620
|
nonCodeLines,
|
|
8552
8621
|
index,
|
|
@@ -8602,6 +8671,9 @@ function parseTasksDoc(content, feature) {
|
|
|
8602
8671
|
),
|
|
8603
8672
|
prePrDecision: sanitizeMetadataValue2(prePrDecision),
|
|
8604
8673
|
prePrDecisionOutcome,
|
|
8674
|
+
prePrReviewRound: parseReviewRound(
|
|
8675
|
+
extractFieldValue2(content, PRE_PR_REVIEW_ROUND_LABELS)
|
|
8676
|
+
),
|
|
8605
8677
|
prePrReviewedHead: sanitizeMetadataValue2(
|
|
8606
8678
|
extractFieldValue2(content, PRE_PR_REVIEWED_HEAD_LABELS)
|
|
8607
8679
|
),
|
|
@@ -8633,6 +8705,9 @@ function parsePlanReview(content) {
|
|
|
8633
8705
|
const decision = sanitizeMetadataValue2(
|
|
8634
8706
|
extractFieldValue2(content, PLAN_REVIEW_DECISION_LABELS)
|
|
8635
8707
|
);
|
|
8708
|
+
const reviewRound = parseReviewRound(
|
|
8709
|
+
extractFieldValue2(content, PLAN_REVIEW_ROUND_LABELS)
|
|
8710
|
+
);
|
|
8636
8711
|
const reviewedSpecHash = sanitizeMetadataValue2(
|
|
8637
8712
|
extractFieldValue2(content, PLAN_REVIEWED_SPEC_HASH_LABELS)
|
|
8638
8713
|
);
|
|
@@ -8644,6 +8719,7 @@ function parsePlanReview(content) {
|
|
|
8644
8719
|
evidence,
|
|
8645
8720
|
decision,
|
|
8646
8721
|
decisionOutcome: parseReviewDecisionOutcome(decision),
|
|
8722
|
+
reviewRound,
|
|
8647
8723
|
reviewedSpecHash,
|
|
8648
8724
|
reviewedPlanHash,
|
|
8649
8725
|
hasMetadata: PLAN_REVIEW_STATUS_LABELS.some((label) => {
|
|
@@ -8669,6 +8745,7 @@ function buildPlanReviewTarget(specContent, planContent) {
|
|
|
8669
8745
|
...PLAN_REVIEW_STATUS_LABELS,
|
|
8670
8746
|
...PLAN_REVIEW_EVIDENCE_LABELS,
|
|
8671
8747
|
...PLAN_REVIEW_DECISION_LABELS,
|
|
8748
|
+
...PLAN_REVIEW_ROUND_LABELS,
|
|
8672
8749
|
...PLAN_REVIEWED_SPEC_HASH_LABELS,
|
|
8673
8750
|
...PLAN_REVIEWED_PLAN_HASH_LABELS
|
|
8674
8751
|
])
|
|
@@ -9167,16 +9244,32 @@ function reviewEvidenceSatisfied(config, feature, scope, evidence) {
|
|
|
9167
9244
|
});
|
|
9168
9245
|
}
|
|
9169
9246
|
function planReviewSatisfied(config, feature, review, target) {
|
|
9170
|
-
return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && review.decisionOutcome === "approve"
|
|
9247
|
+
return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && (review.decisionOutcome === "approve" || reviewRemediationLimitReached(
|
|
9248
|
+
config,
|
|
9249
|
+
review.reviewRound,
|
|
9250
|
+
review.decisionOutcome
|
|
9251
|
+
)) && review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9171
9252
|
}
|
|
9172
9253
|
function featureReviewSatisfied(config, feature, tasks, target) {
|
|
9173
|
-
return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve"
|
|
9254
|
+
return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && (tasks.prePrDecisionOutcome === "approve" || reviewRemediationLimitReached(
|
|
9255
|
+
config,
|
|
9256
|
+
tasks.prePrReviewRound,
|
|
9257
|
+
tasks.prePrDecisionOutcome
|
|
9258
|
+
)) && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
|
|
9174
9259
|
}
|
|
9175
9260
|
function taskReviewEvidenceSatisfied(config, feature, task, target) {
|
|
9176
|
-
return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && task.reviewDecisionOutcome === "approve"
|
|
9261
|
+
return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && (task.reviewDecisionOutcome === "approve" || reviewRemediationLimitReached(
|
|
9262
|
+
config,
|
|
9263
|
+
task.reviewRound,
|
|
9264
|
+
task.reviewDecisionOutcome
|
|
9265
|
+
)) && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
|
|
9177
9266
|
}
|
|
9178
9267
|
function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
|
|
9179
|
-
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve"
|
|
9268
|
+
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" && !reviewRemediationLimitReached(
|
|
9269
|
+
config,
|
|
9270
|
+
task.reviewRound,
|
|
9271
|
+
task.reviewDecisionOutcome
|
|
9272
|
+
) || !reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence)) {
|
|
9180
9273
|
return false;
|
|
9181
9274
|
}
|
|
9182
9275
|
const actualTree = runGitCapture(
|
|
@@ -9585,31 +9678,38 @@ function resolveFeatureSelectionError(selection) {
|
|
|
9585
9678
|
}
|
|
9586
9679
|
function resolvePlanReviewPayload(config, feature, review, target) {
|
|
9587
9680
|
const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9681
|
+
const maxReviewRounds = resolveMaxReviewRounds(config);
|
|
9682
|
+
const reviewRound = review.reviewRound || 1;
|
|
9683
|
+
const nextReviewRound = !targetMatches && review.decisionOutcome ? reviewRound + 1 : reviewRound;
|
|
9588
9684
|
const actionContext = {
|
|
9589
9685
|
reviewScope: "plan",
|
|
9686
|
+
reviewRound: nextReviewRound,
|
|
9687
|
+
maxReviewRounds,
|
|
9590
9688
|
specHash: target.specHash,
|
|
9591
9689
|
planHash: target.planHash,
|
|
9592
9690
|
docsDirectory: config.docsDir
|
|
9593
9691
|
};
|
|
9594
|
-
if (
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9692
|
+
if (review.decisionOutcome === "changes_requested") {
|
|
9693
|
+
if (targetMatches) {
|
|
9694
|
+
return {
|
|
9695
|
+
status: "ok",
|
|
9696
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9697
|
+
docsDir: config.docsDir,
|
|
9698
|
+
featureRef: buildFeatureRef(feature),
|
|
9699
|
+
stage: "plan_review_fix",
|
|
9700
|
+
nextAction: buildAction(
|
|
9701
|
+
"plan_review_fix",
|
|
9702
|
+
`Address the independent Plan review findings in spec.md or plan.md, record review round ${reviewRound + 1}, keep implementation blocked, and request a fresh review for the resulting document hashes.`,
|
|
9703
|
+
false,
|
|
9704
|
+
null,
|
|
9705
|
+
void 0,
|
|
9706
|
+
{ ...actionContext, reviewRound }
|
|
9707
|
+
),
|
|
9708
|
+
approvalRequired: false,
|
|
9709
|
+
implementationAllowed: false,
|
|
9710
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9711
|
+
};
|
|
9712
|
+
}
|
|
9613
9713
|
}
|
|
9614
9714
|
if (targetMatches && review.decisionOutcome === "blocked") {
|
|
9615
9715
|
return {
|
|
@@ -9624,7 +9724,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9624
9724
|
false,
|
|
9625
9725
|
null,
|
|
9626
9726
|
void 0,
|
|
9627
|
-
actionContext
|
|
9727
|
+
{ ...actionContext, reviewRound }
|
|
9628
9728
|
),
|
|
9629
9729
|
approvalRequired: false,
|
|
9630
9730
|
implementationAllowed: false,
|
|
@@ -9639,7 +9739,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9639
9739
|
stage: "plan_review",
|
|
9640
9740
|
nextAction: buildAction(
|
|
9641
9741
|
"plan_review",
|
|
9642
|
-
|
|
9742
|
+
`Delegate fresh read-only Plan review round ${nextReviewRound} of spec.md and plan.md. Verify the Verification Contract, NONE/UPDATE/ADD decisions, requirement coverage, independent oracles, stable observation boundaries, realistic failure/rollback cases, exclusions, and focused/full verification scope. Record Plan Review Round, status, evidence, decision, reviewer metadata, Reviewed Spec Hash, and Reviewed Plan Hash without modifying the documents.`,
|
|
9643
9743
|
false,
|
|
9644
9744
|
null,
|
|
9645
9745
|
resolveAgentReviewer(config, "plan"),
|
|
@@ -9690,6 +9790,11 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9690
9790
|
specContent || "",
|
|
9691
9791
|
planContent || ""
|
|
9692
9792
|
);
|
|
9793
|
+
const planReviewAutoCompleted = planReview.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", planReview.evidence) && reviewRemediationLimitReached(
|
|
9794
|
+
config,
|
|
9795
|
+
planReview.reviewRound,
|
|
9796
|
+
planReview.decisionOutcome
|
|
9797
|
+
) && planReview.reviewedSpecHash === planReviewTarget.specHash && planReview.reviewedPlanHash === planReviewTarget.planHash;
|
|
9693
9798
|
const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
|
|
9694
9799
|
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
9695
9800
|
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
@@ -9734,7 +9839,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9734
9839
|
planReviewTarget
|
|
9735
9840
|
);
|
|
9736
9841
|
}
|
|
9737
|
-
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
|
|
9842
|
+
const approvalRequired = isReviewStage && !planReviewAutoCompleted ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
|
|
9738
9843
|
const actionOptions = approvalRequired ? buildApprovalActionOptions({
|
|
9739
9844
|
approveSummary: "Approve plan.md and continue to the tasks stage.",
|
|
9740
9845
|
holdSummary: "Request plan changes before continuing."
|
|
@@ -9747,7 +9852,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9747
9852
|
stage: "plan",
|
|
9748
9853
|
nextAction: buildAction(
|
|
9749
9854
|
isReviewStage ? "plan_approve" : "plan_write",
|
|
9750
|
-
isReviewStage ? approvalRequired ? "Get user approval and update plan.md status to Approved." : "Promote plan.md from Review to Approved and continue automatically." : "Write or refine plan.md until it is ready for approval.",
|
|
9855
|
+
isReviewStage ? planReviewAutoCompleted ? "The Plan review remediation limit was reached. Preserve the remaining findings as residual risks, promote plan.md from Review to Approved, and continue automatically without asking the user for review approval." : approvalRequired ? "Get user approval and update plan.md status to Approved." : "Promote plan.md from Review to Approved and continue automatically." : "Write or refine plan.md until it is ready for approval.",
|
|
9751
9856
|
approvalRequired
|
|
9752
9857
|
),
|
|
9753
9858
|
approvalRequired,
|
|
@@ -9927,29 +10032,33 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9927
10032
|
}
|
|
9928
10033
|
const reviewContext = {
|
|
9929
10034
|
reviewScope: "task",
|
|
10035
|
+
reviewRound: reviewTask.reviewRound || 1,
|
|
10036
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
9930
10037
|
...reviewTask.taskId ? { taskId: reviewTask.taskId } : {},
|
|
9931
10038
|
...reviewTarget
|
|
9932
10039
|
};
|
|
9933
10040
|
const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
|
|
9934
|
-
if (
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
10041
|
+
if (reviewTask.reviewDecisionOutcome === "changes_requested") {
|
|
10042
|
+
if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
|
|
10043
|
+
return {
|
|
10044
|
+
status: "ok",
|
|
10045
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10046
|
+
docsDir: config.docsDir,
|
|
10047
|
+
featureRef: buildFeatureRef(feature),
|
|
10048
|
+
stage: "task_review_fix",
|
|
10049
|
+
nextAction: buildAction(
|
|
10050
|
+
"task_review_fix",
|
|
10051
|
+
`Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, commit the new code tip, and request fresh review round ${reviewContext.reviewRound + 1}.`,
|
|
10052
|
+
false,
|
|
10053
|
+
null,
|
|
10054
|
+
void 0,
|
|
10055
|
+
reviewContext
|
|
10056
|
+
),
|
|
10057
|
+
approvalRequired: false,
|
|
10058
|
+
implementationAllowed: true,
|
|
10059
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10060
|
+
};
|
|
10061
|
+
}
|
|
9953
10062
|
}
|
|
9954
10063
|
if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "blocked") {
|
|
9955
10064
|
return {
|
|
@@ -9972,7 +10081,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9972
10081
|
};
|
|
9973
10082
|
}
|
|
9974
10083
|
if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
|
|
9975
|
-
const completionSummary = reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.`;
|
|
10084
|
+
const completionSummary = reviewTask.reviewDecisionOutcome === "approve" ? reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.` : reviewTask.status === "DONE" ? `The review remediation limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings as residual risks and commit the review evidence before continuing automatically.` : `The review remediation limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings as residual risks, mark the task DONE, and commit the review evidence before continuing automatically.`;
|
|
9976
10085
|
return {
|
|
9977
10086
|
status: "ok",
|
|
9978
10087
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10000,11 +10109,14 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10000
10109
|
stage: "task_review",
|
|
10001
10110
|
nextAction: buildAction(
|
|
10002
10111
|
"task_review",
|
|
10003
|
-
`Delegate
|
|
10112
|
+
`Delegate fresh read-only review round ${evidenceMatchesTarget ? reviewContext.reviewRound : reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound} of ${reviewTask.taskId || reviewTask.title} for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, evidence, decision, reviewer metadata, Reviewed Head, and Reviewed Tree without modifying code.`,
|
|
10004
10113
|
false,
|
|
10005
10114
|
null,
|
|
10006
10115
|
resolveAgentReviewer(config, "task"),
|
|
10007
|
-
|
|
10116
|
+
{
|
|
10117
|
+
...reviewContext,
|
|
10118
|
+
reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound
|
|
10119
|
+
}
|
|
10008
10120
|
),
|
|
10009
10121
|
approvalRequired: false,
|
|
10010
10122
|
implementationAllowed: false,
|
|
@@ -10104,27 +10216,34 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10104
10216
|
effectiveProjectGitCwd,
|
|
10105
10217
|
"feature"
|
|
10106
10218
|
);
|
|
10107
|
-
const reviewContext = reviewTarget ? {
|
|
10219
|
+
const reviewContext = reviewTarget ? {
|
|
10220
|
+
reviewScope: "feature",
|
|
10221
|
+
reviewRound: tasks.prePrReviewRound || 1,
|
|
10222
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
10223
|
+
...reviewTarget
|
|
10224
|
+
} : null;
|
|
10108
10225
|
const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
|
|
10109
|
-
if (reviewContext &&
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10226
|
+
if (reviewContext && tasks.prePrDecisionOutcome === "changes_requested") {
|
|
10227
|
+
if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
|
|
10228
|
+
return {
|
|
10229
|
+
status: "ok",
|
|
10230
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10231
|
+
docsDir: config.docsDir,
|
|
10232
|
+
featureRef: buildFeatureRef(feature),
|
|
10233
|
+
stage: "feature_review_fix",
|
|
10234
|
+
nextAction: buildAction(
|
|
10235
|
+
"feature_review_fix",
|
|
10236
|
+
`Address the Feature review findings, commit the remediation, record review round ${reviewContext.reviewRound + 1}, and request a fresh review of the new code tree before implementation approval.`,
|
|
10237
|
+
false,
|
|
10238
|
+
null,
|
|
10239
|
+
void 0,
|
|
10240
|
+
reviewContext
|
|
10241
|
+
),
|
|
10242
|
+
approvalRequired: false,
|
|
10243
|
+
implementationAllowed: true,
|
|
10244
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10245
|
+
};
|
|
10246
|
+
}
|
|
10128
10247
|
}
|
|
10129
10248
|
if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "blocked") {
|
|
10130
10249
|
return {
|
|
@@ -10147,6 +10266,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10147
10266
|
};
|
|
10148
10267
|
}
|
|
10149
10268
|
if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
|
|
10269
|
+
const requestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
|
|
10150
10270
|
return {
|
|
10151
10271
|
status: "ok",
|
|
10152
10272
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10155,11 +10275,11 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10155
10275
|
stage: "pre_pr_review",
|
|
10156
10276
|
nextAction: buildAction(
|
|
10157
10277
|
"pre_pr_review",
|
|
10158
|
-
reviewTarget ? `Delegate
|
|
10278
|
+
reviewTarget ? `Delegate independent read-only Feature review round ${requestedRound} to a fresh subagent for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, findings, decision, reviewer metadata, Pre-PR Reviewed Head, and Pre-PR Reviewed Tree as evidence.` : "Create a project commit before requesting the independent Feature review.",
|
|
10159
10279
|
false,
|
|
10160
10280
|
null,
|
|
10161
10281
|
reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10162
|
-
reviewContext
|
|
10282
|
+
reviewContext ? { ...reviewContext, reviewRound: requestedRound } : void 0
|
|
10163
10283
|
),
|
|
10164
10284
|
approvalRequired: false,
|
|
10165
10285
|
implementationAllowed: false,
|