lee-spec-kit 0.9.3 → 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/README.en.md +8 -0
- package/README.md +8 -0
- package/dist/{hooks-373Z6JG2.js → hooks-67CU3GZC.js} +16 -2
- package/dist/hooks-67CU3GZC.js.map +1 -0
- package/dist/index.js +1339 -118
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +55 -5
- package/templates/en/common/agents/agents.md +8 -3
- package/templates/en/common/agents/skills/create-feature.md +3 -1
- package/templates/en/common/agents/skills/create-pr.md +6 -3
- package/templates/en/common/agents/skills/execute-task.md +9 -1
- package/templates/en/common/features/README.md +12 -4
- package/templates/en/common/features/feature-base/plan.md +43 -4
- package/templates/en/common/features/feature-base/tasks.md +16 -3
- package/templates/ko/common/README.md +54 -5
- package/templates/ko/common/agents/agents.md +8 -3
- package/templates/ko/common/agents/skills/create-feature.md +3 -1
- package/templates/ko/common/agents/skills/create-pr.md +6 -3
- package/templates/ko/common/agents/skills/execute-task.md +9 -1
- package/templates/ko/common/features/README.md +12 -4
- package/templates/ko/common/features/feature-base/plan.md +43 -4
- package/templates/ko/common/features/feature-base/tasks.md +16 -3
- package/dist/hooks-373Z6JG2.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -199,6 +199,28 @@ var koCli = {
|
|
|
199
199
|
"init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
|
|
200
200
|
"init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
|
|
201
201
|
"init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
|
|
202
|
+
"init.prompt.workflowMode": "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
203
|
+
"init.choice.workflow.github.title": "GitHub - Issue\uC640 PR \uAE30\uBC18 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
204
|
+
"init.choice.workflow.github.desc": "\uC6D0\uACA9 Issue, \uBE0C\uB79C\uCE58, PR, \uB9AC\uBDF0, \uBCD1\uD569 \uB2E8\uACC4\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4",
|
|
205
|
+
"init.choice.workflow.local.title": "Local - \uB85C\uCEEC \uD1B5\uD569 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
206
|
+
"init.choice.workflow.local.desc": "GitHub \uC5C6\uC774 \uAD00\uB9AC\uD615 worktree\uC640 \uB85C\uCEEC \uAC80\uC99D\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4",
|
|
207
|
+
"init.prompt.workflowSetup": "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC790\uB3D9\uD654 \uBC29\uC2DD\uC744 \uC124\uC815\uD558\uC138\uC694:",
|
|
208
|
+
"init.choice.workflowSetup.recommended.title": "\uAD8C\uC7A5 \uC124\uC815 - \uADE0\uD615 \uC7A1\uD78C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9",
|
|
209
|
+
"init.choice.workflowSetup.recommended.desc": "Task \uAD6C\uD604\uC744 \uC704\uC784\uD558\uACE0 Plan\uACFC Feature\uB97C \uAC80\uC218\uD569\uB2C8\uB2E4",
|
|
210
|
+
"init.choice.workflowSetup.custom.title": "\uC9C1\uC811 \uC124\uC815 - \uC635\uC158 \uC120\uD0DD",
|
|
211
|
+
"init.choice.workflowSetup.custom.desc": "Task \uC704\uC784, \uAC80\uC218 \uB2E8\uACC4, \uB85C\uCEEC \uD1B5\uD569 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD569\uB2C8\uB2E4",
|
|
212
|
+
"init.prompt.taskAgent": "Task \uAD6C\uD604\uC744 \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uC5D0\uAC8C \uC704\uC784\uD560\uAE4C\uC694?",
|
|
213
|
+
"init.choice.taskAgent.on": "\uC0AC\uC6A9 - \uAC01 Task \uAD6C\uD604\uC744 \uC704\uC784",
|
|
214
|
+
"init.choice.taskAgent.off": "\uC0AC\uC6A9 \uC548 \uD568 - \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC9C1\uC811 \uAD6C\uD604",
|
|
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:",
|
|
217
|
+
"init.choice.review.plan": "Plan",
|
|
218
|
+
"init.choice.review.task": "Task",
|
|
219
|
+
"init.choice.review.feature": "Feature",
|
|
220
|
+
"init.prompt.completionStrategy": "\uB85C\uCEEC \uD1B5\uD569 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
221
|
+
"init.choice.completionStrategy.localFf": "local-ff - \uAC80\uC99D\uB41C Feature \uBE0C\uB79C\uCE58\uB97C fast-forward \uD1B5\uD569",
|
|
222
|
+
"init.choice.completionStrategy.localSquash": "local-squash - \uAC80\uC99D\uB41C \uD558\uB098\uC758 \uCEE4\uBC0B\uC73C\uB85C \uD1B5\uD569",
|
|
223
|
+
"init.choice.completionStrategy.none": "none - Feature \uBE0C\uB79C\uCE58\uB97C \uD1B5\uD569\uD558\uC9C0 \uC54A\uACE0 \uC885\uB8CC",
|
|
202
224
|
"init.prompt.componentRepoPath": "{component} \uCEF4\uD3EC\uB10C\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
203
225
|
"init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
204
226
|
"init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
@@ -208,11 +230,20 @@ var koCli = {
|
|
|
208
230
|
"init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
|
|
209
231
|
"init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
210
232
|
"init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
233
|
+
"init.log.configSummaryTitle": "\u2699\uFE0F \uC124\uC815 \uC694\uC57D",
|
|
211
234
|
"init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
|
|
212
235
|
"init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
|
|
213
236
|
"init.log.typeLabel": "\uD0C0\uC785",
|
|
214
237
|
"init.log.langLabel": "\uC5B8\uC5B4",
|
|
215
238
|
"init.log.pathLabel": "\uACBD\uB85C",
|
|
239
|
+
"init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
240
|
+
"init.log.taskAgentLabel": "Task \uAD6C\uD604",
|
|
241
|
+
"init.log.reviewsLabel": "\uAC80\uC218",
|
|
242
|
+
"init.log.maxReviewRoundsLabel": "\uB9AC\uBDF0 \uC9C0\uC801 \uC790\uB3D9 \uBC18\uC601 \uD69F\uC218",
|
|
243
|
+
"init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
|
|
244
|
+
"init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
|
|
245
|
+
"init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
|
|
246
|
+
"init.summary.reviews.none": "\uC5C6\uC74C",
|
|
216
247
|
"init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
|
|
217
248
|
"init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
218
249
|
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
@@ -482,6 +513,28 @@ var enCli = {
|
|
|
482
513
|
"init.choice.docsRepo.embedded.desc": "Pushed together with the project",
|
|
483
514
|
"init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
|
|
484
515
|
"init.choice.docsRepo.standalone.desc": "Configure push settings separately",
|
|
516
|
+
"init.prompt.workflowMode": "Select workflow mode:",
|
|
517
|
+
"init.choice.workflow.github.title": "GitHub - Issue and pull request workflow",
|
|
518
|
+
"init.choice.workflow.github.desc": "Use remote Issue, branch, pull request, review, and merge gates",
|
|
519
|
+
"init.choice.workflow.local.title": "Local - local integration workflow",
|
|
520
|
+
"init.choice.workflow.local.desc": "Use managed worktrees and local verification without requiring GitHub",
|
|
521
|
+
"init.prompt.workflowSetup": "Configure workflow automation:",
|
|
522
|
+
"init.choice.workflowSetup.recommended.title": "Recommended - use balanced defaults",
|
|
523
|
+
"init.choice.workflowSetup.recommended.desc": "Delegate task implementation and review Plan plus Feature",
|
|
524
|
+
"init.choice.workflowSetup.custom.title": "Custom - choose workflow options",
|
|
525
|
+
"init.choice.workflowSetup.custom.desc": "Select task delegation, review gates, and local integration",
|
|
526
|
+
"init.prompt.taskAgent": "Delegate task implementation to a subagent?",
|
|
527
|
+
"init.choice.taskAgent.on": "On - delegate each task implementation",
|
|
528
|
+
"init.choice.taskAgent.off": "Off - implement tasks in the main agent",
|
|
529
|
+
"init.prompt.reviews": "Select independent review gates:",
|
|
530
|
+
"init.prompt.maxReviewRounds": "Maximum automatic review finding remediation rounds before asking the user:",
|
|
531
|
+
"init.choice.review.plan": "Plan",
|
|
532
|
+
"init.choice.review.task": "Task",
|
|
533
|
+
"init.choice.review.feature": "Feature",
|
|
534
|
+
"init.prompt.completionStrategy": "Select local integration strategy:",
|
|
535
|
+
"init.choice.completionStrategy.localFf": "local-ff - fast-forward the verified Feature branch",
|
|
536
|
+
"init.choice.completionStrategy.localSquash": "local-squash - integrate as one verified commit",
|
|
537
|
+
"init.choice.completionStrategy.none": "none - finish without integrating the Feature branch",
|
|
485
538
|
"init.prompt.componentRepoPath": 'Enter repository path for component "{component}":',
|
|
486
539
|
"init.prompt.projectRepoPath": "Enter project repository path:",
|
|
487
540
|
"init.validation.enterPath": "Please enter a path",
|
|
@@ -491,11 +544,20 @@ var enCli = {
|
|
|
491
544
|
"init.prompt.remoteUrl": "Enter remote repository URL:",
|
|
492
545
|
"init.validation.enterUrl": "Please enter a URL",
|
|
493
546
|
"init.prompt.overwrite": "{dir} already exists. Overwrite?",
|
|
547
|
+
"init.log.configSummaryTitle": "\u2699\uFE0F Configuration summary",
|
|
494
548
|
"init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
|
|
495
549
|
"init.log.projectLabel": "Project",
|
|
496
550
|
"init.log.typeLabel": "Type",
|
|
497
551
|
"init.log.langLabel": "Lang",
|
|
498
552
|
"init.log.pathLabel": "Path",
|
|
553
|
+
"init.log.workflowLabel": "Workflow",
|
|
554
|
+
"init.log.taskAgentLabel": "Task implementation",
|
|
555
|
+
"init.log.reviewsLabel": "Reviews",
|
|
556
|
+
"init.log.maxReviewRoundsLabel": "Review remediation rounds",
|
|
557
|
+
"init.log.completionStrategyLabel": "Local integration",
|
|
558
|
+
"init.summary.taskAgent.on": "subagent",
|
|
559
|
+
"init.summary.taskAgent.off": "main agent",
|
|
560
|
+
"init.summary.reviews.none": "none",
|
|
499
561
|
"init.log.docsCreated": "\u2705 Docs structure created!",
|
|
500
562
|
"init.log.nextStepsTitle": "Next steps:",
|
|
501
563
|
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
@@ -1295,7 +1357,7 @@ Before taking the next workflow step:
|
|
|
1295
1357
|
3. When relevant, also read \`issue.md\` and \`pr.md\`
|
|
1296
1358
|
4. Run \`npx lee-spec-kit workflow-stage <feature-ref> --json\` and follow only the returned \`nextAction\`
|
|
1297
1359
|
5. If \`workflow-stage --json\` returns \`primaryActionLabel\` and \`actionOptions\`, treat \`primaryActionLabel\` as the default option label and present the exact \`actionOptions[*].reply\` tokens to the user before continuing
|
|
1298
|
-
6. Do not
|
|
1360
|
+
6. Do not modify implementation code unless \`implementationAllowed === true\`; normal task work uses \`stage === "implementation"\`, while \`task_review_fix\`, \`feature_review_fix\`, and \`feature_remediation\` are the only review/remediation exceptions
|
|
1299
1361
|
7. Treat stages before implementation as hard gates:
|
|
1300
1362
|
- spec approval plus plan / tasks readiness
|
|
1301
1363
|
- issue preparation / issue creation
|
|
@@ -1306,10 +1368,15 @@ Before taking the next workflow step:
|
|
|
1306
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
|
|
1307
1369
|
11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
|
|
1308
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
|
|
1309
|
-
13. When \`workflow-stage --json\` returns \`nextAction.category === "
|
|
1310
|
-
14.
|
|
1311
|
-
15.
|
|
1312
|
-
16.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1313
1380
|
|
|
1314
1381
|
Approval and remote actions:
|
|
1315
1382
|
|
|
@@ -1898,7 +1965,7 @@ var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
|
|
|
1898
1965
|
"implementation_approve",
|
|
1899
1966
|
"local_merge"
|
|
1900
1967
|
];
|
|
1901
|
-
var
|
|
1968
|
+
var AGENT_REVIEW_REASONING_EFFORTS = [
|
|
1902
1969
|
"low",
|
|
1903
1970
|
"medium",
|
|
1904
1971
|
"high",
|
|
@@ -1913,7 +1980,7 @@ function createDefaultApprovalConfig() {
|
|
|
1913
1980
|
requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
|
|
1914
1981
|
};
|
|
1915
1982
|
}
|
|
1916
|
-
function
|
|
1983
|
+
function createDefaultAgentReviewerConfig() {
|
|
1917
1984
|
return {
|
|
1918
1985
|
type: "subagent",
|
|
1919
1986
|
model: "inherit",
|
|
@@ -1921,6 +1988,15 @@ function createDefaultPrePrReviewerConfig() {
|
|
|
1921
1988
|
onUnavailable: "inherit"
|
|
1922
1989
|
};
|
|
1923
1990
|
}
|
|
1991
|
+
function createDefaultAgentExecutionTaskConfig() {
|
|
1992
|
+
return {
|
|
1993
|
+
enabled: true,
|
|
1994
|
+
type: "subagent",
|
|
1995
|
+
model: "inherit",
|
|
1996
|
+
reasoningEffort: "high",
|
|
1997
|
+
onUnavailable: "inherit"
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
1924
2000
|
|
|
1925
2001
|
// src/commands/init.ts
|
|
1926
2002
|
function checkGitRepo(cwd) {
|
|
@@ -2005,6 +2081,44 @@ function hasStandaloneProjectRepoOutsideWorkspaceGitRoot(workspaceGitRoot, proje
|
|
|
2005
2081
|
return !!projectGitRoot && path8.resolve(projectGitRoot) !== path8.resolve(workspaceGitRoot);
|
|
2006
2082
|
});
|
|
2007
2083
|
}
|
|
2084
|
+
var INIT_REVIEW_PHASES = ["plan", "task", "feature"];
|
|
2085
|
+
function parseInitReviews(value) {
|
|
2086
|
+
if (typeof value === "undefined") return void 0;
|
|
2087
|
+
const normalized = value.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
2088
|
+
if (normalized.length === 1 && normalized[0] === "none") return [];
|
|
2089
|
+
if (normalized.length === 0 || normalized.includes("none") || normalized.some(
|
|
2090
|
+
(entry) => !INIT_REVIEW_PHASES.includes(entry)
|
|
2091
|
+
)) {
|
|
2092
|
+
throw createCliError(
|
|
2093
|
+
"INVALID_ARGUMENT",
|
|
2094
|
+
"`--reviews` must be a comma-separated subset of `plan,task,feature`, or `none`."
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
return INIT_REVIEW_PHASES.filter((phase) => normalized.includes(phase));
|
|
2098
|
+
}
|
|
2099
|
+
function assertValidInitWorkflowOptions(options) {
|
|
2100
|
+
if (options.taskAgent && !["on", "off"].includes(options.taskAgent)) {
|
|
2101
|
+
throw createCliError(
|
|
2102
|
+
"INVALID_ARGUMENT",
|
|
2103
|
+
"`--task-agent` must be `on` or `off`."
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
if (options.completionStrategy && !["local-ff", "local-squash", "none"].includes(options.completionStrategy)) {
|
|
2107
|
+
throw createCliError(
|
|
2108
|
+
"INVALID_ARGUMENT",
|
|
2109
|
+
"`--completion-strategy` must be `local-ff`, `local-squash`, or `none`."
|
|
2110
|
+
);
|
|
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
|
+
}
|
|
2121
|
+
}
|
|
2008
2122
|
function initCommand(program2) {
|
|
2009
2123
|
program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option(
|
|
2010
2124
|
"-t, --type <type>",
|
|
@@ -2012,7 +2126,16 @@ function initCommand(program2) {
|
|
|
2012
2126
|
).option(
|
|
2013
2127
|
"--components <list>",
|
|
2014
2128
|
"Component list for multi (comma-separated, e.g. app,api,worker)"
|
|
2015
|
-
).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("
|
|
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(
|
|
2130
|
+
"--reviews <list>",
|
|
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)"
|
|
2135
|
+
).option(
|
|
2136
|
+
"--completion-strategy <strategy>",
|
|
2137
|
+
"Local completion: local-ff | local-squash | none"
|
|
2138
|
+
).option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("--docs-repo <mode>", "Docs repository mode: embedded | standalone").option(
|
|
2016
2139
|
"--project-root <path>",
|
|
2017
2140
|
"Project root path (standalone single) or JSON map for standalone multi"
|
|
2018
2141
|
).option(
|
|
@@ -2042,6 +2165,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2042
2165
|
});
|
|
2043
2166
|
}
|
|
2044
2167
|
async function runInit(options) {
|
|
2168
|
+
assertValidInitWorkflowOptions(options);
|
|
2045
2169
|
const cwd = process.cwd();
|
|
2046
2170
|
const defaultName = path8.basename(cwd);
|
|
2047
2171
|
let projectName = options.name || defaultName;
|
|
@@ -2049,6 +2173,10 @@ async function runInit(options) {
|
|
|
2049
2173
|
let components = parseComponentsOption(options.components);
|
|
2050
2174
|
let lang = options.lang || "en";
|
|
2051
2175
|
let workflowMode = options.workflow || "github";
|
|
2176
|
+
let taskAgentEnabled = options.taskAgent !== "off";
|
|
2177
|
+
let enabledReviews = parseInitReviews(options.reviews) ?? ["plan", "feature"];
|
|
2178
|
+
let maxReviewRounds = Number(options.maxReviewRounds || 1);
|
|
2179
|
+
let completionStrategy = options.completionStrategy || "local-ff";
|
|
2052
2180
|
let docsRepo = options.docsRepo || "embedded";
|
|
2053
2181
|
let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
|
|
2054
2182
|
let docsRemote = options.docsRemote;
|
|
@@ -2168,6 +2296,24 @@ async function runInit(options) {
|
|
|
2168
2296
|
}
|
|
2169
2297
|
],
|
|
2170
2298
|
initial: 0
|
|
2299
|
+
},
|
|
2300
|
+
{
|
|
2301
|
+
type: options.workflow ? null : "select",
|
|
2302
|
+
name: "workflowMode",
|
|
2303
|
+
message: tr(lang, "cli", "init.prompt.workflowMode"),
|
|
2304
|
+
choices: [
|
|
2305
|
+
{
|
|
2306
|
+
title: tr(lang, "cli", "init.choice.workflow.github.title"),
|
|
2307
|
+
value: "github",
|
|
2308
|
+
description: tr(lang, "cli", "init.choice.workflow.github.desc")
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
title: tr(lang, "cli", "init.choice.workflow.local.title"),
|
|
2312
|
+
value: "local",
|
|
2313
|
+
description: tr(lang, "cli", "init.choice.workflow.local.desc")
|
|
2314
|
+
}
|
|
2315
|
+
],
|
|
2316
|
+
initial: 0
|
|
2171
2317
|
}
|
|
2172
2318
|
],
|
|
2173
2319
|
{
|
|
@@ -2179,6 +2325,7 @@ async function runInit(options) {
|
|
|
2179
2325
|
projectName = response.projectName || projectName;
|
|
2180
2326
|
projectType = response.projectType || projectType;
|
|
2181
2327
|
docsRepo = response.docsRepo || "embedded";
|
|
2328
|
+
workflowMode = response.workflowMode || workflowMode;
|
|
2182
2329
|
if (docsRepo === "standalone") {
|
|
2183
2330
|
const resolvedType = normalizeProjectType(
|
|
2184
2331
|
String(projectType || response.projectType || "multi")
|
|
@@ -2290,6 +2437,138 @@ async function runInit(options) {
|
|
|
2290
2437
|
docsRemote = remoteResponse.docsRemote || docsRemote;
|
|
2291
2438
|
}
|
|
2292
2439
|
}
|
|
2440
|
+
const hasExplicitWorkflowSetup = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.maxReviewRounds !== "undefined" || typeof options.completionStrategy !== "undefined";
|
|
2441
|
+
let customizeWorkflow = hasExplicitWorkflowSetup;
|
|
2442
|
+
if (!hasExplicitWorkflowSetup) {
|
|
2443
|
+
const setupResponse = await prompts(
|
|
2444
|
+
[
|
|
2445
|
+
{
|
|
2446
|
+
type: "select",
|
|
2447
|
+
name: "workflowSetup",
|
|
2448
|
+
message: tr(lang, "cli", "init.prompt.workflowSetup"),
|
|
2449
|
+
choices: [
|
|
2450
|
+
{
|
|
2451
|
+
title: tr(
|
|
2452
|
+
lang,
|
|
2453
|
+
"cli",
|
|
2454
|
+
"init.choice.workflowSetup.recommended.title"
|
|
2455
|
+
),
|
|
2456
|
+
value: "recommended",
|
|
2457
|
+
description: tr(
|
|
2458
|
+
lang,
|
|
2459
|
+
"cli",
|
|
2460
|
+
"init.choice.workflowSetup.recommended.desc"
|
|
2461
|
+
)
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
title: tr(
|
|
2465
|
+
lang,
|
|
2466
|
+
"cli",
|
|
2467
|
+
"init.choice.workflowSetup.custom.title"
|
|
2468
|
+
),
|
|
2469
|
+
value: "custom",
|
|
2470
|
+
description: tr(
|
|
2471
|
+
lang,
|
|
2472
|
+
"cli",
|
|
2473
|
+
"init.choice.workflowSetup.custom.desc"
|
|
2474
|
+
)
|
|
2475
|
+
}
|
|
2476
|
+
],
|
|
2477
|
+
initial: 0
|
|
2478
|
+
}
|
|
2479
|
+
],
|
|
2480
|
+
{
|
|
2481
|
+
onCancel: () => {
|
|
2482
|
+
throw new Error("canceled");
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
);
|
|
2486
|
+
customizeWorkflow = setupResponse.workflowSetup === "custom";
|
|
2487
|
+
}
|
|
2488
|
+
if (customizeWorkflow) {
|
|
2489
|
+
const workflowResponse = await prompts(
|
|
2490
|
+
[
|
|
2491
|
+
{
|
|
2492
|
+
type: typeof options.taskAgent === "undefined" ? "select" : null,
|
|
2493
|
+
name: "taskAgent",
|
|
2494
|
+
message: tr(lang, "cli", "init.prompt.taskAgent"),
|
|
2495
|
+
choices: [
|
|
2496
|
+
{
|
|
2497
|
+
title: tr(lang, "cli", "init.choice.taskAgent.on"),
|
|
2498
|
+
value: true
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
title: tr(lang, "cli", "init.choice.taskAgent.off"),
|
|
2502
|
+
value: false
|
|
2503
|
+
}
|
|
2504
|
+
],
|
|
2505
|
+
initial: 0
|
|
2506
|
+
},
|
|
2507
|
+
{
|
|
2508
|
+
type: typeof options.reviews === "undefined" ? "multiselect" : null,
|
|
2509
|
+
name: "reviews",
|
|
2510
|
+
message: tr(lang, "cli", "init.prompt.reviews"),
|
|
2511
|
+
choices: INIT_REVIEW_PHASES.map((phase) => ({
|
|
2512
|
+
title: tr(lang, "cli", `init.choice.review.${phase}`),
|
|
2513
|
+
value: phase,
|
|
2514
|
+
selected: phase !== "task"
|
|
2515
|
+
}))
|
|
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
|
+
},
|
|
2524
|
+
{
|
|
2525
|
+
type: workflowMode === "local" && typeof options.completionStrategy === "undefined" ? "select" : null,
|
|
2526
|
+
name: "completionStrategy",
|
|
2527
|
+
message: tr(lang, "cli", "init.prompt.completionStrategy"),
|
|
2528
|
+
choices: [
|
|
2529
|
+
{
|
|
2530
|
+
title: tr(
|
|
2531
|
+
lang,
|
|
2532
|
+
"cli",
|
|
2533
|
+
"init.choice.completionStrategy.localFf"
|
|
2534
|
+
),
|
|
2535
|
+
value: "local-ff"
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
title: tr(
|
|
2539
|
+
lang,
|
|
2540
|
+
"cli",
|
|
2541
|
+
"init.choice.completionStrategy.localSquash"
|
|
2542
|
+
),
|
|
2543
|
+
value: "local-squash"
|
|
2544
|
+
},
|
|
2545
|
+
{
|
|
2546
|
+
title: tr(lang, "cli", "init.choice.completionStrategy.none"),
|
|
2547
|
+
value: "none"
|
|
2548
|
+
}
|
|
2549
|
+
],
|
|
2550
|
+
initial: 0
|
|
2551
|
+
}
|
|
2552
|
+
],
|
|
2553
|
+
{
|
|
2554
|
+
onCancel: () => {
|
|
2555
|
+
throw new Error("canceled");
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
);
|
|
2559
|
+
if (typeof workflowResponse.taskAgent === "boolean") {
|
|
2560
|
+
taskAgentEnabled = workflowResponse.taskAgent;
|
|
2561
|
+
}
|
|
2562
|
+
if (Array.isArray(workflowResponse.reviews)) {
|
|
2563
|
+
enabledReviews = INIT_REVIEW_PHASES.filter(
|
|
2564
|
+
(phase) => workflowResponse.reviews.includes(phase)
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
if (Number.isInteger(workflowResponse.maxReviewRounds) && workflowResponse.maxReviewRounds > 0) {
|
|
2568
|
+
maxReviewRounds = workflowResponse.maxReviewRounds;
|
|
2569
|
+
}
|
|
2570
|
+
completionStrategy = workflowResponse.completionStrategy || completionStrategy;
|
|
2571
|
+
}
|
|
2293
2572
|
}
|
|
2294
2573
|
if (!projectType) {
|
|
2295
2574
|
projectType = "multi";
|
|
@@ -2315,6 +2594,12 @@ async function runInit(options) {
|
|
|
2315
2594
|
tr(lang, "cli", "validation.context.workflowMode"),
|
|
2316
2595
|
lang
|
|
2317
2596
|
);
|
|
2597
|
+
if (workflowMode !== "local" && options.completionStrategy) {
|
|
2598
|
+
throw createCliError(
|
|
2599
|
+
"INVALID_ARGUMENT",
|
|
2600
|
+
"`--completion-strategy` can only be used with `--workflow local`."
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2318
2603
|
if (projectType === "single") {
|
|
2319
2604
|
if (components.length > 0) {
|
|
2320
2605
|
throw createCliError(
|
|
@@ -2478,7 +2763,7 @@ async function runInit(options) {
|
|
|
2478
2763
|
}
|
|
2479
2764
|
}
|
|
2480
2765
|
console.log();
|
|
2481
|
-
console.log(chalk.blue(tr(lang, "cli", "init.log.
|
|
2766
|
+
console.log(chalk.blue(tr(lang, "cli", "init.log.configSummaryTitle")));
|
|
2482
2767
|
console.log(
|
|
2483
2768
|
chalk.gray(
|
|
2484
2769
|
` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
|
|
@@ -2493,6 +2778,40 @@ async function runInit(options) {
|
|
|
2493
2778
|
console.log(
|
|
2494
2779
|
chalk.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
2495
2780
|
);
|
|
2781
|
+
console.log(
|
|
2782
|
+
chalk.gray(
|
|
2783
|
+
` ${tr(lang, "cli", "init.log.workflowLabel")}: ${workflowMode}`
|
|
2784
|
+
)
|
|
2785
|
+
);
|
|
2786
|
+
console.log(
|
|
2787
|
+
chalk.gray(
|
|
2788
|
+
` ${tr(lang, "cli", "init.log.taskAgentLabel")}: ${tr(
|
|
2789
|
+
lang,
|
|
2790
|
+
"cli",
|
|
2791
|
+
taskAgentEnabled ? "init.summary.taskAgent.on" : "init.summary.taskAgent.off"
|
|
2792
|
+
)}`
|
|
2793
|
+
)
|
|
2794
|
+
);
|
|
2795
|
+
const reviewSummary = enabledReviews.length > 0 ? enabledReviews.map((phase) => tr(lang, "cli", `init.choice.review.${phase}`)).join(", ") : tr(lang, "cli", "init.summary.reviews.none");
|
|
2796
|
+
console.log(
|
|
2797
|
+
chalk.gray(
|
|
2798
|
+
` ${tr(lang, "cli", "init.log.reviewsLabel")}: ${reviewSummary}`
|
|
2799
|
+
)
|
|
2800
|
+
);
|
|
2801
|
+
console.log(
|
|
2802
|
+
chalk.gray(
|
|
2803
|
+
` ${tr(lang, "cli", "init.log.maxReviewRoundsLabel")}: ${maxReviewRounds}`
|
|
2804
|
+
)
|
|
2805
|
+
);
|
|
2806
|
+
if (workflowMode === "local") {
|
|
2807
|
+
console.log(
|
|
2808
|
+
chalk.gray(
|
|
2809
|
+
` ${tr(lang, "cli", "init.log.completionStrategyLabel")}: ${completionStrategy}`
|
|
2810
|
+
)
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2813
|
+
console.log();
|
|
2814
|
+
console.log(chalk.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
2496
2815
|
console.log();
|
|
2497
2816
|
const templatesDir = getTemplatesDir();
|
|
2498
2817
|
const commonPath = path8.join(templatesDir, lang, "common");
|
|
@@ -2541,14 +2860,34 @@ async function runInit(options) {
|
|
|
2541
2860
|
taskCommitGate: "warn",
|
|
2542
2861
|
...workflowMode === "local" ? {
|
|
2543
2862
|
baseBranch: resolveInitialBaseBranch(cwd, docsRepo, projectRoot),
|
|
2544
|
-
completionStrategy
|
|
2863
|
+
completionStrategy,
|
|
2545
2864
|
deleteFeatureBranchAfterMerge: true,
|
|
2546
2865
|
featureChecks: [],
|
|
2547
2866
|
postMergeChecks: []
|
|
2548
2867
|
} : {},
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2868
|
+
agentExecution: {
|
|
2869
|
+
task: {
|
|
2870
|
+
...createDefaultAgentExecutionTaskConfig(),
|
|
2871
|
+
enabled: taskAgentEnabled
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2874
|
+
agentReview: {
|
|
2875
|
+
maxRounds: maxReviewRounds,
|
|
2876
|
+
plan: {
|
|
2877
|
+
enabled: enabledReviews.includes("plan"),
|
|
2878
|
+
evidenceMode: "path_required",
|
|
2879
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2880
|
+
},
|
|
2881
|
+
task: {
|
|
2882
|
+
enabled: enabledReviews.includes("task"),
|
|
2883
|
+
evidenceMode: "path_required",
|
|
2884
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2885
|
+
},
|
|
2886
|
+
feature: {
|
|
2887
|
+
enabled: enabledReviews.includes("feature"),
|
|
2888
|
+
evidenceMode: "path_required",
|
|
2889
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2890
|
+
}
|
|
2552
2891
|
}
|
|
2553
2892
|
},
|
|
2554
2893
|
pr: {
|
|
@@ -2765,17 +3104,12 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2765
3104
|
const lines = next.split("\n");
|
|
2766
3105
|
const filtered = [];
|
|
2767
3106
|
for (const line of lines) {
|
|
2768
|
-
if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|
|
|
2769
|
-
line
|
|
2770
|
-
)) {
|
|
2771
|
-
continue;
|
|
2772
|
-
}
|
|
2773
|
-
if (/^\s*-\s*\*\*(Pre-PR Evidence|PR 전 리뷰 Evidence)\*\*\s*:/.test(
|
|
3107
|
+
if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|PR Review|PR 리뷰|PR Review Evidence|PR 리뷰 Evidence)\*\*\s*:/.test(
|
|
2774
3108
|
line
|
|
2775
3109
|
)) {
|
|
2776
3110
|
continue;
|
|
2777
3111
|
}
|
|
2778
|
-
if (/^\s*-\s*\*\*(
|
|
3112
|
+
if (/^\s*-\s*\*\*(PR Review Decision|PR 리뷰 Decision)\*\*\s*:/.test(
|
|
2779
3113
|
line
|
|
2780
3114
|
)) {
|
|
2781
3115
|
continue;
|
|
@@ -2789,9 +3123,17 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2789
3123
|
if (/^\s*-\s*Example:\s*review note link/i.test(line)) continue;
|
|
2790
3124
|
if (/^\s*-\s*사전 리뷰 최종 결과/.test(line)) continue;
|
|
2791
3125
|
if (/^\s*-\s*예:\s*리뷰 노트 링크/.test(line)) continue;
|
|
3126
|
+
if (/^\s*-\s*PR creation requires/i.test(line)) continue;
|
|
3127
|
+
if (/^\s*-\s*PR 생성 전/.test(line)) continue;
|
|
3128
|
+
if (/agents\/skills\/create-pr\.md/.test(line)) continue;
|
|
3129
|
+
if (/^\s*-\s*Mark .*PR review handoff/i.test(line)) continue;
|
|
3130
|
+
if (/^\s*-\s*PR 리뷰 handoff/.test(line)) continue;
|
|
3131
|
+
if (/^\s*-\s*Record why\/how review comments/i.test(line)) continue;
|
|
3132
|
+
if (/^\s*-\s*리뷰 지적사항을/.test(line)) continue;
|
|
2792
3133
|
filtered.push(line);
|
|
2793
3134
|
}
|
|
2794
3135
|
next = filtered.join("\n");
|
|
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");
|
|
2795
3137
|
next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
|
|
2796
3138
|
return normalizeTrailingBlankLines(next);
|
|
2797
3139
|
}
|
|
@@ -3798,52 +4140,125 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
3798
4140
|
}
|
|
3799
4141
|
}
|
|
3800
4142
|
}
|
|
3801
|
-
if (!isPlainObject(workflow.
|
|
3802
|
-
workflow.
|
|
3803
|
-
changedPaths.push("workflow.
|
|
4143
|
+
if (!isPlainObject(workflow.agentExecution)) {
|
|
4144
|
+
workflow.agentExecution = {};
|
|
4145
|
+
changedPaths.push("workflow.agentExecution");
|
|
3804
4146
|
}
|
|
3805
|
-
const
|
|
4147
|
+
const agentExecution = workflow.agentExecution;
|
|
4148
|
+
if (!isPlainObject(agentExecution.task)) {
|
|
4149
|
+
agentExecution.task = {};
|
|
4150
|
+
changedPaths.push("workflow.agentExecution.task");
|
|
4151
|
+
}
|
|
4152
|
+
const taskExecution = agentExecution.task;
|
|
4153
|
+
const defaultTaskExecution = createDefaultAgentExecutionTaskConfig();
|
|
3806
4154
|
setIfMissing(
|
|
3807
|
-
|
|
3808
|
-
"
|
|
3809
|
-
|
|
3810
|
-
"workflow.
|
|
3811
|
-
);
|
|
3812
|
-
if (
|
|
3813
|
-
|
|
3814
|
-
changedPaths.push("workflow.
|
|
3815
|
-
}
|
|
3816
|
-
if ("
|
|
3817
|
-
|
|
3818
|
-
changedPaths.push("workflow.
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
4155
|
+
taskExecution,
|
|
4156
|
+
"enabled",
|
|
4157
|
+
defaultTaskExecution.enabled,
|
|
4158
|
+
"workflow.agentExecution.task.enabled"
|
|
4159
|
+
);
|
|
4160
|
+
if (typeof taskExecution.enabled !== "boolean") {
|
|
4161
|
+
taskExecution.enabled = defaultTaskExecution.enabled;
|
|
4162
|
+
changedPaths.push("workflow.agentExecution.task.enabled");
|
|
4163
|
+
}
|
|
4164
|
+
if (taskExecution.type !== "subagent") {
|
|
4165
|
+
taskExecution.type = defaultTaskExecution.type;
|
|
4166
|
+
changedPaths.push("workflow.agentExecution.task.type");
|
|
4167
|
+
}
|
|
4168
|
+
if (typeof taskExecution.model !== "string" || !taskExecution.model.trim()) {
|
|
4169
|
+
taskExecution.model = defaultTaskExecution.model;
|
|
4170
|
+
changedPaths.push("workflow.agentExecution.task.model");
|
|
4171
|
+
} else if (taskExecution.model !== taskExecution.model.trim()) {
|
|
4172
|
+
taskExecution.model = taskExecution.model.trim();
|
|
4173
|
+
changedPaths.push("workflow.agentExecution.task.model");
|
|
4174
|
+
}
|
|
4175
|
+
if (typeof taskExecution.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
4176
|
+
taskExecution.reasoningEffort
|
|
4177
|
+
)) {
|
|
4178
|
+
taskExecution.reasoningEffort = defaultTaskExecution.reasoningEffort;
|
|
4179
|
+
changedPaths.push("workflow.agentExecution.task.reasoningEffort");
|
|
4180
|
+
}
|
|
4181
|
+
if (taskExecution.onUnavailable !== "inherit" && taskExecution.onUnavailable !== "error") {
|
|
4182
|
+
taskExecution.onUnavailable = defaultTaskExecution.onUnavailable;
|
|
4183
|
+
changedPaths.push("workflow.agentExecution.task.onUnavailable");
|
|
4184
|
+
}
|
|
4185
|
+
const legacyPrePrReview = isPlainObject(workflow.prePrReview) ? { ...workflow.prePrReview } : null;
|
|
4186
|
+
if (!isPlainObject(workflow.agentReview)) {
|
|
4187
|
+
workflow.agentReview = {};
|
|
4188
|
+
changedPaths.push("workflow.agentReview");
|
|
4189
|
+
}
|
|
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
|
+
}
|
|
4196
|
+
const normalizeAgentReviewPhase = (key, enabledDefault, legacySeed = null) => {
|
|
4197
|
+
const phasePath = `workflow.agentReview.${key}`;
|
|
4198
|
+
if (!isPlainObject(agentReview[key])) {
|
|
4199
|
+
agentReview[key] = legacySeed ? { ...legacySeed } : {};
|
|
4200
|
+
changedPaths.push(phasePath);
|
|
4201
|
+
}
|
|
4202
|
+
const phase = agentReview[key];
|
|
4203
|
+
setIfMissing(phase, "enabled", enabledDefault, `${phasePath}.enabled`);
|
|
4204
|
+
if (typeof phase.enabled !== "boolean") {
|
|
4205
|
+
phase.enabled = enabledDefault;
|
|
4206
|
+
changedPaths.push(`${phasePath}.enabled`);
|
|
4207
|
+
}
|
|
4208
|
+
setIfMissing(
|
|
4209
|
+
phase,
|
|
4210
|
+
"evidenceMode",
|
|
4211
|
+
"path_required",
|
|
4212
|
+
`${phasePath}.evidenceMode`
|
|
4213
|
+
);
|
|
4214
|
+
if (phase.evidenceMode !== "path_required" && phase.evidenceMode !== "any") {
|
|
4215
|
+
phase.evidenceMode = "path_required";
|
|
4216
|
+
changedPaths.push(`${phasePath}.evidenceMode`);
|
|
4217
|
+
}
|
|
4218
|
+
if ("findings" in phase) {
|
|
4219
|
+
delete phase.findings;
|
|
4220
|
+
changedPaths.push(`${phasePath}.findings`);
|
|
4221
|
+
}
|
|
4222
|
+
const defaultReviewer = createDefaultAgentReviewerConfig();
|
|
4223
|
+
if (!isPlainObject(phase.reviewer)) {
|
|
4224
|
+
phase.reviewer = defaultReviewer;
|
|
4225
|
+
changedPaths.push(`${phasePath}.reviewer`);
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
const reviewer = phase.reviewer;
|
|
3826
4229
|
if (reviewer.type !== "subagent") {
|
|
3827
4230
|
reviewer.type = defaultReviewer.type;
|
|
3828
|
-
changedPaths.push(
|
|
4231
|
+
changedPaths.push(`${phasePath}.reviewer.type`);
|
|
3829
4232
|
}
|
|
3830
4233
|
if (typeof reviewer.model !== "string" || !reviewer.model.trim()) {
|
|
3831
4234
|
reviewer.model = defaultReviewer.model;
|
|
3832
|
-
changedPaths.push(
|
|
4235
|
+
changedPaths.push(`${phasePath}.reviewer.model`);
|
|
3833
4236
|
} else if (reviewer.model !== reviewer.model.trim()) {
|
|
3834
4237
|
reviewer.model = reviewer.model.trim();
|
|
3835
|
-
changedPaths.push(
|
|
4238
|
+
changedPaths.push(`${phasePath}.reviewer.model`);
|
|
3836
4239
|
}
|
|
3837
|
-
if (typeof reviewer.reasoningEffort !== "string" || !
|
|
4240
|
+
if (typeof reviewer.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
3838
4241
|
reviewer.reasoningEffort
|
|
3839
4242
|
)) {
|
|
3840
4243
|
reviewer.reasoningEffort = defaultReviewer.reasoningEffort;
|
|
3841
|
-
changedPaths.push(
|
|
4244
|
+
changedPaths.push(`${phasePath}.reviewer.reasoningEffort`);
|
|
3842
4245
|
}
|
|
3843
4246
|
if (reviewer.onUnavailable !== "inherit" && reviewer.onUnavailable !== "error") {
|
|
3844
4247
|
reviewer.onUnavailable = defaultReviewer.onUnavailable;
|
|
3845
|
-
changedPaths.push(
|
|
4248
|
+
changedPaths.push(`${phasePath}.reviewer.onUnavailable`);
|
|
3846
4249
|
}
|
|
4250
|
+
};
|
|
4251
|
+
normalizeAgentReviewPhase("plan", true);
|
|
4252
|
+
normalizeAgentReviewPhase("task", false);
|
|
4253
|
+
const legacyFeatureEnabled = typeof legacyPrePrReview?.enabled === "boolean" ? legacyPrePrReview.enabled : workflow.mode !== "local";
|
|
4254
|
+
normalizeAgentReviewPhase(
|
|
4255
|
+
"feature",
|
|
4256
|
+
legacyFeatureEnabled,
|
|
4257
|
+
legacyPrePrReview
|
|
4258
|
+
);
|
|
4259
|
+
if (hasOwnKey2(workflow, "prePrReview")) {
|
|
4260
|
+
delete workflow.prePrReview;
|
|
4261
|
+
changedPaths.push("workflow.prePrReview");
|
|
3847
4262
|
}
|
|
3848
4263
|
if (!isPlainObject(raw.pr)) {
|
|
3849
4264
|
raw.pr = {};
|
|
@@ -4980,7 +5395,7 @@ function extractTaskTitles(tasksContent) {
|
|
|
4980
5395
|
return uniqItems(
|
|
4981
5396
|
tasksContent.split("\n").map((line) => {
|
|
4982
5397
|
const match = line.match(
|
|
4983
|
-
/^\s*-\s*\[(?:TODO|DOING|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
|
|
5398
|
+
/^\s*-\s*\[(?:TODO|DOING|REVIEW|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
|
|
4984
5399
|
);
|
|
4985
5400
|
if (!match) return void 0;
|
|
4986
5401
|
return sanitizeDraftItem(match[1]);
|
|
@@ -7120,7 +7535,11 @@ function formatTaskBlock(input) {
|
|
|
7120
7535
|
" - Acceptance:",
|
|
7121
7536
|
...input.acceptanceItems.map((item) => ` - ${item}`),
|
|
7122
7537
|
" - Checklist:",
|
|
7123
|
-
...input.checklistItems.map((item) => ` - [ ] ${item}`)
|
|
7538
|
+
...input.checklistItems.map((item) => ` - [ ] ${item}`),
|
|
7539
|
+
" - Review Evidence: -",
|
|
7540
|
+
" - Review Decision: -",
|
|
7541
|
+
" - Reviewed Head: -",
|
|
7542
|
+
" - Reviewed Tree: -"
|
|
7124
7543
|
];
|
|
7125
7544
|
}
|
|
7126
7545
|
async function runTaskAdd(featureName, options) {
|
|
@@ -7548,7 +7967,7 @@ function registerCodexHooksIntegration(parent) {
|
|
|
7548
7967
|
removeLeeSpecKitCodexHooks,
|
|
7549
7968
|
resolveCodexHooksRepoRoot,
|
|
7550
7969
|
upsertLeeSpecKitCodexHooks
|
|
7551
|
-
} = await import('./hooks-
|
|
7970
|
+
} = await import('./hooks-67CU3GZC.js');
|
|
7552
7971
|
const workflowRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
|
|
7553
7972
|
if (!workflowRoot) {
|
|
7554
7973
|
throw createCliError(
|
|
@@ -7953,9 +8372,68 @@ var ISSUE_LABELS = ["Issue", "Issue Number", "\uC774\uC288", "\uC774\uC288 \uBC8
|
|
|
7953
8372
|
var BRANCH_LABELS3 = ["Branch", "\uBE0C\uB79C\uCE58"];
|
|
7954
8373
|
var PR_LABELS = ["PR", "Pull Request"];
|
|
7955
8374
|
var PR_STATUS_LABELS = ["PR Status", "PR \uC0C1\uD0DC"];
|
|
7956
|
-
var PRE_PR_REVIEW_LABELS = [
|
|
7957
|
-
|
|
7958
|
-
|
|
8375
|
+
var PRE_PR_REVIEW_LABELS = [
|
|
8376
|
+
"Pre-PR Review",
|
|
8377
|
+
"PR \uC804 \uB9AC\uBDF0",
|
|
8378
|
+
"Feature Review",
|
|
8379
|
+
"Feature \uB9AC\uBDF0"
|
|
8380
|
+
];
|
|
8381
|
+
var PRE_PR_EVIDENCE_LABELS = [
|
|
8382
|
+
"Pre-PR Evidence",
|
|
8383
|
+
"PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
8384
|
+
"Feature Review Evidence",
|
|
8385
|
+
"Feature \uB9AC\uBDF0 Evidence"
|
|
8386
|
+
];
|
|
8387
|
+
var PRE_PR_DECISION_LABELS = [
|
|
8388
|
+
"Pre-PR Decision",
|
|
8389
|
+
"PR \uC804 \uB9AC\uBDF0 Decision",
|
|
8390
|
+
"Feature Review Decision",
|
|
8391
|
+
"Feature \uB9AC\uBDF0 Decision"
|
|
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
|
+
];
|
|
8399
|
+
var PRE_PR_REVIEWED_HEAD_LABELS = [
|
|
8400
|
+
"Pre-PR Reviewed Head",
|
|
8401
|
+
"PR \uC804 \uB9AC\uBDF0 Head",
|
|
8402
|
+
"Feature Reviewed Head",
|
|
8403
|
+
"Feature \uB9AC\uBDF0 Head"
|
|
8404
|
+
];
|
|
8405
|
+
var PRE_PR_REVIEWED_TREE_LABELS = [
|
|
8406
|
+
"Pre-PR Reviewed Tree",
|
|
8407
|
+
"PR \uC804 \uB9AC\uBDF0 Tree",
|
|
8408
|
+
"Feature Reviewed Tree",
|
|
8409
|
+
"Feature \uB9AC\uBDF0 Tree"
|
|
8410
|
+
];
|
|
8411
|
+
var PLAN_REVIEW_STATUS_LABELS = ["Plan Review", "Plan \uAC80\uC218", "Plan \uB9AC\uBDF0"];
|
|
8412
|
+
var PLAN_REVIEW_EVIDENCE_LABELS = [
|
|
8413
|
+
"Plan Review Evidence",
|
|
8414
|
+
"Plan \uAC80\uC218 Evidence",
|
|
8415
|
+
"Plan \uB9AC\uBDF0 Evidence"
|
|
8416
|
+
];
|
|
8417
|
+
var PLAN_REVIEW_DECISION_LABELS = [
|
|
8418
|
+
"Plan Review Decision",
|
|
8419
|
+
"Plan \uAC80\uC218 Decision",
|
|
8420
|
+
"Plan \uB9AC\uBDF0 Decision"
|
|
8421
|
+
];
|
|
8422
|
+
var PLAN_REVIEW_ROUND_LABELS = [
|
|
8423
|
+
"Plan Review Round",
|
|
8424
|
+
"Plan \uAC80\uC218 Round",
|
|
8425
|
+
"Plan \uB9AC\uBDF0 Round"
|
|
8426
|
+
];
|
|
8427
|
+
var PLAN_REVIEWED_SPEC_HASH_LABELS = [
|
|
8428
|
+
"Plan Reviewed Spec Hash",
|
|
8429
|
+
"Plan \uAC80\uC218 Spec Hash",
|
|
8430
|
+
"Plan \uB9AC\uBDF0 Spec Hash"
|
|
8431
|
+
];
|
|
8432
|
+
var PLAN_REVIEWED_PLAN_HASH_LABELS = [
|
|
8433
|
+
"Plan Reviewed Plan Hash",
|
|
8434
|
+
"Plan \uAC80\uC218 Plan Hash",
|
|
8435
|
+
"Plan \uB9AC\uBDF0 Plan Hash"
|
|
8436
|
+
];
|
|
7959
8437
|
var COMPLETION_MARKERS = {
|
|
7960
8438
|
allTasks: "lee-spec-kit:completion:all-tasks",
|
|
7961
8439
|
tests: "lee-spec-kit:completion:tests",
|
|
@@ -7974,7 +8452,10 @@ function resolveWorkflowRequirements(config) {
|
|
|
7974
8452
|
requirePr: workflow.requirePr ?? !isLocalWorkflow,
|
|
7975
8453
|
requireReview: workflow.requireReview ?? !isLocalWorkflow,
|
|
7976
8454
|
requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
|
|
7977
|
-
|
|
8455
|
+
planReviewEnabled: workflow.agentReview?.plan?.enabled ?? true,
|
|
8456
|
+
taskReviewEnabled: workflow.agentReview?.task?.enabled ?? false,
|
|
8457
|
+
featureReviewEnabled: workflow.agentReview?.feature?.enabled ?? workflow.prePrReview?.enabled ?? !isLocalWorkflow,
|
|
8458
|
+
taskExecutionEnabled: workflow.agentExecution?.task?.enabled ?? true
|
|
7978
8459
|
};
|
|
7979
8460
|
}
|
|
7980
8461
|
function parseApprovalStatus(raw) {
|
|
@@ -8025,7 +8506,70 @@ function parseCompletionCheckbox(lines, marker, legacyPattern) {
|
|
|
8025
8506
|
(line) => legacyPattern.test(line) && parseMarkdownCheckbox(line) === true
|
|
8026
8507
|
);
|
|
8027
8508
|
}
|
|
8028
|
-
function
|
|
8509
|
+
function parseReviewDecisionOutcome(value) {
|
|
8510
|
+
const match = (value || "").trim().toLowerCase().match(/\b(approve|changes_requested|blocked)\b/);
|
|
8511
|
+
return match?.[1] || null;
|
|
8512
|
+
}
|
|
8513
|
+
function parseReviewRound(value) {
|
|
8514
|
+
const parsed = Number((value || "").trim());
|
|
8515
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
8516
|
+
}
|
|
8517
|
+
function resolveMaxReviewRounds(config) {
|
|
8518
|
+
const configured = config.workflow?.agentReview?.maxRounds;
|
|
8519
|
+
return typeof configured === "number" && Number.isInteger(configured) && configured > 0 ? configured : 1;
|
|
8520
|
+
}
|
|
8521
|
+
function parseWorkflowTaskLine(line, index = -1) {
|
|
8522
|
+
const canonical = parseTaskLine(line, index);
|
|
8523
|
+
if (canonical) {
|
|
8524
|
+
return {
|
|
8525
|
+
index,
|
|
8526
|
+
raw: line,
|
|
8527
|
+
status: canonical.status,
|
|
8528
|
+
taskId: canonical.taskId,
|
|
8529
|
+
title: canonical.title
|
|
8530
|
+
};
|
|
8531
|
+
}
|
|
8532
|
+
const legacy = line.match(
|
|
8533
|
+
/^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\](?:\[[^\]]+\])*\s+(?:(T-[A-Za-z0-9-]+)\s+)?(.+?)\s*$/i
|
|
8534
|
+
);
|
|
8535
|
+
if (!legacy) return null;
|
|
8536
|
+
return {
|
|
8537
|
+
index,
|
|
8538
|
+
raw: line,
|
|
8539
|
+
status: legacy[1].toUpperCase(),
|
|
8540
|
+
taskId: legacy[2] || null,
|
|
8541
|
+
title: legacy[3].trim()
|
|
8542
|
+
};
|
|
8543
|
+
}
|
|
8544
|
+
function extractTaskReviewValue(lines, startIndex, labels) {
|
|
8545
|
+
let endIndex = lines.length;
|
|
8546
|
+
for (let index = startIndex + 1; index < lines.length; index += 1) {
|
|
8547
|
+
if (parseWorkflowTaskLine(lines[index]) || /^\s*##\s+/.test(lines[index])) {
|
|
8548
|
+
endIndex = index;
|
|
8549
|
+
break;
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
for (let index = startIndex + 1; index < endIndex; index += 1) {
|
|
8553
|
+
for (const label of labels) {
|
|
8554
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8555
|
+
const match = lines[index].match(
|
|
8556
|
+
new RegExp(
|
|
8557
|
+
`^\\s*-\\s*(?:\\*\\*)?${escaped}(?:\\*\\*)?:\\s*(.*?)\\s*$`,
|
|
8558
|
+
"i"
|
|
8559
|
+
)
|
|
8560
|
+
);
|
|
8561
|
+
if (match) return sanitizeMetadataValue2(match[1]);
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
return null;
|
|
8565
|
+
}
|
|
8566
|
+
function buildSyntheticTaskId(feature, title, sameTitleOccurrence) {
|
|
8567
|
+
const fingerprint = createHash("sha256").update(
|
|
8568
|
+
`${feature.folderName}\0${normalizeCommitTopicText(title).toLowerCase()}\0${sameTitleOccurrence}`
|
|
8569
|
+
).digest("hex").slice(0, 10);
|
|
8570
|
+
return `T-${feature.id}-legacy-${fingerprint}`;
|
|
8571
|
+
}
|
|
8572
|
+
function parseTasksDoc(content, feature) {
|
|
8029
8573
|
const issueRaw = extractFieldValue2(content, ISSUE_LABELS);
|
|
8030
8574
|
const issueNumberMatch = issueRaw?.match(/^#(\d+)$/);
|
|
8031
8575
|
const issueNumber = issueNumberMatch ? Number(issueNumberMatch[1]) : null;
|
|
@@ -8034,15 +8578,51 @@ function parseTasksDoc(content) {
|
|
|
8034
8578
|
const prePrDecision = extractFieldValue2(content, PRE_PR_DECISION_LABELS);
|
|
8035
8579
|
const tasks = [];
|
|
8036
8580
|
const nonCodeLines = withoutFencedCodeBlocks(content);
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8581
|
+
const legacyTitleOccurrences = /* @__PURE__ */ new Map();
|
|
8582
|
+
for (let index = 0; index < nonCodeLines.length; index += 1) {
|
|
8583
|
+
const line = nonCodeLines[index];
|
|
8584
|
+
const parsed = parseWorkflowTaskLine(line, index);
|
|
8585
|
+
if (!parsed) continue;
|
|
8586
|
+
const reviewDecision = extractTaskReviewValue(
|
|
8587
|
+
nonCodeLines,
|
|
8588
|
+
index,
|
|
8589
|
+
["Review Decision", "Task Review Decision", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Decision"]
|
|
8040
8590
|
);
|
|
8041
|
-
|
|
8591
|
+
const reviewRound = parseReviewRound(
|
|
8592
|
+
extractTaskReviewValue(nonCodeLines, index, [
|
|
8593
|
+
"Review Round",
|
|
8594
|
+
"Task Review Round",
|
|
8595
|
+
"\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Round"
|
|
8596
|
+
])
|
|
8597
|
+
);
|
|
8598
|
+
const legacyTitleKey = normalizeCommitTopicText(parsed.title).toLowerCase();
|
|
8599
|
+
const sameTitleOccurrence = (legacyTitleOccurrences.get(legacyTitleKey) || 0) + 1;
|
|
8600
|
+
legacyTitleOccurrences.set(legacyTitleKey, sameTitleOccurrence);
|
|
8601
|
+
const taskId = parsed.taskId || buildSyntheticTaskId(feature, parsed.title, sameTitleOccurrence);
|
|
8042
8602
|
tasks.push({
|
|
8043
8603
|
raw: line,
|
|
8044
|
-
status:
|
|
8045
|
-
|
|
8604
|
+
status: parsed.status,
|
|
8605
|
+
taskId,
|
|
8606
|
+
taskIdSource: parsed.taskId ? "document" : "synthetic",
|
|
8607
|
+
title: parsed.title,
|
|
8608
|
+
reviewEvidence: extractTaskReviewValue(
|
|
8609
|
+
nonCodeLines,
|
|
8610
|
+
index,
|
|
8611
|
+
["Review Evidence", "Task Review Evidence", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Evidence"]
|
|
8612
|
+
),
|
|
8613
|
+
reviewDecision,
|
|
8614
|
+
reviewDecisionOutcome: parseReviewDecisionOutcome(reviewDecision),
|
|
8615
|
+
reviewRound,
|
|
8616
|
+
reviewedHead: extractTaskReviewValue(
|
|
8617
|
+
nonCodeLines,
|
|
8618
|
+
index,
|
|
8619
|
+
["Reviewed Head", "Task Reviewed Head", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Head"]
|
|
8620
|
+
),
|
|
8621
|
+
reviewedTree: extractTaskReviewValue(
|
|
8622
|
+
nonCodeLines,
|
|
8623
|
+
index,
|
|
8624
|
+
["Reviewed Tree", "Task Reviewed Tree", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Tree"]
|
|
8625
|
+
)
|
|
8046
8626
|
});
|
|
8047
8627
|
}
|
|
8048
8628
|
const allTasksChecked = parseCompletionCheckbox(
|
|
@@ -8073,11 +8653,7 @@ function parseTasksDoc(content) {
|
|
|
8073
8653
|
if (value === "done") return "done";
|
|
8074
8654
|
return null;
|
|
8075
8655
|
})();
|
|
8076
|
-
const prePrDecisionOutcome = (
|
|
8077
|
-
const value = (prePrDecision || "").trim().toLowerCase();
|
|
8078
|
-
const match = value.match(/\b(approve|changes_requested|blocked)\b/);
|
|
8079
|
-
return match?.[1] || null;
|
|
8080
|
-
})();
|
|
8656
|
+
const prePrDecisionOutcome = parseReviewDecisionOutcome(prePrDecision);
|
|
8081
8657
|
return {
|
|
8082
8658
|
docStatus: parseApprovalStatus(
|
|
8083
8659
|
extractFieldValue2(content, DOC_STATUS_LABELS) || void 0
|
|
@@ -8092,6 +8668,15 @@ function parseTasksDoc(content) {
|
|
|
8092
8668
|
),
|
|
8093
8669
|
prePrDecision: sanitizeMetadataValue2(prePrDecision),
|
|
8094
8670
|
prePrDecisionOutcome,
|
|
8671
|
+
prePrReviewRound: parseReviewRound(
|
|
8672
|
+
extractFieldValue2(content, PRE_PR_REVIEW_ROUND_LABELS)
|
|
8673
|
+
),
|
|
8674
|
+
prePrReviewedHead: sanitizeMetadataValue2(
|
|
8675
|
+
extractFieldValue2(content, PRE_PR_REVIEWED_HEAD_LABELS)
|
|
8676
|
+
),
|
|
8677
|
+
prePrReviewedTree: sanitizeMetadataValue2(
|
|
8678
|
+
extractFieldValue2(content, PRE_PR_REVIEWED_TREE_LABELS)
|
|
8679
|
+
),
|
|
8095
8680
|
tasks,
|
|
8096
8681
|
completion: {
|
|
8097
8682
|
allTasksChecked,
|
|
@@ -8106,6 +8691,63 @@ function sanitizeMetadataValue2(value) {
|
|
|
8106
8691
|
if (!trimmed || trimmed === "-") return null;
|
|
8107
8692
|
return trimmed;
|
|
8108
8693
|
}
|
|
8694
|
+
function parsePlanReview(content) {
|
|
8695
|
+
const statusValue = sanitizeMetadataValue2(
|
|
8696
|
+
extractFieldValue2(content, PLAN_REVIEW_STATUS_LABELS)
|
|
8697
|
+
)?.toLowerCase();
|
|
8698
|
+
const status = statusValue === "pending" || statusValue === "running" || statusValue === "done" ? statusValue : null;
|
|
8699
|
+
const evidence = sanitizeMetadataValue2(
|
|
8700
|
+
extractFieldValue2(content, PLAN_REVIEW_EVIDENCE_LABELS)
|
|
8701
|
+
);
|
|
8702
|
+
const decision = sanitizeMetadataValue2(
|
|
8703
|
+
extractFieldValue2(content, PLAN_REVIEW_DECISION_LABELS)
|
|
8704
|
+
);
|
|
8705
|
+
const reviewRound = parseReviewRound(
|
|
8706
|
+
extractFieldValue2(content, PLAN_REVIEW_ROUND_LABELS)
|
|
8707
|
+
);
|
|
8708
|
+
const reviewedSpecHash = sanitizeMetadataValue2(
|
|
8709
|
+
extractFieldValue2(content, PLAN_REVIEWED_SPEC_HASH_LABELS)
|
|
8710
|
+
);
|
|
8711
|
+
const reviewedPlanHash = sanitizeMetadataValue2(
|
|
8712
|
+
extractFieldValue2(content, PLAN_REVIEWED_PLAN_HASH_LABELS)
|
|
8713
|
+
);
|
|
8714
|
+
return {
|
|
8715
|
+
status,
|
|
8716
|
+
evidence,
|
|
8717
|
+
decision,
|
|
8718
|
+
decisionOutcome: parseReviewDecisionOutcome(decision),
|
|
8719
|
+
reviewRound,
|
|
8720
|
+
reviewedSpecHash,
|
|
8721
|
+
reviewedPlanHash,
|
|
8722
|
+
hasMetadata: PLAN_REVIEW_STATUS_LABELS.some((label) => {
|
|
8723
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8724
|
+
return new RegExp(`^\\s*-\\s*\\*\\*${escaped}\\*\\*:`, "mi").test(content);
|
|
8725
|
+
})
|
|
8726
|
+
};
|
|
8727
|
+
}
|
|
8728
|
+
function buildReviewDocumentHash(content, excludedLabels) {
|
|
8729
|
+
const excluded = new Set(
|
|
8730
|
+
[...excludedLabels, "Status", "\uC0C1\uD0DC"].map((label) => label.toLowerCase())
|
|
8731
|
+
);
|
|
8732
|
+
const normalized = content.replace(/\r\n/g, "\n").split("\n").filter((line) => {
|
|
8733
|
+
const match = line.match(/^\s*-\s*\*\*(.+?)\*\*\s*:/);
|
|
8734
|
+
return !match || !excluded.has(match[1].trim().toLowerCase());
|
|
8735
|
+
}).join("\n").trim();
|
|
8736
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
8737
|
+
}
|
|
8738
|
+
function buildPlanReviewTarget(specContent, planContent) {
|
|
8739
|
+
return {
|
|
8740
|
+
specHash: buildReviewDocumentHash(specContent, []),
|
|
8741
|
+
planHash: buildReviewDocumentHash(planContent, [
|
|
8742
|
+
...PLAN_REVIEW_STATUS_LABELS,
|
|
8743
|
+
...PLAN_REVIEW_EVIDENCE_LABELS,
|
|
8744
|
+
...PLAN_REVIEW_DECISION_LABELS,
|
|
8745
|
+
...PLAN_REVIEW_ROUND_LABELS,
|
|
8746
|
+
...PLAN_REVIEWED_SPEC_HASH_LABELS,
|
|
8747
|
+
...PLAN_REVIEWED_PLAN_HASH_LABELS
|
|
8748
|
+
])
|
|
8749
|
+
};
|
|
8750
|
+
}
|
|
8109
8751
|
function normalizeCommitTopicText(value) {
|
|
8110
8752
|
return value.replace(/\s+/g, " ").trim();
|
|
8111
8753
|
}
|
|
@@ -8241,7 +8883,7 @@ function hasOpenTask(tasks) {
|
|
|
8241
8883
|
function hasUncommittedChanges(gitCwd) {
|
|
8242
8884
|
if (!gitCwd) return false;
|
|
8243
8885
|
const status = runGitCapture(
|
|
8244
|
-
["status", "--porcelain", "--untracked-files=
|
|
8886
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
8245
8887
|
gitCwd
|
|
8246
8888
|
) || "";
|
|
8247
8889
|
return status.trim().length > 0;
|
|
@@ -8305,7 +8947,7 @@ function resolveProjectCommitTopic(feature, tasks) {
|
|
|
8305
8947
|
const activeTask = tasks.tasks.find(
|
|
8306
8948
|
(task) => task.status === "DOING" || task.status === "REVIEW"
|
|
8307
8949
|
);
|
|
8308
|
-
const raw = activeTask?.title || getLastDoneTask(tasks)?.title ||
|
|
8950
|
+
const raw = activeTask?.title || getLastDoneTask(tasks)?.title || tasks.tasks.find((task) => task.status === "TODO")?.title || feature.folderName;
|
|
8309
8951
|
const withoutTaskId = normalizeCommitTopicText(raw || "").replace(
|
|
8310
8952
|
/^T-[A-Za-z0-9-]+\s+/,
|
|
8311
8953
|
""
|
|
@@ -8362,6 +9004,19 @@ function resolveExpectedBranch(feature, tasks) {
|
|
|
8362
9004
|
if (!tasks.issueNumber) return null;
|
|
8363
9005
|
return `feat/${tasks.issueNumber}-${feature.slug}`;
|
|
8364
9006
|
}
|
|
9007
|
+
function buildExpectedBranchCommand(config, feature, expectedBranch, requireWorktree) {
|
|
9008
|
+
if (requireWorktree) {
|
|
9009
|
+
return buildManagedWorktreeCreateCommand(
|
|
9010
|
+
config,
|
|
9011
|
+
feature.git.projectGitCwd,
|
|
9012
|
+
expectedBranch
|
|
9013
|
+
);
|
|
9014
|
+
}
|
|
9015
|
+
return localBranchExists(
|
|
9016
|
+
resolveProjectRootGitCwd(config, feature),
|
|
9017
|
+
expectedBranch
|
|
9018
|
+
) ? `git checkout ${expectedBranch}` : `git checkout -b ${expectedBranch}`;
|
|
9019
|
+
}
|
|
8365
9020
|
function resolveProjectRootFromGitCwd2(projectGitCwd) {
|
|
8366
9021
|
return runGitCapture(["rev-parse", "--show-toplevel"], projectGitCwd) || path8.resolve(projectGitCwd);
|
|
8367
9022
|
}
|
|
@@ -8526,19 +9181,96 @@ function buildPostMergeCleanupSummary(state) {
|
|
|
8526
9181
|
}
|
|
8527
9182
|
return `Finish the post-merge cleanup before closing the feature: ${remaining.join(", ")}.`;
|
|
8528
9183
|
}
|
|
8529
|
-
function
|
|
8530
|
-
return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "TODO") ||
|
|
9184
|
+
function nextExecutableTask(tasks) {
|
|
9185
|
+
return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "REVIEW") || tasks.tasks.find((task) => task.status === "TODO") || tasks.tasks.find((task) => task.status !== "DONE");
|
|
8531
9186
|
}
|
|
8532
9187
|
function allTasksDone(tasks) {
|
|
8533
9188
|
return tasks.tasks.length > 0 && tasks.tasks.every((task) => task.status === "DONE");
|
|
8534
9189
|
}
|
|
8535
|
-
function
|
|
8536
|
-
|
|
9190
|
+
function resolveAgentReviewPhase(config, scope) {
|
|
9191
|
+
if (scope === "plan") {
|
|
9192
|
+
return config.workflow?.agentReview?.plan || {};
|
|
9193
|
+
}
|
|
9194
|
+
if (scope === "task") {
|
|
9195
|
+
return config.workflow?.agentReview?.task || {};
|
|
9196
|
+
}
|
|
9197
|
+
return config.workflow?.agentReview?.feature || config.workflow?.prePrReview || {};
|
|
9198
|
+
}
|
|
9199
|
+
function resolveProjectReviewTarget(config, projectGitCwd, scope, taskBase = null) {
|
|
9200
|
+
const pathArgs = ["--", "."];
|
|
9201
|
+
const relativeDocsDir = path8.relative(projectGitCwd, config.docsDir);
|
|
9202
|
+
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
9203
|
+
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
9204
|
+
pathArgs.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
9205
|
+
}
|
|
9206
|
+
const targetSha = runGitCapture(["log", "-n", "1", "--pretty=%H", ...pathArgs], projectGitCwd) || runGitCapture(["rev-parse", "HEAD"], projectGitCwd) || "";
|
|
9207
|
+
if (!targetSha) return null;
|
|
9208
|
+
const targetTree = runGitCapture(["rev-parse", `${targetSha}^{tree}`], projectGitCwd) || "";
|
|
9209
|
+
if (!targetTree) return null;
|
|
9210
|
+
let baseSha = "";
|
|
9211
|
+
if (scope === "task" && taskBase) {
|
|
9212
|
+
const actualBaseTree = runGitCapture(
|
|
9213
|
+
["rev-parse", `${taskBase.reviewedHead}^{tree}`],
|
|
9214
|
+
projectGitCwd
|
|
9215
|
+
) || "";
|
|
9216
|
+
if (actualBaseTree === taskBase.reviewedTree && taskBase.reviewedHead !== targetSha && isAncestor(projectGitCwd, taskBase.reviewedHead, targetSha)) {
|
|
9217
|
+
baseSha = taskBase.reviewedHead;
|
|
9218
|
+
}
|
|
9219
|
+
}
|
|
9220
|
+
if (!baseSha) {
|
|
9221
|
+
const baseBranch = config.workflow?.baseBranch?.trim() || "main";
|
|
9222
|
+
for (const candidate of [`origin/${baseBranch}`, baseBranch]) {
|
|
9223
|
+
const candidateBase = runGitCapture(["merge-base", candidate, targetSha], projectGitCwd) || "";
|
|
9224
|
+
if (candidateBase && candidateBase !== targetSha) {
|
|
9225
|
+
baseSha = candidateBase;
|
|
9226
|
+
break;
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
if (!baseSha) {
|
|
9231
|
+
baseSha = runGitCapture(["rev-parse", `${targetSha}^`], projectGitCwd) || targetSha;
|
|
9232
|
+
}
|
|
9233
|
+
return { baseSha, targetSha, targetTree };
|
|
9234
|
+
}
|
|
9235
|
+
function reviewEvidenceSatisfied(config, feature, scope, evidence) {
|
|
9236
|
+
return isPrePrEvidenceSatisfied({
|
|
8537
9237
|
docsDir: config.docsDir,
|
|
8538
9238
|
featureDir: feature.path,
|
|
8539
|
-
evidence
|
|
8540
|
-
evidenceMode: config.
|
|
8541
|
-
})
|
|
9239
|
+
evidence,
|
|
9240
|
+
evidenceMode: resolveAgentReviewPhase(config, scope).evidenceMode
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
9243
|
+
function planReviewSatisfied(config, feature, review, target) {
|
|
9244
|
+
return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && review.decisionOutcome === "approve" && review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9245
|
+
}
|
|
9246
|
+
function featureReviewSatisfied(config, feature, tasks, target) {
|
|
9247
|
+
return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve" && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
|
|
9248
|
+
}
|
|
9249
|
+
function taskReviewEvidenceSatisfied(config, feature, task, target) {
|
|
9250
|
+
return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && task.reviewDecisionOutcome === "approve" && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
|
|
9251
|
+
}
|
|
9252
|
+
function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
|
|
9253
|
+
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" || !reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence)) {
|
|
9254
|
+
return false;
|
|
9255
|
+
}
|
|
9256
|
+
const actualTree = runGitCapture(
|
|
9257
|
+
["rev-parse", `${task.reviewedHead}^{tree}`],
|
|
9258
|
+
projectGitCwd
|
|
9259
|
+
) || "";
|
|
9260
|
+
return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha);
|
|
9261
|
+
}
|
|
9262
|
+
function resolvePreviousTaskReviewBase(config, feature, tasks, task) {
|
|
9263
|
+
const taskIndex = tasks.tasks.indexOf(task);
|
|
9264
|
+
for (let index = taskIndex - 1; index >= 0; index -= 1) {
|
|
9265
|
+
const previous = tasks.tasks[index];
|
|
9266
|
+
if (previous.reviewDecisionOutcome === "approve" && previous.reviewedHead && previous.reviewedTree && reviewEvidenceSatisfied(config, feature, "task", previous.reviewEvidence)) {
|
|
9267
|
+
return {
|
|
9268
|
+
reviewedHead: previous.reviewedHead,
|
|
9269
|
+
reviewedTree: previous.reviewedTree
|
|
9270
|
+
};
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
return null;
|
|
8542
9274
|
}
|
|
8543
9275
|
function issueExistsRemotely(issueNumber, feature) {
|
|
8544
9276
|
if (!issueNumber) return false;
|
|
@@ -8558,25 +9290,40 @@ function prExistsRemotely(prRef, feature) {
|
|
|
8558
9290
|
);
|
|
8559
9291
|
return result.code === 0;
|
|
8560
9292
|
}
|
|
8561
|
-
function buildAction(category, summary, approvalRequired, command = null,
|
|
9293
|
+
function buildAction(category, summary, approvalRequired, command = null, agent, actionContext) {
|
|
8562
9294
|
return {
|
|
8563
9295
|
category,
|
|
8564
9296
|
summary,
|
|
8565
9297
|
approvalRequired,
|
|
8566
9298
|
command,
|
|
8567
|
-
...
|
|
8568
|
-
executor:
|
|
8569
|
-
model:
|
|
8570
|
-
reasoningEffort:
|
|
8571
|
-
onUnavailable:
|
|
8572
|
-
} : {}
|
|
9299
|
+
...agent ? {
|
|
9300
|
+
executor: agent.type,
|
|
9301
|
+
model: agent.model,
|
|
9302
|
+
reasoningEffort: agent.reasoningEffort,
|
|
9303
|
+
onUnavailable: agent.onUnavailable
|
|
9304
|
+
} : {},
|
|
9305
|
+
...actionContext || {}
|
|
9306
|
+
};
|
|
9307
|
+
}
|
|
9308
|
+
function resolveAgentReviewer(config, scope) {
|
|
9309
|
+
const defaults = createDefaultAgentReviewerConfig();
|
|
9310
|
+
const configured = resolveAgentReviewPhase(config, scope).reviewer;
|
|
9311
|
+
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
9312
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
9313
|
+
configured?.reasoningEffort
|
|
9314
|
+
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
9315
|
+
return {
|
|
9316
|
+
type: "subagent",
|
|
9317
|
+
model,
|
|
9318
|
+
reasoningEffort,
|
|
9319
|
+
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
8573
9320
|
};
|
|
8574
9321
|
}
|
|
8575
|
-
function
|
|
8576
|
-
const defaults =
|
|
8577
|
-
const configured = config.workflow?.
|
|
9322
|
+
function resolveTaskExecutor(config) {
|
|
9323
|
+
const defaults = createDefaultAgentExecutionTaskConfig();
|
|
9324
|
+
const configured = config.workflow?.agentExecution?.task;
|
|
8578
9325
|
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
8579
|
-
const reasoningEffort =
|
|
9326
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
8580
9327
|
configured?.reasoningEffort
|
|
8581
9328
|
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
8582
9329
|
return {
|
|
@@ -8586,6 +9333,23 @@ function resolvePrePrReviewer(config) {
|
|
|
8586
9333
|
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
8587
9334
|
};
|
|
8588
9335
|
}
|
|
9336
|
+
function createTaskWorkerContract() {
|
|
9337
|
+
return {
|
|
9338
|
+
role: "task_implementation_worker",
|
|
9339
|
+
executeDirectly: true,
|
|
9340
|
+
spawnSubagents: false,
|
|
9341
|
+
runWorkflowStage: false,
|
|
9342
|
+
editProjectCode: true,
|
|
9343
|
+
runTaskScopedVerification: true,
|
|
9344
|
+
followVerificationContract: true,
|
|
9345
|
+
addUnplannedDurableTests: false,
|
|
9346
|
+
editDocs: false,
|
|
9347
|
+
changeTaskState: false,
|
|
9348
|
+
commit: false,
|
|
9349
|
+
requestApproval: false,
|
|
9350
|
+
remoteActions: false
|
|
9351
|
+
};
|
|
9352
|
+
}
|
|
8589
9353
|
function buildStageOption(label, reply, category, summary, command = null) {
|
|
8590
9354
|
return {
|
|
8591
9355
|
label,
|
|
@@ -8893,11 +9657,115 @@ function resolveFeatureSelectionError(selection) {
|
|
|
8893
9657
|
blockedReasonCode: null
|
|
8894
9658
|
};
|
|
8895
9659
|
}
|
|
8896
|
-
|
|
8897
|
-
const
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
9660
|
+
function resolvePlanReviewPayload(config, feature, review, target) {
|
|
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;
|
|
9665
|
+
const actionContext = {
|
|
9666
|
+
reviewScope: "plan",
|
|
9667
|
+
reviewRound: nextReviewRound,
|
|
9668
|
+
maxReviewRounds,
|
|
9669
|
+
specHash: target.specHash,
|
|
9670
|
+
planHash: target.planHash,
|
|
9671
|
+
docsDirectory: config.docsDir
|
|
9672
|
+
};
|
|
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
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
if (targetMatches && review.decisionOutcome === "blocked") {
|
|
9726
|
+
return {
|
|
9727
|
+
status: "ok",
|
|
9728
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9729
|
+
docsDir: config.docsDir,
|
|
9730
|
+
featureRef: buildFeatureRef(feature),
|
|
9731
|
+
stage: "plan_review",
|
|
9732
|
+
nextAction: buildAction(
|
|
9733
|
+
"plan_review",
|
|
9734
|
+
"Resolve the recorded Plan review blocker before Plan approval.",
|
|
9735
|
+
false,
|
|
9736
|
+
null,
|
|
9737
|
+
void 0,
|
|
9738
|
+
{ ...actionContext, reviewRound }
|
|
9739
|
+
),
|
|
9740
|
+
approvalRequired: false,
|
|
9741
|
+
implementationAllowed: false,
|
|
9742
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9743
|
+
};
|
|
9744
|
+
}
|
|
9745
|
+
return {
|
|
9746
|
+
status: "ok",
|
|
9747
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9748
|
+
docsDir: config.docsDir,
|
|
9749
|
+
featureRef: buildFeatureRef(feature),
|
|
9750
|
+
stage: "plan_review",
|
|
9751
|
+
nextAction: buildAction(
|
|
9752
|
+
"plan_review",
|
|
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.`,
|
|
9754
|
+
false,
|
|
9755
|
+
null,
|
|
9756
|
+
resolveAgentReviewer(config, "plan"),
|
|
9757
|
+
actionContext
|
|
9758
|
+
),
|
|
9759
|
+
approvalRequired: false,
|
|
9760
|
+
implementationAllowed: false,
|
|
9761
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9762
|
+
};
|
|
9763
|
+
}
|
|
9764
|
+
async function collectWorkflowStage(cwd, selector, component) {
|
|
9765
|
+
const config = await getConfig(cwd);
|
|
9766
|
+
if (!config) {
|
|
9767
|
+
return {
|
|
9768
|
+
status: "error",
|
|
8901
9769
|
reasonCode: "CONFIG_NOT_FOUND",
|
|
8902
9770
|
docsDir: null,
|
|
8903
9771
|
featureRef: null,
|
|
@@ -8927,7 +9795,13 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8927
9795
|
const planStatus = parseApprovalStatus(
|
|
8928
9796
|
extractFieldValue2(planContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
|
|
8929
9797
|
);
|
|
8930
|
-
const tasks = parseTasksDoc(tasksContent || "");
|
|
9798
|
+
const tasks = parseTasksDoc(tasksContent || "", feature);
|
|
9799
|
+
const planReview = parsePlanReview(planContent || "");
|
|
9800
|
+
const planReviewTarget = buildPlanReviewTarget(
|
|
9801
|
+
specContent || "",
|
|
9802
|
+
planContent || ""
|
|
9803
|
+
);
|
|
9804
|
+
const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
|
|
8931
9805
|
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
8932
9806
|
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
8933
9807
|
const remoteReviewState = requirements.requireReview && tasks.prLink ? resolveRemotePrReviewState(tasks.prLink, feature) : "unknown";
|
|
@@ -8963,6 +9837,14 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8963
9837
|
}
|
|
8964
9838
|
if (planStatus !== "approved") {
|
|
8965
9839
|
const isReviewStage = planStatus === "review";
|
|
9840
|
+
if (isReviewStage && enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
|
|
9841
|
+
return resolvePlanReviewPayload(
|
|
9842
|
+
config,
|
|
9843
|
+
feature,
|
|
9844
|
+
planReview,
|
|
9845
|
+
planReviewTarget
|
|
9846
|
+
);
|
|
9847
|
+
}
|
|
8966
9848
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
|
|
8967
9849
|
const actionOptions = approvalRequired ? buildApprovalActionOptions({
|
|
8968
9850
|
approveSummary: "Approve plan.md and continue to the tasks stage.",
|
|
@@ -8986,6 +9868,9 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8986
9868
|
blockedReasonCode: "PLAN_NOT_APPROVED"
|
|
8987
9869
|
};
|
|
8988
9870
|
}
|
|
9871
|
+
if (enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
|
|
9872
|
+
return resolvePlanReviewPayload(config, feature, planReview, planReviewTarget);
|
|
9873
|
+
}
|
|
8989
9874
|
if (tasks.tasks.length === 0 || tasks.docStatus !== "approved") {
|
|
8990
9875
|
const isReviewStage = tasks.docStatus === "review";
|
|
8991
9876
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "tasks_approve", true) : false;
|
|
@@ -9064,7 +9949,12 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9064
9949
|
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
9065
9950
|
const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(["rev-parse", "--abbrev-ref", "HEAD"], effectiveProjectGitCwd) || null;
|
|
9066
9951
|
if (expectedBranch && currentBranch !== expectedBranch) {
|
|
9067
|
-
const branchCommand =
|
|
9952
|
+
const branchCommand = buildExpectedBranchCommand(
|
|
9953
|
+
config,
|
|
9954
|
+
feature,
|
|
9955
|
+
expectedBranch,
|
|
9956
|
+
requirements.requireWorktree
|
|
9957
|
+
);
|
|
9068
9958
|
return {
|
|
9069
9959
|
status: "ok",
|
|
9070
9960
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9087,6 +9977,183 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9087
9977
|
const lastDoneTask = getLastDoneTask(tasks);
|
|
9088
9978
|
const docsDirty = hasUncommittedChanges(feature.git.docsGitCwd);
|
|
9089
9979
|
const projectDirty = hasUncommittedChanges(effectiveProjectGitCwd);
|
|
9980
|
+
const currentTaskReviewTip = requirements.taskReviewEnabled ? resolveProjectReviewTarget(config, effectiveProjectGitCwd, "task") : null;
|
|
9981
|
+
const unreviewedDoneTask = requirements.taskReviewEnabled ? tasks.tasks.find(
|
|
9982
|
+
(task) => task.status === "DONE" && !recordedTaskReviewSatisfied(
|
|
9983
|
+
config,
|
|
9984
|
+
feature,
|
|
9985
|
+
effectiveProjectGitCwd,
|
|
9986
|
+
task,
|
|
9987
|
+
currentTaskReviewTip?.targetSha || null
|
|
9988
|
+
)
|
|
9989
|
+
) || null : null;
|
|
9990
|
+
const reviewTask = unreviewedDoneTask || tasks.tasks.find((task) => task.status === "REVIEW") || null;
|
|
9991
|
+
if (requirements.taskReviewEnabled && reviewTask) {
|
|
9992
|
+
if (docsDirty || projectDirty) {
|
|
9993
|
+
return {
|
|
9994
|
+
status: "ok",
|
|
9995
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9996
|
+
docsDir: config.docsDir,
|
|
9997
|
+
featureRef: buildFeatureRef(feature),
|
|
9998
|
+
stage: "task_commit",
|
|
9999
|
+
nextAction: buildAction(
|
|
10000
|
+
"task_commit",
|
|
10001
|
+
buildTaskCommitSummary({
|
|
10002
|
+
feature,
|
|
10003
|
+
tasks,
|
|
10004
|
+
effectiveProjectGitCwd,
|
|
10005
|
+
docsDirty,
|
|
10006
|
+
projectDirty,
|
|
10007
|
+
gateFailureReason: "the task must have a clean checkpoint commit before independent review"
|
|
10008
|
+
}),
|
|
10009
|
+
false
|
|
10010
|
+
),
|
|
10011
|
+
approvalRequired: false,
|
|
10012
|
+
implementationAllowed: false,
|
|
10013
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
10014
|
+
};
|
|
10015
|
+
}
|
|
10016
|
+
const reviewTarget = resolveProjectReviewTarget(
|
|
10017
|
+
config,
|
|
10018
|
+
effectiveProjectGitCwd,
|
|
10019
|
+
"task",
|
|
10020
|
+
resolvePreviousTaskReviewBase(config, feature, tasks, reviewTask)
|
|
10021
|
+
);
|
|
10022
|
+
if (!reviewTarget) {
|
|
10023
|
+
return {
|
|
10024
|
+
status: "ok",
|
|
10025
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10026
|
+
docsDir: config.docsDir,
|
|
10027
|
+
featureRef: buildFeatureRef(feature),
|
|
10028
|
+
stage: "task_commit",
|
|
10029
|
+
nextAction: buildAction(
|
|
10030
|
+
"task_commit",
|
|
10031
|
+
"Create a project commit for the task before requesting independent review.",
|
|
10032
|
+
false
|
|
10033
|
+
),
|
|
10034
|
+
approvalRequired: false,
|
|
10035
|
+
implementationAllowed: false,
|
|
10036
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
10037
|
+
};
|
|
10038
|
+
}
|
|
10039
|
+
const reviewContext = {
|
|
10040
|
+
reviewScope: "task",
|
|
10041
|
+
reviewRound: reviewTask.reviewRound || 1,
|
|
10042
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
10043
|
+
...reviewTask.taskId ? { taskId: reviewTask.taskId } : {},
|
|
10044
|
+
...reviewTarget
|
|
10045
|
+
};
|
|
10046
|
+
const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
|
|
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
|
+
}
|
|
10093
|
+
}
|
|
10094
|
+
if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "blocked") {
|
|
10095
|
+
return {
|
|
10096
|
+
status: "ok",
|
|
10097
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10098
|
+
docsDir: config.docsDir,
|
|
10099
|
+
featureRef: buildFeatureRef(feature),
|
|
10100
|
+
stage: "task_review",
|
|
10101
|
+
nextAction: buildAction(
|
|
10102
|
+
"task_review",
|
|
10103
|
+
`Resolve the recorded review blocker for ${reviewTask.taskId || reviewTask.title} before continuing.`,
|
|
10104
|
+
false,
|
|
10105
|
+
null,
|
|
10106
|
+
void 0,
|
|
10107
|
+
reviewContext
|
|
10108
|
+
),
|
|
10109
|
+
approvalRequired: false,
|
|
10110
|
+
implementationAllowed: false,
|
|
10111
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10112
|
+
};
|
|
10113
|
+
}
|
|
10114
|
+
if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
|
|
10115
|
+
const completionSummary = reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.`;
|
|
10116
|
+
return {
|
|
10117
|
+
status: "ok",
|
|
10118
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10119
|
+
docsDir: config.docsDir,
|
|
10120
|
+
featureRef: buildFeatureRef(feature),
|
|
10121
|
+
stage: "task_review",
|
|
10122
|
+
nextAction: buildAction(
|
|
10123
|
+
"task_review_complete",
|
|
10124
|
+
completionSummary,
|
|
10125
|
+
false,
|
|
10126
|
+
null,
|
|
10127
|
+
void 0,
|
|
10128
|
+
reviewContext
|
|
10129
|
+
),
|
|
10130
|
+
approvalRequired: false,
|
|
10131
|
+
implementationAllowed: false,
|
|
10132
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10133
|
+
};
|
|
10134
|
+
}
|
|
10135
|
+
return {
|
|
10136
|
+
status: "ok",
|
|
10137
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10138
|
+
docsDir: config.docsDir,
|
|
10139
|
+
featureRef: buildFeatureRef(feature),
|
|
10140
|
+
stage: "task_review",
|
|
10141
|
+
nextAction: buildAction(
|
|
10142
|
+
"task_review",
|
|
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.`,
|
|
10144
|
+
false,
|
|
10145
|
+
null,
|
|
10146
|
+
resolveAgentReviewer(config, "task"),
|
|
10147
|
+
{
|
|
10148
|
+
...reviewContext,
|
|
10149
|
+
reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound
|
|
10150
|
+
}
|
|
10151
|
+
),
|
|
10152
|
+
approvalRequired: false,
|
|
10153
|
+
implementationAllowed: false,
|
|
10154
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10155
|
+
};
|
|
10156
|
+
}
|
|
9090
10157
|
const pendingDoneTransitions = countPendingDoneTransitions(feature) || 0;
|
|
9091
10158
|
const taskCommitCheckpointRequired = !activeTaskOpen && !!lastDoneTask && (projectDirty || pendingDoneTransitions > 0);
|
|
9092
10159
|
if (taskCommitCheckpointRequired) {
|
|
@@ -9116,8 +10183,168 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9116
10183
|
}
|
|
9117
10184
|
const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
|
|
9118
10185
|
const committedTaskGateRequiresCheckpoint = taskCommitGatePolicy === "strict" || committedTaskGate.reason === "DONE_TRANSITIONS_COUNT";
|
|
10186
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
10187
|
+
return {
|
|
10188
|
+
status: "ok",
|
|
10189
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10190
|
+
docsDir: config.docsDir,
|
|
10191
|
+
featureRef: buildFeatureRef(feature),
|
|
10192
|
+
stage: "task_commit",
|
|
10193
|
+
nextAction: buildAction(
|
|
10194
|
+
"task_commit",
|
|
10195
|
+
buildTaskCommitSummary({
|
|
10196
|
+
feature,
|
|
10197
|
+
tasks,
|
|
10198
|
+
effectiveProjectGitCwd,
|
|
10199
|
+
docsDirty,
|
|
10200
|
+
projectDirty,
|
|
10201
|
+
gateFailureReason: describeTaskCommitGateFailure(committedTaskGate)
|
|
10202
|
+
}),
|
|
10203
|
+
false
|
|
10204
|
+
),
|
|
10205
|
+
approvalRequired: false,
|
|
10206
|
+
implementationAllowed: false,
|
|
10207
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
10208
|
+
};
|
|
10209
|
+
}
|
|
10210
|
+
let resolvedLocalState = null;
|
|
10211
|
+
if (allTasksDone(tasks) && config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
|
|
10212
|
+
resolvedLocalState = await resolveLocalIntegrationContext(config, feature);
|
|
10213
|
+
}
|
|
10214
|
+
const remoteReviewAlreadyComplete = currentReviewState === "merged" && tasks.prStatus === "approved" && prDraft.prStatus === "approved";
|
|
10215
|
+
const featureReviewNeededForLifecycle = allTasksDone(tasks) && requirements.featureReviewEnabled && !resolvedLocalState?.integrationComplete && !remoteReviewAlreadyComplete;
|
|
10216
|
+
if (featureReviewNeededForLifecycle) {
|
|
10217
|
+
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
10218
|
+
const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(
|
|
10219
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
10220
|
+
effectiveProjectGitCwd
|
|
10221
|
+
) || null;
|
|
10222
|
+
if (requirements.requireBranch && expectedBranch && currentBranch !== expectedBranch) {
|
|
10223
|
+
return {
|
|
10224
|
+
status: "ok",
|
|
10225
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10226
|
+
docsDir: config.docsDir,
|
|
10227
|
+
featureRef: buildFeatureRef(feature),
|
|
10228
|
+
stage: "branch",
|
|
10229
|
+
nextAction: buildAction(
|
|
10230
|
+
"branch_create",
|
|
10231
|
+
requirements.requireWorktree ? `Restore or create the managed worktree for ${expectedBranch} before Feature review.` : `Switch back to ${expectedBranch} before Feature review.`,
|
|
10232
|
+
false,
|
|
10233
|
+
buildExpectedBranchCommand(
|
|
10234
|
+
config,
|
|
10235
|
+
feature,
|
|
10236
|
+
expectedBranch,
|
|
10237
|
+
requirements.requireWorktree
|
|
10238
|
+
)
|
|
10239
|
+
),
|
|
10240
|
+
approvalRequired: false,
|
|
10241
|
+
implementationAllowed: false,
|
|
10242
|
+
blockedReasonCode: "BRANCH_NOT_READY"
|
|
10243
|
+
};
|
|
10244
|
+
}
|
|
10245
|
+
const reviewTarget = resolveProjectReviewTarget(
|
|
10246
|
+
config,
|
|
10247
|
+
effectiveProjectGitCwd,
|
|
10248
|
+
"feature"
|
|
10249
|
+
);
|
|
10250
|
+
const reviewContext = reviewTarget ? {
|
|
10251
|
+
reviewScope: "feature",
|
|
10252
|
+
reviewRound: tasks.prePrReviewRound || 1,
|
|
10253
|
+
maxReviewRounds: resolveMaxReviewRounds(config),
|
|
10254
|
+
...reviewTarget
|
|
10255
|
+
} : null;
|
|
10256
|
+
const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
|
|
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
|
+
}
|
|
10303
|
+
}
|
|
10304
|
+
if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "blocked") {
|
|
10305
|
+
return {
|
|
10306
|
+
status: "ok",
|
|
10307
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10308
|
+
docsDir: config.docsDir,
|
|
10309
|
+
featureRef: buildFeatureRef(feature),
|
|
10310
|
+
stage: "pre_pr_review",
|
|
10311
|
+
nextAction: buildAction(
|
|
10312
|
+
"pre_pr_review",
|
|
10313
|
+
"Resolve the recorded Feature review blocker before implementation approval or integration.",
|
|
10314
|
+
false,
|
|
10315
|
+
null,
|
|
10316
|
+
void 0,
|
|
10317
|
+
reviewContext
|
|
10318
|
+
),
|
|
10319
|
+
approvalRequired: false,
|
|
10320
|
+
implementationAllowed: false,
|
|
10321
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10322
|
+
};
|
|
10323
|
+
}
|
|
10324
|
+
if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
|
|
10325
|
+
const requestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
|
|
10326
|
+
return {
|
|
10327
|
+
status: "ok",
|
|
10328
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10329
|
+
docsDir: config.docsDir,
|
|
10330
|
+
featureRef: buildFeatureRef(feature),
|
|
10331
|
+
stage: "pre_pr_review",
|
|
10332
|
+
nextAction: buildAction(
|
|
10333
|
+
"pre_pr_review",
|
|
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.",
|
|
10335
|
+
false,
|
|
10336
|
+
null,
|
|
10337
|
+
reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10338
|
+
reviewContext ? { ...reviewContext, reviewRound: requestedRound } : void 0
|
|
10339
|
+
),
|
|
10340
|
+
approvalRequired: false,
|
|
10341
|
+
implementationAllowed: false,
|
|
10342
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10343
|
+
};
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
9119
10346
|
if (!allTasksDone(tasks)) {
|
|
9120
|
-
const currentTask =
|
|
10347
|
+
const currentTask = nextExecutableTask(tasks);
|
|
9121
10348
|
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
9122
10349
|
return {
|
|
9123
10350
|
status: "ok",
|
|
@@ -9144,6 +10371,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9144
10371
|
}
|
|
9145
10372
|
const commitWarning = taskCommitGatePolicy === "warn" && !committedTaskGate.pass ? `
|
|
9146
10373
|
Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)}` : "";
|
|
10374
|
+
const delegationGuidance = requirements.taskExecutionEnabled ? " Delegate implementation and task-scoped verification to a fresh configured subagent under the returned workerContract. The worker executes directly and must not run workflow-stage or delegate again. The main agent retains docs updates, task state transitions, commits, approvals, and remote actions" : "";
|
|
9147
10375
|
return {
|
|
9148
10376
|
status: "ok",
|
|
9149
10377
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9152,8 +10380,20 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9152
10380
|
stage: "implementation",
|
|
9153
10381
|
nextAction: buildAction(
|
|
9154
10382
|
"task_execute",
|
|
9155
|
-
|
|
9156
|
-
false
|
|
10383
|
+
`Continue the next implementation task: ${currentTask.title}.${delegationGuidance}${requirements.taskReviewEnabled ? " When implementation and checks are complete, move it to REVIEW instead of DONE" : ""}${commitWarning}`,
|
|
10384
|
+
false,
|
|
10385
|
+
null,
|
|
10386
|
+
requirements.taskExecutionEnabled ? resolveTaskExecutor(config) : void 0,
|
|
10387
|
+
{
|
|
10388
|
+
taskId: currentTask.taskId,
|
|
10389
|
+
taskIdSource: currentTask.taskIdSource,
|
|
10390
|
+
taskTitle: currentTask.title,
|
|
10391
|
+
workingDirectory: effectiveProjectGitCwd,
|
|
10392
|
+
...requirements.taskExecutionEnabled ? {
|
|
10393
|
+
docsDirectory: config.docsDir,
|
|
10394
|
+
workerContract: createTaskWorkerContract()
|
|
10395
|
+
} : {}
|
|
10396
|
+
}
|
|
9157
10397
|
),
|
|
9158
10398
|
approvalRequired: false,
|
|
9159
10399
|
implementationAllowed: true,
|
|
@@ -9217,27 +10457,8 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9217
10457
|
blockedReasonCode: "IMPLEMENTATION_APPROVAL_REQUIRED"
|
|
9218
10458
|
};
|
|
9219
10459
|
}
|
|
9220
|
-
if (requirements.prePrReviewEnabled && !prePrSatisfied(config, feature, tasks)) {
|
|
9221
|
-
return {
|
|
9222
|
-
status: "ok",
|
|
9223
|
-
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9224
|
-
docsDir: config.docsDir,
|
|
9225
|
-
featureRef: buildFeatureRef(feature),
|
|
9226
|
-
stage: "pre_pr_review",
|
|
9227
|
-
nextAction: buildAction(
|
|
9228
|
-
"pre_pr_review",
|
|
9229
|
-
"Delegate an independent read-only Pre-PR review to a fresh subagent and record its findings, decision, and reviewer metadata as evidence.",
|
|
9230
|
-
false,
|
|
9231
|
-
null,
|
|
9232
|
-
resolvePrePrReviewer(config)
|
|
9233
|
-
),
|
|
9234
|
-
approvalRequired: false,
|
|
9235
|
-
implementationAllowed: false,
|
|
9236
|
-
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
9237
|
-
};
|
|
9238
|
-
}
|
|
9239
10460
|
if (config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
|
|
9240
|
-
const localState = await resolveLocalIntegrationContext(config, feature);
|
|
10461
|
+
const localState = resolvedLocalState || await resolveLocalIntegrationContext(config, feature);
|
|
9241
10462
|
const localVerifyCommand = `npx lee-spec-kit local verify ${buildFeatureArgs(feature)} --json`;
|
|
9242
10463
|
const localMergeBaseCommand = `npx lee-spec-kit local merge ${buildFeatureArgs(feature)} --json`;
|
|
9243
10464
|
if (localState.cleanedIntegrationStillValid) {
|