lee-spec-kit 0.9.3 → 0.9.4
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 +1158 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +53 -5
- package/templates/en/common/agents/agents.md +7 -3
- package/templates/en/common/agents/skills/create-feature.md +2 -1
- package/templates/en/common/agents/skills/create-pr.md +4 -3
- package/templates/en/common/agents/skills/execute-task.md +8 -1
- package/templates/en/common/features/README.md +12 -4
- package/templates/en/common/features/feature-base/plan.md +41 -4
- package/templates/en/common/features/feature-base/tasks.md +12 -3
- package/templates/ko/common/README.md +52 -5
- package/templates/ko/common/agents/agents.md +7 -3
- package/templates/ko/common/agents/skills/create-feature.md +2 -1
- package/templates/ko/common/agents/skills/create-pr.md +4 -3
- package/templates/ko/common/agents/skills/execute-task.md +8 -1
- package/templates/ko/common/features/README.md +12 -4
- package/templates/ko/common/features/feature-base/plan.md +41 -4
- package/templates/ko/common/features/feature-base/tasks.md +12 -3
- package/dist/hooks-373Z6JG2.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -199,6 +199,27 @@ 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.choice.review.plan": "Plan",
|
|
217
|
+
"init.choice.review.task": "Task",
|
|
218
|
+
"init.choice.review.feature": "Feature",
|
|
219
|
+
"init.prompt.completionStrategy": "\uB85C\uCEEC \uD1B5\uD569 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
220
|
+
"init.choice.completionStrategy.localFf": "local-ff - \uAC80\uC99D\uB41C Feature \uBE0C\uB79C\uCE58\uB97C fast-forward \uD1B5\uD569",
|
|
221
|
+
"init.choice.completionStrategy.localSquash": "local-squash - \uAC80\uC99D\uB41C \uD558\uB098\uC758 \uCEE4\uBC0B\uC73C\uB85C \uD1B5\uD569",
|
|
222
|
+
"init.choice.completionStrategy.none": "none - Feature \uBE0C\uB79C\uCE58\uB97C \uD1B5\uD569\uD558\uC9C0 \uC54A\uACE0 \uC885\uB8CC",
|
|
202
223
|
"init.prompt.componentRepoPath": "{component} \uCEF4\uD3EC\uB10C\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
203
224
|
"init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
204
225
|
"init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
@@ -208,11 +229,19 @@ var koCli = {
|
|
|
208
229
|
"init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
|
|
209
230
|
"init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
210
231
|
"init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
232
|
+
"init.log.configSummaryTitle": "\u2699\uFE0F \uC124\uC815 \uC694\uC57D",
|
|
211
233
|
"init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
|
|
212
234
|
"init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
|
|
213
235
|
"init.log.typeLabel": "\uD0C0\uC785",
|
|
214
236
|
"init.log.langLabel": "\uC5B8\uC5B4",
|
|
215
237
|
"init.log.pathLabel": "\uACBD\uB85C",
|
|
238
|
+
"init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
|
|
239
|
+
"init.log.taskAgentLabel": "Task \uAD6C\uD604",
|
|
240
|
+
"init.log.reviewsLabel": "\uAC80\uC218",
|
|
241
|
+
"init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
|
|
242
|
+
"init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
|
|
243
|
+
"init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
|
|
244
|
+
"init.summary.reviews.none": "\uC5C6\uC74C",
|
|
216
245
|
"init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
|
|
217
246
|
"init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
218
247
|
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
@@ -482,6 +511,27 @@ var enCli = {
|
|
|
482
511
|
"init.choice.docsRepo.embedded.desc": "Pushed together with the project",
|
|
483
512
|
"init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
|
|
484
513
|
"init.choice.docsRepo.standalone.desc": "Configure push settings separately",
|
|
514
|
+
"init.prompt.workflowMode": "Select workflow mode:",
|
|
515
|
+
"init.choice.workflow.github.title": "GitHub - Issue and pull request workflow",
|
|
516
|
+
"init.choice.workflow.github.desc": "Use remote Issue, branch, pull request, review, and merge gates",
|
|
517
|
+
"init.choice.workflow.local.title": "Local - local integration workflow",
|
|
518
|
+
"init.choice.workflow.local.desc": "Use managed worktrees and local verification without requiring GitHub",
|
|
519
|
+
"init.prompt.workflowSetup": "Configure workflow automation:",
|
|
520
|
+
"init.choice.workflowSetup.recommended.title": "Recommended - use balanced defaults",
|
|
521
|
+
"init.choice.workflowSetup.recommended.desc": "Delegate task implementation and review Plan plus Feature",
|
|
522
|
+
"init.choice.workflowSetup.custom.title": "Custom - choose workflow options",
|
|
523
|
+
"init.choice.workflowSetup.custom.desc": "Select task delegation, review gates, and local integration",
|
|
524
|
+
"init.prompt.taskAgent": "Delegate task implementation to a subagent?",
|
|
525
|
+
"init.choice.taskAgent.on": "On - delegate each task implementation",
|
|
526
|
+
"init.choice.taskAgent.off": "Off - implement tasks in the main agent",
|
|
527
|
+
"init.prompt.reviews": "Select independent review gates:",
|
|
528
|
+
"init.choice.review.plan": "Plan",
|
|
529
|
+
"init.choice.review.task": "Task",
|
|
530
|
+
"init.choice.review.feature": "Feature",
|
|
531
|
+
"init.prompt.completionStrategy": "Select local integration strategy:",
|
|
532
|
+
"init.choice.completionStrategy.localFf": "local-ff - fast-forward the verified Feature branch",
|
|
533
|
+
"init.choice.completionStrategy.localSquash": "local-squash - integrate as one verified commit",
|
|
534
|
+
"init.choice.completionStrategy.none": "none - finish without integrating the Feature branch",
|
|
485
535
|
"init.prompt.componentRepoPath": 'Enter repository path for component "{component}":',
|
|
486
536
|
"init.prompt.projectRepoPath": "Enter project repository path:",
|
|
487
537
|
"init.validation.enterPath": "Please enter a path",
|
|
@@ -491,11 +541,19 @@ var enCli = {
|
|
|
491
541
|
"init.prompt.remoteUrl": "Enter remote repository URL:",
|
|
492
542
|
"init.validation.enterUrl": "Please enter a URL",
|
|
493
543
|
"init.prompt.overwrite": "{dir} already exists. Overwrite?",
|
|
544
|
+
"init.log.configSummaryTitle": "\u2699\uFE0F Configuration summary",
|
|
494
545
|
"init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
|
|
495
546
|
"init.log.projectLabel": "Project",
|
|
496
547
|
"init.log.typeLabel": "Type",
|
|
497
548
|
"init.log.langLabel": "Lang",
|
|
498
549
|
"init.log.pathLabel": "Path",
|
|
550
|
+
"init.log.workflowLabel": "Workflow",
|
|
551
|
+
"init.log.taskAgentLabel": "Task implementation",
|
|
552
|
+
"init.log.reviewsLabel": "Reviews",
|
|
553
|
+
"init.log.completionStrategyLabel": "Local integration",
|
|
554
|
+
"init.summary.taskAgent.on": "subagent",
|
|
555
|
+
"init.summary.taskAgent.off": "main agent",
|
|
556
|
+
"init.summary.reviews.none": "none",
|
|
499
557
|
"init.log.docsCreated": "\u2705 Docs structure created!",
|
|
500
558
|
"init.log.nextStepsTitle": "Next steps:",
|
|
501
559
|
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
@@ -1295,7 +1353,7 @@ Before taking the next workflow step:
|
|
|
1295
1353
|
3. When relevant, also read \`issue.md\` and \`pr.md\`
|
|
1296
1354
|
4. Run \`npx lee-spec-kit workflow-stage <feature-ref> --json\` and follow only the returned \`nextAction\`
|
|
1297
1355
|
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
|
|
1356
|
+
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
1357
|
7. Treat stages before implementation as hard gates:
|
|
1300
1358
|
- spec approval plus plan / tasks readiness
|
|
1301
1359
|
- issue preparation / issue creation
|
|
@@ -1306,10 +1364,14 @@ Before taking the next workflow step:
|
|
|
1306
1364
|
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
1365
|
11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
|
|
1308
1366
|
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.
|
|
1367
|
+
13. When \`workflow-stage --json\` returns \`nextAction.category === "plan_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review of \`spec.md\` and \`plan.md\` using the returned model settings and exact \`specHash\` / \`planHash\`; the main agent records evidence, decision, reviewer metadata, and both hashes, and any later spec/plan content change requires a fresh review
|
|
1368
|
+
14. When \`workflow-stage --json\` returns \`nextAction.category === "task_execute"\` with \`executor === "subagent"\`, mark exactly the returned \`taskId\` as active and delegate its implementation plus task-scoped verification to a fresh subagent in the returned \`workingDirectory\`, using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, and exact \`workerContract\`; no named execution skill is required
|
|
1369
|
+
15. The task implementation worker executes directly: it must follow the approved Verification Contract, must not add unplanned durable tests, and must not run \`workflow-stage\`, spawn another subagent, edit lee-spec-kit docs, change task state, commit, request approvals, or perform remote/destructive actions. It may modify project code and run task-scoped checks only. The main agent inspects the result, synchronizes docs and task state, and owns every commit and workflow transition; official hooks block commits while \`task_execute\` is still active
|
|
1370
|
+
16. When \`workflow-stage --json\` returns \`nextAction.category === "task_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review for the returned \`taskId\`, \`baseSha\`, \`targetSha\`, and \`targetTree\`; the main agent records the evidence and moves the task from \`REVIEW\` to \`DONE\` only after an approve decision
|
|
1371
|
+
17. When \`workflow-stage --json\` returns \`nextAction.category === "pre_pr_review"\` and \`nextAction.executor === "subagent"\`, delegate a fresh read-only Feature review using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, and review target metadata; do not select or require a named review skill
|
|
1372
|
+
18. Review subagents return findings without modifying code; the main agent remediates findings and records the actual reviewer metadata, reviewed scope, evidence, decision, and exact hash/SHA/tree target metadata
|
|
1373
|
+
19. 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
|
|
1374
|
+
20. 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
1375
|
|
|
1314
1376
|
Approval and remote actions:
|
|
1315
1377
|
|
|
@@ -1898,7 +1960,7 @@ var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
|
|
|
1898
1960
|
"implementation_approve",
|
|
1899
1961
|
"local_merge"
|
|
1900
1962
|
];
|
|
1901
|
-
var
|
|
1963
|
+
var AGENT_REVIEW_REASONING_EFFORTS = [
|
|
1902
1964
|
"low",
|
|
1903
1965
|
"medium",
|
|
1904
1966
|
"high",
|
|
@@ -1913,7 +1975,7 @@ function createDefaultApprovalConfig() {
|
|
|
1913
1975
|
requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
|
|
1914
1976
|
};
|
|
1915
1977
|
}
|
|
1916
|
-
function
|
|
1978
|
+
function createDefaultAgentReviewerConfig() {
|
|
1917
1979
|
return {
|
|
1918
1980
|
type: "subagent",
|
|
1919
1981
|
model: "inherit",
|
|
@@ -1921,6 +1983,15 @@ function createDefaultPrePrReviewerConfig() {
|
|
|
1921
1983
|
onUnavailable: "inherit"
|
|
1922
1984
|
};
|
|
1923
1985
|
}
|
|
1986
|
+
function createDefaultAgentExecutionTaskConfig() {
|
|
1987
|
+
return {
|
|
1988
|
+
enabled: true,
|
|
1989
|
+
type: "subagent",
|
|
1990
|
+
model: "inherit",
|
|
1991
|
+
reasoningEffort: "high",
|
|
1992
|
+
onUnavailable: "inherit"
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1924
1995
|
|
|
1925
1996
|
// src/commands/init.ts
|
|
1926
1997
|
function checkGitRepo(cwd) {
|
|
@@ -2005,6 +2076,35 @@ function hasStandaloneProjectRepoOutsideWorkspaceGitRoot(workspaceGitRoot, proje
|
|
|
2005
2076
|
return !!projectGitRoot && path8.resolve(projectGitRoot) !== path8.resolve(workspaceGitRoot);
|
|
2006
2077
|
});
|
|
2007
2078
|
}
|
|
2079
|
+
var INIT_REVIEW_PHASES = ["plan", "task", "feature"];
|
|
2080
|
+
function parseInitReviews(value) {
|
|
2081
|
+
if (typeof value === "undefined") return void 0;
|
|
2082
|
+
const normalized = value.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
2083
|
+
if (normalized.length === 1 && normalized[0] === "none") return [];
|
|
2084
|
+
if (normalized.length === 0 || normalized.includes("none") || normalized.some(
|
|
2085
|
+
(entry) => !INIT_REVIEW_PHASES.includes(entry)
|
|
2086
|
+
)) {
|
|
2087
|
+
throw createCliError(
|
|
2088
|
+
"INVALID_ARGUMENT",
|
|
2089
|
+
"`--reviews` must be a comma-separated subset of `plan,task,feature`, or `none`."
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
return INIT_REVIEW_PHASES.filter((phase) => normalized.includes(phase));
|
|
2093
|
+
}
|
|
2094
|
+
function assertValidInitWorkflowOptions(options) {
|
|
2095
|
+
if (options.taskAgent && !["on", "off"].includes(options.taskAgent)) {
|
|
2096
|
+
throw createCliError(
|
|
2097
|
+
"INVALID_ARGUMENT",
|
|
2098
|
+
"`--task-agent` must be `on` or `off`."
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
if (options.completionStrategy && !["local-ff", "local-squash", "none"].includes(options.completionStrategy)) {
|
|
2102
|
+
throw createCliError(
|
|
2103
|
+
"INVALID_ARGUMENT",
|
|
2104
|
+
"`--completion-strategy` must be `local-ff`, `local-squash`, or `none`."
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2008
2108
|
function initCommand(program2) {
|
|
2009
2109
|
program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option(
|
|
2010
2110
|
"-t, --type <type>",
|
|
@@ -2012,7 +2112,13 @@ function initCommand(program2) {
|
|
|
2012
2112
|
).option(
|
|
2013
2113
|
"--components <list>",
|
|
2014
2114
|
"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("
|
|
2115
|
+
).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("--task-agent <mode>", "Task implementation agent: on | off").option(
|
|
2116
|
+
"--reviews <list>",
|
|
2117
|
+
"Review gates: comma-separated plan,task,feature | none"
|
|
2118
|
+
).option(
|
|
2119
|
+
"--completion-strategy <strategy>",
|
|
2120
|
+
"Local completion: local-ff | local-squash | none"
|
|
2121
|
+
).option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("--docs-repo <mode>", "Docs repository mode: embedded | standalone").option(
|
|
2016
2122
|
"--project-root <path>",
|
|
2017
2123
|
"Project root path (standalone single) or JSON map for standalone multi"
|
|
2018
2124
|
).option(
|
|
@@ -2042,6 +2148,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2042
2148
|
});
|
|
2043
2149
|
}
|
|
2044
2150
|
async function runInit(options) {
|
|
2151
|
+
assertValidInitWorkflowOptions(options);
|
|
2045
2152
|
const cwd = process.cwd();
|
|
2046
2153
|
const defaultName = path8.basename(cwd);
|
|
2047
2154
|
let projectName = options.name || defaultName;
|
|
@@ -2049,6 +2156,9 @@ async function runInit(options) {
|
|
|
2049
2156
|
let components = parseComponentsOption(options.components);
|
|
2050
2157
|
let lang = options.lang || "en";
|
|
2051
2158
|
let workflowMode = options.workflow || "github";
|
|
2159
|
+
let taskAgentEnabled = options.taskAgent !== "off";
|
|
2160
|
+
let enabledReviews = parseInitReviews(options.reviews) ?? ["plan", "feature"];
|
|
2161
|
+
let completionStrategy = options.completionStrategy || "local-ff";
|
|
2052
2162
|
let docsRepo = options.docsRepo || "embedded";
|
|
2053
2163
|
let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
|
|
2054
2164
|
let docsRemote = options.docsRemote;
|
|
@@ -2168,6 +2278,24 @@ async function runInit(options) {
|
|
|
2168
2278
|
}
|
|
2169
2279
|
],
|
|
2170
2280
|
initial: 0
|
|
2281
|
+
},
|
|
2282
|
+
{
|
|
2283
|
+
type: options.workflow ? null : "select",
|
|
2284
|
+
name: "workflowMode",
|
|
2285
|
+
message: tr(lang, "cli", "init.prompt.workflowMode"),
|
|
2286
|
+
choices: [
|
|
2287
|
+
{
|
|
2288
|
+
title: tr(lang, "cli", "init.choice.workflow.github.title"),
|
|
2289
|
+
value: "github",
|
|
2290
|
+
description: tr(lang, "cli", "init.choice.workflow.github.desc")
|
|
2291
|
+
},
|
|
2292
|
+
{
|
|
2293
|
+
title: tr(lang, "cli", "init.choice.workflow.local.title"),
|
|
2294
|
+
value: "local",
|
|
2295
|
+
description: tr(lang, "cli", "init.choice.workflow.local.desc")
|
|
2296
|
+
}
|
|
2297
|
+
],
|
|
2298
|
+
initial: 0
|
|
2171
2299
|
}
|
|
2172
2300
|
],
|
|
2173
2301
|
{
|
|
@@ -2179,6 +2307,7 @@ async function runInit(options) {
|
|
|
2179
2307
|
projectName = response.projectName || projectName;
|
|
2180
2308
|
projectType = response.projectType || projectType;
|
|
2181
2309
|
docsRepo = response.docsRepo || "embedded";
|
|
2310
|
+
workflowMode = response.workflowMode || workflowMode;
|
|
2182
2311
|
if (docsRepo === "standalone") {
|
|
2183
2312
|
const resolvedType = normalizeProjectType(
|
|
2184
2313
|
String(projectType || response.projectType || "multi")
|
|
@@ -2290,6 +2419,128 @@ async function runInit(options) {
|
|
|
2290
2419
|
docsRemote = remoteResponse.docsRemote || docsRemote;
|
|
2291
2420
|
}
|
|
2292
2421
|
}
|
|
2422
|
+
const hasExplicitWorkflowSetup = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.completionStrategy !== "undefined";
|
|
2423
|
+
let customizeWorkflow = hasExplicitWorkflowSetup;
|
|
2424
|
+
if (!hasExplicitWorkflowSetup) {
|
|
2425
|
+
const setupResponse = await prompts(
|
|
2426
|
+
[
|
|
2427
|
+
{
|
|
2428
|
+
type: "select",
|
|
2429
|
+
name: "workflowSetup",
|
|
2430
|
+
message: tr(lang, "cli", "init.prompt.workflowSetup"),
|
|
2431
|
+
choices: [
|
|
2432
|
+
{
|
|
2433
|
+
title: tr(
|
|
2434
|
+
lang,
|
|
2435
|
+
"cli",
|
|
2436
|
+
"init.choice.workflowSetup.recommended.title"
|
|
2437
|
+
),
|
|
2438
|
+
value: "recommended",
|
|
2439
|
+
description: tr(
|
|
2440
|
+
lang,
|
|
2441
|
+
"cli",
|
|
2442
|
+
"init.choice.workflowSetup.recommended.desc"
|
|
2443
|
+
)
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
title: tr(
|
|
2447
|
+
lang,
|
|
2448
|
+
"cli",
|
|
2449
|
+
"init.choice.workflowSetup.custom.title"
|
|
2450
|
+
),
|
|
2451
|
+
value: "custom",
|
|
2452
|
+
description: tr(
|
|
2453
|
+
lang,
|
|
2454
|
+
"cli",
|
|
2455
|
+
"init.choice.workflowSetup.custom.desc"
|
|
2456
|
+
)
|
|
2457
|
+
}
|
|
2458
|
+
],
|
|
2459
|
+
initial: 0
|
|
2460
|
+
}
|
|
2461
|
+
],
|
|
2462
|
+
{
|
|
2463
|
+
onCancel: () => {
|
|
2464
|
+
throw new Error("canceled");
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
);
|
|
2468
|
+
customizeWorkflow = setupResponse.workflowSetup === "custom";
|
|
2469
|
+
}
|
|
2470
|
+
if (customizeWorkflow) {
|
|
2471
|
+
const workflowResponse = await prompts(
|
|
2472
|
+
[
|
|
2473
|
+
{
|
|
2474
|
+
type: typeof options.taskAgent === "undefined" ? "select" : null,
|
|
2475
|
+
name: "taskAgent",
|
|
2476
|
+
message: tr(lang, "cli", "init.prompt.taskAgent"),
|
|
2477
|
+
choices: [
|
|
2478
|
+
{
|
|
2479
|
+
title: tr(lang, "cli", "init.choice.taskAgent.on"),
|
|
2480
|
+
value: true
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
title: tr(lang, "cli", "init.choice.taskAgent.off"),
|
|
2484
|
+
value: false
|
|
2485
|
+
}
|
|
2486
|
+
],
|
|
2487
|
+
initial: 0
|
|
2488
|
+
},
|
|
2489
|
+
{
|
|
2490
|
+
type: typeof options.reviews === "undefined" ? "multiselect" : null,
|
|
2491
|
+
name: "reviews",
|
|
2492
|
+
message: tr(lang, "cli", "init.prompt.reviews"),
|
|
2493
|
+
choices: INIT_REVIEW_PHASES.map((phase) => ({
|
|
2494
|
+
title: tr(lang, "cli", `init.choice.review.${phase}`),
|
|
2495
|
+
value: phase,
|
|
2496
|
+
selected: phase !== "task"
|
|
2497
|
+
}))
|
|
2498
|
+
},
|
|
2499
|
+
{
|
|
2500
|
+
type: workflowMode === "local" && typeof options.completionStrategy === "undefined" ? "select" : null,
|
|
2501
|
+
name: "completionStrategy",
|
|
2502
|
+
message: tr(lang, "cli", "init.prompt.completionStrategy"),
|
|
2503
|
+
choices: [
|
|
2504
|
+
{
|
|
2505
|
+
title: tr(
|
|
2506
|
+
lang,
|
|
2507
|
+
"cli",
|
|
2508
|
+
"init.choice.completionStrategy.localFf"
|
|
2509
|
+
),
|
|
2510
|
+
value: "local-ff"
|
|
2511
|
+
},
|
|
2512
|
+
{
|
|
2513
|
+
title: tr(
|
|
2514
|
+
lang,
|
|
2515
|
+
"cli",
|
|
2516
|
+
"init.choice.completionStrategy.localSquash"
|
|
2517
|
+
),
|
|
2518
|
+
value: "local-squash"
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
title: tr(lang, "cli", "init.choice.completionStrategy.none"),
|
|
2522
|
+
value: "none"
|
|
2523
|
+
}
|
|
2524
|
+
],
|
|
2525
|
+
initial: 0
|
|
2526
|
+
}
|
|
2527
|
+
],
|
|
2528
|
+
{
|
|
2529
|
+
onCancel: () => {
|
|
2530
|
+
throw new Error("canceled");
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
);
|
|
2534
|
+
if (typeof workflowResponse.taskAgent === "boolean") {
|
|
2535
|
+
taskAgentEnabled = workflowResponse.taskAgent;
|
|
2536
|
+
}
|
|
2537
|
+
if (Array.isArray(workflowResponse.reviews)) {
|
|
2538
|
+
enabledReviews = INIT_REVIEW_PHASES.filter(
|
|
2539
|
+
(phase) => workflowResponse.reviews.includes(phase)
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
completionStrategy = workflowResponse.completionStrategy || completionStrategy;
|
|
2543
|
+
}
|
|
2293
2544
|
}
|
|
2294
2545
|
if (!projectType) {
|
|
2295
2546
|
projectType = "multi";
|
|
@@ -2315,6 +2566,12 @@ async function runInit(options) {
|
|
|
2315
2566
|
tr(lang, "cli", "validation.context.workflowMode"),
|
|
2316
2567
|
lang
|
|
2317
2568
|
);
|
|
2569
|
+
if (workflowMode !== "local" && options.completionStrategy) {
|
|
2570
|
+
throw createCliError(
|
|
2571
|
+
"INVALID_ARGUMENT",
|
|
2572
|
+
"`--completion-strategy` can only be used with `--workflow local`."
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2318
2575
|
if (projectType === "single") {
|
|
2319
2576
|
if (components.length > 0) {
|
|
2320
2577
|
throw createCliError(
|
|
@@ -2478,7 +2735,7 @@ async function runInit(options) {
|
|
|
2478
2735
|
}
|
|
2479
2736
|
}
|
|
2480
2737
|
console.log();
|
|
2481
|
-
console.log(chalk.blue(tr(lang, "cli", "init.log.
|
|
2738
|
+
console.log(chalk.blue(tr(lang, "cli", "init.log.configSummaryTitle")));
|
|
2482
2739
|
console.log(
|
|
2483
2740
|
chalk.gray(
|
|
2484
2741
|
` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
|
|
@@ -2493,6 +2750,35 @@ async function runInit(options) {
|
|
|
2493
2750
|
console.log(
|
|
2494
2751
|
chalk.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
2495
2752
|
);
|
|
2753
|
+
console.log(
|
|
2754
|
+
chalk.gray(
|
|
2755
|
+
` ${tr(lang, "cli", "init.log.workflowLabel")}: ${workflowMode}`
|
|
2756
|
+
)
|
|
2757
|
+
);
|
|
2758
|
+
console.log(
|
|
2759
|
+
chalk.gray(
|
|
2760
|
+
` ${tr(lang, "cli", "init.log.taskAgentLabel")}: ${tr(
|
|
2761
|
+
lang,
|
|
2762
|
+
"cli",
|
|
2763
|
+
taskAgentEnabled ? "init.summary.taskAgent.on" : "init.summary.taskAgent.off"
|
|
2764
|
+
)}`
|
|
2765
|
+
)
|
|
2766
|
+
);
|
|
2767
|
+
const reviewSummary = enabledReviews.length > 0 ? enabledReviews.map((phase) => tr(lang, "cli", `init.choice.review.${phase}`)).join(", ") : tr(lang, "cli", "init.summary.reviews.none");
|
|
2768
|
+
console.log(
|
|
2769
|
+
chalk.gray(
|
|
2770
|
+
` ${tr(lang, "cli", "init.log.reviewsLabel")}: ${reviewSummary}`
|
|
2771
|
+
)
|
|
2772
|
+
);
|
|
2773
|
+
if (workflowMode === "local") {
|
|
2774
|
+
console.log(
|
|
2775
|
+
chalk.gray(
|
|
2776
|
+
` ${tr(lang, "cli", "init.log.completionStrategyLabel")}: ${completionStrategy}`
|
|
2777
|
+
)
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
console.log();
|
|
2781
|
+
console.log(chalk.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
2496
2782
|
console.log();
|
|
2497
2783
|
const templatesDir = getTemplatesDir();
|
|
2498
2784
|
const commonPath = path8.join(templatesDir, lang, "common");
|
|
@@ -2541,14 +2827,33 @@ async function runInit(options) {
|
|
|
2541
2827
|
taskCommitGate: "warn",
|
|
2542
2828
|
...workflowMode === "local" ? {
|
|
2543
2829
|
baseBranch: resolveInitialBaseBranch(cwd, docsRepo, projectRoot),
|
|
2544
|
-
completionStrategy
|
|
2830
|
+
completionStrategy,
|
|
2545
2831
|
deleteFeatureBranchAfterMerge: true,
|
|
2546
2832
|
featureChecks: [],
|
|
2547
2833
|
postMergeChecks: []
|
|
2548
2834
|
} : {},
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2835
|
+
agentExecution: {
|
|
2836
|
+
task: {
|
|
2837
|
+
...createDefaultAgentExecutionTaskConfig(),
|
|
2838
|
+
enabled: taskAgentEnabled
|
|
2839
|
+
}
|
|
2840
|
+
},
|
|
2841
|
+
agentReview: {
|
|
2842
|
+
plan: {
|
|
2843
|
+
enabled: enabledReviews.includes("plan"),
|
|
2844
|
+
evidenceMode: "path_required",
|
|
2845
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2846
|
+
},
|
|
2847
|
+
task: {
|
|
2848
|
+
enabled: enabledReviews.includes("task"),
|
|
2849
|
+
evidenceMode: "path_required",
|
|
2850
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2851
|
+
},
|
|
2852
|
+
feature: {
|
|
2853
|
+
enabled: enabledReviews.includes("feature"),
|
|
2854
|
+
evidenceMode: "path_required",
|
|
2855
|
+
reviewer: createDefaultAgentReviewerConfig()
|
|
2856
|
+
}
|
|
2552
2857
|
}
|
|
2553
2858
|
},
|
|
2554
2859
|
pr: {
|
|
@@ -2765,17 +3070,12 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2765
3070
|
const lines = next.split("\n");
|
|
2766
3071
|
const filtered = [];
|
|
2767
3072
|
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(
|
|
3073
|
+
if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|PR Review|PR 리뷰|PR Review Evidence|PR 리뷰 Evidence)\*\*\s*:/.test(
|
|
2774
3074
|
line
|
|
2775
3075
|
)) {
|
|
2776
3076
|
continue;
|
|
2777
3077
|
}
|
|
2778
|
-
if (/^\s*-\s*\*\*(
|
|
3078
|
+
if (/^\s*-\s*\*\*(PR Review Decision|PR 리뷰 Decision)\*\*\s*:/.test(
|
|
2779
3079
|
line
|
|
2780
3080
|
)) {
|
|
2781
3081
|
continue;
|
|
@@ -2789,9 +3089,17 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2789
3089
|
if (/^\s*-\s*Example:\s*review note link/i.test(line)) continue;
|
|
2790
3090
|
if (/^\s*-\s*사전 리뷰 최종 결과/.test(line)) continue;
|
|
2791
3091
|
if (/^\s*-\s*예:\s*리뷰 노트 링크/.test(line)) continue;
|
|
3092
|
+
if (/^\s*-\s*PR creation requires/i.test(line)) continue;
|
|
3093
|
+
if (/^\s*-\s*PR 생성 전/.test(line)) continue;
|
|
3094
|
+
if (/agents\/skills\/create-pr\.md/.test(line)) continue;
|
|
3095
|
+
if (/^\s*-\s*Mark .*PR review handoff/i.test(line)) continue;
|
|
3096
|
+
if (/^\s*-\s*PR 리뷰 handoff/.test(line)) continue;
|
|
3097
|
+
if (/^\s*-\s*Record why\/how review comments/i.test(line)) continue;
|
|
3098
|
+
if (/^\s*-\s*리뷰 지적사항을/.test(line)) continue;
|
|
2792
3099
|
filtered.push(line);
|
|
2793
3100
|
}
|
|
2794
3101
|
next = filtered.join("\n");
|
|
3102
|
+
next = lang === "ko" ? next.replaceAll("**PR \uC804 \uB9AC\uBDF0**", "**Feature \uB9AC\uBDF0**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Evidence**", "**Feature \uB9AC\uBDF0 Evidence**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Decision**", "**Feature \uB9AC\uBDF0 Decision**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Head**", "**Feature \uB9AC\uBDF0 Head**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Tree**", "**Feature \uB9AC\uBDF0 Tree**").replaceAll("pre-PR \uB9AC\uBDF0 handoff", "Feature \uB9AC\uBDF0 handoff") : next.replaceAll("**Pre-PR Review**", "**Feature Review**").replaceAll("**Pre-PR Evidence**", "**Feature Review Evidence**").replaceAll("**Pre-PR Decision**", "**Feature Review Decision**").replaceAll("**Pre-PR Reviewed Head**", "**Feature Reviewed Head**").replaceAll("**Pre-PR Reviewed Tree**", "**Feature Reviewed Tree**").replaceAll("pre-PR review handoff", "Feature review handoff");
|
|
2795
3103
|
next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
|
|
2796
3104
|
return normalizeTrailingBlankLines(next);
|
|
2797
3105
|
}
|
|
@@ -3798,52 +4106,120 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
|
|
|
3798
4106
|
}
|
|
3799
4107
|
}
|
|
3800
4108
|
}
|
|
3801
|
-
if (!isPlainObject(workflow.
|
|
3802
|
-
workflow.
|
|
3803
|
-
changedPaths.push("workflow.
|
|
4109
|
+
if (!isPlainObject(workflow.agentExecution)) {
|
|
4110
|
+
workflow.agentExecution = {};
|
|
4111
|
+
changedPaths.push("workflow.agentExecution");
|
|
4112
|
+
}
|
|
4113
|
+
const agentExecution = workflow.agentExecution;
|
|
4114
|
+
if (!isPlainObject(agentExecution.task)) {
|
|
4115
|
+
agentExecution.task = {};
|
|
4116
|
+
changedPaths.push("workflow.agentExecution.task");
|
|
3804
4117
|
}
|
|
3805
|
-
const
|
|
4118
|
+
const taskExecution = agentExecution.task;
|
|
4119
|
+
const defaultTaskExecution = createDefaultAgentExecutionTaskConfig();
|
|
3806
4120
|
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
|
-
|
|
4121
|
+
taskExecution,
|
|
4122
|
+
"enabled",
|
|
4123
|
+
defaultTaskExecution.enabled,
|
|
4124
|
+
"workflow.agentExecution.task.enabled"
|
|
4125
|
+
);
|
|
4126
|
+
if (typeof taskExecution.enabled !== "boolean") {
|
|
4127
|
+
taskExecution.enabled = defaultTaskExecution.enabled;
|
|
4128
|
+
changedPaths.push("workflow.agentExecution.task.enabled");
|
|
4129
|
+
}
|
|
4130
|
+
if (taskExecution.type !== "subagent") {
|
|
4131
|
+
taskExecution.type = defaultTaskExecution.type;
|
|
4132
|
+
changedPaths.push("workflow.agentExecution.task.type");
|
|
4133
|
+
}
|
|
4134
|
+
if (typeof taskExecution.model !== "string" || !taskExecution.model.trim()) {
|
|
4135
|
+
taskExecution.model = defaultTaskExecution.model;
|
|
4136
|
+
changedPaths.push("workflow.agentExecution.task.model");
|
|
4137
|
+
} else if (taskExecution.model !== taskExecution.model.trim()) {
|
|
4138
|
+
taskExecution.model = taskExecution.model.trim();
|
|
4139
|
+
changedPaths.push("workflow.agentExecution.task.model");
|
|
4140
|
+
}
|
|
4141
|
+
if (typeof taskExecution.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
4142
|
+
taskExecution.reasoningEffort
|
|
4143
|
+
)) {
|
|
4144
|
+
taskExecution.reasoningEffort = defaultTaskExecution.reasoningEffort;
|
|
4145
|
+
changedPaths.push("workflow.agentExecution.task.reasoningEffort");
|
|
4146
|
+
}
|
|
4147
|
+
if (taskExecution.onUnavailable !== "inherit" && taskExecution.onUnavailable !== "error") {
|
|
4148
|
+
taskExecution.onUnavailable = defaultTaskExecution.onUnavailable;
|
|
4149
|
+
changedPaths.push("workflow.agentExecution.task.onUnavailable");
|
|
4150
|
+
}
|
|
4151
|
+
const legacyPrePrReview = isPlainObject(workflow.prePrReview) ? { ...workflow.prePrReview } : null;
|
|
4152
|
+
if (!isPlainObject(workflow.agentReview)) {
|
|
4153
|
+
workflow.agentReview = {};
|
|
4154
|
+
changedPaths.push("workflow.agentReview");
|
|
4155
|
+
}
|
|
4156
|
+
const agentReview = workflow.agentReview;
|
|
4157
|
+
const normalizeAgentReviewPhase = (key, enabledDefault, legacySeed = null) => {
|
|
4158
|
+
const phasePath = `workflow.agentReview.${key}`;
|
|
4159
|
+
if (!isPlainObject(agentReview[key])) {
|
|
4160
|
+
agentReview[key] = legacySeed ? { ...legacySeed } : {};
|
|
4161
|
+
changedPaths.push(phasePath);
|
|
4162
|
+
}
|
|
4163
|
+
const phase = agentReview[key];
|
|
4164
|
+
setIfMissing(phase, "enabled", enabledDefault, `${phasePath}.enabled`);
|
|
4165
|
+
if (typeof phase.enabled !== "boolean") {
|
|
4166
|
+
phase.enabled = enabledDefault;
|
|
4167
|
+
changedPaths.push(`${phasePath}.enabled`);
|
|
4168
|
+
}
|
|
4169
|
+
setIfMissing(
|
|
4170
|
+
phase,
|
|
4171
|
+
"evidenceMode",
|
|
4172
|
+
"path_required",
|
|
4173
|
+
`${phasePath}.evidenceMode`
|
|
4174
|
+
);
|
|
4175
|
+
if (phase.evidenceMode !== "path_required" && phase.evidenceMode !== "any") {
|
|
4176
|
+
phase.evidenceMode = "path_required";
|
|
4177
|
+
changedPaths.push(`${phasePath}.evidenceMode`);
|
|
4178
|
+
}
|
|
4179
|
+
if ("findings" in phase) {
|
|
4180
|
+
delete phase.findings;
|
|
4181
|
+
changedPaths.push(`${phasePath}.findings`);
|
|
4182
|
+
}
|
|
4183
|
+
const defaultReviewer = createDefaultAgentReviewerConfig();
|
|
4184
|
+
if (!isPlainObject(phase.reviewer)) {
|
|
4185
|
+
phase.reviewer = defaultReviewer;
|
|
4186
|
+
changedPaths.push(`${phasePath}.reviewer`);
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
const reviewer = phase.reviewer;
|
|
3826
4190
|
if (reviewer.type !== "subagent") {
|
|
3827
4191
|
reviewer.type = defaultReviewer.type;
|
|
3828
|
-
changedPaths.push(
|
|
4192
|
+
changedPaths.push(`${phasePath}.reviewer.type`);
|
|
3829
4193
|
}
|
|
3830
4194
|
if (typeof reviewer.model !== "string" || !reviewer.model.trim()) {
|
|
3831
4195
|
reviewer.model = defaultReviewer.model;
|
|
3832
|
-
changedPaths.push(
|
|
4196
|
+
changedPaths.push(`${phasePath}.reviewer.model`);
|
|
3833
4197
|
} else if (reviewer.model !== reviewer.model.trim()) {
|
|
3834
4198
|
reviewer.model = reviewer.model.trim();
|
|
3835
|
-
changedPaths.push(
|
|
4199
|
+
changedPaths.push(`${phasePath}.reviewer.model`);
|
|
3836
4200
|
}
|
|
3837
|
-
if (typeof reviewer.reasoningEffort !== "string" || !
|
|
4201
|
+
if (typeof reviewer.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
3838
4202
|
reviewer.reasoningEffort
|
|
3839
4203
|
)) {
|
|
3840
4204
|
reviewer.reasoningEffort = defaultReviewer.reasoningEffort;
|
|
3841
|
-
changedPaths.push(
|
|
4205
|
+
changedPaths.push(`${phasePath}.reviewer.reasoningEffort`);
|
|
3842
4206
|
}
|
|
3843
4207
|
if (reviewer.onUnavailable !== "inherit" && reviewer.onUnavailable !== "error") {
|
|
3844
4208
|
reviewer.onUnavailable = defaultReviewer.onUnavailable;
|
|
3845
|
-
changedPaths.push(
|
|
4209
|
+
changedPaths.push(`${phasePath}.reviewer.onUnavailable`);
|
|
3846
4210
|
}
|
|
4211
|
+
};
|
|
4212
|
+
normalizeAgentReviewPhase("plan", true);
|
|
4213
|
+
normalizeAgentReviewPhase("task", false);
|
|
4214
|
+
const legacyFeatureEnabled = typeof legacyPrePrReview?.enabled === "boolean" ? legacyPrePrReview.enabled : workflow.mode !== "local";
|
|
4215
|
+
normalizeAgentReviewPhase(
|
|
4216
|
+
"feature",
|
|
4217
|
+
legacyFeatureEnabled,
|
|
4218
|
+
legacyPrePrReview
|
|
4219
|
+
);
|
|
4220
|
+
if (hasOwnKey2(workflow, "prePrReview")) {
|
|
4221
|
+
delete workflow.prePrReview;
|
|
4222
|
+
changedPaths.push("workflow.prePrReview");
|
|
3847
4223
|
}
|
|
3848
4224
|
if (!isPlainObject(raw.pr)) {
|
|
3849
4225
|
raw.pr = {};
|
|
@@ -4980,7 +5356,7 @@ function extractTaskTitles(tasksContent) {
|
|
|
4980
5356
|
return uniqItems(
|
|
4981
5357
|
tasksContent.split("\n").map((line) => {
|
|
4982
5358
|
const match = line.match(
|
|
4983
|
-
/^\s*-\s*\[(?:TODO|DOING|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
|
|
5359
|
+
/^\s*-\s*\[(?:TODO|DOING|REVIEW|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
|
|
4984
5360
|
);
|
|
4985
5361
|
if (!match) return void 0;
|
|
4986
5362
|
return sanitizeDraftItem(match[1]);
|
|
@@ -7120,7 +7496,11 @@ function formatTaskBlock(input) {
|
|
|
7120
7496
|
" - Acceptance:",
|
|
7121
7497
|
...input.acceptanceItems.map((item) => ` - ${item}`),
|
|
7122
7498
|
" - Checklist:",
|
|
7123
|
-
...input.checklistItems.map((item) => ` - [ ] ${item}`)
|
|
7499
|
+
...input.checklistItems.map((item) => ` - [ ] ${item}`),
|
|
7500
|
+
" - Review Evidence: -",
|
|
7501
|
+
" - Review Decision: -",
|
|
7502
|
+
" - Reviewed Head: -",
|
|
7503
|
+
" - Reviewed Tree: -"
|
|
7124
7504
|
];
|
|
7125
7505
|
}
|
|
7126
7506
|
async function runTaskAdd(featureName, options) {
|
|
@@ -7548,7 +7928,7 @@ function registerCodexHooksIntegration(parent) {
|
|
|
7548
7928
|
removeLeeSpecKitCodexHooks,
|
|
7549
7929
|
resolveCodexHooksRepoRoot,
|
|
7550
7930
|
upsertLeeSpecKitCodexHooks
|
|
7551
|
-
} = await import('./hooks-
|
|
7931
|
+
} = await import('./hooks-67CU3GZC.js');
|
|
7552
7932
|
const workflowRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
|
|
7553
7933
|
if (!workflowRoot) {
|
|
7554
7934
|
throw createCliError(
|
|
@@ -7953,9 +8333,57 @@ var ISSUE_LABELS = ["Issue", "Issue Number", "\uC774\uC288", "\uC774\uC288 \uBC8
|
|
|
7953
8333
|
var BRANCH_LABELS3 = ["Branch", "\uBE0C\uB79C\uCE58"];
|
|
7954
8334
|
var PR_LABELS = ["PR", "Pull Request"];
|
|
7955
8335
|
var PR_STATUS_LABELS = ["PR Status", "PR \uC0C1\uD0DC"];
|
|
7956
|
-
var PRE_PR_REVIEW_LABELS = [
|
|
7957
|
-
|
|
7958
|
-
|
|
8336
|
+
var PRE_PR_REVIEW_LABELS = [
|
|
8337
|
+
"Pre-PR Review",
|
|
8338
|
+
"PR \uC804 \uB9AC\uBDF0",
|
|
8339
|
+
"Feature Review",
|
|
8340
|
+
"Feature \uB9AC\uBDF0"
|
|
8341
|
+
];
|
|
8342
|
+
var PRE_PR_EVIDENCE_LABELS = [
|
|
8343
|
+
"Pre-PR Evidence",
|
|
8344
|
+
"PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
8345
|
+
"Feature Review Evidence",
|
|
8346
|
+
"Feature \uB9AC\uBDF0 Evidence"
|
|
8347
|
+
];
|
|
8348
|
+
var PRE_PR_DECISION_LABELS = [
|
|
8349
|
+
"Pre-PR Decision",
|
|
8350
|
+
"PR \uC804 \uB9AC\uBDF0 Decision",
|
|
8351
|
+
"Feature Review Decision",
|
|
8352
|
+
"Feature \uB9AC\uBDF0 Decision"
|
|
8353
|
+
];
|
|
8354
|
+
var PRE_PR_REVIEWED_HEAD_LABELS = [
|
|
8355
|
+
"Pre-PR Reviewed Head",
|
|
8356
|
+
"PR \uC804 \uB9AC\uBDF0 Head",
|
|
8357
|
+
"Feature Reviewed Head",
|
|
8358
|
+
"Feature \uB9AC\uBDF0 Head"
|
|
8359
|
+
];
|
|
8360
|
+
var PRE_PR_REVIEWED_TREE_LABELS = [
|
|
8361
|
+
"Pre-PR Reviewed Tree",
|
|
8362
|
+
"PR \uC804 \uB9AC\uBDF0 Tree",
|
|
8363
|
+
"Feature Reviewed Tree",
|
|
8364
|
+
"Feature \uB9AC\uBDF0 Tree"
|
|
8365
|
+
];
|
|
8366
|
+
var PLAN_REVIEW_STATUS_LABELS = ["Plan Review", "Plan \uAC80\uC218", "Plan \uB9AC\uBDF0"];
|
|
8367
|
+
var PLAN_REVIEW_EVIDENCE_LABELS = [
|
|
8368
|
+
"Plan Review Evidence",
|
|
8369
|
+
"Plan \uAC80\uC218 Evidence",
|
|
8370
|
+
"Plan \uB9AC\uBDF0 Evidence"
|
|
8371
|
+
];
|
|
8372
|
+
var PLAN_REVIEW_DECISION_LABELS = [
|
|
8373
|
+
"Plan Review Decision",
|
|
8374
|
+
"Plan \uAC80\uC218 Decision",
|
|
8375
|
+
"Plan \uB9AC\uBDF0 Decision"
|
|
8376
|
+
];
|
|
8377
|
+
var PLAN_REVIEWED_SPEC_HASH_LABELS = [
|
|
8378
|
+
"Plan Reviewed Spec Hash",
|
|
8379
|
+
"Plan \uAC80\uC218 Spec Hash",
|
|
8380
|
+
"Plan \uB9AC\uBDF0 Spec Hash"
|
|
8381
|
+
];
|
|
8382
|
+
var PLAN_REVIEWED_PLAN_HASH_LABELS = [
|
|
8383
|
+
"Plan Reviewed Plan Hash",
|
|
8384
|
+
"Plan \uAC80\uC218 Plan Hash",
|
|
8385
|
+
"Plan \uB9AC\uBDF0 Plan Hash"
|
|
8386
|
+
];
|
|
7959
8387
|
var COMPLETION_MARKERS = {
|
|
7960
8388
|
allTasks: "lee-spec-kit:completion:all-tasks",
|
|
7961
8389
|
tests: "lee-spec-kit:completion:tests",
|
|
@@ -7974,7 +8402,10 @@ function resolveWorkflowRequirements(config) {
|
|
|
7974
8402
|
requirePr: workflow.requirePr ?? !isLocalWorkflow,
|
|
7975
8403
|
requireReview: workflow.requireReview ?? !isLocalWorkflow,
|
|
7976
8404
|
requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
|
|
7977
|
-
|
|
8405
|
+
planReviewEnabled: workflow.agentReview?.plan?.enabled ?? true,
|
|
8406
|
+
taskReviewEnabled: workflow.agentReview?.task?.enabled ?? false,
|
|
8407
|
+
featureReviewEnabled: workflow.agentReview?.feature?.enabled ?? workflow.prePrReview?.enabled ?? !isLocalWorkflow,
|
|
8408
|
+
taskExecutionEnabled: workflow.agentExecution?.task?.enabled ?? true
|
|
7978
8409
|
};
|
|
7979
8410
|
}
|
|
7980
8411
|
function parseApprovalStatus(raw) {
|
|
@@ -8025,7 +8456,62 @@ function parseCompletionCheckbox(lines, marker, legacyPattern) {
|
|
|
8025
8456
|
(line) => legacyPattern.test(line) && parseMarkdownCheckbox(line) === true
|
|
8026
8457
|
);
|
|
8027
8458
|
}
|
|
8028
|
-
function
|
|
8459
|
+
function parseReviewDecisionOutcome(value) {
|
|
8460
|
+
const match = (value || "").trim().toLowerCase().match(/\b(approve|changes_requested|blocked)\b/);
|
|
8461
|
+
return match?.[1] || null;
|
|
8462
|
+
}
|
|
8463
|
+
function parseWorkflowTaskLine(line, index = -1) {
|
|
8464
|
+
const canonical = parseTaskLine(line, index);
|
|
8465
|
+
if (canonical) {
|
|
8466
|
+
return {
|
|
8467
|
+
index,
|
|
8468
|
+
raw: line,
|
|
8469
|
+
status: canonical.status,
|
|
8470
|
+
taskId: canonical.taskId,
|
|
8471
|
+
title: canonical.title
|
|
8472
|
+
};
|
|
8473
|
+
}
|
|
8474
|
+
const legacy = line.match(
|
|
8475
|
+
/^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\](?:\[[^\]]+\])*\s+(?:(T-[A-Za-z0-9-]+)\s+)?(.+?)\s*$/i
|
|
8476
|
+
);
|
|
8477
|
+
if (!legacy) return null;
|
|
8478
|
+
return {
|
|
8479
|
+
index,
|
|
8480
|
+
raw: line,
|
|
8481
|
+
status: legacy[1].toUpperCase(),
|
|
8482
|
+
taskId: legacy[2] || null,
|
|
8483
|
+
title: legacy[3].trim()
|
|
8484
|
+
};
|
|
8485
|
+
}
|
|
8486
|
+
function extractTaskReviewValue(lines, startIndex, labels) {
|
|
8487
|
+
let endIndex = lines.length;
|
|
8488
|
+
for (let index = startIndex + 1; index < lines.length; index += 1) {
|
|
8489
|
+
if (parseWorkflowTaskLine(lines[index]) || /^\s*##\s+/.test(lines[index])) {
|
|
8490
|
+
endIndex = index;
|
|
8491
|
+
break;
|
|
8492
|
+
}
|
|
8493
|
+
}
|
|
8494
|
+
for (let index = startIndex + 1; index < endIndex; index += 1) {
|
|
8495
|
+
for (const label of labels) {
|
|
8496
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8497
|
+
const match = lines[index].match(
|
|
8498
|
+
new RegExp(
|
|
8499
|
+
`^\\s*-\\s*(?:\\*\\*)?${escaped}(?:\\*\\*)?:\\s*(.*?)\\s*$`,
|
|
8500
|
+
"i"
|
|
8501
|
+
)
|
|
8502
|
+
);
|
|
8503
|
+
if (match) return sanitizeMetadataValue2(match[1]);
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
return null;
|
|
8507
|
+
}
|
|
8508
|
+
function buildSyntheticTaskId(feature, title, sameTitleOccurrence) {
|
|
8509
|
+
const fingerprint = createHash("sha256").update(
|
|
8510
|
+
`${feature.folderName}\0${normalizeCommitTopicText(title).toLowerCase()}\0${sameTitleOccurrence}`
|
|
8511
|
+
).digest("hex").slice(0, 10);
|
|
8512
|
+
return `T-${feature.id}-legacy-${fingerprint}`;
|
|
8513
|
+
}
|
|
8514
|
+
function parseTasksDoc(content, feature) {
|
|
8029
8515
|
const issueRaw = extractFieldValue2(content, ISSUE_LABELS);
|
|
8030
8516
|
const issueNumberMatch = issueRaw?.match(/^#(\d+)$/);
|
|
8031
8517
|
const issueNumber = issueNumberMatch ? Number(issueNumberMatch[1]) : null;
|
|
@@ -8034,15 +8520,43 @@ function parseTasksDoc(content) {
|
|
|
8034
8520
|
const prePrDecision = extractFieldValue2(content, PRE_PR_DECISION_LABELS);
|
|
8035
8521
|
const tasks = [];
|
|
8036
8522
|
const nonCodeLines = withoutFencedCodeBlocks(content);
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8523
|
+
const legacyTitleOccurrences = /* @__PURE__ */ new Map();
|
|
8524
|
+
for (let index = 0; index < nonCodeLines.length; index += 1) {
|
|
8525
|
+
const line = nonCodeLines[index];
|
|
8526
|
+
const parsed = parseWorkflowTaskLine(line, index);
|
|
8527
|
+
if (!parsed) continue;
|
|
8528
|
+
const reviewDecision = extractTaskReviewValue(
|
|
8529
|
+
nonCodeLines,
|
|
8530
|
+
index,
|
|
8531
|
+
["Review Decision", "Task Review Decision", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Decision"]
|
|
8040
8532
|
);
|
|
8041
|
-
|
|
8533
|
+
const legacyTitleKey = normalizeCommitTopicText(parsed.title).toLowerCase();
|
|
8534
|
+
const sameTitleOccurrence = (legacyTitleOccurrences.get(legacyTitleKey) || 0) + 1;
|
|
8535
|
+
legacyTitleOccurrences.set(legacyTitleKey, sameTitleOccurrence);
|
|
8536
|
+
const taskId = parsed.taskId || buildSyntheticTaskId(feature, parsed.title, sameTitleOccurrence);
|
|
8042
8537
|
tasks.push({
|
|
8043
8538
|
raw: line,
|
|
8044
|
-
status:
|
|
8045
|
-
|
|
8539
|
+
status: parsed.status,
|
|
8540
|
+
taskId,
|
|
8541
|
+
taskIdSource: parsed.taskId ? "document" : "synthetic",
|
|
8542
|
+
title: parsed.title,
|
|
8543
|
+
reviewEvidence: extractTaskReviewValue(
|
|
8544
|
+
nonCodeLines,
|
|
8545
|
+
index,
|
|
8546
|
+
["Review Evidence", "Task Review Evidence", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Evidence"]
|
|
8547
|
+
),
|
|
8548
|
+
reviewDecision,
|
|
8549
|
+
reviewDecisionOutcome: parseReviewDecisionOutcome(reviewDecision),
|
|
8550
|
+
reviewedHead: extractTaskReviewValue(
|
|
8551
|
+
nonCodeLines,
|
|
8552
|
+
index,
|
|
8553
|
+
["Reviewed Head", "Task Reviewed Head", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Head"]
|
|
8554
|
+
),
|
|
8555
|
+
reviewedTree: extractTaskReviewValue(
|
|
8556
|
+
nonCodeLines,
|
|
8557
|
+
index,
|
|
8558
|
+
["Reviewed Tree", "Task Reviewed Tree", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Tree"]
|
|
8559
|
+
)
|
|
8046
8560
|
});
|
|
8047
8561
|
}
|
|
8048
8562
|
const allTasksChecked = parseCompletionCheckbox(
|
|
@@ -8073,11 +8587,7 @@ function parseTasksDoc(content) {
|
|
|
8073
8587
|
if (value === "done") return "done";
|
|
8074
8588
|
return null;
|
|
8075
8589
|
})();
|
|
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
|
-
})();
|
|
8590
|
+
const prePrDecisionOutcome = parseReviewDecisionOutcome(prePrDecision);
|
|
8081
8591
|
return {
|
|
8082
8592
|
docStatus: parseApprovalStatus(
|
|
8083
8593
|
extractFieldValue2(content, DOC_STATUS_LABELS) || void 0
|
|
@@ -8092,6 +8602,12 @@ function parseTasksDoc(content) {
|
|
|
8092
8602
|
),
|
|
8093
8603
|
prePrDecision: sanitizeMetadataValue2(prePrDecision),
|
|
8094
8604
|
prePrDecisionOutcome,
|
|
8605
|
+
prePrReviewedHead: sanitizeMetadataValue2(
|
|
8606
|
+
extractFieldValue2(content, PRE_PR_REVIEWED_HEAD_LABELS)
|
|
8607
|
+
),
|
|
8608
|
+
prePrReviewedTree: sanitizeMetadataValue2(
|
|
8609
|
+
extractFieldValue2(content, PRE_PR_REVIEWED_TREE_LABELS)
|
|
8610
|
+
),
|
|
8095
8611
|
tasks,
|
|
8096
8612
|
completion: {
|
|
8097
8613
|
allTasksChecked,
|
|
@@ -8106,6 +8622,58 @@ function sanitizeMetadataValue2(value) {
|
|
|
8106
8622
|
if (!trimmed || trimmed === "-") return null;
|
|
8107
8623
|
return trimmed;
|
|
8108
8624
|
}
|
|
8625
|
+
function parsePlanReview(content) {
|
|
8626
|
+
const statusValue = sanitizeMetadataValue2(
|
|
8627
|
+
extractFieldValue2(content, PLAN_REVIEW_STATUS_LABELS)
|
|
8628
|
+
)?.toLowerCase();
|
|
8629
|
+
const status = statusValue === "pending" || statusValue === "running" || statusValue === "done" ? statusValue : null;
|
|
8630
|
+
const evidence = sanitizeMetadataValue2(
|
|
8631
|
+
extractFieldValue2(content, PLAN_REVIEW_EVIDENCE_LABELS)
|
|
8632
|
+
);
|
|
8633
|
+
const decision = sanitizeMetadataValue2(
|
|
8634
|
+
extractFieldValue2(content, PLAN_REVIEW_DECISION_LABELS)
|
|
8635
|
+
);
|
|
8636
|
+
const reviewedSpecHash = sanitizeMetadataValue2(
|
|
8637
|
+
extractFieldValue2(content, PLAN_REVIEWED_SPEC_HASH_LABELS)
|
|
8638
|
+
);
|
|
8639
|
+
const reviewedPlanHash = sanitizeMetadataValue2(
|
|
8640
|
+
extractFieldValue2(content, PLAN_REVIEWED_PLAN_HASH_LABELS)
|
|
8641
|
+
);
|
|
8642
|
+
return {
|
|
8643
|
+
status,
|
|
8644
|
+
evidence,
|
|
8645
|
+
decision,
|
|
8646
|
+
decisionOutcome: parseReviewDecisionOutcome(decision),
|
|
8647
|
+
reviewedSpecHash,
|
|
8648
|
+
reviewedPlanHash,
|
|
8649
|
+
hasMetadata: PLAN_REVIEW_STATUS_LABELS.some((label) => {
|
|
8650
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8651
|
+
return new RegExp(`^\\s*-\\s*\\*\\*${escaped}\\*\\*:`, "mi").test(content);
|
|
8652
|
+
})
|
|
8653
|
+
};
|
|
8654
|
+
}
|
|
8655
|
+
function buildReviewDocumentHash(content, excludedLabels) {
|
|
8656
|
+
const excluded = new Set(
|
|
8657
|
+
[...excludedLabels, "Status", "\uC0C1\uD0DC"].map((label) => label.toLowerCase())
|
|
8658
|
+
);
|
|
8659
|
+
const normalized = content.replace(/\r\n/g, "\n").split("\n").filter((line) => {
|
|
8660
|
+
const match = line.match(/^\s*-\s*\*\*(.+?)\*\*\s*:/);
|
|
8661
|
+
return !match || !excluded.has(match[1].trim().toLowerCase());
|
|
8662
|
+
}).join("\n").trim();
|
|
8663
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
8664
|
+
}
|
|
8665
|
+
function buildPlanReviewTarget(specContent, planContent) {
|
|
8666
|
+
return {
|
|
8667
|
+
specHash: buildReviewDocumentHash(specContent, []),
|
|
8668
|
+
planHash: buildReviewDocumentHash(planContent, [
|
|
8669
|
+
...PLAN_REVIEW_STATUS_LABELS,
|
|
8670
|
+
...PLAN_REVIEW_EVIDENCE_LABELS,
|
|
8671
|
+
...PLAN_REVIEW_DECISION_LABELS,
|
|
8672
|
+
...PLAN_REVIEWED_SPEC_HASH_LABELS,
|
|
8673
|
+
...PLAN_REVIEWED_PLAN_HASH_LABELS
|
|
8674
|
+
])
|
|
8675
|
+
};
|
|
8676
|
+
}
|
|
8109
8677
|
function normalizeCommitTopicText(value) {
|
|
8110
8678
|
return value.replace(/\s+/g, " ").trim();
|
|
8111
8679
|
}
|
|
@@ -8241,7 +8809,7 @@ function hasOpenTask(tasks) {
|
|
|
8241
8809
|
function hasUncommittedChanges(gitCwd) {
|
|
8242
8810
|
if (!gitCwd) return false;
|
|
8243
8811
|
const status = runGitCapture(
|
|
8244
|
-
["status", "--porcelain", "--untracked-files=
|
|
8812
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
8245
8813
|
gitCwd
|
|
8246
8814
|
) || "";
|
|
8247
8815
|
return status.trim().length > 0;
|
|
@@ -8305,7 +8873,7 @@ function resolveProjectCommitTopic(feature, tasks) {
|
|
|
8305
8873
|
const activeTask = tasks.tasks.find(
|
|
8306
8874
|
(task) => task.status === "DOING" || task.status === "REVIEW"
|
|
8307
8875
|
);
|
|
8308
|
-
const raw = activeTask?.title || getLastDoneTask(tasks)?.title ||
|
|
8876
|
+
const raw = activeTask?.title || getLastDoneTask(tasks)?.title || tasks.tasks.find((task) => task.status === "TODO")?.title || feature.folderName;
|
|
8309
8877
|
const withoutTaskId = normalizeCommitTopicText(raw || "").replace(
|
|
8310
8878
|
/^T-[A-Za-z0-9-]+\s+/,
|
|
8311
8879
|
""
|
|
@@ -8362,6 +8930,19 @@ function resolveExpectedBranch(feature, tasks) {
|
|
|
8362
8930
|
if (!tasks.issueNumber) return null;
|
|
8363
8931
|
return `feat/${tasks.issueNumber}-${feature.slug}`;
|
|
8364
8932
|
}
|
|
8933
|
+
function buildExpectedBranchCommand(config, feature, expectedBranch, requireWorktree) {
|
|
8934
|
+
if (requireWorktree) {
|
|
8935
|
+
return buildManagedWorktreeCreateCommand(
|
|
8936
|
+
config,
|
|
8937
|
+
feature.git.projectGitCwd,
|
|
8938
|
+
expectedBranch
|
|
8939
|
+
);
|
|
8940
|
+
}
|
|
8941
|
+
return localBranchExists(
|
|
8942
|
+
resolveProjectRootGitCwd(config, feature),
|
|
8943
|
+
expectedBranch
|
|
8944
|
+
) ? `git checkout ${expectedBranch}` : `git checkout -b ${expectedBranch}`;
|
|
8945
|
+
}
|
|
8365
8946
|
function resolveProjectRootFromGitCwd2(projectGitCwd) {
|
|
8366
8947
|
return runGitCapture(["rev-parse", "--show-toplevel"], projectGitCwd) || path8.resolve(projectGitCwd);
|
|
8367
8948
|
}
|
|
@@ -8526,19 +9107,96 @@ function buildPostMergeCleanupSummary(state) {
|
|
|
8526
9107
|
}
|
|
8527
9108
|
return `Finish the post-merge cleanup before closing the feature: ${remaining.join(", ")}.`;
|
|
8528
9109
|
}
|
|
8529
|
-
function
|
|
8530
|
-
return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "TODO") ||
|
|
9110
|
+
function nextExecutableTask(tasks) {
|
|
9111
|
+
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
9112
|
}
|
|
8532
9113
|
function allTasksDone(tasks) {
|
|
8533
9114
|
return tasks.tasks.length > 0 && tasks.tasks.every((task) => task.status === "DONE");
|
|
8534
9115
|
}
|
|
8535
|
-
function
|
|
8536
|
-
|
|
9116
|
+
function resolveAgentReviewPhase(config, scope) {
|
|
9117
|
+
if (scope === "plan") {
|
|
9118
|
+
return config.workflow?.agentReview?.plan || {};
|
|
9119
|
+
}
|
|
9120
|
+
if (scope === "task") {
|
|
9121
|
+
return config.workflow?.agentReview?.task || {};
|
|
9122
|
+
}
|
|
9123
|
+
return config.workflow?.agentReview?.feature || config.workflow?.prePrReview || {};
|
|
9124
|
+
}
|
|
9125
|
+
function resolveProjectReviewTarget(config, projectGitCwd, scope, taskBase = null) {
|
|
9126
|
+
const pathArgs = ["--", "."];
|
|
9127
|
+
const relativeDocsDir = path8.relative(projectGitCwd, config.docsDir);
|
|
9128
|
+
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
9129
|
+
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
9130
|
+
pathArgs.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
9131
|
+
}
|
|
9132
|
+
const targetSha = runGitCapture(["log", "-n", "1", "--pretty=%H", ...pathArgs], projectGitCwd) || runGitCapture(["rev-parse", "HEAD"], projectGitCwd) || "";
|
|
9133
|
+
if (!targetSha) return null;
|
|
9134
|
+
const targetTree = runGitCapture(["rev-parse", `${targetSha}^{tree}`], projectGitCwd) || "";
|
|
9135
|
+
if (!targetTree) return null;
|
|
9136
|
+
let baseSha = "";
|
|
9137
|
+
if (scope === "task" && taskBase) {
|
|
9138
|
+
const actualBaseTree = runGitCapture(
|
|
9139
|
+
["rev-parse", `${taskBase.reviewedHead}^{tree}`],
|
|
9140
|
+
projectGitCwd
|
|
9141
|
+
) || "";
|
|
9142
|
+
if (actualBaseTree === taskBase.reviewedTree && taskBase.reviewedHead !== targetSha && isAncestor(projectGitCwd, taskBase.reviewedHead, targetSha)) {
|
|
9143
|
+
baseSha = taskBase.reviewedHead;
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
if (!baseSha) {
|
|
9147
|
+
const baseBranch = config.workflow?.baseBranch?.trim() || "main";
|
|
9148
|
+
for (const candidate of [`origin/${baseBranch}`, baseBranch]) {
|
|
9149
|
+
const candidateBase = runGitCapture(["merge-base", candidate, targetSha], projectGitCwd) || "";
|
|
9150
|
+
if (candidateBase && candidateBase !== targetSha) {
|
|
9151
|
+
baseSha = candidateBase;
|
|
9152
|
+
break;
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
}
|
|
9156
|
+
if (!baseSha) {
|
|
9157
|
+
baseSha = runGitCapture(["rev-parse", `${targetSha}^`], projectGitCwd) || targetSha;
|
|
9158
|
+
}
|
|
9159
|
+
return { baseSha, targetSha, targetTree };
|
|
9160
|
+
}
|
|
9161
|
+
function reviewEvidenceSatisfied(config, feature, scope, evidence) {
|
|
9162
|
+
return isPrePrEvidenceSatisfied({
|
|
8537
9163
|
docsDir: config.docsDir,
|
|
8538
9164
|
featureDir: feature.path,
|
|
8539
|
-
evidence
|
|
8540
|
-
evidenceMode: config.
|
|
8541
|
-
})
|
|
9165
|
+
evidence,
|
|
9166
|
+
evidenceMode: resolveAgentReviewPhase(config, scope).evidenceMode
|
|
9167
|
+
});
|
|
9168
|
+
}
|
|
9169
|
+
function planReviewSatisfied(config, feature, review, target) {
|
|
9170
|
+
return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && review.decisionOutcome === "approve" && review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9171
|
+
}
|
|
9172
|
+
function featureReviewSatisfied(config, feature, tasks, target) {
|
|
9173
|
+
return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve" && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
|
|
9174
|
+
}
|
|
9175
|
+
function taskReviewEvidenceSatisfied(config, feature, task, target) {
|
|
9176
|
+
return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && task.reviewDecisionOutcome === "approve" && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
|
|
9177
|
+
}
|
|
9178
|
+
function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
|
|
9179
|
+
if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" || !reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence)) {
|
|
9180
|
+
return false;
|
|
9181
|
+
}
|
|
9182
|
+
const actualTree = runGitCapture(
|
|
9183
|
+
["rev-parse", `${task.reviewedHead}^{tree}`],
|
|
9184
|
+
projectGitCwd
|
|
9185
|
+
) || "";
|
|
9186
|
+
return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha);
|
|
9187
|
+
}
|
|
9188
|
+
function resolvePreviousTaskReviewBase(config, feature, tasks, task) {
|
|
9189
|
+
const taskIndex = tasks.tasks.indexOf(task);
|
|
9190
|
+
for (let index = taskIndex - 1; index >= 0; index -= 1) {
|
|
9191
|
+
const previous = tasks.tasks[index];
|
|
9192
|
+
if (previous.reviewDecisionOutcome === "approve" && previous.reviewedHead && previous.reviewedTree && reviewEvidenceSatisfied(config, feature, "task", previous.reviewEvidence)) {
|
|
9193
|
+
return {
|
|
9194
|
+
reviewedHead: previous.reviewedHead,
|
|
9195
|
+
reviewedTree: previous.reviewedTree
|
|
9196
|
+
};
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
return null;
|
|
8542
9200
|
}
|
|
8543
9201
|
function issueExistsRemotely(issueNumber, feature) {
|
|
8544
9202
|
if (!issueNumber) return false;
|
|
@@ -8558,25 +9216,26 @@ function prExistsRemotely(prRef, feature) {
|
|
|
8558
9216
|
);
|
|
8559
9217
|
return result.code === 0;
|
|
8560
9218
|
}
|
|
8561
|
-
function buildAction(category, summary, approvalRequired, command = null,
|
|
9219
|
+
function buildAction(category, summary, approvalRequired, command = null, agent, actionContext) {
|
|
8562
9220
|
return {
|
|
8563
9221
|
category,
|
|
8564
9222
|
summary,
|
|
8565
9223
|
approvalRequired,
|
|
8566
9224
|
command,
|
|
8567
|
-
...
|
|
8568
|
-
executor:
|
|
8569
|
-
model:
|
|
8570
|
-
reasoningEffort:
|
|
8571
|
-
onUnavailable:
|
|
8572
|
-
} : {}
|
|
9225
|
+
...agent ? {
|
|
9226
|
+
executor: agent.type,
|
|
9227
|
+
model: agent.model,
|
|
9228
|
+
reasoningEffort: agent.reasoningEffort,
|
|
9229
|
+
onUnavailable: agent.onUnavailable
|
|
9230
|
+
} : {},
|
|
9231
|
+
...actionContext || {}
|
|
8573
9232
|
};
|
|
8574
9233
|
}
|
|
8575
|
-
function
|
|
8576
|
-
const defaults =
|
|
8577
|
-
const configured = config.
|
|
9234
|
+
function resolveAgentReviewer(config, scope) {
|
|
9235
|
+
const defaults = createDefaultAgentReviewerConfig();
|
|
9236
|
+
const configured = resolveAgentReviewPhase(config, scope).reviewer;
|
|
8578
9237
|
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
8579
|
-
const reasoningEffort =
|
|
9238
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
8580
9239
|
configured?.reasoningEffort
|
|
8581
9240
|
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
8582
9241
|
return {
|
|
@@ -8586,6 +9245,37 @@ function resolvePrePrReviewer(config) {
|
|
|
8586
9245
|
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
8587
9246
|
};
|
|
8588
9247
|
}
|
|
9248
|
+
function resolveTaskExecutor(config) {
|
|
9249
|
+
const defaults = createDefaultAgentExecutionTaskConfig();
|
|
9250
|
+
const configured = config.workflow?.agentExecution?.task;
|
|
9251
|
+
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
9252
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
9253
|
+
configured?.reasoningEffort
|
|
9254
|
+
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
9255
|
+
return {
|
|
9256
|
+
type: "subagent",
|
|
9257
|
+
model,
|
|
9258
|
+
reasoningEffort,
|
|
9259
|
+
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
9260
|
+
};
|
|
9261
|
+
}
|
|
9262
|
+
function createTaskWorkerContract() {
|
|
9263
|
+
return {
|
|
9264
|
+
role: "task_implementation_worker",
|
|
9265
|
+
executeDirectly: true,
|
|
9266
|
+
spawnSubagents: false,
|
|
9267
|
+
runWorkflowStage: false,
|
|
9268
|
+
editProjectCode: true,
|
|
9269
|
+
runTaskScopedVerification: true,
|
|
9270
|
+
followVerificationContract: true,
|
|
9271
|
+
addUnplannedDurableTests: false,
|
|
9272
|
+
editDocs: false,
|
|
9273
|
+
changeTaskState: false,
|
|
9274
|
+
commit: false,
|
|
9275
|
+
requestApproval: false,
|
|
9276
|
+
remoteActions: false
|
|
9277
|
+
};
|
|
9278
|
+
}
|
|
8589
9279
|
function buildStageOption(label, reply, category, summary, command = null) {
|
|
8590
9280
|
return {
|
|
8591
9281
|
label,
|
|
@@ -8893,6 +9583,73 @@ function resolveFeatureSelectionError(selection) {
|
|
|
8893
9583
|
blockedReasonCode: null
|
|
8894
9584
|
};
|
|
8895
9585
|
}
|
|
9586
|
+
function resolvePlanReviewPayload(config, feature, review, target) {
|
|
9587
|
+
const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
|
|
9588
|
+
const actionContext = {
|
|
9589
|
+
reviewScope: "plan",
|
|
9590
|
+
specHash: target.specHash,
|
|
9591
|
+
planHash: target.planHash,
|
|
9592
|
+
docsDirectory: config.docsDir
|
|
9593
|
+
};
|
|
9594
|
+
if (targetMatches && review.decisionOutcome === "changes_requested") {
|
|
9595
|
+
return {
|
|
9596
|
+
status: "ok",
|
|
9597
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9598
|
+
docsDir: config.docsDir,
|
|
9599
|
+
featureRef: buildFeatureRef(feature),
|
|
9600
|
+
stage: "plan_review_fix",
|
|
9601
|
+
nextAction: buildAction(
|
|
9602
|
+
"plan_review_fix",
|
|
9603
|
+
"Address the independent Plan review findings in spec.md or plan.md, keep implementation blocked, and request a fresh review for the resulting document hashes.",
|
|
9604
|
+
false,
|
|
9605
|
+
null,
|
|
9606
|
+
void 0,
|
|
9607
|
+
actionContext
|
|
9608
|
+
),
|
|
9609
|
+
approvalRequired: false,
|
|
9610
|
+
implementationAllowed: false,
|
|
9611
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
9614
|
+
if (targetMatches && review.decisionOutcome === "blocked") {
|
|
9615
|
+
return {
|
|
9616
|
+
status: "ok",
|
|
9617
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9618
|
+
docsDir: config.docsDir,
|
|
9619
|
+
featureRef: buildFeatureRef(feature),
|
|
9620
|
+
stage: "plan_review",
|
|
9621
|
+
nextAction: buildAction(
|
|
9622
|
+
"plan_review",
|
|
9623
|
+
"Resolve the recorded Plan review blocker before Plan approval.",
|
|
9624
|
+
false,
|
|
9625
|
+
null,
|
|
9626
|
+
void 0,
|
|
9627
|
+
actionContext
|
|
9628
|
+
),
|
|
9629
|
+
approvalRequired: false,
|
|
9630
|
+
implementationAllowed: false,
|
|
9631
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9632
|
+
};
|
|
9633
|
+
}
|
|
9634
|
+
return {
|
|
9635
|
+
status: "ok",
|
|
9636
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9637
|
+
docsDir: config.docsDir,
|
|
9638
|
+
featureRef: buildFeatureRef(feature),
|
|
9639
|
+
stage: "plan_review",
|
|
9640
|
+
nextAction: buildAction(
|
|
9641
|
+
"plan_review",
|
|
9642
|
+
"Delegate a fresh read-only review 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 status, evidence, decision, reviewer metadata, Reviewed Spec Hash, and Reviewed Plan Hash without modifying the documents.",
|
|
9643
|
+
false,
|
|
9644
|
+
null,
|
|
9645
|
+
resolveAgentReviewer(config, "plan"),
|
|
9646
|
+
actionContext
|
|
9647
|
+
),
|
|
9648
|
+
approvalRequired: false,
|
|
9649
|
+
implementationAllowed: false,
|
|
9650
|
+
blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
|
|
9651
|
+
};
|
|
9652
|
+
}
|
|
8896
9653
|
async function collectWorkflowStage(cwd, selector, component) {
|
|
8897
9654
|
const config = await getConfig(cwd);
|
|
8898
9655
|
if (!config) {
|
|
@@ -8927,7 +9684,13 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8927
9684
|
const planStatus = parseApprovalStatus(
|
|
8928
9685
|
extractFieldValue2(planContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
|
|
8929
9686
|
);
|
|
8930
|
-
const tasks = parseTasksDoc(tasksContent || "");
|
|
9687
|
+
const tasks = parseTasksDoc(tasksContent || "", feature);
|
|
9688
|
+
const planReview = parsePlanReview(planContent || "");
|
|
9689
|
+
const planReviewTarget = buildPlanReviewTarget(
|
|
9690
|
+
specContent || "",
|
|
9691
|
+
planContent || ""
|
|
9692
|
+
);
|
|
9693
|
+
const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
|
|
8931
9694
|
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
8932
9695
|
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
8933
9696
|
const remoteReviewState = requirements.requireReview && tasks.prLink ? resolveRemotePrReviewState(tasks.prLink, feature) : "unknown";
|
|
@@ -8963,6 +9726,14 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8963
9726
|
}
|
|
8964
9727
|
if (planStatus !== "approved") {
|
|
8965
9728
|
const isReviewStage = planStatus === "review";
|
|
9729
|
+
if (isReviewStage && enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
|
|
9730
|
+
return resolvePlanReviewPayload(
|
|
9731
|
+
config,
|
|
9732
|
+
feature,
|
|
9733
|
+
planReview,
|
|
9734
|
+
planReviewTarget
|
|
9735
|
+
);
|
|
9736
|
+
}
|
|
8966
9737
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
|
|
8967
9738
|
const actionOptions = approvalRequired ? buildApprovalActionOptions({
|
|
8968
9739
|
approveSummary: "Approve plan.md and continue to the tasks stage.",
|
|
@@ -8986,6 +9757,9 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8986
9757
|
blockedReasonCode: "PLAN_NOT_APPROVED"
|
|
8987
9758
|
};
|
|
8988
9759
|
}
|
|
9760
|
+
if (enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
|
|
9761
|
+
return resolvePlanReviewPayload(config, feature, planReview, planReviewTarget);
|
|
9762
|
+
}
|
|
8989
9763
|
if (tasks.tasks.length === 0 || tasks.docStatus !== "approved") {
|
|
8990
9764
|
const isReviewStage = tasks.docStatus === "review";
|
|
8991
9765
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "tasks_approve", true) : false;
|
|
@@ -9064,7 +9838,12 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9064
9838
|
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
9065
9839
|
const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(["rev-parse", "--abbrev-ref", "HEAD"], effectiveProjectGitCwd) || null;
|
|
9066
9840
|
if (expectedBranch && currentBranch !== expectedBranch) {
|
|
9067
|
-
const branchCommand =
|
|
9841
|
+
const branchCommand = buildExpectedBranchCommand(
|
|
9842
|
+
config,
|
|
9843
|
+
feature,
|
|
9844
|
+
expectedBranch,
|
|
9845
|
+
requirements.requireWorktree
|
|
9846
|
+
);
|
|
9068
9847
|
return {
|
|
9069
9848
|
status: "ok",
|
|
9070
9849
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9087,6 +9866,151 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9087
9866
|
const lastDoneTask = getLastDoneTask(tasks);
|
|
9088
9867
|
const docsDirty = hasUncommittedChanges(feature.git.docsGitCwd);
|
|
9089
9868
|
const projectDirty = hasUncommittedChanges(effectiveProjectGitCwd);
|
|
9869
|
+
const currentTaskReviewTip = requirements.taskReviewEnabled ? resolveProjectReviewTarget(config, effectiveProjectGitCwd, "task") : null;
|
|
9870
|
+
const unreviewedDoneTask = requirements.taskReviewEnabled ? tasks.tasks.find(
|
|
9871
|
+
(task) => task.status === "DONE" && !recordedTaskReviewSatisfied(
|
|
9872
|
+
config,
|
|
9873
|
+
feature,
|
|
9874
|
+
effectiveProjectGitCwd,
|
|
9875
|
+
task,
|
|
9876
|
+
currentTaskReviewTip?.targetSha || null
|
|
9877
|
+
)
|
|
9878
|
+
) || null : null;
|
|
9879
|
+
const reviewTask = unreviewedDoneTask || tasks.tasks.find((task) => task.status === "REVIEW") || null;
|
|
9880
|
+
if (requirements.taskReviewEnabled && reviewTask) {
|
|
9881
|
+
if (docsDirty || projectDirty) {
|
|
9882
|
+
return {
|
|
9883
|
+
status: "ok",
|
|
9884
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9885
|
+
docsDir: config.docsDir,
|
|
9886
|
+
featureRef: buildFeatureRef(feature),
|
|
9887
|
+
stage: "task_commit",
|
|
9888
|
+
nextAction: buildAction(
|
|
9889
|
+
"task_commit",
|
|
9890
|
+
buildTaskCommitSummary({
|
|
9891
|
+
feature,
|
|
9892
|
+
tasks,
|
|
9893
|
+
effectiveProjectGitCwd,
|
|
9894
|
+
docsDirty,
|
|
9895
|
+
projectDirty,
|
|
9896
|
+
gateFailureReason: "the task must have a clean checkpoint commit before independent review"
|
|
9897
|
+
}),
|
|
9898
|
+
false
|
|
9899
|
+
),
|
|
9900
|
+
approvalRequired: false,
|
|
9901
|
+
implementationAllowed: false,
|
|
9902
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
9903
|
+
};
|
|
9904
|
+
}
|
|
9905
|
+
const reviewTarget = resolveProjectReviewTarget(
|
|
9906
|
+
config,
|
|
9907
|
+
effectiveProjectGitCwd,
|
|
9908
|
+
"task",
|
|
9909
|
+
resolvePreviousTaskReviewBase(config, feature, tasks, reviewTask)
|
|
9910
|
+
);
|
|
9911
|
+
if (!reviewTarget) {
|
|
9912
|
+
return {
|
|
9913
|
+
status: "ok",
|
|
9914
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9915
|
+
docsDir: config.docsDir,
|
|
9916
|
+
featureRef: buildFeatureRef(feature),
|
|
9917
|
+
stage: "task_commit",
|
|
9918
|
+
nextAction: buildAction(
|
|
9919
|
+
"task_commit",
|
|
9920
|
+
"Create a project commit for the task before requesting independent review.",
|
|
9921
|
+
false
|
|
9922
|
+
),
|
|
9923
|
+
approvalRequired: false,
|
|
9924
|
+
implementationAllowed: false,
|
|
9925
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
9926
|
+
};
|
|
9927
|
+
}
|
|
9928
|
+
const reviewContext = {
|
|
9929
|
+
reviewScope: "task",
|
|
9930
|
+
...reviewTask.taskId ? { taskId: reviewTask.taskId } : {},
|
|
9931
|
+
...reviewTarget
|
|
9932
|
+
};
|
|
9933
|
+
const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
|
|
9934
|
+
if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "changes_requested") {
|
|
9935
|
+
return {
|
|
9936
|
+
status: "ok",
|
|
9937
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9938
|
+
docsDir: config.docsDir,
|
|
9939
|
+
featureRef: buildFeatureRef(feature),
|
|
9940
|
+
stage: "task_review_fix",
|
|
9941
|
+
nextAction: buildAction(
|
|
9942
|
+
"task_review_fix",
|
|
9943
|
+
`Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, commit the new code tip, and request a fresh review.`,
|
|
9944
|
+
false,
|
|
9945
|
+
null,
|
|
9946
|
+
void 0,
|
|
9947
|
+
reviewContext
|
|
9948
|
+
),
|
|
9949
|
+
approvalRequired: false,
|
|
9950
|
+
implementationAllowed: true,
|
|
9951
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
9952
|
+
};
|
|
9953
|
+
}
|
|
9954
|
+
if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "blocked") {
|
|
9955
|
+
return {
|
|
9956
|
+
status: "ok",
|
|
9957
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9958
|
+
docsDir: config.docsDir,
|
|
9959
|
+
featureRef: buildFeatureRef(feature),
|
|
9960
|
+
stage: "task_review",
|
|
9961
|
+
nextAction: buildAction(
|
|
9962
|
+
"task_review",
|
|
9963
|
+
`Resolve the recorded review blocker for ${reviewTask.taskId || reviewTask.title} before continuing.`,
|
|
9964
|
+
false,
|
|
9965
|
+
null,
|
|
9966
|
+
void 0,
|
|
9967
|
+
reviewContext
|
|
9968
|
+
),
|
|
9969
|
+
approvalRequired: false,
|
|
9970
|
+
implementationAllowed: false,
|
|
9971
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
9972
|
+
};
|
|
9973
|
+
}
|
|
9974
|
+
if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
|
|
9975
|
+
const completionSummary = reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.`;
|
|
9976
|
+
return {
|
|
9977
|
+
status: "ok",
|
|
9978
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9979
|
+
docsDir: config.docsDir,
|
|
9980
|
+
featureRef: buildFeatureRef(feature),
|
|
9981
|
+
stage: "task_review",
|
|
9982
|
+
nextAction: buildAction(
|
|
9983
|
+
"task_review_complete",
|
|
9984
|
+
completionSummary,
|
|
9985
|
+
false,
|
|
9986
|
+
null,
|
|
9987
|
+
void 0,
|
|
9988
|
+
reviewContext
|
|
9989
|
+
),
|
|
9990
|
+
approvalRequired: false,
|
|
9991
|
+
implementationAllowed: false,
|
|
9992
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
9993
|
+
};
|
|
9994
|
+
}
|
|
9995
|
+
return {
|
|
9996
|
+
status: "ok",
|
|
9997
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9998
|
+
docsDir: config.docsDir,
|
|
9999
|
+
featureRef: buildFeatureRef(feature),
|
|
10000
|
+
stage: "task_review",
|
|
10001
|
+
nextAction: buildAction(
|
|
10002
|
+
"task_review",
|
|
10003
|
+
`Delegate a fresh read-only review of ${reviewTask.taskId || reviewTask.title} for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record evidence, decision, reviewer metadata, Reviewed Head, and Reviewed Tree without modifying code.`,
|
|
10004
|
+
false,
|
|
10005
|
+
null,
|
|
10006
|
+
resolveAgentReviewer(config, "task"),
|
|
10007
|
+
reviewContext
|
|
10008
|
+
),
|
|
10009
|
+
approvalRequired: false,
|
|
10010
|
+
implementationAllowed: false,
|
|
10011
|
+
blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
9090
10014
|
const pendingDoneTransitions = countPendingDoneTransitions(feature) || 0;
|
|
9091
10015
|
const taskCommitCheckpointRequired = !activeTaskOpen && !!lastDoneTask && (projectDirty || pendingDoneTransitions > 0);
|
|
9092
10016
|
if (taskCommitCheckpointRequired) {
|
|
@@ -9116,8 +10040,135 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9116
10040
|
}
|
|
9117
10041
|
const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
|
|
9118
10042
|
const committedTaskGateRequiresCheckpoint = taskCommitGatePolicy === "strict" || committedTaskGate.reason === "DONE_TRANSITIONS_COUNT";
|
|
10043
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
10044
|
+
return {
|
|
10045
|
+
status: "ok",
|
|
10046
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10047
|
+
docsDir: config.docsDir,
|
|
10048
|
+
featureRef: buildFeatureRef(feature),
|
|
10049
|
+
stage: "task_commit",
|
|
10050
|
+
nextAction: buildAction(
|
|
10051
|
+
"task_commit",
|
|
10052
|
+
buildTaskCommitSummary({
|
|
10053
|
+
feature,
|
|
10054
|
+
tasks,
|
|
10055
|
+
effectiveProjectGitCwd,
|
|
10056
|
+
docsDirty,
|
|
10057
|
+
projectDirty,
|
|
10058
|
+
gateFailureReason: describeTaskCommitGateFailure(committedTaskGate)
|
|
10059
|
+
}),
|
|
10060
|
+
false
|
|
10061
|
+
),
|
|
10062
|
+
approvalRequired: false,
|
|
10063
|
+
implementationAllowed: false,
|
|
10064
|
+
blockedReasonCode: "TASK_COMMIT_REQUIRED"
|
|
10065
|
+
};
|
|
10066
|
+
}
|
|
10067
|
+
let resolvedLocalState = null;
|
|
10068
|
+
if (allTasksDone(tasks) && config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
|
|
10069
|
+
resolvedLocalState = await resolveLocalIntegrationContext(config, feature);
|
|
10070
|
+
}
|
|
10071
|
+
const remoteReviewAlreadyComplete = currentReviewState === "merged" && tasks.prStatus === "approved" && prDraft.prStatus === "approved";
|
|
10072
|
+
const featureReviewNeededForLifecycle = allTasksDone(tasks) && requirements.featureReviewEnabled && !resolvedLocalState?.integrationComplete && !remoteReviewAlreadyComplete;
|
|
10073
|
+
if (featureReviewNeededForLifecycle) {
|
|
10074
|
+
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
10075
|
+
const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(
|
|
10076
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
10077
|
+
effectiveProjectGitCwd
|
|
10078
|
+
) || null;
|
|
10079
|
+
if (requirements.requireBranch && expectedBranch && currentBranch !== expectedBranch) {
|
|
10080
|
+
return {
|
|
10081
|
+
status: "ok",
|
|
10082
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10083
|
+
docsDir: config.docsDir,
|
|
10084
|
+
featureRef: buildFeatureRef(feature),
|
|
10085
|
+
stage: "branch",
|
|
10086
|
+
nextAction: buildAction(
|
|
10087
|
+
"branch_create",
|
|
10088
|
+
requirements.requireWorktree ? `Restore or create the managed worktree for ${expectedBranch} before Feature review.` : `Switch back to ${expectedBranch} before Feature review.`,
|
|
10089
|
+
false,
|
|
10090
|
+
buildExpectedBranchCommand(
|
|
10091
|
+
config,
|
|
10092
|
+
feature,
|
|
10093
|
+
expectedBranch,
|
|
10094
|
+
requirements.requireWorktree
|
|
10095
|
+
)
|
|
10096
|
+
),
|
|
10097
|
+
approvalRequired: false,
|
|
10098
|
+
implementationAllowed: false,
|
|
10099
|
+
blockedReasonCode: "BRANCH_NOT_READY"
|
|
10100
|
+
};
|
|
10101
|
+
}
|
|
10102
|
+
const reviewTarget = resolveProjectReviewTarget(
|
|
10103
|
+
config,
|
|
10104
|
+
effectiveProjectGitCwd,
|
|
10105
|
+
"feature"
|
|
10106
|
+
);
|
|
10107
|
+
const reviewContext = reviewTarget ? { reviewScope: "feature", ...reviewTarget } : null;
|
|
10108
|
+
const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
|
|
10109
|
+
if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "changes_requested") {
|
|
10110
|
+
return {
|
|
10111
|
+
status: "ok",
|
|
10112
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10113
|
+
docsDir: config.docsDir,
|
|
10114
|
+
featureRef: buildFeatureRef(feature),
|
|
10115
|
+
stage: "feature_review_fix",
|
|
10116
|
+
nextAction: buildAction(
|
|
10117
|
+
"feature_review_fix",
|
|
10118
|
+
"Address the Feature review findings, commit the remediation, and request a fresh review of the new code tree before implementation approval.",
|
|
10119
|
+
false,
|
|
10120
|
+
null,
|
|
10121
|
+
void 0,
|
|
10122
|
+
reviewContext
|
|
10123
|
+
),
|
|
10124
|
+
approvalRequired: false,
|
|
10125
|
+
implementationAllowed: true,
|
|
10126
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10127
|
+
};
|
|
10128
|
+
}
|
|
10129
|
+
if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "blocked") {
|
|
10130
|
+
return {
|
|
10131
|
+
status: "ok",
|
|
10132
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10133
|
+
docsDir: config.docsDir,
|
|
10134
|
+
featureRef: buildFeatureRef(feature),
|
|
10135
|
+
stage: "pre_pr_review",
|
|
10136
|
+
nextAction: buildAction(
|
|
10137
|
+
"pre_pr_review",
|
|
10138
|
+
"Resolve the recorded Feature review blocker before implementation approval or integration.",
|
|
10139
|
+
false,
|
|
10140
|
+
null,
|
|
10141
|
+
void 0,
|
|
10142
|
+
reviewContext
|
|
10143
|
+
),
|
|
10144
|
+
approvalRequired: false,
|
|
10145
|
+
implementationAllowed: false,
|
|
10146
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10147
|
+
};
|
|
10148
|
+
}
|
|
10149
|
+
if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
|
|
10150
|
+
return {
|
|
10151
|
+
status: "ok",
|
|
10152
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10153
|
+
docsDir: config.docsDir,
|
|
10154
|
+
featureRef: buildFeatureRef(feature),
|
|
10155
|
+
stage: "pre_pr_review",
|
|
10156
|
+
nextAction: buildAction(
|
|
10157
|
+
"pre_pr_review",
|
|
10158
|
+
reviewTarget ? `Delegate an independent read-only Feature review to a fresh subagent for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record findings, decision, reviewer metadata, Pre-PR Reviewed Head, and Pre-PR Reviewed Tree as evidence.` : "Create a project commit before requesting the independent Feature review.",
|
|
10159
|
+
false,
|
|
10160
|
+
null,
|
|
10161
|
+
reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
|
|
10162
|
+
reviewContext || void 0
|
|
10163
|
+
),
|
|
10164
|
+
approvalRequired: false,
|
|
10165
|
+
implementationAllowed: false,
|
|
10166
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
10167
|
+
};
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
9119
10170
|
if (!allTasksDone(tasks)) {
|
|
9120
|
-
const currentTask =
|
|
10171
|
+
const currentTask = nextExecutableTask(tasks);
|
|
9121
10172
|
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
9122
10173
|
return {
|
|
9123
10174
|
status: "ok",
|
|
@@ -9144,6 +10195,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9144
10195
|
}
|
|
9145
10196
|
const commitWarning = taskCommitGatePolicy === "warn" && !committedTaskGate.pass ? `
|
|
9146
10197
|
Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)}` : "";
|
|
10198
|
+
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
10199
|
return {
|
|
9148
10200
|
status: "ok",
|
|
9149
10201
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9152,8 +10204,20 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9152
10204
|
stage: "implementation",
|
|
9153
10205
|
nextAction: buildAction(
|
|
9154
10206
|
"task_execute",
|
|
9155
|
-
|
|
9156
|
-
false
|
|
10207
|
+
`Continue the next implementation task: ${currentTask.title}.${delegationGuidance}${requirements.taskReviewEnabled ? " When implementation and checks are complete, move it to REVIEW instead of DONE" : ""}${commitWarning}`,
|
|
10208
|
+
false,
|
|
10209
|
+
null,
|
|
10210
|
+
requirements.taskExecutionEnabled ? resolveTaskExecutor(config) : void 0,
|
|
10211
|
+
{
|
|
10212
|
+
taskId: currentTask.taskId,
|
|
10213
|
+
taskIdSource: currentTask.taskIdSource,
|
|
10214
|
+
taskTitle: currentTask.title,
|
|
10215
|
+
workingDirectory: effectiveProjectGitCwd,
|
|
10216
|
+
...requirements.taskExecutionEnabled ? {
|
|
10217
|
+
docsDirectory: config.docsDir,
|
|
10218
|
+
workerContract: createTaskWorkerContract()
|
|
10219
|
+
} : {}
|
|
10220
|
+
}
|
|
9157
10221
|
),
|
|
9158
10222
|
approvalRequired: false,
|
|
9159
10223
|
implementationAllowed: true,
|
|
@@ -9217,27 +10281,8 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9217
10281
|
blockedReasonCode: "IMPLEMENTATION_APPROVAL_REQUIRED"
|
|
9218
10282
|
};
|
|
9219
10283
|
}
|
|
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
10284
|
if (config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
|
|
9240
|
-
const localState = await resolveLocalIntegrationContext(config, feature);
|
|
10285
|
+
const localState = resolvedLocalState || await resolveLocalIntegrationContext(config, feature);
|
|
9241
10286
|
const localVerifyCommand = `npx lee-spec-kit local verify ${buildFeatureArgs(feature)} --json`;
|
|
9242
10287
|
const localMergeBaseCommand = `npx lee-spec-kit local merge ${buildFeatureArgs(feature)} --json`;
|
|
9243
10288
|
if (localState.cleanedIntegrationStillValid) {
|