lee-spec-kit 0.9.4 → 0.9.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/dist/index.js +247 -71
- 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": "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD310\uB2E8\uC744 \uC694\uCCAD\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 review finding remediation rounds before asking the user:",
|
|
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. When workflow-stage returns nextAction.category review_escalation, those passes have been exhausted; do not remediate or delegate another review, keep implementation blocked, and present the exact returned action options to the user. Another remediation pass requires increasing maxRounds first
|
|
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 user escalation (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,14 @@ 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
|
+
}
|
|
8463
8521
|
function parseWorkflowTaskLine(line, index = -1) {
|
|
8464
8522
|
const canonical = parseTaskLine(line, index);
|
|
8465
8523
|
if (canonical) {
|
|
@@ -8530,6 +8588,13 @@ function parseTasksDoc(content, feature) {
|
|
|
8530
8588
|
index,
|
|
8531
8589
|
["Review Decision", "Task Review Decision", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Decision"]
|
|
8532
8590
|
);
|
|
8591
|
+
const reviewRound = parseReviewRound(
|
|
8592
|
+
extractTaskReviewValue(nonCodeLines, index, [
|
|
8593
|
+
"Review Round",
|
|
8594
|
+
"Task Review Round",
|
|
8595
|
+
"\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Round"
|
|
8596
|
+
])
|
|
8597
|
+
);
|
|
8533
8598
|
const legacyTitleKey = normalizeCommitTopicText(parsed.title).toLowerCase();
|
|
8534
8599
|
const sameTitleOccurrence = (legacyTitleOccurrences.get(legacyTitleKey) || 0) + 1;
|
|
8535
8600
|
legacyTitleOccurrences.set(legacyTitleKey, sameTitleOccurrence);
|
|
@@ -8547,6 +8612,7 @@ function parseTasksDoc(content, feature) {
|
|
|
8547
8612
|
),
|
|
8548
8613
|
reviewDecision,
|
|
8549
8614
|
reviewDecisionOutcome: parseReviewDecisionOutcome(reviewDecision),
|
|
8615
|
+
reviewRound,
|
|
8550
8616
|
reviewedHead: extractTaskReviewValue(
|
|
8551
8617
|
nonCodeLines,
|
|
8552
8618
|
index,
|
|
@@ -8602,6 +8668,9 @@ function parseTasksDoc(content, feature) {
|
|
|
8602
8668
|
),
|
|
8603
8669
|
prePrDecision: sanitizeMetadataValue2(prePrDecision),
|
|
8604
8670
|
prePrDecisionOutcome,
|
|
8671
|
+
prePrReviewRound: parseReviewRound(
|
|
8672
|
+
extractFieldValue2(content, PRE_PR_REVIEW_ROUND_LABELS)
|
|
8673
|
+
),
|
|
8605
8674
|
prePrReviewedHead: sanitizeMetadataValue2(
|
|
8606
8675
|
extractFieldValue2(content, PRE_PR_REVIEWED_HEAD_LABELS)
|
|
8607
8676
|
),
|
|
@@ -8633,6 +8702,9 @@ function parsePlanReview(content) {
|
|
|
8633
8702
|
const decision = sanitizeMetadataValue2(
|
|
8634
8703
|
extractFieldValue2(content, PLAN_REVIEW_DECISION_LABELS)
|
|
8635
8704
|
);
|
|
8705
|
+
const reviewRound = parseReviewRound(
|
|
8706
|
+
extractFieldValue2(content, PLAN_REVIEW_ROUND_LABELS)
|
|
8707
|
+
);
|
|
8636
8708
|
const reviewedSpecHash = sanitizeMetadataValue2(
|
|
8637
8709
|
extractFieldValue2(content, PLAN_REVIEWED_SPEC_HASH_LABELS)
|
|
8638
8710
|
);
|
|
@@ -8644,6 +8716,7 @@ function parsePlanReview(content) {
|
|
|
8644
8716
|
evidence,
|
|
8645
8717
|
decision,
|
|
8646
8718
|
decisionOutcome: parseReviewDecisionOutcome(decision),
|
|
8719
|
+
reviewRound,
|
|
8647
8720
|
reviewedSpecHash,
|
|
8648
8721
|
reviewedPlanHash,
|
|
8649
8722
|
hasMetadata: PLAN_REVIEW_STATUS_LABELS.some((label) => {
|
|
@@ -8669,6 +8742,7 @@ function buildPlanReviewTarget(specContent, planContent) {
|
|
|
8669
8742
|
...PLAN_REVIEW_STATUS_LABELS,
|
|
8670
8743
|
...PLAN_REVIEW_EVIDENCE_LABELS,
|
|
8671
8744
|
...PLAN_REVIEW_DECISION_LABELS,
|
|
8745
|
+
...PLAN_REVIEW_ROUND_LABELS,
|
|
8672
8746
|
...PLAN_REVIEWED_SPEC_HASH_LABELS,
|
|
8673
8747
|
...PLAN_REVIEWED_PLAN_HASH_LABELS
|
|
8674
8748
|
])
|
|
@@ -9585,31 +9659,68 @@ function resolveFeatureSelectionError(selection) {
|
|
|
9585
9659
|
}
|
|
9586
9660
|
function resolvePlanReviewPayload(config, feature, review, target) {
|
|
9587
9661
|
const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9662
|
+
const maxReviewRounds = resolveMaxReviewRounds(config);
|
|
9663
|
+
const reviewRound = review.reviewRound || 1;
|
|
9664
|
+
const nextReviewRound = !targetMatches && review.decisionOutcome ? reviewRound + 1 : reviewRound;
|
|
9588
9665
|
const actionContext = {
|
|
9589
9666
|
reviewScope: "plan",
|
|
9667
|
+
reviewRound: nextReviewRound,
|
|
9668
|
+
maxReviewRounds,
|
|
9590
9669
|
specHash: target.specHash,
|
|
9591
9670
|
planHash: target.planHash,
|
|
9592
9671
|
docsDirectory: config.docsDir
|
|
9593
9672
|
};
|
|
9594
|
-
if (
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9673
|
+
if (review.decisionOutcome === "changes_requested") {
|
|
9674
|
+
if (reviewRound > maxReviewRounds) {
|
|
9675
|
+
return {
|
|
9676
|
+
status: "ok",
|
|
9677
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9678
|
+
docsDir: config.docsDir,
|
|
9679
|
+
featureRef: buildFeatureRef(feature),
|
|
9680
|
+
stage: "review_escalation",
|
|
9681
|
+
nextAction: buildAction(
|
|
9682
|
+
"review_escalation",
|
|
9683
|
+
`Plan review requested more changes after ${maxReviewRounds} automatic remediation round(s) were used. Ask the user whether to revise again, request a different reviewer, accept the documented risk, change scope, or hold the Feature.`,
|
|
9684
|
+
true,
|
|
9685
|
+
null,
|
|
9686
|
+
void 0,
|
|
9687
|
+
{ ...actionContext, reviewRound }
|
|
9688
|
+
),
|
|
9689
|
+
approvalRequired: true,
|
|
9690
|
+
implementationAllowed: false,
|
|
9691
|
+
primaryActionLabel: "A",
|
|
9692
|
+
actionOptions: [
|
|
9693
|
+
buildStageOption(
|
|
9694
|
+
"A",
|
|
9695
|
+
"A",
|
|
9696
|
+
"review_retry",
|
|
9697
|
+
"Increase workflow.agentReview.maxRounds, then revise the Plan and run one more fresh review round."
|
|
9698
|
+
),
|
|
9699
|
+
buildStageOption("B", "B", "hold", "Hold the Feature for user resolution.")
|
|
9700
|
+
],
|
|
9701
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9702
|
+
};
|
|
9703
|
+
}
|
|
9704
|
+
if (targetMatches) {
|
|
9705
|
+
return {
|
|
9706
|
+
status: "ok",
|
|
9707
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9708
|
+
docsDir: config.docsDir,
|
|
9709
|
+
featureRef: buildFeatureRef(feature),
|
|
9710
|
+
stage: "plan_review_fix",
|
|
9711
|
+
nextAction: buildAction(
|
|
9712
|
+
"plan_review_fix",
|
|
9713
|
+
`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.`,
|
|
9714
|
+
false,
|
|
9715
|
+
null,
|
|
9716
|
+
void 0,
|
|
9717
|
+
{ ...actionContext, reviewRound }
|
|
9718
|
+
),
|
|
9719
|
+
approvalRequired: false,
|
|
9720
|
+
implementationAllowed: false,
|
|
9721
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9722
|
+
};
|
|
9723
|
+
}
|
|
9613
9724
|
}
|
|
9614
9725
|
if (targetMatches && review.decisionOutcome === "blocked") {
|
|
9615
9726
|
return {
|
|
@@ -9624,7 +9735,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9624
9735
|
false,
|
|
9625
9736
|
null,
|
|
9626
9737
|
void 0,
|
|
9627
|
-
actionContext
|
|
9738
|
+
{ ...actionContext, reviewRound }
|
|
9628
9739
|
),
|
|
9629
9740
|
approvalRequired: false,
|
|
9630
9741
|
implementationAllowed: false,
|
|
@@ -9639,7 +9750,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9639
9750
|
stage: "plan_review",
|
|
9640
9751
|
nextAction: buildAction(
|
|
9641
9752
|
"plan_review",
|
|
9642
|
-
|
|
9753
|
+
`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
9754
|
false,
|
|
9644
9755
|
null,
|
|
9645
9756
|
resolveAgentReviewer(config, "plan"),
|
|
@@ -9927,29 +10038,58 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9927
10038
|
}
|
|
9928
10039
|
const reviewContext = {
|
|
9929
10040
|
reviewScope: "task",
|
|
10041
|
+
reviewRound: reviewTask.reviewRound || 1,
|
|
10042
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
9930
10043
|
...reviewTask.taskId ? { taskId: reviewTask.taskId } : {},
|
|
9931
10044
|
...reviewTarget
|
|
9932
10045
|
};
|
|
9933
10046
|
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
|
-
|
|
10047
|
+
if (reviewTask.reviewDecisionOutcome === "changes_requested") {
|
|
10048
|
+
if (reviewContext.reviewRound > reviewContext.maxReviewRounds) {
|
|
10049
|
+
return {
|
|
10050
|
+
status: "ok",
|
|
10051
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10052
|
+
docsDir: config.docsDir,
|
|
10053
|
+
featureRef: buildFeatureRef(feature),
|
|
10054
|
+
stage: "review_escalation",
|
|
10055
|
+
nextAction: buildAction(
|
|
10056
|
+
"review_escalation",
|
|
10057
|
+
`Task review for ${reviewTask.taskId || reviewTask.title} requested more changes after ${reviewContext.maxReviewRounds} automatic remediation round(s) were used. Ask the user whether to authorize another remediation round or hold the Feature.`,
|
|
10058
|
+
true,
|
|
10059
|
+
null,
|
|
10060
|
+
void 0,
|
|
10061
|
+
reviewContext
|
|
10062
|
+
),
|
|
10063
|
+
approvalRequired: true,
|
|
10064
|
+
implementationAllowed: false,
|
|
10065
|
+
primaryActionLabel: "A",
|
|
10066
|
+
actionOptions: [
|
|
10067
|
+
buildStageOption("A", "A", "review_retry", "Increase workflow.agentReview.maxRounds, then run one more task remediation and fresh review round."),
|
|
10068
|
+
buildStageOption("B", "B", "hold", "Hold the Feature for user resolution.")
|
|
10069
|
+
],
|
|
10070
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10071
|
+
};
|
|
10072
|
+
}
|
|
10073
|
+
if (evidenceMatchesTarget) {
|
|
10074
|
+
return {
|
|
10075
|
+
status: "ok",
|
|
10076
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10077
|
+
docsDir: config.docsDir,
|
|
10078
|
+
featureRef: buildFeatureRef(feature),
|
|
10079
|
+
stage: "task_review_fix",
|
|
10080
|
+
nextAction: buildAction(
|
|
10081
|
+
"task_review_fix",
|
|
10082
|
+
`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}.`,
|
|
10083
|
+
false,
|
|
10084
|
+
null,
|
|
10085
|
+
void 0,
|
|
10086
|
+
reviewContext
|
|
10087
|
+
),
|
|
10088
|
+
approvalRequired: false,
|
|
10089
|
+
implementationAllowed: true,
|
|
10090
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10091
|
+
};
|
|
10092
|
+
}
|
|
9953
10093
|
}
|
|
9954
10094
|
if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "blocked") {
|
|
9955
10095
|
return {
|
|
@@ -10000,11 +10140,14 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10000
10140
|
stage: "task_review",
|
|
10001
10141
|
nextAction: buildAction(
|
|
10002
10142
|
"task_review",
|
|
10003
|
-
`Delegate
|
|
10143
|
+
`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
10144
|
false,
|
|
10005
10145
|
null,
|
|
10006
10146
|
resolveAgentReviewer(config, "task"),
|
|
10007
|
-
|
|
10147
|
+
{
|
|
10148
|
+
...reviewContext,
|
|
10149
|
+
reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound
|
|
10150
|
+
}
|
|
10008
10151
|
),
|
|
10009
10152
|
approvalRequired: false,
|
|
10010
10153
|
implementationAllowed: false,
|
|
@@ -10104,27 +10247,59 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10104
10247
|
effectiveProjectGitCwd,
|
|
10105
10248
|
"feature"
|
|
10106
10249
|
);
|
|
10107
|
-
const reviewContext = reviewTarget ? {
|
|
10250
|
+
const reviewContext = reviewTarget ? {
|
|
10251
|
+
reviewScope: "feature",
|
|
10252
|
+
reviewRound: tasks.prePrReviewRound || 1,
|
|
10253
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
10254
|
+
...reviewTarget
|
|
10255
|
+
} : null;
|
|
10108
10256
|
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
|
-
|
|
10257
|
+
if (reviewContext && tasks.prePrDecisionOutcome === "changes_requested") {
|
|
10258
|
+
if (reviewContext.reviewRound > reviewContext.maxReviewRounds) {
|
|
10259
|
+
return {
|
|
10260
|
+
status: "ok",
|
|
10261
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10262
|
+
docsDir: config.docsDir,
|
|
10263
|
+
featureRef: buildFeatureRef(feature),
|
|
10264
|
+
stage: "review_escalation",
|
|
10265
|
+
nextAction: buildAction(
|
|
10266
|
+
"review_escalation",
|
|
10267
|
+
`Feature review requested more changes after ${reviewContext.maxReviewRounds} automatic remediation round(s) were used. Ask the user whether to authorize another remediation round, accept the documented risk, change scope, or hold the Feature.`,
|
|
10268
|
+
true,
|
|
10269
|
+
null,
|
|
10270
|
+
void 0,
|
|
10271
|
+
reviewContext
|
|
10272
|
+
),
|
|
10273
|
+
approvalRequired: true,
|
|
10274
|
+
implementationAllowed: false,
|
|
10275
|
+
primaryActionLabel: "A",
|
|
10276
|
+
actionOptions: [
|
|
10277
|
+
buildStageOption("A", "A", "review_retry", "Increase workflow.agentReview.maxRounds, then run one more Feature remediation and fresh review round."),
|
|
10278
|
+
buildStageOption("B", "B", "hold", "Hold the Feature for user resolution.")
|
|
10279
|
+
],
|
|
10280
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10281
|
+
};
|
|
10282
|
+
}
|
|
10283
|
+
if (evidenceMatchesTarget) {
|
|
10284
|
+
return {
|
|
10285
|
+
status: "ok",
|
|
10286
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10287
|
+
docsDir: config.docsDir,
|
|
10288
|
+
featureRef: buildFeatureRef(feature),
|
|
10289
|
+
stage: "feature_review_fix",
|
|
10290
|
+
nextAction: buildAction(
|
|
10291
|
+
"feature_review_fix",
|
|
10292
|
+
`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.`,
|
|
10293
|
+
false,
|
|
10294
|
+
null,
|
|
10295
|
+
void 0,
|
|
10296
|
+
reviewContext
|
|
10297
|
+
),
|
|
10298
|
+
approvalRequired: false,
|
|
10299
|
+
implementationAllowed: true,
|
|
10300
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10301
|
+
};
|
|
10302
|
+
}
|
|
10128
10303
|
}
|
|
10129
10304
|
if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "blocked") {
|
|
10130
10305
|
return {
|
|
@@ -10147,6 +10322,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10147
10322
|
};
|
|
10148
10323
|
}
|
|
10149
10324
|
if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
|
|
10325
|
+
const requestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
|
|
10150
10326
|
return {
|
|
10151
10327
|
status: "ok",
|
|
10152
10328
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10155,11 +10331,11 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10155
10331
|
stage: "pre_pr_review",
|
|
10156
10332
|
nextAction: buildAction(
|
|
10157
10333
|
"pre_pr_review",
|
|
10158
|
-
reviewTarget ? `Delegate
|
|
10334
|
+
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
10335
|
false,
|
|
10160
10336
|
null,
|
|
10161
10337
|
reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10162
|
-
reviewContext
|
|
10338
|
+
reviewContext ? { ...reviewContext, reviewRound: requestedRound } : void 0
|
|
10163
10339
|
),
|
|
10164
10340
|
approvalRequired: false,
|
|
10165
10341
|
implementationAllowed: false,
|