lee-spec-kit 0.9.6 → 0.9.8
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 +520 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +17 -5
- package/templates/en/common/agents/agents.md +4 -1
- package/templates/en/common/agents/skills/create-feature.md +2 -2
- package/templates/en/common/agents/skills/create-pr.md +2 -2
- package/templates/en/common/agents/skills/execute-task.md +4 -2
- package/templates/en/common/features/feature-base/tasks.md +1 -1
- package/templates/ko/common/README.md +17 -5
- package/templates/ko/common/agents/agents.md +4 -1
- package/templates/ko/common/agents/skills/create-feature.md +2 -2
- package/templates/ko/common/agents/skills/create-pr.md +2 -2
- package/templates/ko/common/agents/skills/execute-task.md +4 -2
- package/templates/ko/common/features/feature-base/tasks.md +1 -1
package/dist/index.js
CHANGED
|
@@ -167,6 +167,7 @@ var koCli = {
|
|
|
167
167
|
"config.fullstackRepoRequired": "Multi \uD504\uB85C\uC81D\uD2B8\uB294 --component\uB85C \uB300\uC0C1 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
168
168
|
"config.projectRootSet": "\u2705 {repo} projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
|
|
169
169
|
"config.projectRootSetSingle": "\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
|
|
170
|
+
"config.workflowUpdated": "\u2705 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815\uC744 \uC5C5\uB370\uC774\uD2B8\uD588\uC2B5\uB2C8\uB2E4.",
|
|
170
171
|
"update.start": "\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...",
|
|
171
172
|
"update.langLabel": "\uC5B8\uC5B4",
|
|
172
173
|
"update.typeLabel": "\uD0C0\uC785",
|
|
@@ -179,6 +180,7 @@ var koCli = {
|
|
|
179
180
|
"update.fileUpdated": "{file} \uC5C5\uB370\uC774\uD2B8",
|
|
180
181
|
"update.gitStatusUnavailable": "git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (git repo\uAC00 \uC544\uB2C8\uAC70\uB098 git \uC2E4\uD589 \uBD88\uAC00) --force\uB85C \uAC15\uC81C \uB36E\uC5B4\uC4F0\uAE30\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
181
182
|
"update.docsWorktreeDirty": "docs \uC791\uC5C5\uD2B8\uB9AC\uC5D0 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC5B4 update\uB97C \uC9C4\uD589\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBCC0\uACBD\uC0AC\uD56D\uC744 \uCEE4\uBC0B/\uC2A4\uD0DC\uC2DC \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uAC70\uB098 --force\uB85C \uB36E\uC5B4\uC4F0\uC138\uC694.",
|
|
183
|
+
"update.legacyAutomationSafe": "\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8\uB294 \uBA85\uC2DC\uC801\uC73C\uB85C \uC0AC\uC6A9\uD55C \uACBD\uC6B0\uAC00 \uC544\uB2C8\uBA74 \uC0C8 Task \uC704\uC784\uACFC Plan/Task \uAC80\uC218\uB97C \uAEBC\uC9C4 \uC0C1\uD0DC\uB85C \uC720\uC9C0\uD569\uB2C8\uB2E4. `npx lee-spec-kit config --interactive` \uB610\uB294 config \uD50C\uB798\uADF8\uB85C \uC124\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
182
184
|
"init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
183
185
|
"init.currentDirectoryLabel": "\u{1F4CD} \uD604\uC7AC \uC704\uCE58",
|
|
184
186
|
"init.gitDetected": "\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428",
|
|
@@ -213,7 +215,7 @@ var koCli = {
|
|
|
213
215
|
"init.choice.taskAgent.on": "\uC0AC\uC6A9 - \uAC01 Task \uAD6C\uD604\uC744 \uC704\uC784",
|
|
214
216
|
"init.choice.taskAgent.off": "\uC0AC\uC6A9 \uC548 \uD568 - \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC9C1\uC811 \uAD6C\uD604",
|
|
215
217
|
"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 \
|
|
218
|
+
"init.prompt.maxReviewRounds": "\uC794\uC5EC \uC704\uD5D8\uC744 \uAE30\uB85D\uD558\uACE0 \uACC4\uC18D\uD558\uAE30 \uC804 fresh \uB9AC\uBDF0 \uCD5C\uB300 \uC2E4\uD589 \uD69F\uC218:",
|
|
217
219
|
"init.choice.review.plan": "Plan",
|
|
218
220
|
"init.choice.review.task": "Task",
|
|
219
221
|
"init.choice.review.feature": "Feature",
|
|
@@ -239,7 +241,7 @@ var koCli = {
|
|
|
239
241
|
"init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
240
242
|
"init.log.taskAgentLabel": "Task \uAD6C\uD604",
|
|
241
243
|
"init.log.reviewsLabel": "\uAC80\uC218",
|
|
242
|
-
"init.log.maxReviewRoundsLabel": "\uB9AC\uBDF0 \
|
|
244
|
+
"init.log.maxReviewRoundsLabel": "fresh \uB9AC\uBDF0 \uCD5C\uB300 \uC2E4\uD589 \uD69F\uC218",
|
|
243
245
|
"init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
|
|
244
246
|
"init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
|
|
245
247
|
"init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
|
|
@@ -481,6 +483,7 @@ var enCli = {
|
|
|
481
483
|
"config.fullstackRepoRequired": "For multi projects, specify a target component via `--component`.",
|
|
482
484
|
"config.projectRootSet": "\u2705 {repo} projectRoot set: {path}",
|
|
483
485
|
"config.projectRootSetSingle": "\u2705 projectRoot set: {path}",
|
|
486
|
+
"config.workflowUpdated": "\u2705 Workflow configuration updated.",
|
|
484
487
|
"update.start": "\u{1F4E6} Starting template update...",
|
|
485
488
|
"update.langLabel": "Lang",
|
|
486
489
|
"update.typeLabel": "Type",
|
|
@@ -493,6 +496,7 @@ var enCli = {
|
|
|
493
496
|
"update.fileUpdated": "{file} updated",
|
|
494
497
|
"update.gitStatusUnavailable": "Cannot determine git status (not a git repo or git unavailable). Use --force to overwrite.",
|
|
495
498
|
"update.docsWorktreeDirty": "Docs working tree has changes. Commit/stash your changes, or run with --force to overwrite.",
|
|
499
|
+
"update.legacyAutomationSafe": "Existing projects keep newly introduced task delegation and Plan/Task review disabled unless they had explicitly opted in. Configure them with `npx lee-spec-kit config --interactive` or the config flags.",
|
|
496
500
|
"init.selectLangPrompt": "Select docs language:",
|
|
497
501
|
"init.currentDirectoryLabel": "\u{1F4CD} Current directory",
|
|
498
502
|
"init.gitDetected": "\u2705 Git repository detected",
|
|
@@ -527,7 +531,7 @@ var enCli = {
|
|
|
527
531
|
"init.choice.taskAgent.on": "On - delegate each task implementation",
|
|
528
532
|
"init.choice.taskAgent.off": "Off - implement tasks in the main agent",
|
|
529
533
|
"init.prompt.reviews": "Select independent review gates:",
|
|
530
|
-
"init.prompt.maxReviewRounds": "Maximum
|
|
534
|
+
"init.prompt.maxReviewRounds": "Maximum fresh review rounds before continuing with residual risks:",
|
|
531
535
|
"init.choice.review.plan": "Plan",
|
|
532
536
|
"init.choice.review.task": "Task",
|
|
533
537
|
"init.choice.review.feature": "Feature",
|
|
@@ -553,7 +557,7 @@ var enCli = {
|
|
|
553
557
|
"init.log.workflowLabel": "Workflow",
|
|
554
558
|
"init.log.taskAgentLabel": "Task implementation",
|
|
555
559
|
"init.log.reviewsLabel": "Reviews",
|
|
556
|
-
"init.log.maxReviewRoundsLabel": "
|
|
560
|
+
"init.log.maxReviewRoundsLabel": "Maximum fresh review rounds",
|
|
557
561
|
"init.log.completionStrategyLabel": "Local integration",
|
|
558
562
|
"init.summary.taskAgent.on": "subagent",
|
|
559
563
|
"init.summary.taskAgent.off": "main agent",
|
|
@@ -1371,12 +1375,15 @@ Before taking the next workflow step:
|
|
|
1371
1375
|
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
|
|
1372
1376
|
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
|
|
1373
1377
|
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
|
|
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\`
|
|
1378
|
+
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\` after an approve decision or the automatic exhausted-\`changes_requested\` path described below
|
|
1375
1379
|
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
|
|
1376
1380
|
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
|
|
1377
|
-
19.
|
|
1378
|
-
20.
|
|
1379
|
-
21.
|
|
1381
|
+
19. After delegating to a subagent, wait until it returns a terminal outcome: completed, explicit failure, cancellation, or an approval/user-input request that requires action
|
|
1382
|
+
20. While the subagent remains running, use repeated bounded waits, preferably longer waits. A bounded wait that returns no update, a lack of status messages, or a lack of file changes means only that the subagent is still pending; none is evidence of failure or stalled work. Read-only review subagents are expected not to modify files
|
|
1383
|
+
21. Do not interrupt, replace, or abandon a running subagent solely because it has been quiet or has not changed files. Stop it only after an explicit user request, a terminal failure/cancellation, or an unrecoverable runtime status
|
|
1384
|
+
22. workflow.agentReview.maxRounds is the maximum number of fresh reviews for each Plan/task/Feature gate. A changes_requested decision on the final allowed review is remediated once, but the changed target is not reviewed again; keep remaining findings and the post-review target change as residual risks and automatically complete the gate without asking the user for a review-approval token. For example, maxRounds=1 means review round 1, remediate once, then continue with no round 2; blocked decisions never auto-complete
|
|
1385
|
+
23. 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
|
|
1386
|
+
24. 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
|
|
1380
1387
|
|
|
1381
1388
|
Approval and remote actions:
|
|
1382
1389
|
|
|
@@ -1696,6 +1703,7 @@ function toProjectConfig(docsDir, configFile, projectType, components) {
|
|
|
1696
1703
|
return {
|
|
1697
1704
|
schemaId: "lee-spec",
|
|
1698
1705
|
docsDir,
|
|
1706
|
+
createdAt: configFile.createdAt,
|
|
1699
1707
|
projectName: configFile.projectName,
|
|
1700
1708
|
projectType,
|
|
1701
1709
|
components: projectType === "multi" ? components : void 0,
|
|
@@ -2131,7 +2139,7 @@ function initCommand(program2) {
|
|
|
2131
2139
|
"Review gates: comma-separated plan,task,feature | none"
|
|
2132
2140
|
).option(
|
|
2133
2141
|
"--max-review-rounds <count>",
|
|
2134
|
-
"
|
|
2142
|
+
"Maximum fresh review rounds before continuing with residual risks (default: 1)"
|
|
2135
2143
|
).option(
|
|
2136
2144
|
"--completion-strategy <strategy>",
|
|
2137
2145
|
"Local completion: local-ff | local-squash | none"
|
|
@@ -2865,6 +2873,7 @@ async function runInit(options) {
|
|
|
2865
2873
|
featureChecks: [],
|
|
2866
2874
|
postMergeChecks: []
|
|
2867
2875
|
} : {},
|
|
2876
|
+
agentAutomationConfigured: true,
|
|
2868
2877
|
agentExecution: {
|
|
2869
2878
|
task: {
|
|
2870
2879
|
...createDefaultAgentExecutionTaskConfig(),
|
|
@@ -3816,6 +3825,50 @@ function migrateLegacyApprovalSettings(approval) {
|
|
|
3816
3825
|
return { approval, changedPaths };
|
|
3817
3826
|
}
|
|
3818
3827
|
|
|
3828
|
+
// src/config/agent-automation.ts
|
|
3829
|
+
var AGENT_AUTOMATION_OPT_IN_INIT_DATE = "2026-08-27";
|
|
3830
|
+
function isPlainRecord2(value) {
|
|
3831
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3832
|
+
}
|
|
3833
|
+
function matchesGeneratedAgentRuntime(value, options = {}) {
|
|
3834
|
+
if (!isPlainRecord2(value)) return false;
|
|
3835
|
+
const expectedKeys = [
|
|
3836
|
+
...typeof options.enabled === "boolean" ? ["enabled"] : [],
|
|
3837
|
+
"model",
|
|
3838
|
+
"onUnavailable",
|
|
3839
|
+
"reasoningEffort",
|
|
3840
|
+
"type"
|
|
3841
|
+
].sort();
|
|
3842
|
+
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expectedKeys)) {
|
|
3843
|
+
return false;
|
|
3844
|
+
}
|
|
3845
|
+
return (typeof options.enabled !== "boolean" || value.enabled === options.enabled) && value.type === "subagent" && value.model === "inherit" && value.reasoningEffort === "high" && value.onUnavailable === "inherit";
|
|
3846
|
+
}
|
|
3847
|
+
function matchesGeneratedReviewPhase(value, enabled) {
|
|
3848
|
+
if (!isPlainRecord2(value)) return false;
|
|
3849
|
+
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(["enabled", "evidenceMode", "reviewer"])) {
|
|
3850
|
+
return false;
|
|
3851
|
+
}
|
|
3852
|
+
return value.enabled === enabled && value.evidenceMode === "path_required" && matchesGeneratedAgentRuntime(value.reviewer);
|
|
3853
|
+
}
|
|
3854
|
+
function resolveLegacyBackfilledAgentAutomation(config) {
|
|
3855
|
+
const none = { taskExecution: false, planReview: false };
|
|
3856
|
+
if (!isPlainRecord2(config.workflow)) return none;
|
|
3857
|
+
const workflow = config.workflow;
|
|
3858
|
+
if (workflow.agentAutomationConfigured === true) return none;
|
|
3859
|
+
const createdAt = typeof config.createdAt === "string" ? config.createdAt.trim() : "";
|
|
3860
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(createdAt)) return none;
|
|
3861
|
+
if (createdAt >= AGENT_AUTOMATION_OPT_IN_INIT_DATE) return none;
|
|
3862
|
+
const taskExecution = isPlainRecord2(workflow.agentExecution) ? workflow.agentExecution.task : null;
|
|
3863
|
+
const planReview = isPlainRecord2(workflow.agentReview) ? workflow.agentReview.plan : null;
|
|
3864
|
+
return {
|
|
3865
|
+
taskExecution: matchesGeneratedAgentRuntime(taskExecution, {
|
|
3866
|
+
enabled: true
|
|
3867
|
+
}),
|
|
3868
|
+
planReview: matchesGeneratedReviewPhase(planReview, true)
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3819
3872
|
// src/commands/update.ts
|
|
3820
3873
|
function hasOwnKey2(value, key) {
|
|
3821
3874
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
@@ -3903,7 +3956,9 @@ async function runUpdate(options) {
|
|
|
3903
3956
|
const updateAgents = options.agents || !hasExplicitSelection;
|
|
3904
3957
|
const updateAgentsMd = options.agentsMd || !hasExplicitSelection;
|
|
3905
3958
|
console.log(chalk.blue(tr(lang, "cli", "update.start")));
|
|
3906
|
-
console.log(
|
|
3959
|
+
console.log(
|
|
3960
|
+
chalk.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`)
|
|
3961
|
+
);
|
|
3907
3962
|
console.log(
|
|
3908
3963
|
chalk.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
|
|
3909
3964
|
);
|
|
@@ -3911,7 +3966,12 @@ async function runUpdate(options) {
|
|
|
3911
3966
|
let updatedCount = 0;
|
|
3912
3967
|
if (updateAgents) {
|
|
3913
3968
|
console.log(chalk.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
3914
|
-
const commonAgentsBase = path8.join(
|
|
3969
|
+
const commonAgentsBase = path8.join(
|
|
3970
|
+
templatesDir,
|
|
3971
|
+
lang,
|
|
3972
|
+
"common",
|
|
3973
|
+
"agents"
|
|
3974
|
+
);
|
|
3915
3975
|
const targetAgentsBase = path8.join(docsDir, "agents");
|
|
3916
3976
|
const commonAgents = commonAgentsBase;
|
|
3917
3977
|
const targetAgents = targetAgentsBase;
|
|
@@ -3940,13 +4000,14 @@ async function runUpdate(options) {
|
|
|
3940
4000
|
updatedCount += count;
|
|
3941
4001
|
}
|
|
3942
4002
|
console.log(
|
|
3943
|
-
chalk.green(
|
|
3944
|
-
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
3945
|
-
)
|
|
4003
|
+
chalk.green(` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`)
|
|
3946
4004
|
);
|
|
3947
4005
|
}
|
|
3948
4006
|
if (updateAgentsMd) {
|
|
3949
|
-
const agentsMdTargets = await collectAgentsMdTargets(
|
|
4007
|
+
const agentsMdTargets = await collectAgentsMdTargets(
|
|
4008
|
+
cwd,
|
|
4009
|
+
currentConfig
|
|
4010
|
+
);
|
|
3950
4011
|
for (const target of agentsMdTargets) {
|
|
3951
4012
|
const result = await upsertLeeSpecKitAgentsMd(target, {
|
|
3952
4013
|
lang,
|
|
@@ -3975,13 +4036,24 @@ async function runUpdate(options) {
|
|
|
3975
4036
|
)
|
|
3976
4037
|
);
|
|
3977
4038
|
console.log(
|
|
3978
|
-
chalk.gray(
|
|
3979
|
-
` (${configBackfill.changedPaths.join(", ")})`
|
|
3980
|
-
)
|
|
4039
|
+
chalk.gray(` (${configBackfill.changedPaths.join(", ")})`)
|
|
3981
4040
|
);
|
|
4041
|
+
if (configBackfill.changedPaths.includes(
|
|
4042
|
+
"workflow.agentExecution.task.enabled"
|
|
4043
|
+
) || configBackfill.changedPaths.includes(
|
|
4044
|
+
"workflow.agentReview.plan.enabled"
|
|
4045
|
+
)) {
|
|
4046
|
+
console.log(
|
|
4047
|
+
chalk.yellow(
|
|
4048
|
+
` - ${tr(lang, "cli", "update.legacyAutomationSafe")}`
|
|
4049
|
+
)
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
3982
4052
|
}
|
|
3983
4053
|
console.log(
|
|
3984
|
-
chalk.green(
|
|
4054
|
+
chalk.green(
|
|
4055
|
+
`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`
|
|
4056
|
+
)
|
|
3985
4057
|
);
|
|
3986
4058
|
},
|
|
3987
4059
|
{ owner: "update" }
|
|
@@ -4050,6 +4122,7 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
4050
4122
|
}
|
|
4051
4123
|
const workflow = raw.workflow;
|
|
4052
4124
|
changedPaths.push(...migrateLegacyWorkflowSettings(workflow));
|
|
4125
|
+
const restoreLegacyAgentAutomationDefaults = resolveLegacyBackfilledAgentAutomation(raw);
|
|
4053
4126
|
setIfMissing(
|
|
4054
4127
|
workflow,
|
|
4055
4128
|
"requireWorktree",
|
|
@@ -4154,11 +4227,15 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
4154
4227
|
setIfMissing(
|
|
4155
4228
|
taskExecution,
|
|
4156
4229
|
"enabled",
|
|
4157
|
-
|
|
4230
|
+
false,
|
|
4158
4231
|
"workflow.agentExecution.task.enabled"
|
|
4159
4232
|
);
|
|
4160
4233
|
if (typeof taskExecution.enabled !== "boolean") {
|
|
4161
|
-
taskExecution.enabled =
|
|
4234
|
+
taskExecution.enabled = false;
|
|
4235
|
+
changedPaths.push("workflow.agentExecution.task.enabled");
|
|
4236
|
+
}
|
|
4237
|
+
if (restoreLegacyAgentAutomationDefaults.taskExecution && taskExecution.enabled !== false) {
|
|
4238
|
+
taskExecution.enabled = false;
|
|
4162
4239
|
changedPaths.push("workflow.agentExecution.task.enabled");
|
|
4163
4240
|
}
|
|
4164
4241
|
if (taskExecution.type !== "subagent") {
|
|
@@ -4248,14 +4325,17 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
4248
4325
|
changedPaths.push(`${phasePath}.reviewer.onUnavailable`);
|
|
4249
4326
|
}
|
|
4250
4327
|
};
|
|
4251
|
-
normalizeAgentReviewPhase("plan",
|
|
4328
|
+
normalizeAgentReviewPhase("plan", false);
|
|
4329
|
+
if (restoreLegacyAgentAutomationDefaults.planReview) {
|
|
4330
|
+
const planReview = agentReview.plan;
|
|
4331
|
+
if (planReview.enabled !== false) {
|
|
4332
|
+
planReview.enabled = false;
|
|
4333
|
+
changedPaths.push("workflow.agentReview.plan.enabled");
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4252
4336
|
normalizeAgentReviewPhase("task", false);
|
|
4253
4337
|
const legacyFeatureEnabled = typeof legacyPrePrReview?.enabled === "boolean" ? legacyPrePrReview.enabled : workflow.mode !== "local";
|
|
4254
|
-
normalizeAgentReviewPhase(
|
|
4255
|
-
"feature",
|
|
4256
|
-
legacyFeatureEnabled,
|
|
4257
|
-
legacyPrePrReview
|
|
4258
|
-
);
|
|
4338
|
+
normalizeAgentReviewPhase("feature", legacyFeatureEnabled, legacyPrePrReview);
|
|
4259
4339
|
if (hasOwnKey2(workflow, "prePrReview")) {
|
|
4260
4340
|
delete workflow.prePrReview;
|
|
4261
4341
|
changedPaths.push("workflow.prePrReview");
|
|
@@ -4383,11 +4463,15 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
4383
4463
|
if (!top) return null;
|
|
4384
4464
|
const rel = path8.relative(top, docsDir) || ".";
|
|
4385
4465
|
try {
|
|
4386
|
-
const output = execFileSync(
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4466
|
+
const output = execFileSync(
|
|
4467
|
+
"git",
|
|
4468
|
+
["status", "--porcelain=v1", "--", rel],
|
|
4469
|
+
{
|
|
4470
|
+
cwd: top,
|
|
4471
|
+
encoding: "utf-8",
|
|
4472
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4473
|
+
}
|
|
4474
|
+
);
|
|
4391
4475
|
if (ignoredAbsPaths.length === 0) {
|
|
4392
4476
|
return output;
|
|
4393
4477
|
}
|
|
@@ -4423,8 +4507,62 @@ async function isDocsWorktreeCleanOrThrow(docsDir, lang, ignoredAbsPaths = []) {
|
|
|
4423
4507
|
}
|
|
4424
4508
|
return true;
|
|
4425
4509
|
}
|
|
4510
|
+
var CONFIG_REVIEW_PHASES = ["plan", "task", "feature"];
|
|
4511
|
+
function isPlainObject2(value) {
|
|
4512
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4513
|
+
}
|
|
4514
|
+
function parseReviews(value) {
|
|
4515
|
+
if (typeof value === "undefined") return void 0;
|
|
4516
|
+
const normalized = value.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
4517
|
+
if (normalized.length === 1 && normalized[0] === "none") return [];
|
|
4518
|
+
if (normalized.length === 0 || normalized.includes("none") || normalized.some(
|
|
4519
|
+
(entry) => !CONFIG_REVIEW_PHASES.includes(entry)
|
|
4520
|
+
)) {
|
|
4521
|
+
throw createCliError(
|
|
4522
|
+
"INVALID_ARGUMENT",
|
|
4523
|
+
"`--reviews` must be a comma-separated subset of `plan,task,feature`, or `none`."
|
|
4524
|
+
);
|
|
4525
|
+
}
|
|
4526
|
+
return CONFIG_REVIEW_PHASES.filter((phase) => normalized.includes(phase));
|
|
4527
|
+
}
|
|
4528
|
+
function parseMaxReviewRounds(value) {
|
|
4529
|
+
if (typeof value === "undefined") return void 0;
|
|
4530
|
+
const parsed = Number(value);
|
|
4531
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
4532
|
+
throw createCliError(
|
|
4533
|
+
"INVALID_ARGUMENT",
|
|
4534
|
+
"`--max-review-rounds` must be a positive integer."
|
|
4535
|
+
);
|
|
4536
|
+
}
|
|
4537
|
+
return parsed;
|
|
4538
|
+
}
|
|
4539
|
+
function validateWorkflowOptions(options) {
|
|
4540
|
+
if (options.taskAgent && !["on", "off"].includes(options.taskAgent)) {
|
|
4541
|
+
throw createCliError(
|
|
4542
|
+
"INVALID_ARGUMENT",
|
|
4543
|
+
"`--task-agent` must be `on` or `off`."
|
|
4544
|
+
);
|
|
4545
|
+
}
|
|
4546
|
+
if (options.completionStrategy && !["local-ff", "local-squash", "none"].includes(options.completionStrategy)) {
|
|
4547
|
+
throw createCliError(
|
|
4548
|
+
"INVALID_ARGUMENT",
|
|
4549
|
+
"`--completion-strategy` must be `local-ff`, `local-squash`, or `none`."
|
|
4550
|
+
);
|
|
4551
|
+
}
|
|
4552
|
+
parseReviews(options.reviews);
|
|
4553
|
+
parseMaxReviewRounds(options.maxReviewRounds);
|
|
4554
|
+
}
|
|
4426
4555
|
function configCommand(program2) {
|
|
4427
|
-
program2.command("config").description("View or modify project configuration").option("--dir <dir>", "Docs directory or project path to target").option("--project-root <path>", "Set project root path").option("--component <component>", "Component name for multi projects").option("--
|
|
4556
|
+
program2.command("config").description("View or modify project configuration").option("--dir <dir>", "Docs directory or project path to target").option("--project-root <path>", "Set project root path").option("--component <component>", "Component name for multi projects").option("--task-agent <mode>", "Task implementation agent: on | off").option(
|
|
4557
|
+
"--reviews <list>",
|
|
4558
|
+
"Review gates: comma-separated plan,task,feature | none"
|
|
4559
|
+
).option(
|
|
4560
|
+
"--max-review-rounds <count>",
|
|
4561
|
+
"Maximum fresh review rounds before continuing with residual risks"
|
|
4562
|
+
).option(
|
|
4563
|
+
"--completion-strategy <strategy>",
|
|
4564
|
+
"Local completion: local-ff | local-squash | none"
|
|
4565
|
+
).option("--interactive", "Configure workflow options interactively").option("--non-interactive", "Fail instead of prompting for input").action(async (options) => {
|
|
4428
4566
|
try {
|
|
4429
4567
|
await runConfig(options);
|
|
4430
4568
|
} catch (error) {
|
|
@@ -4450,6 +4588,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
4450
4588
|
});
|
|
4451
4589
|
}
|
|
4452
4590
|
async function runConfig(options) {
|
|
4591
|
+
validateWorkflowOptions(options);
|
|
4453
4592
|
const cwd = process.cwd();
|
|
4454
4593
|
const targetCwd = options.dir ? path8.resolve(cwd, options.dir) : cwd;
|
|
4455
4594
|
const config = await getConfig(targetCwd);
|
|
@@ -4460,7 +4599,8 @@ async function runConfig(options) {
|
|
|
4460
4599
|
);
|
|
4461
4600
|
}
|
|
4462
4601
|
const configPath = path8.join(config.docsDir, ".lee-spec-kit.json");
|
|
4463
|
-
|
|
4602
|
+
const hasWorkflowOptions = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.maxReviewRounds !== "undefined" || typeof options.completionStrategy !== "undefined" || !!options.interactive;
|
|
4603
|
+
if (!options.projectRoot && !hasWorkflowOptions) {
|
|
4464
4604
|
console.log();
|
|
4465
4605
|
console.log(chalk.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
4466
4606
|
console.log();
|
|
@@ -4475,85 +4615,21 @@ async function runConfig(options) {
|
|
|
4475
4615
|
console.log();
|
|
4476
4616
|
return;
|
|
4477
4617
|
}
|
|
4478
|
-
const projectRoot = options.projectRoot;
|
|
4479
4618
|
await withFileLock(
|
|
4480
4619
|
getDocsLockPath(config.docsDir),
|
|
4481
4620
|
async () => {
|
|
4482
4621
|
const configFile = await fs.readJson(configPath);
|
|
4483
|
-
if (configFile
|
|
4484
|
-
|
|
4485
|
-
|
|
4622
|
+
if (!isPlainObject2(configFile)) {
|
|
4623
|
+
throw createCliError(
|
|
4624
|
+
"PRECONDITION_FAILED",
|
|
4625
|
+
"Invalid project configuration."
|
|
4486
4626
|
);
|
|
4487
|
-
return;
|
|
4488
4627
|
}
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
if (!targetComponent) {
|
|
4495
|
-
if (components.length === 1) {
|
|
4496
|
-
targetComponent = components[0];
|
|
4497
|
-
} else if (options.nonInteractive) {
|
|
4498
|
-
throw createCliError(
|
|
4499
|
-
"PROMPT_BLOCKED",
|
|
4500
|
-
"`--component` is required for multi projectRoot update when using `--non-interactive`."
|
|
4501
|
-
);
|
|
4502
|
-
} else {
|
|
4503
|
-
const response = await prompts(
|
|
4504
|
-
[
|
|
4505
|
-
{
|
|
4506
|
-
type: "select",
|
|
4507
|
-
name: "component",
|
|
4508
|
-
message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
|
|
4509
|
-
choices: components.map((value) => ({
|
|
4510
|
-
title: value.toUpperCase(),
|
|
4511
|
-
value
|
|
4512
|
-
}))
|
|
4513
|
-
}
|
|
4514
|
-
],
|
|
4515
|
-
{
|
|
4516
|
-
onCancel: () => {
|
|
4517
|
-
throw new Error("canceled");
|
|
4518
|
-
}
|
|
4519
|
-
}
|
|
4520
|
-
);
|
|
4521
|
-
targetComponent = response.component;
|
|
4522
|
-
}
|
|
4523
|
-
}
|
|
4524
|
-
if (!targetComponent) {
|
|
4525
|
-
throw createCliError(
|
|
4526
|
-
"INVALID_ARGUMENT",
|
|
4527
|
-
"Component selection is required."
|
|
4528
|
-
);
|
|
4529
|
-
}
|
|
4530
|
-
assertAllowedComponent(targetComponent, components);
|
|
4531
|
-
const currentRoot = typeof configFile.projectRoot === "object" && configFile.projectRoot ? configFile.projectRoot : {};
|
|
4532
|
-
currentRoot[targetComponent] = projectRoot;
|
|
4533
|
-
configFile.projectRoot = currentRoot;
|
|
4534
|
-
console.log(
|
|
4535
|
-
chalk.green(
|
|
4536
|
-
tr(config.lang, "cli", "config.projectRootSet", {
|
|
4537
|
-
repo: targetComponent.toUpperCase(),
|
|
4538
|
-
path: projectRoot
|
|
4539
|
-
})
|
|
4540
|
-
)
|
|
4541
|
-
);
|
|
4542
|
-
} else {
|
|
4543
|
-
if (targetFromOptions) {
|
|
4544
|
-
throw createCliError(
|
|
4545
|
-
"INVALID_ARGUMENT",
|
|
4546
|
-
"`--component` is only valid for multi projectRoot updates."
|
|
4547
|
-
);
|
|
4548
|
-
}
|
|
4549
|
-
configFile.projectRoot = projectRoot;
|
|
4550
|
-
console.log(
|
|
4551
|
-
chalk.green(
|
|
4552
|
-
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
4553
|
-
path: projectRoot
|
|
4554
|
-
})
|
|
4555
|
-
)
|
|
4556
|
-
);
|
|
4628
|
+
if (options.projectRoot) {
|
|
4629
|
+
await updateProjectRoot(configFile, options, config.lang);
|
|
4630
|
+
}
|
|
4631
|
+
if (hasWorkflowOptions) {
|
|
4632
|
+
await updateWorkflowConfig(configFile, options, config.lang);
|
|
4557
4633
|
}
|
|
4558
4634
|
await fs.writeJson(configPath, configFile, { spaces: 2 });
|
|
4559
4635
|
console.log();
|
|
@@ -4561,6 +4637,255 @@ async function runConfig(options) {
|
|
|
4561
4637
|
{ owner: "config" }
|
|
4562
4638
|
);
|
|
4563
4639
|
}
|
|
4640
|
+
async function updateProjectRoot(configFile, options, lang) {
|
|
4641
|
+
if (configFile.docsRepo !== "standalone") {
|
|
4642
|
+
console.log(
|
|
4643
|
+
chalk.yellow(tr(lang, "cli", "config.projectRootStandaloneOnly"))
|
|
4644
|
+
);
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
const projectRoot = options.projectRoot;
|
|
4648
|
+
if (!projectRoot) return;
|
|
4649
|
+
const projectType = normalizeProjectType(
|
|
4650
|
+
String(configFile.projectType || "single")
|
|
4651
|
+
);
|
|
4652
|
+
const targetFromOptions = options.component?.trim().toLowerCase();
|
|
4653
|
+
if (projectType === "multi") {
|
|
4654
|
+
const components = resolveProjectComponents(
|
|
4655
|
+
projectType,
|
|
4656
|
+
configFile.components
|
|
4657
|
+
);
|
|
4658
|
+
let targetComponent = targetFromOptions;
|
|
4659
|
+
if (!targetComponent) {
|
|
4660
|
+
if (components.length === 1) {
|
|
4661
|
+
targetComponent = components[0];
|
|
4662
|
+
} else if (options.nonInteractive) {
|
|
4663
|
+
throw createCliError(
|
|
4664
|
+
"PROMPT_BLOCKED",
|
|
4665
|
+
"`--component` is required for multi projectRoot update when using `--non-interactive`."
|
|
4666
|
+
);
|
|
4667
|
+
} else {
|
|
4668
|
+
const response = await prompts(
|
|
4669
|
+
[
|
|
4670
|
+
{
|
|
4671
|
+
type: "select",
|
|
4672
|
+
name: "component",
|
|
4673
|
+
message: tr(lang, "cli", "config.selectRepoToUpdate"),
|
|
4674
|
+
choices: components.map((value) => ({
|
|
4675
|
+
title: value.toUpperCase(),
|
|
4676
|
+
value
|
|
4677
|
+
}))
|
|
4678
|
+
}
|
|
4679
|
+
],
|
|
4680
|
+
{
|
|
4681
|
+
onCancel: () => {
|
|
4682
|
+
throw new Error("canceled");
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
);
|
|
4686
|
+
targetComponent = response.component;
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (!targetComponent) {
|
|
4690
|
+
throw createCliError(
|
|
4691
|
+
"INVALID_ARGUMENT",
|
|
4692
|
+
"Component selection is required."
|
|
4693
|
+
);
|
|
4694
|
+
}
|
|
4695
|
+
assertAllowedComponent(targetComponent, components);
|
|
4696
|
+
const currentRoot = isPlainObject2(
|
|
4697
|
+
configFile.projectRoot
|
|
4698
|
+
) ? Object.fromEntries(
|
|
4699
|
+
Object.entries(configFile.projectRoot).filter(
|
|
4700
|
+
(entry) => typeof entry[1] === "string"
|
|
4701
|
+
)
|
|
4702
|
+
) : {};
|
|
4703
|
+
currentRoot[targetComponent] = projectRoot;
|
|
4704
|
+
configFile.projectRoot = currentRoot;
|
|
4705
|
+
console.log(
|
|
4706
|
+
chalk.green(
|
|
4707
|
+
tr(lang, "cli", "config.projectRootSet", {
|
|
4708
|
+
repo: targetComponent.toUpperCase(),
|
|
4709
|
+
path: projectRoot
|
|
4710
|
+
})
|
|
4711
|
+
)
|
|
4712
|
+
);
|
|
4713
|
+
} else {
|
|
4714
|
+
if (targetFromOptions) {
|
|
4715
|
+
throw createCliError(
|
|
4716
|
+
"INVALID_ARGUMENT",
|
|
4717
|
+
"`--component` is only valid for multi projectRoot updates."
|
|
4718
|
+
);
|
|
4719
|
+
}
|
|
4720
|
+
configFile.projectRoot = projectRoot;
|
|
4721
|
+
console.log(
|
|
4722
|
+
chalk.green(
|
|
4723
|
+
tr(lang, "cli", "config.projectRootSetSingle", {
|
|
4724
|
+
path: projectRoot
|
|
4725
|
+
})
|
|
4726
|
+
)
|
|
4727
|
+
);
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
function ensureWorkflow(configFile) {
|
|
4731
|
+
if (!isPlainObject2(configFile.workflow)) configFile.workflow = {};
|
|
4732
|
+
return configFile.workflow;
|
|
4733
|
+
}
|
|
4734
|
+
function ensureTaskExecution(workflow) {
|
|
4735
|
+
if (!isPlainObject2(workflow.agentExecution)) workflow.agentExecution = {};
|
|
4736
|
+
const agentExecution = workflow.agentExecution;
|
|
4737
|
+
if (!isPlainObject2(agentExecution.task)) {
|
|
4738
|
+
agentExecution.task = createDefaultAgentExecutionTaskConfig();
|
|
4739
|
+
}
|
|
4740
|
+
return agentExecution.task;
|
|
4741
|
+
}
|
|
4742
|
+
function ensureAgentReview(workflow) {
|
|
4743
|
+
if (!isPlainObject2(workflow.agentReview)) workflow.agentReview = {};
|
|
4744
|
+
return workflow.agentReview;
|
|
4745
|
+
}
|
|
4746
|
+
function ensureReviewPhase(agentReview, phaseName) {
|
|
4747
|
+
if (!isPlainObject2(agentReview[phaseName])) {
|
|
4748
|
+
agentReview[phaseName] = {
|
|
4749
|
+
enabled: false,
|
|
4750
|
+
evidenceMode: "path_required",
|
|
4751
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
4752
|
+
};
|
|
4753
|
+
}
|
|
4754
|
+
const phase = agentReview[phaseName];
|
|
4755
|
+
if (!isPlainObject2(phase.reviewer)) {
|
|
4756
|
+
phase.reviewer = createDefaultAgentReviewerConfig();
|
|
4757
|
+
}
|
|
4758
|
+
if (phase.evidenceMode !== "any" && phase.evidenceMode !== "path_required") {
|
|
4759
|
+
phase.evidenceMode = "path_required";
|
|
4760
|
+
}
|
|
4761
|
+
return phase;
|
|
4762
|
+
}
|
|
4763
|
+
function currentReviewSelection(workflow) {
|
|
4764
|
+
const agentReview = isPlainObject2(workflow.agentReview) ? workflow.agentReview : {};
|
|
4765
|
+
const legacyFeature = isPlainObject2(workflow.prePrReview) ? workflow.prePrReview.enabled : void 0;
|
|
4766
|
+
const isLocal = workflow.mode === "local" || workflow.preset === "local";
|
|
4767
|
+
return CONFIG_REVIEW_PHASES.filter((phase) => {
|
|
4768
|
+
const phaseConfig = isPlainObject2(agentReview[phase]) ? agentReview[phase] : {};
|
|
4769
|
+
if (typeof phaseConfig.enabled === "boolean") return phaseConfig.enabled;
|
|
4770
|
+
if (phase === "feature") {
|
|
4771
|
+
return typeof legacyFeature === "boolean" ? legacyFeature : !isLocal;
|
|
4772
|
+
}
|
|
4773
|
+
return false;
|
|
4774
|
+
});
|
|
4775
|
+
}
|
|
4776
|
+
async function updateWorkflowConfig(configFile, options, lang) {
|
|
4777
|
+
const workflow = ensureWorkflow(configFile);
|
|
4778
|
+
const legacyBackfilledAgentAutomation = resolveLegacyBackfilledAgentAutomation(configFile);
|
|
4779
|
+
if (legacyBackfilledAgentAutomation.taskExecution) {
|
|
4780
|
+
ensureTaskExecution(workflow).enabled = false;
|
|
4781
|
+
}
|
|
4782
|
+
if (legacyBackfilledAgentAutomation.planReview) {
|
|
4783
|
+
ensureReviewPhase(ensureAgentReview(workflow), "plan").enabled = false;
|
|
4784
|
+
}
|
|
4785
|
+
const isLocal = workflow.mode === "local" || workflow.preset === "local";
|
|
4786
|
+
if (options.interactive && options.nonInteractive) {
|
|
4787
|
+
throw createCliError(
|
|
4788
|
+
"PROMPT_BLOCKED",
|
|
4789
|
+
"`--interactive` cannot be used with `--non-interactive`."
|
|
4790
|
+
);
|
|
4791
|
+
}
|
|
4792
|
+
let taskAgentEnabled = options.taskAgent === "on" ? true : options.taskAgent === "off" ? false : void 0;
|
|
4793
|
+
let reviews = parseReviews(options.reviews);
|
|
4794
|
+
let maxReviewRounds = parseMaxReviewRounds(options.maxReviewRounds);
|
|
4795
|
+
let completionStrategy = options.completionStrategy;
|
|
4796
|
+
if (options.interactive) {
|
|
4797
|
+
const selectedReviews = currentReviewSelection(workflow);
|
|
4798
|
+
const currentTaskExecution = isPlainObject2(workflow.agentExecution) && isPlainObject2(workflow.agentExecution.task) ? workflow.agentExecution.task.enabled === true : false;
|
|
4799
|
+
const currentAgentReview = isPlainObject2(workflow.agentReview) ? workflow.agentReview : {};
|
|
4800
|
+
const currentMaxRounds = typeof currentAgentReview.maxRounds === "number" && Number.isInteger(currentAgentReview.maxRounds) && currentAgentReview.maxRounds > 0 ? currentAgentReview.maxRounds : 1;
|
|
4801
|
+
const response = await prompts(
|
|
4802
|
+
[
|
|
4803
|
+
{
|
|
4804
|
+
type: typeof options.taskAgent === "undefined" ? "select" : null,
|
|
4805
|
+
name: "taskAgent",
|
|
4806
|
+
message: tr(lang, "cli", "init.prompt.taskAgent"),
|
|
4807
|
+
choices: [
|
|
4808
|
+
{ title: tr(lang, "cli", "init.choice.taskAgent.on"), value: true },
|
|
4809
|
+
{
|
|
4810
|
+
title: tr(lang, "cli", "init.choice.taskAgent.off"),
|
|
4811
|
+
value: false
|
|
4812
|
+
}
|
|
4813
|
+
],
|
|
4814
|
+
initial: currentTaskExecution ? 0 : 1
|
|
4815
|
+
},
|
|
4816
|
+
{
|
|
4817
|
+
type: typeof options.reviews === "undefined" ? "multiselect" : null,
|
|
4818
|
+
name: "reviews",
|
|
4819
|
+
message: tr(lang, "cli", "init.prompt.reviews"),
|
|
4820
|
+
choices: CONFIG_REVIEW_PHASES.map((phase) => ({
|
|
4821
|
+
title: tr(lang, "cli", `init.choice.review.${phase}`),
|
|
4822
|
+
value: phase,
|
|
4823
|
+
selected: selectedReviews.includes(phase)
|
|
4824
|
+
}))
|
|
4825
|
+
},
|
|
4826
|
+
{
|
|
4827
|
+
type: typeof options.maxReviewRounds === "undefined" ? "number" : null,
|
|
4828
|
+
name: "maxReviewRounds",
|
|
4829
|
+
message: tr(lang, "cli", "init.prompt.maxReviewRounds"),
|
|
4830
|
+
initial: currentMaxRounds,
|
|
4831
|
+
min: 1
|
|
4832
|
+
},
|
|
4833
|
+
{
|
|
4834
|
+
type: isLocal && typeof options.completionStrategy === "undefined" ? "select" : null,
|
|
4835
|
+
name: "completionStrategy",
|
|
4836
|
+
message: tr(lang, "cli", "init.prompt.completionStrategy"),
|
|
4837
|
+
choices: [
|
|
4838
|
+
{ title: "local-ff", value: "local-ff" },
|
|
4839
|
+
{ title: "local-squash", value: "local-squash" },
|
|
4840
|
+
{ title: "none", value: "none" }
|
|
4841
|
+
],
|
|
4842
|
+
initial: ["local-ff", "local-squash", "none"].indexOf(
|
|
4843
|
+
String(workflow.completionStrategy || "none")
|
|
4844
|
+
)
|
|
4845
|
+
}
|
|
4846
|
+
],
|
|
4847
|
+
{
|
|
4848
|
+
onCancel: () => {
|
|
4849
|
+
throw new Error("canceled");
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
);
|
|
4853
|
+
if (typeof response.taskAgent === "boolean")
|
|
4854
|
+
taskAgentEnabled = response.taskAgent;
|
|
4855
|
+
if (Array.isArray(response.reviews)) {
|
|
4856
|
+
reviews = CONFIG_REVIEW_PHASES.filter(
|
|
4857
|
+
(phase) => response.reviews.includes(phase)
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4860
|
+
if (Number.isInteger(response.maxReviewRounds) && response.maxReviewRounds > 0) {
|
|
4861
|
+
maxReviewRounds = response.maxReviewRounds;
|
|
4862
|
+
}
|
|
4863
|
+
completionStrategy = response.completionStrategy || completionStrategy;
|
|
4864
|
+
}
|
|
4865
|
+
if (completionStrategy && !isLocal) {
|
|
4866
|
+
throw createCliError(
|
|
4867
|
+
"INVALID_ARGUMENT",
|
|
4868
|
+
"`--completion-strategy` can only be used with a local workflow."
|
|
4869
|
+
);
|
|
4870
|
+
}
|
|
4871
|
+
if (typeof taskAgentEnabled === "boolean") {
|
|
4872
|
+
ensureTaskExecution(workflow).enabled = taskAgentEnabled;
|
|
4873
|
+
}
|
|
4874
|
+
if (reviews) {
|
|
4875
|
+
const agentReview = ensureAgentReview(workflow);
|
|
4876
|
+
for (const phase of CONFIG_REVIEW_PHASES) {
|
|
4877
|
+
ensureReviewPhase(agentReview, phase).enabled = reviews.includes(phase);
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
if (typeof maxReviewRounds === "number") {
|
|
4881
|
+
ensureAgentReview(workflow).maxRounds = maxReviewRounds;
|
|
4882
|
+
}
|
|
4883
|
+
if (completionStrategy) workflow.completionStrategy = completionStrategy;
|
|
4884
|
+
if (typeof taskAgentEnabled === "boolean" || reviews) {
|
|
4885
|
+
workflow.agentAutomationConfigured = true;
|
|
4886
|
+
}
|
|
4887
|
+
console.log(chalk.green(tr(lang, "cli", "config.workflowUpdated")));
|
|
4888
|
+
}
|
|
4564
4889
|
function runProcess(bin, args, cwd) {
|
|
4565
4890
|
const result = spawnSync(bin, args, {
|
|
4566
4891
|
cwd,
|
|
@@ -8441,6 +8766,7 @@ var COMPLETION_MARKERS = {
|
|
|
8441
8766
|
};
|
|
8442
8767
|
function resolveWorkflowRequirements(config) {
|
|
8443
8768
|
const workflow = config.workflow || {};
|
|
8769
|
+
const legacyBackfilledAgentAutomation = resolveLegacyBackfilledAgentAutomation(config);
|
|
8444
8770
|
const hasCanonicalMode = workflow.mode === "github" || workflow.mode === "local";
|
|
8445
8771
|
const workflowMode = hasCanonicalMode ? workflow.mode : workflow.preset === "local" ? "local" : "github";
|
|
8446
8772
|
const isLocalWorkflow = workflowMode === "local";
|
|
@@ -8452,10 +8778,13 @@ function resolveWorkflowRequirements(config) {
|
|
|
8452
8778
|
requirePr: workflow.requirePr ?? !isLocalWorkflow,
|
|
8453
8779
|
requireReview: workflow.requireReview ?? !isLocalWorkflow,
|
|
8454
8780
|
requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
|
|
8455
|
-
|
|
8781
|
+
// Missing agent automation keys identify projects created before these
|
|
8782
|
+
// policies existed. Keep their historical main-agent/no-Plan-review
|
|
8783
|
+
// behavior; new projects opt in explicitly through init.
|
|
8784
|
+
planReviewEnabled: legacyBackfilledAgentAutomation.planReview ? false : workflow.agentReview?.plan?.enabled ?? false,
|
|
8456
8785
|
taskReviewEnabled: workflow.agentReview?.task?.enabled ?? false,
|
|
8457
8786
|
featureReviewEnabled: workflow.agentReview?.feature?.enabled ?? workflow.prePrReview?.enabled ?? !isLocalWorkflow,
|
|
8458
|
-
taskExecutionEnabled: workflow.agentExecution?.task?.enabled ??
|
|
8787
|
+
taskExecutionEnabled: legacyBackfilledAgentAutomation.taskExecution ? false : workflow.agentExecution?.task?.enabled ?? false
|
|
8459
8788
|
};
|
|
8460
8789
|
}
|
|
8461
8790
|
function parseApprovalStatus(raw) {
|
|
@@ -8518,8 +8847,8 @@ function resolveMaxReviewRounds(config) {
|
|
|
8518
8847
|
const configured = config.workflow?.agentReview?.maxRounds;
|
|
8519
8848
|
return typeof configured === "number" && Number.isInteger(configured) && configured > 0 ? configured : 1;
|
|
8520
8849
|
}
|
|
8521
|
-
function
|
|
8522
|
-
return decisionOutcome === "changes_requested" && (reviewRound || 1)
|
|
8850
|
+
function reviewRoundLimitReached(config, reviewRound, decisionOutcome) {
|
|
8851
|
+
return decisionOutcome === "changes_requested" && (reviewRound || 1) >= resolveMaxReviewRounds(config);
|
|
8523
8852
|
}
|
|
8524
8853
|
function parseWorkflowTaskLine(line, index = -1) {
|
|
8525
8854
|
const canonical = parseTaskLine(line, index);
|
|
@@ -9244,28 +9573,34 @@ function reviewEvidenceSatisfied(config, feature, scope, evidence) {
|
|
|
9244
9573
|
});
|
|
9245
9574
|
}
|
|
9246
9575
|
function planReviewSatisfied(config, feature, review, target) {
|
|
9247
|
-
|
|
9576
|
+
const reviewLimitReached = reviewRoundLimitReached(
|
|
9248
9577
|
config,
|
|
9249
9578
|
review.reviewRound,
|
|
9250
9579
|
review.decisionOutcome
|
|
9251
|
-
)
|
|
9580
|
+
);
|
|
9581
|
+
const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9582
|
+
return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && (review.decisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
|
|
9252
9583
|
}
|
|
9253
9584
|
function featureReviewSatisfied(config, feature, tasks, target) {
|
|
9254
|
-
|
|
9585
|
+
const reviewLimitReached = reviewRoundLimitReached(
|
|
9255
9586
|
config,
|
|
9256
9587
|
tasks.prePrReviewRound,
|
|
9257
9588
|
tasks.prePrDecisionOutcome
|
|
9258
|
-
)
|
|
9589
|
+
);
|
|
9590
|
+
const targetMatches = !!target && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
|
|
9591
|
+
return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && (tasks.prePrDecisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
|
|
9259
9592
|
}
|
|
9260
9593
|
function taskReviewEvidenceSatisfied(config, feature, task, target) {
|
|
9261
|
-
|
|
9594
|
+
const reviewLimitReached = reviewRoundLimitReached(
|
|
9262
9595
|
config,
|
|
9263
9596
|
task.reviewRound,
|
|
9264
9597
|
task.reviewDecisionOutcome
|
|
9265
|
-
)
|
|
9598
|
+
);
|
|
9599
|
+
const targetMatches = !!target && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
|
|
9600
|
+
return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && (task.reviewDecisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
|
|
9266
9601
|
}
|
|
9267
9602
|
function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
|
|
9268
|
-
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" && !
|
|
9603
|
+
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" && !reviewRoundLimitReached(
|
|
9269
9604
|
config,
|
|
9270
9605
|
task.reviewRound,
|
|
9271
9606
|
task.reviewDecisionOutcome
|
|
@@ -9276,7 +9611,7 @@ function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, curre
|
|
|
9276
9611
|
["rev-parse", `${task.reviewedHead}^{tree}`],
|
|
9277
9612
|
projectGitCwd
|
|
9278
9613
|
) || "";
|
|
9279
|
-
return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha);
|
|
9614
|
+
return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha) && (task.reviewDecisionOutcome === "approve" || task.reviewedHead !== currentTargetSha);
|
|
9280
9615
|
}
|
|
9281
9616
|
function resolvePreviousTaskReviewBase(config, feature, tasks, task) {
|
|
9282
9617
|
const taskIndex = tasks.tasks.indexOf(task);
|
|
@@ -9680,7 +10015,8 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9680
10015
|
const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9681
10016
|
const maxReviewRounds = resolveMaxReviewRounds(config);
|
|
9682
10017
|
const reviewRound = review.reviewRound || 1;
|
|
9683
|
-
const
|
|
10018
|
+
const requestedReviewRound = !targetMatches && review.decisionOutcome ? reviewRound + 1 : reviewRound;
|
|
10019
|
+
const nextReviewRound = Math.min(requestedReviewRound, maxReviewRounds);
|
|
9684
10020
|
const actionContext = {
|
|
9685
10021
|
reviewScope: "plan",
|
|
9686
10022
|
reviewRound: nextReviewRound,
|
|
@@ -9690,7 +10026,8 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9690
10026
|
docsDirectory: config.docsDir
|
|
9691
10027
|
};
|
|
9692
10028
|
if (review.decisionOutcome === "changes_requested") {
|
|
9693
|
-
if (targetMatches) {
|
|
10029
|
+
if (targetMatches && reviewRound <= maxReviewRounds) {
|
|
10030
|
+
const finalReviewRound = reviewRound >= maxReviewRounds;
|
|
9694
10031
|
return {
|
|
9695
10032
|
status: "ok",
|
|
9696
10033
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9699,7 +10036,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9699
10036
|
stage: "plan_review_fix",
|
|
9700
10037
|
nextAction: buildAction(
|
|
9701
10038
|
"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.`,
|
|
10039
|
+
finalReviewRound ? `Address the independent Plan review findings in spec.md or plan.md, keep review round ${reviewRound}, and do not request another review. The resulting document hash changes will be preserved with the remaining findings as residual risks before automatic Plan promotion.` : `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
10040
|
false,
|
|
9704
10041
|
null,
|
|
9705
10042
|
void 0,
|
|
@@ -9711,6 +10048,26 @@ function resolvePlanReviewPayload(config, feature, review, target) {
|
|
|
9711
10048
|
};
|
|
9712
10049
|
}
|
|
9713
10050
|
}
|
|
10051
|
+
if (review.decisionOutcome === "changes_requested" && reviewRound >= maxReviewRounds) {
|
|
10052
|
+
return {
|
|
10053
|
+
status: "ok",
|
|
10054
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10055
|
+
docsDir: config.docsDir,
|
|
10056
|
+
featureRef: buildFeatureRef(feature),
|
|
10057
|
+
stage: "plan_review",
|
|
10058
|
+
nextAction: buildAction(
|
|
10059
|
+
"plan_review",
|
|
10060
|
+
`The Plan review round limit was reached at round ${reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed, preserve the remaining findings and any post-review hash changes as residual risks, and continue through automatic Plan promotion.`,
|
|
10061
|
+
false,
|
|
10062
|
+
null,
|
|
10063
|
+
void 0,
|
|
10064
|
+
{ ...actionContext, reviewRound: maxReviewRounds }
|
|
10065
|
+
),
|
|
10066
|
+
approvalRequired: false,
|
|
10067
|
+
implementationAllowed: false,
|
|
10068
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
10069
|
+
};
|
|
10070
|
+
}
|
|
9714
10071
|
if (targetMatches && review.decisionOutcome === "blocked") {
|
|
9715
10072
|
return {
|
|
9716
10073
|
status: "ok",
|
|
@@ -9790,11 +10147,11 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9790
10147
|
specContent || "",
|
|
9791
10148
|
planContent || ""
|
|
9792
10149
|
);
|
|
9793
|
-
const planReviewAutoCompleted = planReview.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", planReview.evidence) &&
|
|
10150
|
+
const planReviewAutoCompleted = planReview.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", planReview.evidence) && reviewRoundLimitReached(
|
|
9794
10151
|
config,
|
|
9795
10152
|
planReview.reviewRound,
|
|
9796
10153
|
planReview.decisionOutcome
|
|
9797
|
-
) && planReview.reviewedSpecHash
|
|
10154
|
+
) && (planReview.reviewedSpecHash !== planReviewTarget.specHash || planReview.reviewedPlanHash !== planReviewTarget.planHash);
|
|
9798
10155
|
const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
|
|
9799
10156
|
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
9800
10157
|
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
@@ -9852,7 +10209,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9852
10209
|
stage: "plan",
|
|
9853
10210
|
nextAction: buildAction(
|
|
9854
10211
|
isReviewStage ? "plan_approve" : "plan_write",
|
|
9855
|
-
isReviewStage ? planReviewAutoCompleted ? "The Plan review
|
|
10212
|
+
isReviewStage ? planReviewAutoCompleted ? "The Plan review round limit was reached. Preserve the remaining findings and post-review hash changes 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.",
|
|
9856
10213
|
approvalRequired
|
|
9857
10214
|
),
|
|
9858
10215
|
approvalRequired,
|
|
@@ -10040,6 +10397,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10040
10397
|
const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
|
|
10041
10398
|
if (reviewTask.reviewDecisionOutcome === "changes_requested") {
|
|
10042
10399
|
if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
|
|
10400
|
+
const finalReviewRound = reviewContext.reviewRound >= reviewContext.maxReviewRounds;
|
|
10043
10401
|
return {
|
|
10044
10402
|
status: "ok",
|
|
10045
10403
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10048,7 +10406,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10048
10406
|
stage: "task_review_fix",
|
|
10049
10407
|
nextAction: buildAction(
|
|
10050
10408
|
"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}.`,
|
|
10409
|
+
finalReviewRound ? `Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, and commit the new code tip without requesting another review. Preserve any remaining findings and the post-review target change as residual risks before automatic task completion.` : `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
10410
|
false,
|
|
10053
10411
|
null,
|
|
10054
10412
|
void 0,
|
|
@@ -10081,7 +10439,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10081
10439
|
};
|
|
10082
10440
|
}
|
|
10083
10441
|
if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
|
|
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
|
|
10442
|
+
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 round limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings${evidenceMatchesTarget ? "" : " and any post-review unreviewed target changes"} as residual risks and commit the review evidence before continuing automatically.` : `The review round limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings${evidenceMatchesTarget ? "" : " and any post-review unreviewed target changes"} as residual risks, mark the task DONE, and commit the review evidence before continuing automatically.`;
|
|
10085
10443
|
return {
|
|
10086
10444
|
status: "ok",
|
|
10087
10445
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10101,6 +10459,26 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10101
10459
|
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10102
10460
|
};
|
|
10103
10461
|
}
|
|
10462
|
+
if (reviewTask.reviewDecisionOutcome === "changes_requested" && reviewContext.reviewRound >= reviewContext.maxReviewRounds) {
|
|
10463
|
+
return {
|
|
10464
|
+
status: "ok",
|
|
10465
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10466
|
+
docsDir: config.docsDir,
|
|
10467
|
+
featureRef: buildFeatureRef(feature),
|
|
10468
|
+
stage: "task_review",
|
|
10469
|
+
nextAction: buildAction(
|
|
10470
|
+
"task_review",
|
|
10471
|
+
`The review round limit was reached for ${reviewTask.taskId || reviewTask.title} at round ${reviewContext.reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed and preserve the remaining findings and any post-review target changes as residual risks before completing the task automatically.`,
|
|
10472
|
+
false,
|
|
10473
|
+
null,
|
|
10474
|
+
void 0,
|
|
10475
|
+
reviewContext
|
|
10476
|
+
),
|
|
10477
|
+
approvalRequired: false,
|
|
10478
|
+
implementationAllowed: false,
|
|
10479
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10480
|
+
};
|
|
10481
|
+
}
|
|
10104
10482
|
return {
|
|
10105
10483
|
status: "ok",
|
|
10106
10484
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10109,13 +10487,16 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10109
10487
|
stage: "task_review",
|
|
10110
10488
|
nextAction: buildAction(
|
|
10111
10489
|
"task_review",
|
|
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.`,
|
|
10490
|
+
`Delegate fresh read-only review round ${evidenceMatchesTarget ? reviewContext.reviewRound : reviewTask.reviewDecisionOutcome ? Math.min(reviewContext.reviewRound + 1, reviewContext.maxReviewRounds) : 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.`,
|
|
10113
10491
|
false,
|
|
10114
10492
|
null,
|
|
10115
10493
|
resolveAgentReviewer(config, "task"),
|
|
10116
10494
|
{
|
|
10117
10495
|
...reviewContext,
|
|
10118
|
-
reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ?
|
|
10496
|
+
reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? Math.min(
|
|
10497
|
+
reviewContext.reviewRound + 1,
|
|
10498
|
+
reviewContext.maxReviewRounds
|
|
10499
|
+
) : reviewContext.reviewRound
|
|
10119
10500
|
}
|
|
10120
10501
|
),
|
|
10121
10502
|
approvalRequired: false,
|
|
@@ -10225,6 +10606,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10225
10606
|
const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
|
|
10226
10607
|
if (reviewContext && tasks.prePrDecisionOutcome === "changes_requested") {
|
|
10227
10608
|
if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
|
|
10609
|
+
const finalReviewRound = reviewContext.reviewRound >= reviewContext.maxReviewRounds;
|
|
10228
10610
|
return {
|
|
10229
10611
|
status: "ok",
|
|
10230
10612
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10233,7 +10615,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10233
10615
|
stage: "feature_review_fix",
|
|
10234
10616
|
nextAction: buildAction(
|
|
10235
10617
|
"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.`,
|
|
10618
|
+
finalReviewRound ? `Address the Feature review findings and commit the remediation without requesting another review. Preserve any remaining findings and the post-review target change as residual risks before continuing automatically.` : `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
10619
|
false,
|
|
10238
10620
|
null,
|
|
10239
10621
|
void 0,
|
|
@@ -10266,7 +10648,12 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10266
10648
|
};
|
|
10267
10649
|
}
|
|
10268
10650
|
if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
|
|
10269
|
-
const
|
|
10651
|
+
const rawRequestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
|
|
10652
|
+
const requestedRound = reviewContext && typeof rawRequestedRound === "number" ? Math.min(
|
|
10653
|
+
rawRequestedRound,
|
|
10654
|
+
reviewContext.maxReviewRounds
|
|
10655
|
+
) : rawRequestedRound;
|
|
10656
|
+
const reviewLimitExhausted = !!reviewContext && tasks.prePrDecisionOutcome === "changes_requested" && reviewContext.reviewRound >= reviewContext.maxReviewRounds;
|
|
10270
10657
|
return {
|
|
10271
10658
|
status: "ok",
|
|
10272
10659
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -10275,10 +10662,10 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
10275
10662
|
stage: "pre_pr_review",
|
|
10276
10663
|
nextAction: buildAction(
|
|
10277
10664
|
"pre_pr_review",
|
|
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.",
|
|
10665
|
+
reviewLimitExhausted ? `The Feature review round limit was reached at round ${reviewContext.reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed and preserve the remaining findings and any post-review target changes as residual risks before continuing automatically.` : 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.",
|
|
10279
10666
|
false,
|
|
10280
10667
|
null,
|
|
10281
|
-
reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10668
|
+
reviewTarget && !reviewLimitExhausted ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10282
10669
|
reviewContext ? { ...reviewContext, reviewRound: requestedRound } : void 0
|
|
10283
10670
|
),
|
|
10284
10671
|
approvalRequired: false,
|