lee-spec-kit 0.9.2 → 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 +1180 -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 상태|
|
|
3073
|
+
if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|PR Review|PR 리뷰|PR Review Evidence|PR 리뷰 Evidence)\*\*\s*:/.test(
|
|
2769
3074
|
line
|
|
2770
3075
|
)) {
|
|
2771
3076
|
continue;
|
|
2772
3077
|
}
|
|
2773
|
-
if (/^\s*-\s*\*\*(
|
|
2774
|
-
line
|
|
2775
|
-
)) {
|
|
2776
|
-
continue;
|
|
2777
|
-
}
|
|
2778
|
-
if (/^\s*-\s*\*\*(Pre-PR Decision|PR Review Decision|PR 전 리뷰 Decision|PR 리뷰 Decision)\*\*\s*:/.test(
|
|
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");
|
|
3804
4112
|
}
|
|
3805
|
-
const
|
|
4113
|
+
const agentExecution = workflow.agentExecution;
|
|
4114
|
+
if (!isPlainObject(agentExecution.task)) {
|
|
4115
|
+
agentExecution.task = {};
|
|
4116
|
+
changedPaths.push("workflow.agentExecution.task");
|
|
4117
|
+
}
|
|
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(
|
|
@@ -7724,6 +8104,14 @@ async function resolveLocalIntegrationContext(config, feature) {
|
|
|
7724
8104
|
const squashCommitMatchesSource = completionStrategy === "local-squash" && state?.strategy === "local-squash" && !!featureTip && state.featureTip === featureTip && !!integratedCommit && !!baseTip && integratedCommit === baseTip && !!featureTree && integratedTree === featureTree && state.integratedTree === featureTree;
|
|
7725
8105
|
const squashEvidencePresent = !!featureTip && evidenceTip === featureTip;
|
|
7726
8106
|
const squashIntegrationComplete = squashCommitMatchesSource && squashEvidencePresent;
|
|
8107
|
+
const recordedStrategy = state?.strategy || "local-ff";
|
|
8108
|
+
const recordedIntegratedCommit = state?.integratedCommit || state?.mergedBaseTip || null;
|
|
8109
|
+
const baseContainsRecordedIntegration = !!recordedIntegratedCommit && isAncestor(
|
|
8110
|
+
projectRoot,
|
|
8111
|
+
recordedIntegratedCommit,
|
|
8112
|
+
`refs/heads/${baseBranch}`
|
|
8113
|
+
);
|
|
8114
|
+
const cleanedIntegrationStillValid = state?.status === "cleaned" && recordedStrategy === completionStrategy && state.featureTip === featureTip && baseContainsRecordedIntegration && (completionStrategy === "local-squash" ? state.integratedCommit === recordedIntegratedCommit && state.integratedTree === featureTree && squashEvidencePresent : state.mergedBaseTip === featureTip && baseContainsFeature);
|
|
7727
8115
|
return {
|
|
7728
8116
|
projectRoot,
|
|
7729
8117
|
featureWorktree: effectiveFeatureWorktree,
|
|
@@ -7738,6 +8126,7 @@ async function resolveLocalIntegrationContext(config, feature) {
|
|
|
7738
8126
|
completionStrategy,
|
|
7739
8127
|
baseContainsFeature,
|
|
7740
8128
|
integrationComplete: completionStrategy === "local-squash" ? squashIntegrationComplete : !!featureTip && baseTip === featureTip,
|
|
8129
|
+
cleanedIntegrationStillValid,
|
|
7741
8130
|
evidenceRef,
|
|
7742
8131
|
squashCommitMatchesSource,
|
|
7743
8132
|
squashEvidencePresent,
|
|
@@ -7944,9 +8333,57 @@ var ISSUE_LABELS = ["Issue", "Issue Number", "\uC774\uC288", "\uC774\uC288 \uBC8
|
|
|
7944
8333
|
var BRANCH_LABELS3 = ["Branch", "\uBE0C\uB79C\uCE58"];
|
|
7945
8334
|
var PR_LABELS = ["PR", "Pull Request"];
|
|
7946
8335
|
var PR_STATUS_LABELS = ["PR Status", "PR \uC0C1\uD0DC"];
|
|
7947
|
-
var PRE_PR_REVIEW_LABELS = [
|
|
7948
|
-
|
|
7949
|
-
|
|
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
|
+
];
|
|
7950
8387
|
var COMPLETION_MARKERS = {
|
|
7951
8388
|
allTasks: "lee-spec-kit:completion:all-tasks",
|
|
7952
8389
|
tests: "lee-spec-kit:completion:tests",
|
|
@@ -7965,7 +8402,10 @@ function resolveWorkflowRequirements(config) {
|
|
|
7965
8402
|
requirePr: workflow.requirePr ?? !isLocalWorkflow,
|
|
7966
8403
|
requireReview: workflow.requireReview ?? !isLocalWorkflow,
|
|
7967
8404
|
requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
|
|
7968
|
-
|
|
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
|
|
7969
8409
|
};
|
|
7970
8410
|
}
|
|
7971
8411
|
function parseApprovalStatus(raw) {
|
|
@@ -8016,7 +8456,62 @@ function parseCompletionCheckbox(lines, marker, legacyPattern) {
|
|
|
8016
8456
|
(line) => legacyPattern.test(line) && parseMarkdownCheckbox(line) === true
|
|
8017
8457
|
);
|
|
8018
8458
|
}
|
|
8019
|
-
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) {
|
|
8020
8515
|
const issueRaw = extractFieldValue2(content, ISSUE_LABELS);
|
|
8021
8516
|
const issueNumberMatch = issueRaw?.match(/^#(\d+)$/);
|
|
8022
8517
|
const issueNumber = issueNumberMatch ? Number(issueNumberMatch[1]) : null;
|
|
@@ -8025,15 +8520,43 @@ function parseTasksDoc(content) {
|
|
|
8025
8520
|
const prePrDecision = extractFieldValue2(content, PRE_PR_DECISION_LABELS);
|
|
8026
8521
|
const tasks = [];
|
|
8027
8522
|
const nonCodeLines = withoutFencedCodeBlocks(content);
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
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"]
|
|
8031
8532
|
);
|
|
8032
|
-
|
|
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);
|
|
8033
8537
|
tasks.push({
|
|
8034
8538
|
raw: line,
|
|
8035
|
-
status:
|
|
8036
|
-
|
|
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
|
+
)
|
|
8037
8560
|
});
|
|
8038
8561
|
}
|
|
8039
8562
|
const allTasksChecked = parseCompletionCheckbox(
|
|
@@ -8064,11 +8587,7 @@ function parseTasksDoc(content) {
|
|
|
8064
8587
|
if (value === "done") return "done";
|
|
8065
8588
|
return null;
|
|
8066
8589
|
})();
|
|
8067
|
-
const prePrDecisionOutcome = (
|
|
8068
|
-
const value = (prePrDecision || "").trim().toLowerCase();
|
|
8069
|
-
const match = value.match(/\b(approve|changes_requested|blocked)\b/);
|
|
8070
|
-
return match?.[1] || null;
|
|
8071
|
-
})();
|
|
8590
|
+
const prePrDecisionOutcome = parseReviewDecisionOutcome(prePrDecision);
|
|
8072
8591
|
return {
|
|
8073
8592
|
docStatus: parseApprovalStatus(
|
|
8074
8593
|
extractFieldValue2(content, DOC_STATUS_LABELS) || void 0
|
|
@@ -8083,6 +8602,12 @@ function parseTasksDoc(content) {
|
|
|
8083
8602
|
),
|
|
8084
8603
|
prePrDecision: sanitizeMetadataValue2(prePrDecision),
|
|
8085
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
|
+
),
|
|
8086
8611
|
tasks,
|
|
8087
8612
|
completion: {
|
|
8088
8613
|
allTasksChecked,
|
|
@@ -8097,6 +8622,58 @@ function sanitizeMetadataValue2(value) {
|
|
|
8097
8622
|
if (!trimmed || trimmed === "-") return null;
|
|
8098
8623
|
return trimmed;
|
|
8099
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
|
+
}
|
|
8100
8677
|
function normalizeCommitTopicText(value) {
|
|
8101
8678
|
return value.replace(/\s+/g, " ").trim();
|
|
8102
8679
|
}
|
|
@@ -8232,7 +8809,7 @@ function hasOpenTask(tasks) {
|
|
|
8232
8809
|
function hasUncommittedChanges(gitCwd) {
|
|
8233
8810
|
if (!gitCwd) return false;
|
|
8234
8811
|
const status = runGitCapture(
|
|
8235
|
-
["status", "--porcelain", "--untracked-files=
|
|
8812
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
8236
8813
|
gitCwd
|
|
8237
8814
|
) || "";
|
|
8238
8815
|
return status.trim().length > 0;
|
|
@@ -8296,7 +8873,7 @@ function resolveProjectCommitTopic(feature, tasks) {
|
|
|
8296
8873
|
const activeTask = tasks.tasks.find(
|
|
8297
8874
|
(task) => task.status === "DOING" || task.status === "REVIEW"
|
|
8298
8875
|
);
|
|
8299
|
-
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;
|
|
8300
8877
|
const withoutTaskId = normalizeCommitTopicText(raw || "").replace(
|
|
8301
8878
|
/^T-[A-Za-z0-9-]+\s+/,
|
|
8302
8879
|
""
|
|
@@ -8353,6 +8930,19 @@ function resolveExpectedBranch(feature, tasks) {
|
|
|
8353
8930
|
if (!tasks.issueNumber) return null;
|
|
8354
8931
|
return `feat/${tasks.issueNumber}-${feature.slug}`;
|
|
8355
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
|
+
}
|
|
8356
8946
|
function resolveProjectRootFromGitCwd2(projectGitCwd) {
|
|
8357
8947
|
return runGitCapture(["rev-parse", "--show-toplevel"], projectGitCwd) || path8.resolve(projectGitCwd);
|
|
8358
8948
|
}
|
|
@@ -8517,19 +9107,96 @@ function buildPostMergeCleanupSummary(state) {
|
|
|
8517
9107
|
}
|
|
8518
9108
|
return `Finish the post-merge cleanup before closing the feature: ${remaining.join(", ")}.`;
|
|
8519
9109
|
}
|
|
8520
|
-
function
|
|
8521
|
-
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");
|
|
8522
9112
|
}
|
|
8523
9113
|
function allTasksDone(tasks) {
|
|
8524
9114
|
return tasks.tasks.length > 0 && tasks.tasks.every((task) => task.status === "DONE");
|
|
8525
9115
|
}
|
|
8526
|
-
function
|
|
8527
|
-
|
|
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({
|
|
8528
9163
|
docsDir: config.docsDir,
|
|
8529
9164
|
featureDir: feature.path,
|
|
8530
|
-
evidence
|
|
8531
|
-
evidenceMode: config.
|
|
8532
|
-
})
|
|
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;
|
|
8533
9200
|
}
|
|
8534
9201
|
function issueExistsRemotely(issueNumber, feature) {
|
|
8535
9202
|
if (!issueNumber) return false;
|
|
@@ -8549,25 +9216,40 @@ function prExistsRemotely(prRef, feature) {
|
|
|
8549
9216
|
);
|
|
8550
9217
|
return result.code === 0;
|
|
8551
9218
|
}
|
|
8552
|
-
function buildAction(category, summary, approvalRequired, command = null,
|
|
9219
|
+
function buildAction(category, summary, approvalRequired, command = null, agent, actionContext) {
|
|
8553
9220
|
return {
|
|
8554
9221
|
category,
|
|
8555
9222
|
summary,
|
|
8556
9223
|
approvalRequired,
|
|
8557
9224
|
command,
|
|
8558
|
-
...
|
|
8559
|
-
executor:
|
|
8560
|
-
model:
|
|
8561
|
-
reasoningEffort:
|
|
8562
|
-
onUnavailable:
|
|
8563
|
-
} : {}
|
|
9225
|
+
...agent ? {
|
|
9226
|
+
executor: agent.type,
|
|
9227
|
+
model: agent.model,
|
|
9228
|
+
reasoningEffort: agent.reasoningEffort,
|
|
9229
|
+
onUnavailable: agent.onUnavailable
|
|
9230
|
+
} : {},
|
|
9231
|
+
...actionContext || {}
|
|
9232
|
+
};
|
|
9233
|
+
}
|
|
9234
|
+
function resolveAgentReviewer(config, scope) {
|
|
9235
|
+
const defaults = createDefaultAgentReviewerConfig();
|
|
9236
|
+
const configured = resolveAgentReviewPhase(config, scope).reviewer;
|
|
9237
|
+
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
9238
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
9239
|
+
configured?.reasoningEffort
|
|
9240
|
+
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
9241
|
+
return {
|
|
9242
|
+
type: "subagent",
|
|
9243
|
+
model,
|
|
9244
|
+
reasoningEffort,
|
|
9245
|
+
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
8564
9246
|
};
|
|
8565
9247
|
}
|
|
8566
|
-
function
|
|
8567
|
-
const defaults =
|
|
8568
|
-
const configured = config.workflow?.
|
|
9248
|
+
function resolveTaskExecutor(config) {
|
|
9249
|
+
const defaults = createDefaultAgentExecutionTaskConfig();
|
|
9250
|
+
const configured = config.workflow?.agentExecution?.task;
|
|
8569
9251
|
const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
|
|
8570
|
-
const reasoningEffort =
|
|
9252
|
+
const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
|
|
8571
9253
|
configured?.reasoningEffort
|
|
8572
9254
|
) ? configured?.reasoningEffort : defaults.reasoningEffort;
|
|
8573
9255
|
return {
|
|
@@ -8577,6 +9259,23 @@ function resolvePrePrReviewer(config) {
|
|
|
8577
9259
|
onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
|
|
8578
9260
|
};
|
|
8579
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
|
+
}
|
|
8580
9279
|
function buildStageOption(label, reply, category, summary, command = null) {
|
|
8581
9280
|
return {
|
|
8582
9281
|
label,
|
|
@@ -8884,6 +9583,73 @@ function resolveFeatureSelectionError(selection) {
|
|
|
8884
9583
|
blockedReasonCode: null
|
|
8885
9584
|
};
|
|
8886
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
|
+
}
|
|
8887
9653
|
async function collectWorkflowStage(cwd, selector, component) {
|
|
8888
9654
|
const config = await getConfig(cwd);
|
|
8889
9655
|
if (!config) {
|
|
@@ -8918,7 +9684,13 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8918
9684
|
const planStatus = parseApprovalStatus(
|
|
8919
9685
|
extractFieldValue2(planContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
|
|
8920
9686
|
);
|
|
8921
|
-
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);
|
|
8922
9694
|
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
8923
9695
|
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
8924
9696
|
const remoteReviewState = requirements.requireReview && tasks.prLink ? resolveRemotePrReviewState(tasks.prLink, feature) : "unknown";
|
|
@@ -8954,6 +9726,14 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8954
9726
|
}
|
|
8955
9727
|
if (planStatus !== "approved") {
|
|
8956
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
|
+
}
|
|
8957
9737
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
|
|
8958
9738
|
const actionOptions = approvalRequired ? buildApprovalActionOptions({
|
|
8959
9739
|
approveSummary: "Approve plan.md and continue to the tasks stage.",
|
|
@@ -8977,6 +9757,9 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8977
9757
|
blockedReasonCode: "PLAN_NOT_APPROVED"
|
|
8978
9758
|
};
|
|
8979
9759
|
}
|
|
9760
|
+
if (enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
|
|
9761
|
+
return resolvePlanReviewPayload(config, feature, planReview, planReviewTarget);
|
|
9762
|
+
}
|
|
8980
9763
|
if (tasks.tasks.length === 0 || tasks.docStatus !== "approved") {
|
|
8981
9764
|
const isReviewStage = tasks.docStatus === "review";
|
|
8982
9765
|
const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "tasks_approve", true) : false;
|
|
@@ -9055,7 +9838,12 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9055
9838
|
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
9056
9839
|
const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(["rev-parse", "--abbrev-ref", "HEAD"], effectiveProjectGitCwd) || null;
|
|
9057
9840
|
if (expectedBranch && currentBranch !== expectedBranch) {
|
|
9058
|
-
const branchCommand =
|
|
9841
|
+
const branchCommand = buildExpectedBranchCommand(
|
|
9842
|
+
config,
|
|
9843
|
+
feature,
|
|
9844
|
+
expectedBranch,
|
|
9845
|
+
requirements.requireWorktree
|
|
9846
|
+
);
|
|
9059
9847
|
return {
|
|
9060
9848
|
status: "ok",
|
|
9061
9849
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9078,6 +9866,151 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9078
9866
|
const lastDoneTask = getLastDoneTask(tasks);
|
|
9079
9867
|
const docsDirty = hasUncommittedChanges(feature.git.docsGitCwd);
|
|
9080
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
|
+
}
|
|
9081
10014
|
const pendingDoneTransitions = countPendingDoneTransitions(feature) || 0;
|
|
9082
10015
|
const taskCommitCheckpointRequired = !activeTaskOpen && !!lastDoneTask && (projectDirty || pendingDoneTransitions > 0);
|
|
9083
10016
|
if (taskCommitCheckpointRequired) {
|
|
@@ -9107,8 +10040,135 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9107
10040
|
}
|
|
9108
10041
|
const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
|
|
9109
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
|
+
}
|
|
9110
10170
|
if (!allTasksDone(tasks)) {
|
|
9111
|
-
const currentTask =
|
|
10171
|
+
const currentTask = nextExecutableTask(tasks);
|
|
9112
10172
|
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
9113
10173
|
return {
|
|
9114
10174
|
status: "ok",
|
|
@@ -9135,6 +10195,7 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
9135
10195
|
}
|
|
9136
10196
|
const commitWarning = taskCommitGatePolicy === "warn" && !committedTaskGate.pass ? `
|
|
9137
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" : "";
|
|
9138
10199
|
return {
|
|
9139
10200
|
status: "ok",
|
|
9140
10201
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -9143,8 +10204,20 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9143
10204
|
stage: "implementation",
|
|
9144
10205
|
nextAction: buildAction(
|
|
9145
10206
|
"task_execute",
|
|
9146
|
-
|
|
9147
|
-
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
|
+
}
|
|
9148
10221
|
),
|
|
9149
10222
|
approvalRequired: false,
|
|
9150
10223
|
implementationAllowed: true,
|
|
@@ -9208,29 +10281,23 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
9208
10281
|
blockedReasonCode: "IMPLEMENTATION_APPROVAL_REQUIRED"
|
|
9209
10282
|
};
|
|
9210
10283
|
}
|
|
9211
|
-
if (requirements.prePrReviewEnabled && !prePrSatisfied(config, feature, tasks)) {
|
|
9212
|
-
return {
|
|
9213
|
-
status: "ok",
|
|
9214
|
-
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
9215
|
-
docsDir: config.docsDir,
|
|
9216
|
-
featureRef: buildFeatureRef(feature),
|
|
9217
|
-
stage: "pre_pr_review",
|
|
9218
|
-
nextAction: buildAction(
|
|
9219
|
-
"pre_pr_review",
|
|
9220
|
-
"Delegate an independent read-only Pre-PR review to a fresh subagent and record its findings, decision, and reviewer metadata as evidence.",
|
|
9221
|
-
false,
|
|
9222
|
-
null,
|
|
9223
|
-
resolvePrePrReviewer(config)
|
|
9224
|
-
),
|
|
9225
|
-
approvalRequired: false,
|
|
9226
|
-
implementationAllowed: false,
|
|
9227
|
-
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
9228
|
-
};
|
|
9229
|
-
}
|
|
9230
10284
|
if (config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
|
|
9231
|
-
const localState = await resolveLocalIntegrationContext(config, feature);
|
|
10285
|
+
const localState = resolvedLocalState || await resolveLocalIntegrationContext(config, feature);
|
|
9232
10286
|
const localVerifyCommand = `npx lee-spec-kit local verify ${buildFeatureArgs(feature)} --json`;
|
|
9233
10287
|
const localMergeBaseCommand = `npx lee-spec-kit local merge ${buildFeatureArgs(feature)} --json`;
|
|
10288
|
+
if (localState.cleanedIntegrationStillValid) {
|
|
10289
|
+
return {
|
|
10290
|
+
status: "ok",
|
|
10291
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
10292
|
+
docsDir: config.docsDir,
|
|
10293
|
+
featureRef: buildFeatureRef(feature),
|
|
10294
|
+
stage: "done",
|
|
10295
|
+
nextAction: null,
|
|
10296
|
+
approvalRequired: false,
|
|
10297
|
+
implementationAllowed: false,
|
|
10298
|
+
blockedReasonCode: null
|
|
10299
|
+
};
|
|
10300
|
+
}
|
|
9234
10301
|
if (!localState.integrationComplete) {
|
|
9235
10302
|
const featureVerified = !!localState.state && ["feature_verified", "merged", "verified", "cleaned"].includes(
|
|
9236
10303
|
localState.state.status
|