lee-spec-kit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import path13 from 'path';
2
+ import path6 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
5
  import fs2 from 'fs-extra';
@@ -11,7 +11,7 @@ import { createHash } from 'crypto';
11
11
  import os from 'os';
12
12
 
13
13
  var getFilename = () => fileURLToPath(import.meta.url);
14
- var getDirname = () => path13.dirname(getFilename());
14
+ var getDirname = () => path6.dirname(getFilename());
15
15
  var __dirname$1 = /* @__PURE__ */ getDirname();
16
16
  async function copyTemplates(src, dest) {
17
17
  await fs2.copy(src, dest, {
@@ -42,10 +42,10 @@ async function replaceInFiles(dir, replacements) {
42
42
  }
43
43
  }
44
44
  var __filename2 = fileURLToPath(import.meta.url);
45
- var __dirname2 = path13.dirname(__filename2);
45
+ var __dirname2 = path6.dirname(__filename2);
46
46
  function getTemplatesDir() {
47
- const rootDir = path13.resolve(__dirname2, "..");
48
- return path13.join(rootDir, "templates");
47
+ const rootDir = path6.resolve(__dirname2, "..");
48
+ return path6.join(rootDir, "templates");
49
49
  }
50
50
 
51
51
  // src/utils/i18n.ts
@@ -73,7 +73,7 @@ var I18N = {
73
73
  "status.wrote": "\u2705 {path} \uC0DD\uC131 \uC644\uB8CC",
74
74
  "feature.selectRepo": "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
75
75
  "feature.folderExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: {path}",
76
- "feature.baseNotFound": "feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
76
+ "feature.baseNotFound": "CLI \uB0B4\uC7A5 feature \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
77
77
  "feature.created": "\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: {path}",
78
78
  "feature.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
79
79
  "feature.nextSteps1": " 1. {path}/spec.md \uC791\uC131",
@@ -94,6 +94,9 @@ var I18N = {
94
94
  "update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
95
95
  "update.skillsUpdated": "agents/skills \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
96
96
  "update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
97
+ "update.engineManagedSkillsBuiltin": "agents/skills\uB294 CLI \uB0B4\uC7A5 \uADDC\uCE59\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
98
+ "update.engineManagedFeatureBaseBuiltin": "features/feature-base\uB294 CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
99
+ "update.engineManagedPruned": "docs\uC5D0\uC11C CLI \uAD00\uB9AC \uBB38\uC11C {count}\uAC1C\uB97C \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.",
97
100
  "update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
98
101
  "update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
99
102
  "update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
@@ -128,15 +131,17 @@ var I18N = {
128
131
  "context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
129
132
  "context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
130
133
  "context.checkRequired": "[\uD655\uC778 \uD544\uC694] ",
131
- "context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uADDC\uCE59: /docs/agents/agents.md \uCC38\uACE0 (git push/merge/merge commit \uD3EC\uD568) \u2014 [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`) \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589 (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
134
+ "context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uC815\uCC45 \uC548\uB0B4(\uD604\uC7AC Next Action \uC544\uB2D8): CLI \uB0B4\uC7A5 \uC5D0\uC774\uC804\uD2B8 \uC815\uCC45 \uCC38\uACE0 (git push/merge/merge commit \uD3EC\uD568). [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`) \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589 (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
132
135
  "context.actionOptionHint": "\uC2B9\uC778 \uC751\uB2F5 \uD615\uC2DD: `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`)",
133
136
  "context.actionExplainHint": "\uC2B9\uC778 \uC694\uCCAD \uC804, \uAC01 \uB77C\uBCA8\uC774 \uBB34\uC5C7\uC744 \uC2E4\uD589/\uBCC0\uACBD\uD558\uB294\uC9C0 \uD55C \uC904 \uC694\uC57D\uACFC \uD568\uAED8 \uC124\uBA85\uD558\uC138\uC694.",
134
- "context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: /docs/agents/git-workflow.md \uCC38\uACE0",
137
+ "context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: CLI \uB0B4\uC7A5 git-workflow \uADDC\uCE59 \uCC38\uACE0",
135
138
  "context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
136
139
  "context.list.projectCommitNeeded": "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694",
137
140
  "context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
138
141
  "context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
139
142
  "context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
143
+ "context.list.addPrePrReviewField": "Pre-PR Review \uD544\uB4DC \uCD94\uAC00",
144
+ "context.list.completePrePrReview": "Pre-PR \uB9AC\uBDF0 \uC644\uB8CC \uCC98\uB9AC",
140
145
  "context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
141
146
  "context.list.prStatusToApproved": "PR \uC0C1\uD0DC {status} \u2192 Approved",
142
147
  "context.list.approveSpec": "spec \uC2B9\uC778 \uD544\uC694",
@@ -205,23 +210,24 @@ var I18N = {
205
210
  branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
206
211
  tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
207
212
  docsCommitSync: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
213
+ prePrReview: "Pre-PR \uB9AC\uBDF0",
208
214
  prCreate: "PR \uC0DD\uC131",
209
215
  codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
210
216
  featureDone: "Feature \uC644\uB8CC"
211
217
  },
212
218
  messages: {
213
- specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
219
+ specCreate: "spec.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
214
220
  specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
215
221
  specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694.",
216
- planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
222
+ planCreate: "plan.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
217
223
  planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
218
224
  planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694.",
219
- tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
225
+ tasksCreate: "tasks.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
220
226
  tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
221
227
  tasksImprove: "tasks.md\uB97C \uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
222
228
  tasksApproval: "tasks.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC9C4\uD589 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694. (\uC2B9\uC778 \uD6C4 \uBB38\uC11C \uC0C1\uD0DC\uB97C Approved\uB85C \uBCC0\uACBD)",
223
229
  docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
224
- issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (skills/create-issue.md \uCC38\uACE0)",
230
+ issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (CLI \uB0B4\uC7A5 create-issue \uAC00\uC774\uB4DC)",
225
231
  docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
226
232
  docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
227
233
  projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} \uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8"',
@@ -230,11 +236,15 @@ var I18N = {
230
236
  createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
231
237
  tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
232
238
  tasksAllDoneButChecklist: "\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uAC00 \uC644\uC804\uD788 \uCCB4\uD06C\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ({checked}/{total})",
233
- finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC644\uB8CC \uC804 \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC) (skills/execute-task.md \uCC38\uACE0)',
234
- startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC2DC\uC791 \uC804 \uC81C\uBAA9 \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DOING \uCC98\uB9AC) (skills/execute-task.md \uCC38\uACE0)',
235
- checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
239
+ finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC644\uB8CC \uC804 \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)',
240
+ startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC2DC\uC791 \uC804 \uC81C\uBAA9 \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DOING \uCC98\uB9AC) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)',
241
+ checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)",
236
242
  prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (\uD655\uC778 \uD544\uC694)",
237
- prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
243
+ prePrReviewFieldMissing: "tasks.md\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
244
+ prePrReviewRun: "PR \uC0DD\uC131 \uC804 \uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: {skills} (\uC124\uCE58\uB41C \uB354 \uC801\uD569\uD55C \uC2A4\uD0AC\uC774 \uC788\uB2E4\uBA74 \uBA3C\uC800 \uC81C\uC548 \uD6C4 \uC0AC\uC6A9). \uC2A4\uD0AC\uC744 \uC4F8 \uC218 \uC5C6\uC73C\uBA74 `{fallback}` \uC815\uCC45\uC73C\uB85C \uC9C4\uD589\uD558\uACE0 `PR \uC804 \uB9AC\uBDF0`\uB97C Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. Findings \uC815\uCC45: {findingsPolicy}",
245
+ prePrReviewFindingsBlock: "\uC911\uC694 \uC774\uC288\uB294 \uC218\uC815/\uD569\uC758 \uD6C4\uC5D0\uB9CC PR \uC0DD\uC131",
246
+ prePrReviewFindingsWarn: "\uB9AC\uC2A4\uD06C\uB97C \uACF5\uC720\uD558\uBA74 PR \uC0DD\uC131 \uC9C4\uD589 \uAC00\uB2A5",
247
+ prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (CLI \uB0B4\uC7A5 create-pr \uAC00\uC774\uB4DC)",
238
248
  prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694. (merge \uD6C4 Approved\uB85C \uC5C5\uB370\uC774\uD2B8)",
239
249
  prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
240
250
  prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
@@ -245,17 +255,20 @@ var I18N = {
245
255
  projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
246
256
  docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
247
257
  docsPathIgnored: "\uD604\uC7AC Feature \uBB38\uC11C \uACBD\uB85C\uAC00 git ignore \uB300\uC0C1\uC785\uB2C8\uB2E4: {path} (docs \uCEE4\uBC0B \uAC10\uC9C0\uAC00 \uC81C\uD55C\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
248
- docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: /docs/agents/git-workflow.md \uCC38\uACE0",
258
+ docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: CLI \uB0B4\uC7A5 git-workflow \uADDC\uCE59 \uCC38\uACE0",
249
259
  projectUncommittedChanges: "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694)",
250
260
  legacyTasksDocStatusField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. `\uBB38\uC11C \uC0C1\uD0DC` \uD544\uB4DC(Review/Approved)\uB97C \uCD94\uAC00\uD574 \uD0DC\uC2A4\uD06C \uC2B9\uC778 \uB2E8\uACC4\uB97C \uD65C\uC131\uD654\uD558\uC138\uC694.",
251
261
  legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
262
+ legacyTasksPrePrReviewField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`)",
252
263
  workflowSpecNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC spec.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (spec.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
253
264
  workflowPlanNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC plan.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (plan.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
254
265
  workflowIssueMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC \uC774\uC288 \uBC88\uD638\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uC138\uC694.)",
255
266
  workflowProjectUncommittedChanges: "\uC644\uB8CC \uC870\uAC74 \uC774\uC804\uC5D0 \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC744 \uCEE4\uBC0B\uD574\uC57C \uD569\uB2C8\uB2E4. (\uD504\uB85C\uC81D\uD2B8 \uC6CC\uD06C\uD2B8\uB9AC \uBBF8\uCEE4\uBC0B \uBCC0\uACBD \uC874\uC7AC)",
256
267
  workflowPrLinkMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uB9C1\uD06C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uD544\uB4DC\uB97C \uCC44\uC6B0\uC138\uC694.)",
257
268
  workflowPrStatusMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
258
- workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
269
+ workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
270
+ workflowPrePrReviewMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC5D0 `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`\uC744 \uCD94\uAC00\uD558\uC138\uC694.)",
271
+ workflowPrePrReviewNotDone: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0`\uAC00 Done\uC774 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0 \uD6C4 Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
259
272
  }
260
273
  },
261
274
  en: {
@@ -270,7 +283,7 @@ var I18N = {
270
283
  "status.wrote": "\u2705 Wrote {path}",
271
284
  "feature.selectRepo": "Select a repository:",
272
285
  "feature.folderExists": "Folder already exists: {path}",
273
- "feature.baseNotFound": "feature-base template not found.",
286
+ "feature.baseNotFound": "Built-in feature template not found.",
274
287
  "feature.created": "\u2705 Feature folder created: {path}",
275
288
  "feature.nextStepsTitle": "Next steps:",
276
289
  "feature.nextSteps1": " 1. Write {path}/spec.md",
@@ -291,6 +304,9 @@ var I18N = {
291
304
  "update.agentsUpdated": "agents/ updated",
292
305
  "update.skillsUpdated": "agents/skills updated",
293
306
  "update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
307
+ "update.engineManagedSkillsBuiltin": "agents/skills is CLI-managed and is not synced into docs.",
308
+ "update.engineManagedFeatureBaseBuiltin": "features/feature-base is CLI-managed and is not synced into docs.",
309
+ "update.engineManagedPruned": "Removed {count} CLI-managed docs entries from this docs tree.",
294
310
  "update.filesUpdated": "{count} files updated",
295
311
  "update.updatedTotal": "Updated {count} files!",
296
312
  "update.changeDetected": "changes detected (use --force to overwrite)",
@@ -325,15 +341,17 @@ var I18N = {
325
341
  "context.tipShowAll": "Show all",
326
342
  "context.tipShowDone": "Show done only",
327
343
  "context.checkRequired": "[CHECK required] ",
328
- "context.checkPolicyHint": "\u2139\uFE0F User check policy: see /docs/agents/agents.md (includes git push/merge and merge commits) \u2014 if you see [CHECK required], wait for `<label>` or `<label> OK` (e.g. `A`, `A OK`) before proceeding (config: approval can override)",
344
+ "context.checkPolicyHint": "\u2139\uFE0F User check policy notice (not the current next action): see the CLI built-in agent policy (includes git push/merge and merge commits). If you see [CHECK required], wait for `<label>` or `<label> OK` (e.g. `A`, `A OK`) before proceeding (config: approval can override)",
329
345
  "context.actionOptionHint": "Approval reply format: `<label>` or `<label> OK` (e.g. `A`, `A OK`)",
330
346
  "context.actionExplainHint": "Before requesting approval, explain what each label will run/change with a one-line summary.",
331
- "context.tipDocsCommitRules": "Commit message rules: /docs/agents/git-workflow.md",
347
+ "context.tipDocsCommitRules": "Commit message rules: CLI built-in git-workflow policy",
332
348
  "context.list.docsCommitNeeded": "Commit docs changes",
333
349
  "context.list.projectCommitNeeded": "Commit project code changes",
334
350
  "context.list.issueNumberNeeded": "Fill issue number in docs",
335
351
  "context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
336
352
  "context.list.recordPrLink": "Record PR link",
353
+ "context.list.addPrePrReviewField": "Add Pre-PR Review field",
354
+ "context.list.completePrePrReview": "Complete Pre-PR review",
337
355
  "context.list.setPrStatus": "Set PR Status",
338
356
  "context.list.prStatusToApproved": "PR Status {status} \u2192 Approved",
339
357
  "context.list.approveSpec": "Approve spec",
@@ -402,23 +420,24 @@ var I18N = {
402
420
  branchCreate: "Create branch",
403
421
  tasksExecute: "Execute tasks",
404
422
  docsCommitSync: "Commit docs (sync)",
423
+ prePrReview: "Pre-PR review",
405
424
  prCreate: "Create PR",
406
425
  codeReview: "Code review",
407
426
  featureDone: "Feature done"
408
427
  },
409
428
  messages: {
410
- specCreate: "Create spec.md by copying the template. (See features/feature-base/spec.md)",
429
+ specCreate: "Create spec.md using the CLI built-in template policy.",
411
430
  specImprove: "Improve spec.md and change Status to Review.",
412
431
  specApproval: "Share spec.md with the user and get approval (`A` or `A OK` format).",
413
- planCreate: "Create plan.md by copying the template. (See features/feature-base/plan.md)",
432
+ planCreate: "Create plan.md using the CLI built-in template policy.",
414
433
  planImprove: "Improve plan.md and change Status to Review.",
415
434
  planApproval: "Share plan.md with the user and get approval (`A` or `A OK` format).",
416
- tasksCreate: "Create tasks.md by copying the template. (See features/feature-base/tasks.md)",
435
+ tasksCreate: "Create tasks.md using the CLI built-in template policy.",
417
436
  tasksNeedAtLeastOne: "Write at least 1 task in tasks.md.",
418
437
  tasksImprove: "Improve tasks.md and change Doc Status to Review.",
419
438
  tasksApproval: "Share tasks.md with the user and get progress approval (`A` or `A OK` format). (Then set Doc Status to Approved)",
420
439
  docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
421
- issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (See skills/create-issue.md)",
440
+ issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (CLI built-in create-issue guide)",
422
441
  docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
423
442
  docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} docs update"',
424
443
  projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} implementation update"',
@@ -427,11 +446,15 @@ var I18N = {
427
446
  createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
428
447
  tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
429
448
  tasksAllDoneButChecklist: "All tasks are DONE, but the completion checklist is not fully checked. ({checked}/{total})",
430
- finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (Share outcome/verification + get OK before marking DONE) (See skills/execute-task.md)',
431
- startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (Share title + get OK before marking DOING) (See skills/execute-task.md)',
432
- checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
449
+ finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (Share outcome/verification + get OK before marking DONE) (CLI built-in execute-task guide)',
450
+ startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (Share title + get OK before marking DOING) (CLI built-in execute-task guide)',
451
+ checkTaskStatuses: "Check task statuses. ({done}/{total}) (CLI built-in execute-task guide)",
433
452
  prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
434
- prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
453
+ prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
454
+ prePrReviewRun: "Run a pre-PR code review before creating the PR. Preferred skills: {skills} (if a better installed skill fits this change, propose it first). If no skill can run, use `{fallback}` and set `Pre-PR Review` to Done in tasks.md. Findings policy: {findingsPolicy}",
455
+ prePrReviewFindingsBlock: "major findings must be fixed/aligned before PR creation",
456
+ prePrReviewFindingsWarn: "you may proceed after sharing the risks",
457
+ prCreate: "Create a PR and record the PR link in tasks.md. (CLI built-in create-pr guide)",
435
458
  prFillStatus: "Set PR Status in tasks.md to Review/Approved. (After merge, update it to Approved.)",
436
459
  prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
437
460
  prRequestReview: "Request review and update PR Status to Review.",
@@ -442,17 +465,20 @@ var I18N = {
442
465
  projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
443
466
  docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
444
467
  docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
445
- docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Commit message rules: /docs/agents/git-workflow.md",
468
+ docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Commit message rules: CLI built-in git-workflow policy",
446
469
  projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
447
470
  legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Review/Approved) to enable tasks approval.",
448
471
  legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
472
+ legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
449
473
  workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
450
474
  workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
451
475
  workflowIssueMissing: "Implementation is done but Issue Number is missing. (Fill Issue Number in spec.md/tasks.md.)",
452
476
  workflowProjectUncommittedChanges: "Commit project code changes before completing workflow. (Project worktree has uncommitted changes.)",
453
477
  workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
454
478
  workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Review/Approved in tasks.md.)",
455
- workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
479
+ workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)",
480
+ workflowPrePrReviewMissing: "Implementation is done but `Pre-PR Review` is missing. (Add `- **Pre-PR Review**: Pending | Done` in tasks.md.)",
481
+ workflowPrePrReviewNotDone: "Implementation is done but `Pre-PR Review` is not Done. (Run pre-PR review, then update it to Done.)"
456
482
  }
457
483
  }
458
484
  };
@@ -1033,12 +1059,12 @@ function sleep(ms) {
1033
1059
  return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
1034
1060
  }
1035
1061
  function getDocsLockPath(docsDir) {
1036
- return path13.join(docsDir, ".lee-spec-kit.lock");
1062
+ return path6.join(docsDir, ".lee-spec-kit.lock");
1037
1063
  }
1038
1064
  function getInitLockPath(targetDir) {
1039
- return path13.join(
1040
- path13.dirname(targetDir),
1041
- `.lee-spec-kit.${path13.basename(targetDir)}.lock`
1065
+ return path6.join(
1066
+ path6.dirname(targetDir),
1067
+ `.lee-spec-kit.${path6.basename(targetDir)}.lock`
1042
1068
  );
1043
1069
  }
1044
1070
  async function isStaleLock(lockPath, staleMs) {
@@ -1080,7 +1106,7 @@ function isProcessAlive(pid) {
1080
1106
  }
1081
1107
  }
1082
1108
  async function tryAcquire(lockPath, owner) {
1083
- await fs2.ensureDir(path13.dirname(lockPath));
1109
+ await fs2.ensureDir(path6.dirname(lockPath));
1084
1110
  try {
1085
1111
  const fd = await fs2.open(lockPath, "wx");
1086
1112
  const payload = JSON.stringify(
@@ -1150,48 +1176,39 @@ function getLocalDateString(date = /* @__PURE__ */ new Date()) {
1150
1176
  const day = String(date.getDate()).padStart(2, "0");
1151
1177
  return `${year}-${month}-${day}`;
1152
1178
  }
1153
- function normalizeTrailingBlankLines(content) {
1154
- return content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1155
- }
1156
- function sanitizeSpecForLocal(content) {
1157
- const withoutIssueLine = content.split("\n").filter(
1158
- (line) => !/^\s*-\s*\*\*(Issue Number|이슈 번호)\*\*\s*:/.test(line)
1159
- ).join("\n");
1160
- return normalizeTrailingBlankLines(withoutIssueLine);
1161
- }
1162
- function sanitizeTasksForLocal(content, lang) {
1163
- let next = content;
1164
- next = next.replace(
1165
- /^##\s+GitHub Issue\s*$/m,
1166
- lang === "ko" ? "## \uB85C\uCEEC \uCD94\uC801 \uC815\uBCF4" : "## Local Tracking"
1167
- );
1168
- const lines = next.split("\n");
1169
- const filtered = [];
1170
- for (const line of lines) {
1171
- if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태)\*\*\s*:/.test(line)) continue;
1172
- if (/^\s*-\s*(Example|Values)\s*:/.test(line)) continue;
1173
- if (/^\s*-\s*(예|값)\s*:/.test(line)) continue;
1174
- filtered.push(line);
1179
+ var ENGINE_MANAGED_AGENT_FILES = [
1180
+ "agents.md",
1181
+ "git-workflow.md",
1182
+ "issue-template.md",
1183
+ "pr-template.md"
1184
+ ];
1185
+ var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1186
+ var ENGINE_MANAGED_FEATURE_PATH = path6.join(
1187
+ "features",
1188
+ "feature-base"
1189
+ );
1190
+ async function pruneEngineManagedDocs(docsDir) {
1191
+ const removed = [];
1192
+ for (const file of ENGINE_MANAGED_AGENT_FILES) {
1193
+ const target = path6.join(docsDir, "agents", file);
1194
+ if (await fs2.pathExists(target)) {
1195
+ await fs2.remove(target);
1196
+ removed.push(path6.relative(docsDir, target));
1197
+ }
1175
1198
  }
1176
- next = filtered.join("\n");
1177
- next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
1178
- return normalizeTrailingBlankLines(next);
1179
- }
1180
- async function patchMarkdownIfExists(filePath, transform) {
1181
- if (!await fs2.pathExists(filePath)) return;
1182
- const content = await fs2.readFile(filePath, "utf-8");
1183
- await fs2.writeFile(filePath, transform(content), "utf-8");
1184
- }
1185
- async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
1186
- await patchMarkdownIfExists(path13.join(featureDir, "spec.md"), sanitizeSpecForLocal);
1187
- await patchMarkdownIfExists(
1188
- path13.join(featureDir, "tasks.md"),
1189
- (content) => sanitizeTasksForLocal(content, lang)
1190
- );
1191
- }
1192
- async function applyLocalWorkflowTemplateToFeatureBase(docsDir, lang) {
1193
- const baseDir = path13.join(docsDir, "features", "feature-base");
1194
- await applyLocalWorkflowTemplateToFeatureDir(baseDir, lang);
1199
+ for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1200
+ const target = path6.join(docsDir, "agents", dir);
1201
+ if (await fs2.pathExists(target)) {
1202
+ await fs2.remove(target);
1203
+ removed.push(path6.relative(docsDir, target));
1204
+ }
1205
+ }
1206
+ const featureBasePath = path6.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1207
+ if (await fs2.pathExists(featureBasePath)) {
1208
+ await fs2.remove(featureBasePath);
1209
+ removed.push(path6.relative(docsDir, featureBasePath));
1210
+ }
1211
+ return removed;
1195
1212
  }
1196
1213
 
1197
1214
  // src/commands/init.ts
@@ -1242,7 +1259,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
1242
1259
  }
1243
1260
  async function runInit(options) {
1244
1261
  const cwd = process.cwd();
1245
- const defaultName = path13.basename(cwd);
1262
+ const defaultName = path6.basename(cwd);
1246
1263
  let projectName = options.name || defaultName;
1247
1264
  let projectType = options.type;
1248
1265
  let components = parseComponentsOption(options.components);
@@ -1252,7 +1269,7 @@ async function runInit(options) {
1252
1269
  let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
1253
1270
  let docsRemote = options.docsRemote;
1254
1271
  let projectRoot;
1255
- const targetDir = path13.resolve(cwd, options.dir || "./docs");
1272
+ const targetDir = path6.resolve(cwd, options.dir || "./docs");
1256
1273
  const skipPrompts = !!options.yes || !!options.nonInteractive;
1257
1274
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
1258
1275
  throw createCliError(
@@ -1591,9 +1608,9 @@ async function runInit(options) {
1591
1608
  );
1592
1609
  console.log();
1593
1610
  const templatesDir = getTemplatesDir();
1594
- const commonPath = path13.join(templatesDir, lang, "common");
1611
+ const commonPath = path6.join(templatesDir, lang, "common");
1595
1612
  const templateProjectType = toTemplateProjectType(projectType);
1596
- const typePath = path13.join(templatesDir, lang, templateProjectType);
1613
+ const typePath = path6.join(templatesDir, lang, templateProjectType);
1597
1614
  if (await fs2.pathExists(commonPath)) {
1598
1615
  await copyTemplates(commonPath, targetDir);
1599
1616
  }
@@ -1604,13 +1621,13 @@ async function runInit(options) {
1604
1621
  }
1605
1622
  await copyTemplates(typePath, targetDir);
1606
1623
  if (projectType === "multi" && !isDefaultFullstackComponents(components)) {
1607
- const featuresRoot = path13.join(targetDir, "features");
1608
- await fs2.remove(path13.join(featuresRoot, "fe"));
1609
- await fs2.remove(path13.join(featuresRoot, "be"));
1624
+ const featuresRoot = path6.join(targetDir, "features");
1625
+ await fs2.remove(path6.join(featuresRoot, "fe"));
1626
+ await fs2.remove(path6.join(featuresRoot, "be"));
1610
1627
  for (const component of components) {
1611
- const componentDir = path13.join(featuresRoot, component);
1628
+ const componentDir = path6.join(featuresRoot, component);
1612
1629
  await fs2.ensureDir(componentDir);
1613
- const readmePath = path13.join(componentDir, "README.md");
1630
+ const readmePath = path6.join(componentDir, "README.md");
1614
1631
  if (!await fs2.pathExists(readmePath)) {
1615
1632
  await fs2.writeFile(
1616
1633
  readmePath,
@@ -1630,9 +1647,7 @@ Store ${component} feature specs here.
1630
1647
  "{{featurePath}}": featurePath
1631
1648
  };
1632
1649
  await replaceInFiles(targetDir, replacements);
1633
- if (workflowMode === "local") {
1634
- await applyLocalWorkflowTemplateToFeatureBase(targetDir, lang);
1635
- }
1650
+ await pruneEngineManagedDocs(targetDir);
1636
1651
  const config = {
1637
1652
  projectName,
1638
1653
  projectType,
@@ -1640,7 +1655,11 @@ Store ${component} feature specs here.
1640
1655
  lang,
1641
1656
  createdAt: getLocalDateString(),
1642
1657
  docsRepo,
1643
- workflow: { mode: workflowMode, codeDirtyScope: "auto" },
1658
+ workflow: {
1659
+ mode: workflowMode,
1660
+ codeDirtyScope: "auto",
1661
+ prePrReview: { skills: ["code-review-excellence"] }
1662
+ },
1644
1663
  pr: {
1645
1664
  screenshots: { upload: false }
1646
1665
  },
@@ -1659,7 +1678,7 @@ Store ${component} feature specs here.
1659
1678
  config.projectRoot = projectRoot;
1660
1679
  }
1661
1680
  }
1662
- const configPath = path13.join(targetDir, ".lee-spec-kit.json");
1681
+ const configPath = path6.join(targetDir, ".lee-spec-kit.json");
1663
1682
  await fs2.writeJson(configPath, config, { spaces: 2 });
1664
1683
  console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
1665
1684
  console.log();
@@ -1728,7 +1747,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1728
1747
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
1729
1748
  runGit(["init"], cwd);
1730
1749
  }
1731
- const relativePath = path13.relative(cwd, targetDir);
1750
+ const relativePath = path6.relative(cwd, targetDir);
1732
1751
  const stagedBeforeAdd = getCachedStagedFiles(cwd);
1733
1752
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
1734
1753
  console.log(
@@ -1785,17 +1804,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1785
1804
  }
1786
1805
  function getAncestorDirs(startDir) {
1787
1806
  const dirs = [];
1788
- let current = path13.resolve(startDir);
1807
+ let current = path6.resolve(startDir);
1789
1808
  while (true) {
1790
1809
  dirs.push(current);
1791
- const parent = path13.dirname(current);
1810
+ const parent = path6.dirname(current);
1792
1811
  if (parent === current) break;
1793
1812
  current = parent;
1794
1813
  }
1795
1814
  return dirs;
1796
1815
  }
1797
1816
  function hasWorkspaceBoundary(dir) {
1798
- return fs2.existsSync(path13.join(dir, "package.json")) || fs2.existsSync(path13.join(dir, ".git"));
1817
+ return fs2.existsSync(path6.join(dir, "package.json")) || fs2.existsSync(path6.join(dir, ".git"));
1799
1818
  }
1800
1819
  function getSearchBaseDirs(cwd) {
1801
1820
  const ancestors = getAncestorDirs(cwd);
@@ -1808,21 +1827,21 @@ function getSearchBaseDirs(cwd) {
1808
1827
  async function getConfig(cwd) {
1809
1828
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
1810
1829
  const baseDirs = [
1811
- ...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
1830
+ ...explicitDocsDir ? [path6.resolve(explicitDocsDir)] : [],
1812
1831
  ...getSearchBaseDirs(cwd)
1813
1832
  ];
1814
1833
  const visitedBaseDirs = /* @__PURE__ */ new Set();
1815
1834
  const visitedDocsDirs = /* @__PURE__ */ new Set();
1816
1835
  for (const baseDir of baseDirs) {
1817
- const resolvedBaseDir = path13.resolve(baseDir);
1836
+ const resolvedBaseDir = path6.resolve(baseDir);
1818
1837
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
1819
1838
  visitedBaseDirs.add(resolvedBaseDir);
1820
- const possibleDocsDirs = [path13.join(resolvedBaseDir, "docs"), resolvedBaseDir];
1839
+ const possibleDocsDirs = [path6.join(resolvedBaseDir, "docs"), resolvedBaseDir];
1821
1840
  for (const docsDir of possibleDocsDirs) {
1822
- const resolvedDocsDir = path13.resolve(docsDir);
1841
+ const resolvedDocsDir = path6.resolve(docsDir);
1823
1842
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
1824
1843
  visitedDocsDirs.add(resolvedDocsDir);
1825
- const configPath = path13.join(resolvedDocsDir, ".lee-spec-kit.json");
1844
+ const configPath = path6.join(resolvedDocsDir, ".lee-spec-kit.json");
1826
1845
  if (await fs2.pathExists(configPath)) {
1827
1846
  try {
1828
1847
  const configFile = await fs2.readJson(configPath);
@@ -1848,18 +1867,26 @@ async function getConfig(cwd) {
1848
1867
  } catch {
1849
1868
  }
1850
1869
  }
1851
- const agentsPath = path13.join(resolvedDocsDir, "agents");
1852
- const featuresPath = path13.join(resolvedDocsDir, "features");
1870
+ const agentsPath = path6.join(resolvedDocsDir, "agents");
1871
+ const featuresPath = path6.join(resolvedDocsDir, "features");
1853
1872
  if (await fs2.pathExists(agentsPath) && await fs2.pathExists(featuresPath)) {
1854
- const bePath = path13.join(featuresPath, "be");
1855
- const fePath = path13.join(featuresPath, "fe");
1873
+ const bePath = path6.join(featuresPath, "be");
1874
+ const fePath = path6.join(featuresPath, "fe");
1856
1875
  const projectType = await fs2.pathExists(bePath) || await fs2.pathExists(fePath) ? "multi" : "single";
1857
1876
  const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
1858
- const agentsMdPath = path13.join(agentsPath, "agents.md");
1877
+ const langProbeCandidates = [
1878
+ path6.join(agentsPath, "custom.md"),
1879
+ path6.join(agentsPath, "constitution.md"),
1880
+ path6.join(agentsPath, "agents.md")
1881
+ ];
1859
1882
  let lang = "en";
1860
- if (await fs2.pathExists(agentsMdPath)) {
1861
- const content = await fs2.readFile(agentsMdPath, "utf-8");
1862
- if (/[가-힣]/.test(content)) lang = "ko";
1883
+ for (const candidate of langProbeCandidates) {
1884
+ if (!await fs2.pathExists(candidate)) continue;
1885
+ const content = await fs2.readFile(candidate, "utf-8");
1886
+ if (/[가-힣]/.test(content)) {
1887
+ lang = "ko";
1888
+ break;
1889
+ }
1863
1890
  }
1864
1891
  return { docsDir: resolvedDocsDir, projectType, components, lang };
1865
1892
  }
@@ -1867,6 +1894,51 @@ async function getConfig(cwd) {
1867
1894
  }
1868
1895
  return null;
1869
1896
  }
1897
+ function normalizeTrailingBlankLines(content) {
1898
+ return content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1899
+ }
1900
+ function sanitizeSpecForLocal(content) {
1901
+ const withoutIssueLine = content.split("\n").filter(
1902
+ (line) => !/^\s*-\s*\*\*(Issue Number|이슈 번호)\*\*\s*:/.test(line)
1903
+ ).join("\n");
1904
+ return normalizeTrailingBlankLines(withoutIssueLine);
1905
+ }
1906
+ function sanitizeTasksForLocal(content, lang) {
1907
+ let next = content;
1908
+ next = next.replace(
1909
+ /^##\s+GitHub Issue\s*$/m,
1910
+ lang === "ko" ? "## \uB85C\uCEEC \uCD94\uC801 \uC815\uBCF4" : "## Local Tracking"
1911
+ );
1912
+ const lines = next.split("\n");
1913
+ const filtered = [];
1914
+ for (const line of lines) {
1915
+ if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|Pre-PR Review|PR 전 리뷰)\*\*\s*:/.test(
1916
+ line
1917
+ )) {
1918
+ continue;
1919
+ }
1920
+ if (/^\s*-\s*(Example|Values)\s*:/.test(line)) continue;
1921
+ if (/^\s*-\s*(예|값)\s*:/.test(line)) continue;
1922
+ if (/^\s*-\s*Mark\s+`?Done`?/i.test(line)) continue;
1923
+ if (/^\s*-\s*사전 코드리뷰 완료 후/.test(line)) continue;
1924
+ filtered.push(line);
1925
+ }
1926
+ next = filtered.join("\n");
1927
+ next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
1928
+ return normalizeTrailingBlankLines(next);
1929
+ }
1930
+ async function patchMarkdownIfExists(filePath, transform) {
1931
+ if (!await fs2.pathExists(filePath)) return;
1932
+ const content = await fs2.readFile(filePath, "utf-8");
1933
+ await fs2.writeFile(filePath, transform(content), "utf-8");
1934
+ }
1935
+ async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
1936
+ await patchMarkdownIfExists(path6.join(featureDir, "spec.md"), sanitizeSpecForLocal);
1937
+ await patchMarkdownIfExists(
1938
+ path6.join(featureDir, "tasks.md"),
1939
+ (content) => sanitizeTasksForLocal(content, lang)
1940
+ );
1941
+ }
1870
1942
 
1871
1943
  // src/commands/feature.ts
1872
1944
  function featureCommand(program2) {
@@ -2005,19 +2077,25 @@ async function runFeature(name, options) {
2005
2077
  }
2006
2078
  let featuresDir;
2007
2079
  if (projectType === "multi") {
2008
- featuresDir = path13.join(docsDir, "features", component);
2080
+ featuresDir = path6.join(docsDir, "features", component);
2009
2081
  } else {
2010
- featuresDir = path13.join(docsDir, "features");
2082
+ featuresDir = path6.join(docsDir, "features");
2011
2083
  }
2012
2084
  const featureFolderName = `${featureId}-${name}`;
2013
- const featureDir = path13.join(featuresDir, featureFolderName);
2085
+ const featureDir = path6.join(featuresDir, featureFolderName);
2014
2086
  if (await fs2.pathExists(featureDir)) {
2015
2087
  throw createCliError(
2016
2088
  "INVALID_ARGUMENT",
2017
2089
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
2018
2090
  );
2019
2091
  }
2020
- const featureBasePath = path13.join(docsDir, "features", "feature-base");
2092
+ const featureBasePath = path6.join(
2093
+ getTemplatesDir(),
2094
+ lang,
2095
+ toTemplateProjectType(projectType),
2096
+ "features",
2097
+ "feature-base"
2098
+ );
2021
2099
  if (!await fs2.pathExists(featureBasePath)) {
2022
2100
  throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
2023
2101
  }
@@ -2069,7 +2147,7 @@ async function runFeature(name, options) {
2069
2147
  featureName: name,
2070
2148
  component: projectType === "multi" ? component : void 0,
2071
2149
  featurePath: featureDir,
2072
- featurePathFromDocs: path13.relative(docsDir, featureDir)
2150
+ featurePathFromDocs: path6.relative(docsDir, featureDir)
2073
2151
  };
2074
2152
  },
2075
2153
  { owner: "feature" }
@@ -2081,9 +2159,9 @@ function sleep2(ms) {
2081
2159
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2082
2160
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2083
2161
  const candidates = [
2084
- ...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
2085
- path13.resolve(cwd, "docs"),
2086
- path13.resolve(cwd)
2162
+ ...explicitDocsDir ? [path6.resolve(explicitDocsDir)] : [],
2163
+ path6.resolve(cwd, "docs"),
2164
+ path6.resolve(cwd)
2087
2165
  ];
2088
2166
  const endAt = Date.now() + timeoutMs;
2089
2167
  while (Date.now() < endAt) {
@@ -2110,11 +2188,11 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2110
2188
  return getConfig(cwd);
2111
2189
  }
2112
2190
  async function getNextFeatureId(docsDir, projectType, components) {
2113
- const featuresDir = path13.join(docsDir, "features");
2191
+ const featuresDir = path6.join(docsDir, "features");
2114
2192
  let max = 0;
2115
2193
  const scanDirs = [];
2116
2194
  if (projectType === "multi") {
2117
- scanDirs.push(...components.map((component) => path13.join(featuresDir, component)));
2195
+ scanDirs.push(...components.map((component) => path6.join(featuresDir, component)));
2118
2196
  } else {
2119
2197
  scanDirs.push(featuresDir);
2120
2198
  }
@@ -2136,6 +2214,7 @@ async function getNextFeatureId(docsDir, projectType, components) {
2136
2214
  }
2137
2215
 
2138
2216
  // src/utils/workflow.ts
2217
+ var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
2139
2218
  function resolveWorkflowPolicy(workflow) {
2140
2219
  const mode = workflow?.mode === "local" ? "local" : "github";
2141
2220
  const policy = mode === "local" ? {
@@ -2182,6 +2261,28 @@ function resolveCodeDirtyScopePolicy(workflow, projectType) {
2182
2261
  }
2183
2262
  return projectType === "multi" ? "component" : "repo";
2184
2263
  }
2264
+ function normalizeSkillList(input) {
2265
+ if (!Array.isArray(input)) return [];
2266
+ const deduped = /* @__PURE__ */ new Set();
2267
+ for (const raw of input) {
2268
+ const value = String(raw || "").trim();
2269
+ if (!value) continue;
2270
+ deduped.add(value);
2271
+ }
2272
+ return [...deduped];
2273
+ }
2274
+ function resolvePrePrReviewPolicy(workflow) {
2275
+ const workflowPolicy = resolveWorkflowPolicy(workflow);
2276
+ const configured = workflow?.prePrReview;
2277
+ const configuredSkills = normalizeSkillList(configured?.skills);
2278
+ const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
2279
+ return {
2280
+ enabled: workflowPolicy.requirePr ? configuredEnabled : false,
2281
+ skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
2282
+ fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
2283
+ blockOnFindings: typeof configured?.blockOnFindings === "boolean" ? configured.blockOnFindings : true
2284
+ };
2285
+ }
2185
2286
 
2186
2287
  // src/utils/context/steps.ts
2187
2288
  function isCompletionChecklistDone(feature) {
@@ -2196,11 +2297,18 @@ function isImplementationDone(feature) {
2196
2297
  function isPrMetadataConfigured(feature) {
2197
2298
  return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
2198
2299
  }
2199
- function isFeatureDone(feature, workflowPolicy) {
2200
- return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireReview || feature.pr.status === "Approved");
2300
+ function isFeatureDone(feature, workflowPolicy, prePrReviewPolicy) {
2301
+ return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireReview || feature.pr.status === "Approved") && (!prePrReviewPolicy.enabled || feature.docs.prePrReviewFieldExists && feature.prePrReview.status === "Done");
2302
+ }
2303
+ function formatSkillList(skills) {
2304
+ return skills.join(", ");
2305
+ }
2306
+ function getFindingsPolicyText(lang, blockOnFindings) {
2307
+ return blockOnFindings ? tr(lang, "messages", "prePrReviewFindingsBlock") : tr(lang, "messages", "prePrReviewFindingsWarn");
2201
2308
  }
2202
2309
  function getStepDefinitions(lang, workflow) {
2203
2310
  const workflowPolicy = resolveWorkflowPolicy(workflow);
2311
+ const prePrReviewPolicy = resolvePrePrReviewPolicy(workflow);
2204
2312
  return [
2205
2313
  {
2206
2314
  step: 1,
@@ -2401,10 +2509,10 @@ function getStepDefinitions(lang, workflow) {
2401
2509
  step: 9,
2402
2510
  name: tr(lang, "steps", "branchCreate"),
2403
2511
  checklist: {
2404
- done: (f) => !workflowPolicy.requireBranch || f.git.onExpectedBranch || isImplementationDone(f) || isFeatureDone(f, workflowPolicy)
2512
+ done: (f) => !workflowPolicy.requireBranch || f.git.onExpectedBranch || isImplementationDone(f) || isFeatureDone(f, workflowPolicy, prePrReviewPolicy)
2405
2513
  },
2406
2514
  current: {
2407
- when: (f) => workflowPolicy.requireBranch && !!f.issueNumber && f.tasks.total > 0 && f.tasks.done < f.tasks.total && !isFeatureDone(f, workflowPolicy) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
2515
+ when: (f) => workflowPolicy.requireBranch && !!f.issueNumber && f.tasks.total > 0 && f.tasks.done < f.tasks.total && !isFeatureDone(f, workflowPolicy, prePrReviewPolicy) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
2408
2516
  actions: (f) => {
2409
2517
  if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
2410
2518
  return [
@@ -2499,6 +2607,34 @@ function getStepDefinitions(lang, workflow) {
2499
2607
  }
2500
2608
  ];
2501
2609
  }
2610
+ if (f.git.projectHasUncommittedChanges) {
2611
+ if (!f.git.projectGitCwd) {
2612
+ return [
2613
+ {
2614
+ type: "instruction",
2615
+ category: "task_execute",
2616
+ message: tr(lang, "messages", "standaloneNeedsProjectRoot")
2617
+ }
2618
+ ];
2619
+ }
2620
+ return [
2621
+ {
2622
+ type: "command",
2623
+ category: "task_execute",
2624
+ requiresUserCheck: true,
2625
+ scope: "project",
2626
+ cwd: f.git.projectGitCwd,
2627
+ cmd: f.issueNumber ? tr(lang, "messages", "projectCommitIssueUpdate", {
2628
+ projectGitCwd: f.git.projectGitCwd,
2629
+ issueNumber: f.issueNumber,
2630
+ folderName: f.folderName
2631
+ }) : tr(lang, "messages", "projectCommitUpdate", {
2632
+ projectGitCwd: f.git.projectGitCwd,
2633
+ folderName: f.folderName
2634
+ })
2635
+ }
2636
+ ];
2637
+ }
2502
2638
  return [
2503
2639
  {
2504
2640
  type: "instruction",
@@ -2587,6 +2723,61 @@ function getStepDefinitions(lang, workflow) {
2587
2723
  },
2588
2724
  {
2589
2725
  step: 12,
2726
+ name: tr(lang, "steps", "prePrReview"),
2727
+ checklist: {
2728
+ done: (f) => !prePrReviewPolicy.enabled || f.docs.prePrReviewFieldExists && f.prePrReview.status === "Done"
2729
+ },
2730
+ current: {
2731
+ when: (f) => prePrReviewPolicy.enabled && workflowPolicy.requirePr && f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && !f.git.docsHasUncommittedChanges && !f.git.projectHasUncommittedChanges && (!isPrMetadataConfigured(f) || !f.pr.link) && (!f.docs.prePrReviewFieldExists || f.prePrReview.status !== "Done"),
2732
+ actions: (f) => {
2733
+ if (!prePrReviewPolicy.enabled) return [];
2734
+ if (!f.docs.prePrReviewFieldExists) {
2735
+ return [
2736
+ {
2737
+ type: "instruction",
2738
+ category: "pr_metadata_migrate",
2739
+ requiresUserCheck: true,
2740
+ message: tr(lang, "messages", "prePrReviewFieldMissing")
2741
+ }
2742
+ ];
2743
+ }
2744
+ if (!prePrReviewPolicy.skills.length) {
2745
+ return [
2746
+ {
2747
+ type: "instruction",
2748
+ category: "pre_pr_review",
2749
+ requiresUserCheck: true,
2750
+ message: tr(lang, "messages", "prePrReviewRun", {
2751
+ skills: "code-review-excellence",
2752
+ fallback: prePrReviewPolicy.fallback,
2753
+ findingsPolicy: getFindingsPolicyText(
2754
+ lang,
2755
+ prePrReviewPolicy.blockOnFindings
2756
+ )
2757
+ })
2758
+ }
2759
+ ];
2760
+ }
2761
+ return [
2762
+ {
2763
+ type: "instruction",
2764
+ category: "pre_pr_review",
2765
+ requiresUserCheck: true,
2766
+ message: tr(lang, "messages", "prePrReviewRun", {
2767
+ skills: formatSkillList(prePrReviewPolicy.skills),
2768
+ fallback: prePrReviewPolicy.fallback,
2769
+ findingsPolicy: getFindingsPolicyText(
2770
+ lang,
2771
+ prePrReviewPolicy.blockOnFindings
2772
+ )
2773
+ })
2774
+ }
2775
+ ];
2776
+ }
2777
+ }
2778
+ },
2779
+ {
2780
+ step: 13,
2590
2781
  name: tr(lang, "steps", "prCreate"),
2591
2782
  checklist: {
2592
2783
  done: (f) => !workflowPolicy.requirePr || isPrMetadataConfigured(f) && !!f.pr.link
@@ -2616,7 +2807,7 @@ function getStepDefinitions(lang, workflow) {
2616
2807
  }
2617
2808
  },
2618
2809
  {
2619
- step: 13,
2810
+ step: 14,
2620
2811
  name: tr(lang, "steps", "codeReview"),
2621
2812
  checklist: {
2622
2813
  done: (f) => !workflowPolicy.requireReview || isPrMetadataConfigured(f) && f.pr.status === "Approved"
@@ -2654,11 +2845,13 @@ function getStepDefinitions(lang, workflow) {
2654
2845
  }
2655
2846
  },
2656
2847
  {
2657
- step: 14,
2848
+ step: 15,
2658
2849
  name: tr(lang, "steps", "featureDone"),
2659
- checklist: { done: (f) => isFeatureDone(f, workflowPolicy) },
2850
+ checklist: {
2851
+ done: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy)
2852
+ },
2660
2853
  current: {
2661
- when: (f) => isFeatureDone(f, workflowPolicy),
2854
+ when: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy),
2662
2855
  actions: () => [
2663
2856
  {
2664
2857
  type: "instruction",
@@ -2889,6 +3082,14 @@ function parseDocStatus(value) {
2889
3082
  if (normalized === "review") return "Review";
2890
3083
  return "Approved";
2891
3084
  }
3085
+ function parsePrePrReviewStatus(value) {
3086
+ if (!value) return void 0;
3087
+ const trimmed = value.trim();
3088
+ if (!trimmed || trimmed.includes("|")) return void 0;
3089
+ if (/^done$/i.test(trimmed)) return "Done";
3090
+ if (/^pending$/i.test(trimmed)) return "Pending";
3091
+ return void 0;
3092
+ }
2892
3093
  function parseIssueNumber(value) {
2893
3094
  if (!value) return void 0;
2894
3095
  const match = value.match(/#?(\d+)/);
@@ -2906,13 +3107,13 @@ function parsePrLink(value) {
2906
3107
  return trimmed;
2907
3108
  }
2908
3109
  function normalizeGitPath(value) {
2909
- return value.split(path13.sep).join("/");
3110
+ return value.split(path6.sep).join("/");
2910
3111
  }
2911
3112
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
2912
- const relativeDocsDir = path13.relative(projectGitCwd, docsDir);
3113
+ const relativeDocsDir = path6.relative(projectGitCwd, docsDir);
2913
3114
  if (!relativeDocsDir) return [];
2914
- if (path13.isAbsolute(relativeDocsDir)) return [];
2915
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path13.sep}`)) {
3115
+ if (path6.isAbsolute(relativeDocsDir)) return [];
3116
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path6.sep}`)) {
2916
3117
  return [];
2917
3118
  }
2918
3119
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
@@ -2944,16 +3145,16 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
2944
3145
  const normalizedCandidates = uniqueNormalizedPaths(
2945
3146
  candidates.map((candidate) => {
2946
3147
  if (!candidate) return "";
2947
- if (!path13.isAbsolute(candidate)) return candidate;
2948
- const relative = path13.relative(projectGitCwd, candidate);
3148
+ if (!path6.isAbsolute(candidate)) return candidate;
3149
+ const relative = path6.relative(projectGitCwd, candidate);
2949
3150
  if (!relative) return "";
2950
- if (relative === ".." || relative.startsWith(`..${path13.sep}`)) return "";
3151
+ if (relative === ".." || relative.startsWith(`..${path6.sep}`)) return "";
2951
3152
  return relative;
2952
3153
  }).filter(Boolean)
2953
3154
  );
2954
3155
  const existing = [];
2955
3156
  for (const candidate of normalizedCandidates) {
2956
- if (await fs2.pathExists(path13.join(projectGitCwd, candidate))) {
3157
+ if (await fs2.pathExists(path6.join(projectGitCwd, candidate))) {
2957
3158
  existing.push(candidate);
2958
3159
  }
2959
3160
  }
@@ -3015,13 +3216,14 @@ function isPrMetadataConfigured2(feature) {
3015
3216
  async function parseFeature(featurePath, type, context, options) {
3016
3217
  const lang = options.lang;
3017
3218
  const workflowPolicy = resolveWorkflowPolicy(options.workflow);
3018
- const folderName = path13.basename(featurePath);
3219
+ const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
3220
+ const folderName = path6.basename(featurePath);
3019
3221
  const match = folderName.match(/^(F\d+)-(.+)$/);
3020
3222
  const id = match?.[1];
3021
3223
  const slug = match?.[2] || folderName;
3022
- const specPath = path13.join(featurePath, "spec.md");
3023
- const planPath = path13.join(featurePath, "plan.md");
3024
- const tasksPath = path13.join(featurePath, "tasks.md");
3224
+ const specPath = path6.join(featurePath, "spec.md");
3225
+ const planPath = path6.join(featurePath, "plan.md");
3226
+ const tasksPath = path6.join(featurePath, "tasks.md");
3025
3227
  let specStatus;
3026
3228
  let issueNumber;
3027
3229
  const specExists = await fs2.pathExists(specPath);
@@ -3046,10 +3248,12 @@ async function parseFeature(featurePath, type, context, options) {
3046
3248
  let tasksDocStatus;
3047
3249
  let tasksDocStatusFieldExists = false;
3048
3250
  let completionChecklist;
3251
+ let prePrReviewStatus;
3049
3252
  let prLink;
3050
3253
  let prStatus;
3051
3254
  let prFieldExists = false;
3052
3255
  let prStatusFieldExists = false;
3256
+ let prePrReviewFieldExists = false;
3053
3257
  if (tasksExists) {
3054
3258
  const content = await fs2.readFile(tasksPath, "utf-8");
3055
3259
  const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
@@ -3073,6 +3277,15 @@ async function parseFeature(featurePath, type, context, options) {
3073
3277
  const prStatusValue = extractFirstSpecValue(content, ["PR \uC0C1\uD0DC", "PR Status"]);
3074
3278
  prStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
3075
3279
  prStatus = parseDocStatus(prStatusValue);
3280
+ const prePrReviewValue = extractFirstSpecValue(content, [
3281
+ "PR \uC804 \uB9AC\uBDF0",
3282
+ "Pre-PR Review"
3283
+ ]);
3284
+ prePrReviewFieldExists = hasAnySpecKey(content, [
3285
+ "PR \uC804 \uB9AC\uBDF0",
3286
+ "Pre-PR Review"
3287
+ ]);
3288
+ prePrReviewStatus = parsePrePrReviewStatus(prePrReviewValue);
3076
3289
  }
3077
3290
  const warnings = [];
3078
3291
  if (context.projectBranchAvailable === false) {
@@ -3084,7 +3297,7 @@ async function parseFeature(featurePath, type, context, options) {
3084
3297
  slug,
3085
3298
  folderName
3086
3299
  );
3087
- const relativeFeaturePathFromDocs = path13.relative(context.docsDir, featurePath);
3300
+ const relativeFeaturePathFromDocs = path6.relative(context.docsDir, featurePath);
3088
3301
  const docsPathIgnored = isGitPathIgnored(
3089
3302
  context.docsGitCwd,
3090
3303
  relativeFeaturePathFromDocs
@@ -3128,6 +3341,9 @@ async function parseFeature(featurePath, type, context, options) {
3128
3341
  if (tasksExists && workflowPolicy.requirePr && (!prFieldExists || !prStatusFieldExists)) {
3129
3342
  warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
3130
3343
  }
3344
+ if (tasksExists && prePrReviewPolicy.enabled && !prePrReviewFieldExists) {
3345
+ warnings.push(tr(lang, "warnings", "legacyTasksPrePrReviewField"));
3346
+ }
3131
3347
  if (tasksExists && !tasksDocStatusFieldExists) {
3132
3348
  warnings.push(tr(lang, "warnings", "legacyTasksDocStatusField"));
3133
3349
  }
@@ -3139,7 +3355,7 @@ async function parseFeature(featurePath, type, context, options) {
3139
3355
  }
3140
3356
  const tasksDocApproved = !tasksDocStatusFieldExists || tasksDocStatus === "Approved";
3141
3357
  const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist }) && tasksDocApproved;
3142
- const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireReview || prStatus === "Approved");
3358
+ const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireReview || prStatus === "Approved") && (!prePrReviewPolicy.enabled || prePrReviewFieldExists && prePrReviewStatus === "Done");
3143
3359
  if (implementationDone && !workflowDone) {
3144
3360
  if (specStatus !== "Approved") {
3145
3361
  warnings.push(tr(lang, "warnings", "workflowSpecNotApproved"));
@@ -3162,6 +3378,13 @@ async function parseFeature(featurePath, type, context, options) {
3162
3378
  }
3163
3379
  }
3164
3380
  }
3381
+ if (prePrReviewPolicy.enabled) {
3382
+ if (!prePrReviewFieldExists) {
3383
+ warnings.push(tr(lang, "warnings", "workflowPrePrReviewMissing"));
3384
+ } else if (prePrReviewStatus !== "Done") {
3385
+ warnings.push(tr(lang, "warnings", "workflowPrePrReviewNotDone"));
3386
+ }
3387
+ }
3165
3388
  }
3166
3389
  const featureState = {
3167
3390
  id,
@@ -3181,6 +3404,9 @@ async function parseFeature(featurePath, type, context, options) {
3181
3404
  activeTask,
3182
3405
  nextTodoTask,
3183
3406
  completionChecklist,
3407
+ prePrReview: {
3408
+ status: prePrReviewStatus
3409
+ },
3184
3410
  pr: { link: prLink, status: prStatus },
3185
3411
  git: {
3186
3412
  docsBranch: context.docsBranch,
@@ -3201,7 +3427,8 @@ async function parseFeature(featurePath, type, context, options) {
3201
3427
  tasksExists,
3202
3428
  tasksDocStatusFieldExists,
3203
3429
  prFieldExists,
3204
- prStatusFieldExists
3430
+ prStatusFieldExists,
3431
+ prePrReviewFieldExists
3205
3432
  }
3206
3433
  };
3207
3434
  const { currentStep, actions, nextAction } = resolveFeatureProgress(
@@ -3335,13 +3562,13 @@ async function runStatus(options) {
3335
3562
  );
3336
3563
  }
3337
3564
  const { docsDir, projectType, projectName, lang } = config;
3338
- const featuresDir = path13.join(docsDir, "features");
3565
+ const featuresDir = path6.join(docsDir, "features");
3339
3566
  const scan = await scanFeatures(config);
3340
3567
  const features = [];
3341
3568
  const idMap = /* @__PURE__ */ new Map();
3342
3569
  for (const f of scan.features) {
3343
3570
  const id = f.id || "UNKNOWN";
3344
- const relPath = path13.relative(docsDir, f.path);
3571
+ const relPath = path6.relative(docsDir, f.path);
3345
3572
  if (!idMap.has(id)) idMap.set(id, []);
3346
3573
  idMap.get(id).push(relPath);
3347
3574
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -3422,7 +3649,7 @@ async function runStatus(options) {
3422
3649
  }
3423
3650
  console.log();
3424
3651
  if (options.write) {
3425
- const outputPath = path13.join(featuresDir, "status.md");
3652
+ const outputPath = path6.join(featuresDir, "status.md");
3426
3653
  const date = getLocalDateString();
3427
3654
  const content = [
3428
3655
  "# Feature Status",
@@ -3450,7 +3677,7 @@ function escapeRegExp2(value) {
3450
3677
  }
3451
3678
  async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
3452
3679
  try {
3453
- const specPath = path13.join(featureDir, "spec.md");
3680
+ const specPath = path6.join(featureDir, "spec.md");
3454
3681
  if (!await fs2.pathExists(specPath)) return fallbackSlug;
3455
3682
  const content = await fs2.readFile(specPath, "utf-8");
3456
3683
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
@@ -3468,7 +3695,7 @@ async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderNa
3468
3695
  return fallbackSlug || fallbackFolderName;
3469
3696
  }
3470
3697
  function updateCommand(program2) {
3471
- program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Update agents/skills folder only").option("--templates", "Update feature-base/ folder only").option(
3698
+ program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Cleanup legacy agents/skills copies (CLI-managed)").option("--templates", "Cleanup legacy feature-base copies (CLI-managed)").option(
3472
3699
  "-f, --force",
3473
3700
  "Force overwrite even if docs has uncommitted changes"
3474
3701
  ).action(async (options) => {
@@ -3507,7 +3734,6 @@ async function runUpdate(options) {
3507
3734
  getDocsLockPath(docsDir),
3508
3735
  async () => {
3509
3736
  const templatesDir = getTemplatesDir();
3510
- const sourceDir = path13.join(templatesDir, lang, toTemplateProjectType(projectType));
3511
3737
  const docsLockPath = getDocsLockPath(docsDir);
3512
3738
  const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang, [docsLockPath]);
3513
3739
  const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
@@ -3522,78 +3748,93 @@ async function runUpdate(options) {
3522
3748
  console.log();
3523
3749
  let updatedCount = 0;
3524
3750
  if (updateAgents) {
3525
- console.log(
3526
- chalk6.blue(
3527
- agentsMode === "skills" ? tr(lang, "cli", "update.updatingSkills") : tr(lang, "cli", "update.updatingAgents")
3528
- )
3529
- );
3530
- const commonAgentsBase = path13.join(templatesDir, lang, "common", "agents");
3531
- const typeAgentsBase = path13.join(
3532
- templatesDir,
3533
- lang,
3534
- toTemplateProjectType(projectType),
3535
- "agents"
3536
- );
3537
- const targetAgentsBase = path13.join(docsDir, "agents");
3538
- const commonAgents = agentsMode === "skills" ? path13.join(commonAgentsBase, "skills") : commonAgentsBase;
3539
- const typeAgents = agentsMode === "skills" ? path13.join(typeAgentsBase, "skills") : typeAgentsBase;
3540
- const targetAgents = agentsMode === "skills" ? path13.join(targetAgentsBase, "skills") : targetAgentsBase;
3541
- const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
3542
- const projectName = config.projectName ?? "{{projectName}}";
3543
- const commonReplacements = {
3544
- "{{projectName}}": projectName,
3545
- "{{featurePath}}": featurePath
3546
- };
3547
- const typeReplacements = {
3548
- "{{projectName}}": projectName
3549
- };
3550
- if (await fs2.pathExists(commonAgents)) {
3551
- const count = await updateFolder(
3552
- commonAgents,
3553
- targetAgents,
3554
- forceOverwrite,
3555
- commonReplacements,
3556
- lang
3751
+ if (agentsMode === "skills") {
3752
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingSkills")));
3753
+ console.log(
3754
+ chalk6.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
3557
3755
  );
3558
- updatedCount += count;
3756
+ console.log(chalk6.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
3757
+ } else {
3758
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
3559
3759
  }
3560
- if (await fs2.pathExists(typeAgents)) {
3561
- const count = await updateFolder(
3562
- typeAgents,
3563
- targetAgents,
3564
- forceOverwrite,
3565
- typeReplacements,
3566
- lang
3760
+ if (agentsMode === "all") {
3761
+ const commonAgentsBase = path6.join(templatesDir, lang, "common", "agents");
3762
+ const typeAgentsBase = path6.join(
3763
+ templatesDir,
3764
+ lang,
3765
+ toTemplateProjectType(projectType),
3766
+ "agents"
3567
3767
  );
3568
- updatedCount += count;
3569
- }
3570
- console.log(
3571
- chalk6.green(
3572
- ` \u2705 ${agentsMode === "skills" ? tr(lang, "cli", "update.skillsUpdated") : tr(lang, "cli", "update.agentsUpdated")}`
3573
- )
3574
- );
3575
- }
3576
- if (updateTemplates) {
3577
- console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
3578
- const sourceFeatureBase = path13.join(sourceDir, "features", "feature-base");
3579
- const targetFeatureBase = path13.join(docsDir, "features", "feature-base");
3580
- if (await fs2.pathExists(sourceFeatureBase)) {
3581
- const replacements = {
3582
- "{{projectName}}": config.projectName ?? "{{projectName}}"
3768
+ const targetAgentsBase = path6.join(docsDir, "agents");
3769
+ const commonAgents = agentsMode === "skills" ? path6.join(commonAgentsBase, "skills") : commonAgentsBase;
3770
+ const typeAgents = agentsMode === "skills" ? path6.join(typeAgentsBase, "skills") : typeAgentsBase;
3771
+ const targetAgents = agentsMode === "skills" ? path6.join(targetAgentsBase, "skills") : targetAgentsBase;
3772
+ const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
3773
+ const projectName = config.projectName ?? "{{projectName}}";
3774
+ const commonReplacements = {
3775
+ "{{projectName}}": projectName,
3776
+ "{{featurePath}}": featurePath
3583
3777
  };
3584
- const count = await updateFolder(
3585
- sourceFeatureBase,
3586
- targetFeatureBase,
3587
- forceOverwrite,
3588
- replacements,
3589
- lang
3590
- );
3591
- updatedCount += count;
3778
+ const typeReplacements = {
3779
+ "{{projectName}}": projectName
3780
+ };
3781
+ if (await fs2.pathExists(commonAgents)) {
3782
+ const count = await updateFolder(
3783
+ commonAgents,
3784
+ targetAgents,
3785
+ forceOverwrite,
3786
+ commonReplacements,
3787
+ lang,
3788
+ {
3789
+ protectedFiles: /* @__PURE__ */ new Set([
3790
+ "custom.md",
3791
+ "constitution.md",
3792
+ ...ENGINE_MANAGED_AGENT_FILES
3793
+ ]),
3794
+ skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
3795
+ }
3796
+ );
3797
+ updatedCount += count;
3798
+ }
3799
+ if (await fs2.pathExists(typeAgents)) {
3800
+ const count = await updateFolder(
3801
+ typeAgents,
3802
+ targetAgents,
3803
+ forceOverwrite,
3804
+ typeReplacements,
3805
+ lang,
3806
+ {
3807
+ protectedFiles: /* @__PURE__ */ new Set([
3808
+ "custom.md",
3809
+ "constitution.md",
3810
+ ...ENGINE_MANAGED_AGENT_FILES
3811
+ ]),
3812
+ skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
3813
+ }
3814
+ );
3815
+ updatedCount += count;
3816
+ }
3592
3817
  console.log(
3593
- chalk6.green(` \u2705 ${tr(lang, "cli", "update.filesUpdated", { count })}`)
3818
+ chalk6.green(
3819
+ ` \u2705 ${agentsMode === "skills" ? tr(lang, "cli", "update.skillsUpdated") : tr(lang, "cli", "update.agentsUpdated")}`
3820
+ )
3594
3821
  );
3595
3822
  }
3596
3823
  }
3824
+ if (updateTemplates) {
3825
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
3826
+ console.log(chalk6.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
3827
+ }
3828
+ const pruned = await pruneEngineManagedDocs(docsDir);
3829
+ if (pruned.length > 0) {
3830
+ console.log(
3831
+ chalk6.gray(
3832
+ ` - ${tr(lang, "cli", "update.engineManagedPruned", {
3833
+ count: pruned.length
3834
+ })}`
3835
+ )
3836
+ );
3837
+ }
3597
3838
  console.log();
3598
3839
  console.log(
3599
3840
  chalk6.green(`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`)
@@ -3602,14 +3843,15 @@ async function runUpdate(options) {
3602
3843
  { owner: "update" }
3603
3844
  );
3604
3845
  }
3605
- async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG) {
3606
- const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
3846
+ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
3847
+ const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
3848
+ const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
3607
3849
  await fs2.ensureDir(targetDir);
3608
3850
  const files = await fs2.readdir(sourceDir);
3609
3851
  let updatedCount = 0;
3610
3852
  for (const file of files) {
3611
- const sourcePath = path13.join(sourceDir, file);
3612
- const targetPath = path13.join(targetDir, file);
3853
+ const sourcePath = path6.join(sourceDir, file);
3854
+ const targetPath = path6.join(targetDir, file);
3613
3855
  const stat = await fs2.stat(sourcePath);
3614
3856
  if (stat.isFile()) {
3615
3857
  if (protectedFiles.has(file)) {
@@ -3642,12 +3884,16 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
3642
3884
  updatedCount++;
3643
3885
  }
3644
3886
  } else if (stat.isDirectory()) {
3887
+ if (skipDirectories.has(file)) {
3888
+ continue;
3889
+ }
3645
3890
  const subCount = await updateFolder(
3646
3891
  sourcePath,
3647
3892
  targetPath,
3648
3893
  force,
3649
3894
  replacements,
3650
- lang
3895
+ lang,
3896
+ options
3651
3897
  );
3652
3898
  updatedCount += subCount;
3653
3899
  }
@@ -3688,7 +3934,7 @@ function extractPorcelainPaths(line) {
3688
3934
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
3689
3935
  const top = getGitTopLevel2(docsDir);
3690
3936
  if (!top) return null;
3691
- const rel = path13.relative(top, docsDir) || ".";
3937
+ const rel = path6.relative(top, docsDir) || ".";
3692
3938
  try {
3693
3939
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
3694
3940
  cwd: top,
@@ -3700,7 +3946,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
3700
3946
  }
3701
3947
  const ignoredRelPaths = new Set(
3702
3948
  ignoredAbsPaths.map(
3703
- (absPath) => normalizeGitPath2(path13.relative(top, absPath) || ".")
3949
+ (absPath) => normalizeGitPath2(path6.relative(top, absPath) || ".")
3704
3950
  )
3705
3951
  );
3706
3952
  const filtered = output.split("\n").filter((line) => {
@@ -3757,7 +4003,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
3757
4003
  }
3758
4004
  async function runConfig(options) {
3759
4005
  const cwd = process.cwd();
3760
- const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
4006
+ const targetCwd = options.dir ? path6.resolve(cwd, options.dir) : cwd;
3761
4007
  const config = await getConfig(targetCwd);
3762
4008
  if (!config) {
3763
4009
  throw createCliError(
@@ -3765,7 +4011,7 @@ async function runConfig(options) {
3765
4011
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
3766
4012
  );
3767
4013
  }
3768
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
4014
+ const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
3769
4015
  if (!options.projectRoot) {
3770
4016
  console.log();
3771
4017
  console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -3869,6 +4115,22 @@ async function runConfig(options) {
3869
4115
  { owner: "config" }
3870
4116
  );
3871
4117
  }
4118
+ var REMOTE_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
4119
+ "issue_create",
4120
+ "pr_create",
4121
+ "pr_status_update",
4122
+ "code_review"
4123
+ ]);
4124
+ var LOCAL_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
4125
+ "docs_commit",
4126
+ "branch_create",
4127
+ "task_execute"
4128
+ ]);
4129
+ var REMOTE_COMMAND_PATTERN = /\b(?:git\s+push|git\s+merge|gh\s+(?:issue|pr)\b)/i;
4130
+ function resolveComponentOption(options) {
4131
+ const component = (options.component || options.repo || "").trim().toLowerCase();
4132
+ return component || void 0;
4133
+ }
3872
4134
  function getActionLabel(index) {
3873
4135
  let n = index + 1;
3874
4136
  let label = "";
@@ -3879,11 +4141,35 @@ function getActionLabel(index) {
3879
4141
  }
3880
4142
  return label;
3881
4143
  }
4144
+ function resolveActionOperationType(action) {
4145
+ if (action.operationType) return action.operationType;
4146
+ if (action.type === "command") {
4147
+ if (REMOTE_COMMAND_PATTERN.test(action.cmd)) return "remote";
4148
+ return "local";
4149
+ }
4150
+ if (action.category && REMOTE_ACTION_CATEGORIES.has(action.category)) {
4151
+ return "remote";
4152
+ }
4153
+ if (action.category && LOCAL_ACTION_CATEGORIES.has(action.category)) {
4154
+ return "local";
4155
+ }
4156
+ return "manual";
4157
+ }
4158
+ function annotateActionOperationType(action) {
4159
+ return {
4160
+ ...action,
4161
+ operationType: resolveActionOperationType(action)
4162
+ };
4163
+ }
4164
+ function annotateActions(actions) {
4165
+ return actions.map((action) => annotateActionOperationType(action));
4166
+ }
3882
4167
  function getActionSummary(action) {
3883
4168
  if (action.category === "docs_commit") return "Commit docs updates";
3884
4169
  if (action.category === "issue_create") return "Create and record issue";
3885
4170
  if (action.category === "branch_create") return "Create feature branch";
3886
4171
  if (action.category === "pr_create") return "Create PR and record link";
4172
+ if (action.category === "pre_pr_review") return "Run pre-PR self review";
3887
4173
  if (action.category === "pr_status_update") return "Update PR status";
3888
4174
  if (action.category === "code_review") return "Process code review feedback";
3889
4175
  if (action.category === "task_execute") return "Proceed with task execution";
@@ -3898,6 +4184,12 @@ function getActionSummary(action) {
3898
4184
  }
3899
4185
  return action.message;
3900
4186
  }
4187
+ function formatActionSummary(action) {
4188
+ if (action.type === "command") {
4189
+ return `(${action.scope}) ${action.cmd}`;
4190
+ }
4191
+ return action.message;
4192
+ }
3901
4193
  function toActionOptions(actions) {
3902
4194
  return actions.map((action, index) => {
3903
4195
  const label = getActionLabel(index);
@@ -3922,6 +4214,7 @@ function buildActionSnapshot(actionOptions) {
3922
4214
  cwd: action.cwd,
3923
4215
  cmd: action.cmd,
3924
4216
  category: action.category,
4217
+ operationType: action.operationType,
3925
4218
  requiresUserCheck: !!action.requiresUserCheck
3926
4219
  };
3927
4220
  }
@@ -3930,6 +4223,7 @@ function buildActionSnapshot(actionOptions) {
3930
4223
  type: action.type,
3931
4224
  message: action.message,
3932
4225
  category: action.category,
4226
+ operationType: action.operationType,
3933
4227
  requiresUserCheck: !!action.requiresUserCheck
3934
4228
  };
3935
4229
  });
@@ -3944,20 +4238,21 @@ function getContextVersion(feature, actionOptions) {
3944
4238
  });
3945
4239
  return createHash("sha256").update(payload).digest("hex").slice(0, 12);
3946
4240
  }
3947
- function parseApprovalLabel(input) {
3948
- const match = input.trim().match(/^([A-Z]+)(?:\s+OK)?$/i);
3949
- if (!match) return null;
3950
- return match[1].toUpperCase();
3951
- }
3952
- function listLabels(actionOptions) {
3953
- if (actionOptions.length === 0) return "-";
3954
- return actionOptions.map((o) => o.label).join(", ");
4241
+ function matchesFeatureSelector(f, selector) {
4242
+ const s = selector.trim();
4243
+ if (!s) return false;
4244
+ if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
4245
+ if (f.slug.toLowerCase() === s.toLowerCase()) return true;
4246
+ if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
4247
+ return false;
3955
4248
  }
3956
- function formatActionSummary(action) {
3957
- if (action.type === "command") {
3958
- return `(${action.scope}) ${action.cmd}`;
3959
- }
3960
- return action.message;
4249
+ function detectFromBranch(branchName, features) {
4250
+ const match = branchName.match(/^feat\/\d+-(.+)$/);
4251
+ if (!match) return [];
4252
+ const detected = match[1];
4253
+ return features.filter(
4254
+ (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
4255
+ );
3961
4256
  }
3962
4257
  function toSelectionStatus(features, selectionMode, openFeatures, targetFeatures) {
3963
4258
  const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
@@ -3974,15 +4269,9 @@ function toReasonCode(status) {
3974
4269
  if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
3975
4270
  return "NO_MATCHED_FEATURES";
3976
4271
  }
3977
- async function resolveContextState(config, featureName, options) {
3978
- if (!config) {
3979
- throw createCliError(
3980
- "CONFIG_NOT_FOUND",
3981
- tr(DEFAULT_LANG, "cli", "common.configNotFound")
3982
- );
3983
- }
4272
+ async function resolveContextSelection(config, featureName, options) {
3984
4273
  const { features, branches, warnings } = await scanFeatures(config);
3985
- const selectedComponent = (options.component || options.repo || "").trim().toLowerCase();
4274
+ const selectedComponent = resolveComponentOption(options);
3986
4275
  const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
3987
4276
  const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
3988
4277
  const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
@@ -3994,6 +4283,7 @@ async function resolveContextState(config, featureName, options) {
3994
4283
  );
3995
4284
  let targetFeatures = [];
3996
4285
  let selectionMode = "explicit";
4286
+ let selectionFallback = "none";
3997
4287
  if (featureName) {
3998
4288
  targetFeatures = scopedFeatures.filter(
3999
4289
  (f) => matchesFeatureSelector(f, featureName)
@@ -4026,15 +4316,19 @@ async function resolveContextState(config, featureName, options) {
4026
4316
  }
4027
4317
  if (targetFeatures.length > 0) {
4028
4318
  selectionMode = "branch";
4319
+ selectionFallback = "none";
4029
4320
  } else if (options.all) {
4030
4321
  targetFeatures = scopedFeatures;
4031
4322
  selectionMode = "all";
4323
+ selectionFallback = "all_features";
4032
4324
  } else if (options.done) {
4033
4325
  targetFeatures = doneFeatures;
4034
4326
  selectionMode = "done";
4327
+ selectionFallback = "done_features";
4035
4328
  } else {
4036
4329
  targetFeatures = openFeatures;
4037
4330
  selectionMode = "open";
4331
+ selectionFallback = "open_features";
4038
4332
  }
4039
4333
  }
4040
4334
  const status = toSelectionStatus(
@@ -4044,7 +4338,7 @@ async function resolveContextState(config, featureName, options) {
4044
4338
  targetFeatures
4045
4339
  );
4046
4340
  const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
4047
- const actions = matchedFeature?.actions ?? [];
4341
+ const actions = annotateActions(matchedFeature?.actions ?? []);
4048
4342
  const actionOptions = toActionOptions(actions);
4049
4343
  const contextVersion = getContextVersion(matchedFeature, actionOptions);
4050
4344
  return {
@@ -4056,6 +4350,7 @@ async function resolveContextState(config, featureName, options) {
4056
4350
  inProgressFeatures,
4057
4351
  readyToCloseFeatures,
4058
4352
  selectionMode,
4353
+ selectionFallback,
4059
4354
  targetFeatures,
4060
4355
  status,
4061
4356
  matchedFeature,
@@ -4064,6 +4359,32 @@ async function resolveContextState(config, featureName, options) {
4064
4359
  contextVersion
4065
4360
  };
4066
4361
  }
4362
+
4363
+ // src/commands/context.ts
4364
+ async function resolveContextState(config, featureName, options) {
4365
+ if (!config) {
4366
+ throw createCliError(
4367
+ "CONFIG_NOT_FOUND",
4368
+ tr(DEFAULT_LANG, "cli", "common.configNotFound")
4369
+ );
4370
+ }
4371
+ return resolveContextSelection(config, featureName, options);
4372
+ }
4373
+ function parseApprovalLabel(input) {
4374
+ const match = input.trim().match(/^([A-Z]+)(?:\s+OK)?$/i);
4375
+ if (!match) return null;
4376
+ return match[1].toUpperCase();
4377
+ }
4378
+ function listLabels(actionOptions) {
4379
+ if (actionOptions.length === 0) return "-";
4380
+ return actionOptions.map((o) => o.label).join(", ");
4381
+ }
4382
+ function formatActionSummary2(action) {
4383
+ if (action.type === "command") {
4384
+ return `(${action.scope}) ${action.cmd}`;
4385
+ }
4386
+ return action.message;
4387
+ }
4067
4388
  function executeCommandAction(cmd, jsonMode, cwd) {
4068
4389
  const shellPath = process.env.SHELL || (process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh");
4069
4390
  if (jsonMode) {
@@ -4086,7 +4407,7 @@ function getCommandExecutionLockPath(action, config) {
4086
4407
  if (action.scope === "docs") {
4087
4408
  return getDocsLockPath(config.docsDir);
4088
4409
  }
4089
- return path13.join(action.cwd, ".lee-spec-kit.project.lock");
4410
+ return path6.join(action.cwd, ".lee-spec-kit.project.lock");
4090
4411
  }
4091
4412
  function contextCommand(program2) {
4092
4413
  program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Component name for multi projects").option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option("--approve <reply>", "Approve one labeled option: A or A OK").option("--execute", "Execute approved option when it is a command").option(
@@ -4122,23 +4443,7 @@ function contextCommand(program2) {
4122
4443
  }
4123
4444
  );
4124
4445
  }
4125
- function matchesFeatureSelector(f, selector) {
4126
- const s = selector.trim();
4127
- if (!s) return false;
4128
- if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
4129
- if (f.slug.toLowerCase() === s.toLowerCase()) return true;
4130
- if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
4131
- return false;
4132
- }
4133
- function detectFromBranch(branchName, features) {
4134
- const match = branchName.match(/^feat\/\d+-(.+)$/);
4135
- if (!match) return [];
4136
- const detected = match[1];
4137
- return features.filter(
4138
- (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
4139
- );
4140
- }
4141
- function getListLabel(f, stepsMap, lang, workflowPolicy) {
4446
+ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
4142
4447
  if (f.completion.implementationDone && !f.completion.workflowDone) {
4143
4448
  if (f.git.docsHasUncommittedChanges) {
4144
4449
  return tr(lang, "cli", "context.list.docsCommitNeeded");
@@ -4152,6 +4457,12 @@ function getListLabel(f, stepsMap, lang, workflowPolicy) {
4152
4457
  if (workflowPolicy.requirePr && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
4153
4458
  return tr(lang, "cli", "context.list.addPrMetadata");
4154
4459
  }
4460
+ if (prePrReviewPolicy.enabled && !f.docs.prePrReviewFieldExists) {
4461
+ return tr(lang, "cli", "context.list.addPrePrReviewField");
4462
+ }
4463
+ if (prePrReviewPolicy.enabled && f.prePrReview.status !== "Done") {
4464
+ return tr(lang, "cli", "context.list.completePrePrReview");
4465
+ }
4155
4466
  if (workflowPolicy.requirePr && !f.pr.link) {
4156
4467
  return tr(lang, "cli", "context.list.recordPrLink");
4157
4468
  }
@@ -4186,6 +4497,7 @@ async function runContext(featureName, options) {
4186
4497
  const config = await getConfig(cwd);
4187
4498
  const lang = config?.lang ?? "en";
4188
4499
  const workflowPolicy = resolveWorkflowPolicy(config?.workflow);
4500
+ const prePrReviewPolicy = resolvePrePrReviewPolicy(config?.workflow);
4189
4501
  if (!config) {
4190
4502
  throw createCliError(
4191
4503
  "CONFIG_NOT_FOUND",
@@ -4231,10 +4543,12 @@ async function runContext(featureName, options) {
4231
4543
  return;
4232
4544
  }
4233
4545
  if (options.json) {
4546
+ const primaryAction = state.actionOptions[0] ?? null;
4234
4547
  const result = {
4235
4548
  status: state.status,
4236
4549
  reasonCode: toReasonCode(state.status),
4237
4550
  selectionMode: state.selectionMode,
4551
+ selectionFallback: state.selectionFallback,
4238
4552
  branches: state.branches,
4239
4553
  warnings: state.warnings,
4240
4554
  matchedFeature: state.matchedFeature,
@@ -4246,10 +4560,16 @@ async function runContext(featureName, options) {
4246
4560
  readyToCloseCandidates: state.selectionMode === "open" ? state.readyToCloseFeatures : [],
4247
4561
  actions: state.actions,
4248
4562
  actionOptions: state.actionOptions,
4563
+ primaryActionLabel: primaryAction?.label ?? null,
4564
+ primaryActionType: primaryAction?.action.type ?? null,
4565
+ primaryActionCategory: primaryAction?.action.category ?? null,
4566
+ primaryActionOperationType: primaryAction?.action.operationType ?? null,
4249
4567
  workflowPolicy,
4568
+ prePrReviewPolicy,
4250
4569
  checkPolicy: {
4251
- docPath: "/docs/agents/agents.md",
4570
+ docPath: "builtin://agents/policy",
4252
4571
  hint: tr(lang, "cli", "context.checkPolicyHint"),
4572
+ policyOnly: true,
4253
4573
  token: "<LABEL>",
4254
4574
  acceptedTokens: ["<LABEL>", "<LABEL> OK"],
4255
4575
  tokenPattern: "^([A-Z]+)(?:\\s+OK)?$",
@@ -4268,7 +4588,8 @@ async function runContext(featureName, options) {
4268
4588
  label: o.label,
4269
4589
  summary: o.summary,
4270
4590
  approvalPrompt: o.approvalPrompt,
4271
- requiresUserCheck: !!o.action.requiresUserCheck
4591
+ requiresUserCheck: !!o.action.requiresUserCheck,
4592
+ operationType: o.action.operationType
4272
4593
  }))
4273
4594
  },
4274
4595
  prPolicy: {
@@ -4378,7 +4699,13 @@ async function runContext(featureName, options) {
4378
4699
  )
4379
4700
  );
4380
4701
  state.inProgressFeatures.forEach((f2) => {
4381
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4702
+ const stepName2 = getListLabel(
4703
+ f2,
4704
+ stepsMap,
4705
+ lang,
4706
+ workflowPolicy,
4707
+ prePrReviewPolicy
4708
+ );
4382
4709
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4383
4710
  console.log(
4384
4711
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4391,7 +4718,13 @@ async function runContext(featureName, options) {
4391
4718
  )
4392
4719
  );
4393
4720
  state.readyToCloseFeatures.forEach((f2) => {
4394
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4721
+ const stepName2 = getListLabel(
4722
+ f2,
4723
+ stepsMap,
4724
+ lang,
4725
+ workflowPolicy,
4726
+ prePrReviewPolicy
4727
+ );
4395
4728
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4396
4729
  console.log(
4397
4730
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4402,7 +4735,13 @@ async function runContext(featureName, options) {
4402
4735
  console.log(chalk6.blue(title));
4403
4736
  console.log();
4404
4737
  state.targetFeatures.forEach((f2) => {
4405
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4738
+ const stepName2 = getListLabel(
4739
+ f2,
4740
+ stepsMap,
4741
+ lang,
4742
+ workflowPolicy,
4743
+ prePrReviewPolicy
4744
+ );
4406
4745
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4407
4746
  console.log(
4408
4747
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4443,7 +4782,7 @@ async function runContext(featureName, options) {
4443
4782
  if (f.issueNumber) {
4444
4783
  console.log(` \u2022 Issue: #${f.issueNumber}`);
4445
4784
  }
4446
- console.log(` \u2022 Path: ${path13.relative(cwd, f.path)}`);
4785
+ console.log(` \u2022 Path: ${path6.relative(cwd, f.path)}`);
4447
4786
  if (f.git.projectBranch) {
4448
4787
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
4449
4788
  }
@@ -4476,7 +4815,7 @@ async function runContext(featureName, options) {
4476
4815
  console.log();
4477
4816
  return;
4478
4817
  }
4479
- const actionOptions = toActionOptions(f.actions);
4818
+ const actionOptions = state.actionOptions;
4480
4819
  console.log(chalk6.green(chalk6.bold("\u{1F449} Next Options (Atomic):")));
4481
4820
  let hasDocsCommand = false;
4482
4821
  actionOptions.forEach(({ label, action }) => {
@@ -4562,7 +4901,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
4562
4901
  }
4563
4902
  console.log();
4564
4903
  console.log(chalk6.green(`\u2705 Approved option: ${parsedLabel}`));
4565
- console.log(chalk6.gray(` - Action: ${formatActionSummary(selectedAction)}`));
4904
+ console.log(chalk6.gray(` - Action: ${formatActionSummary2(selectedAction)}`));
4566
4905
  if (selectedAction.type === "command") {
4567
4906
  console.log(chalk6.gray(" - Run with: --execute"));
4568
4907
  } else {
@@ -4669,7 +5008,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
4669
5008
  ]);
4670
5009
  function formatPath(cwd, p) {
4671
5010
  if (!p) return "";
4672
- return path13.isAbsolute(p) ? path13.relative(cwd, p) : p;
5011
+ return path6.isAbsolute(p) ? path6.relative(cwd, p) : p;
4673
5012
  }
4674
5013
  function detectPlaceholders(content) {
4675
5014
  const patterns = [
@@ -4812,7 +5151,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
4812
5151
  const placeholderContext = {
4813
5152
  projectName: config.projectName,
4814
5153
  featureName: f.slug,
4815
- featurePath: f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path),
5154
+ featurePath: f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path),
4816
5155
  repoType: f.type,
4817
5156
  featureNumber
4818
5157
  };
@@ -4822,7 +5161,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
4822
5161
  "tasks.md"
4823
5162
  ];
4824
5163
  for (const file of files) {
4825
- const fullPath = path13.join(f.path, file);
5164
+ const fullPath = path6.join(f.path, file);
4826
5165
  if (!await fs2.pathExists(fullPath)) continue;
4827
5166
  const original = await fs2.readFile(fullPath, "utf-8");
4828
5167
  let next = original;
@@ -4867,7 +5206,7 @@ async function checkDocsStructure(config, cwd) {
4867
5206
  const issues = [];
4868
5207
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
4869
5208
  for (const dir of requiredDirs) {
4870
- const p = path13.join(config.docsDir, dir);
5209
+ const p = path6.join(config.docsDir, dir);
4871
5210
  if (!await fs2.pathExists(p)) {
4872
5211
  issues.push({
4873
5212
  level: "error",
@@ -4877,7 +5216,7 @@ async function checkDocsStructure(config, cwd) {
4877
5216
  });
4878
5217
  }
4879
5218
  }
4880
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
5219
+ const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
4881
5220
  if (!await fs2.pathExists(configPath)) {
4882
5221
  issues.push({
4883
5222
  level: "warn",
@@ -4900,7 +5239,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4900
5239
  }
4901
5240
  const idMap = /* @__PURE__ */ new Map();
4902
5241
  for (const f of features) {
4903
- const rel = f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path);
5242
+ const rel = f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path);
4904
5243
  const id = f.id || "UNKNOWN";
4905
5244
  if (!idMap.has(id)) idMap.set(id, []);
4906
5245
  idMap.get(id).push(rel);
@@ -4908,7 +5247,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4908
5247
  if (!isInitialTemplateState) {
4909
5248
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
4910
5249
  for (const file of featureDocs) {
4911
- const p = path13.join(f.path, file);
5250
+ const p = path6.join(f.path, file);
4912
5251
  if (!await fs2.pathExists(p)) continue;
4913
5252
  const content = await fs2.readFile(p, "utf-8");
4914
5253
  const placeholders = detectPlaceholders(content);
@@ -4923,7 +5262,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4923
5262
  });
4924
5263
  }
4925
5264
  if (decisionsPlaceholderMode !== "off") {
4926
- const decisionsPath = path13.join(f.path, "decisions.md");
5265
+ const decisionsPath = path6.join(f.path, "decisions.md");
4927
5266
  if (await fs2.pathExists(decisionsPath)) {
4928
5267
  const content = await fs2.readFile(decisionsPath, "utf-8");
4929
5268
  const placeholders = detectPlaceholders(content);
@@ -4952,7 +5291,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4952
5291
  level: "warn",
4953
5292
  code: "spec_status_unset",
4954
5293
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
4955
- path: formatPath(cwd, path13.join(f.path, "spec.md"))
5294
+ path: formatPath(cwd, path6.join(f.path, "spec.md"))
4956
5295
  });
4957
5296
  }
4958
5297
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -4960,7 +5299,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4960
5299
  level: "warn",
4961
5300
  code: "plan_status_unset",
4962
5301
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
4963
- path: formatPath(cwd, path13.join(f.path, "plan.md"))
5302
+ path: formatPath(cwd, path6.join(f.path, "plan.md"))
4964
5303
  });
4965
5304
  }
4966
5305
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -4968,7 +5307,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4968
5307
  level: "warn",
4969
5308
  code: "tasks_empty",
4970
5309
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
4971
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5310
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
4972
5311
  });
4973
5312
  }
4974
5313
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
@@ -4976,7 +5315,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4976
5315
  level: "warn",
4977
5316
  code: "tasks_doc_status_missing",
4978
5317
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
4979
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5318
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
4980
5319
  });
4981
5320
  }
4982
5321
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -4984,7 +5323,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4984
5323
  level: "warn",
4985
5324
  code: "tasks_doc_status_unset",
4986
5325
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
4987
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5326
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
4988
5327
  });
4989
5328
  }
4990
5329
  }
@@ -5008,7 +5347,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5008
5347
  level: "warn",
5009
5348
  code: "missing_feature_id",
5010
5349
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
5011
- path: formatPath(cwd, path13.join(config.docsDir, p))
5350
+ path: formatPath(cwd, path6.join(config.docsDir, p))
5012
5351
  });
5013
5352
  }
5014
5353
  return issues;
@@ -5130,7 +5469,7 @@ function doctorCommand(program2) {
5130
5469
  }
5131
5470
  console.log();
5132
5471
  console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
5133
- console.log(chalk6.gray(`- Docs: ${path13.relative(cwd, docsDir)}`));
5472
+ console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, docsDir)}`));
5134
5473
  console.log(chalk6.gray(`- Type: ${projectType}`));
5135
5474
  console.log(chalk6.gray(`- Lang: ${lang}`));
5136
5475
  console.log();
@@ -5230,208 +5569,6 @@ function doctorCommand(program2) {
5230
5569
  }
5231
5570
  });
5232
5571
  }
5233
- function resolveComponentOption(options) {
5234
- const component = (options.component || options.repo || "").trim().toLowerCase();
5235
- return component || void 0;
5236
- }
5237
- function getActionLabel2(index) {
5238
- let n = index + 1;
5239
- let label = "";
5240
- while (n > 0) {
5241
- const rem = (n - 1) % 26;
5242
- label = String.fromCharCode(65 + rem) + label;
5243
- n = Math.floor((n - 1) / 26);
5244
- }
5245
- return label;
5246
- }
5247
- function getActionSummary2(action) {
5248
- if (action.category === "docs_commit") return "Commit docs updates";
5249
- if (action.category === "issue_create") return "Create and record issue";
5250
- if (action.category === "branch_create") return "Create feature branch";
5251
- if (action.category === "pr_create") return "Create PR and record link";
5252
- if (action.category === "pr_status_update") return "Update PR status";
5253
- if (action.category === "code_review") return "Process code review feedback";
5254
- if (action.category === "task_execute") return "Proceed with task execution";
5255
- if (action.category === "feature_done") return "Feature is complete";
5256
- if (action.category === "spec_approve") return "Request spec approval";
5257
- if (action.category === "plan_approve") return "Request plan approval";
5258
- if (action.category === "tasks_approve") return "Request tasks approval";
5259
- if (action.category === "pr_metadata_migrate") return "Update tasks.md to latest PR fields";
5260
- if (action.category === "fallback") return "Re-check context and rerun";
5261
- if (action.type === "command") {
5262
- return action.scope === "docs" ? "Run docs command" : "Run project command";
5263
- }
5264
- return action.message;
5265
- }
5266
- function formatActionSummary2(action) {
5267
- if (action.type === "command") {
5268
- return `(${action.scope}) ${action.cmd}`;
5269
- }
5270
- return action.message;
5271
- }
5272
- function toActionOptions2(actions) {
5273
- return actions.map((action, index) => {
5274
- const label = getActionLabel2(index);
5275
- const summary = getActionSummary2(action);
5276
- const detail = formatActionSummary2(action);
5277
- return {
5278
- label,
5279
- summary,
5280
- detail,
5281
- approvalPrompt: `${label}: ${summary}`,
5282
- action
5283
- };
5284
- });
5285
- }
5286
- function buildActionSnapshot2(actionOptions) {
5287
- return actionOptions.map(({ label, action }) => {
5288
- if (action.type === "command") {
5289
- return {
5290
- label,
5291
- type: action.type,
5292
- scope: action.scope,
5293
- cwd: action.cwd,
5294
- cmd: action.cmd,
5295
- category: action.category,
5296
- requiresUserCheck: !!action.requiresUserCheck
5297
- };
5298
- }
5299
- return {
5300
- label,
5301
- type: action.type,
5302
- message: action.message,
5303
- category: action.category,
5304
- requiresUserCheck: !!action.requiresUserCheck
5305
- };
5306
- });
5307
- }
5308
- function getContextVersion2(feature, actionOptions) {
5309
- if (!feature) return null;
5310
- const payload = JSON.stringify({
5311
- id: feature.id || "",
5312
- folderName: feature.folderName,
5313
- currentStep: feature.currentStep,
5314
- actionSnapshot: buildActionSnapshot2(actionOptions)
5315
- });
5316
- return createHash("sha256").update(payload).digest("hex").slice(0, 12);
5317
- }
5318
- function matchesFeatureSelector2(f, selector) {
5319
- const s = selector.trim();
5320
- if (!s) return false;
5321
- if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
5322
- if (f.slug.toLowerCase() === s.toLowerCase()) return true;
5323
- if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
5324
- return false;
5325
- }
5326
- function detectFromBranch2(branchName, features) {
5327
- const match = branchName.match(/^feat\/\d+-(.+)$/);
5328
- if (!match) return [];
5329
- const detected = match[1];
5330
- return features.filter(
5331
- (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
5332
- );
5333
- }
5334
- function toSelectionStatus2(features, selectionMode, openFeatures, targetFeatures) {
5335
- const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
5336
- if (features.length === 0) return "no_features";
5337
- if (isNoOpen) return "no_open";
5338
- if (targetFeatures.length === 1) return "single_matched";
5339
- if (targetFeatures.length > 1) return "multiple_active";
5340
- return "no_match";
5341
- }
5342
- function toReasonCode2(status) {
5343
- if (status === "no_features") return "NO_FEATURES";
5344
- if (status === "no_open") return "NO_OPEN_FEATURES";
5345
- if (status === "single_matched") return "SINGLE_MATCHED";
5346
- if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
5347
- return "NO_MATCHED_FEATURES";
5348
- }
5349
- async function resolveContextSelection(config, featureName, options) {
5350
- const { features, branches, warnings } = await scanFeatures(config);
5351
- const selectedComponent = resolveComponentOption(options);
5352
- const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
5353
- const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
5354
- const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
5355
- const inProgressFeatures = openFeatures.filter(
5356
- (f) => !f.completion.implementationDone
5357
- );
5358
- const readyToCloseFeatures = openFeatures.filter(
5359
- (f) => f.completion.implementationDone
5360
- );
5361
- let targetFeatures = [];
5362
- let selectionMode = "explicit";
5363
- if (featureName) {
5364
- targetFeatures = scopedFeatures.filter(
5365
- (f) => matchesFeatureSelector2(f, featureName)
5366
- );
5367
- selectionMode = "explicit";
5368
- } else {
5369
- if (config.projectType === "single") {
5370
- const branchName = branches.project.single || "";
5371
- targetFeatures = detectFromBranch2(branchName, scopedFeatures);
5372
- } else if (selectedComponent) {
5373
- const branchName = branches.project[selectedComponent] || "";
5374
- targetFeatures = detectFromBranch2(
5375
- branchName,
5376
- scopedFeatures
5377
- );
5378
- } else {
5379
- const matches = [];
5380
- const componentKeys = [...new Set(scopedFeatures.map((f) => f.type))].filter((key) => key !== "single");
5381
- for (const component of componentKeys) {
5382
- const branchName = branches.project[component] || "";
5383
- if (!branchName) continue;
5384
- matches.push(
5385
- ...detectFromBranch2(
5386
- branchName,
5387
- scopedFeatures.filter((f) => f.type === component)
5388
- )
5389
- );
5390
- }
5391
- targetFeatures = matches;
5392
- }
5393
- if (targetFeatures.length > 0) {
5394
- selectionMode = "branch";
5395
- } else if (options.all) {
5396
- targetFeatures = scopedFeatures;
5397
- selectionMode = "all";
5398
- } else if (options.done) {
5399
- targetFeatures = doneFeatures;
5400
- selectionMode = "done";
5401
- } else {
5402
- targetFeatures = openFeatures;
5403
- selectionMode = "open";
5404
- }
5405
- }
5406
- const status = toSelectionStatus2(
5407
- scopedFeatures,
5408
- selectionMode,
5409
- openFeatures,
5410
- targetFeatures
5411
- );
5412
- const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
5413
- const actions = matchedFeature?.actions ?? [];
5414
- const actionOptions = toActionOptions2(actions);
5415
- const contextVersion = getContextVersion2(matchedFeature, actionOptions);
5416
- return {
5417
- features: scopedFeatures,
5418
- branches,
5419
- warnings,
5420
- doneFeatures,
5421
- openFeatures,
5422
- inProgressFeatures,
5423
- readyToCloseFeatures,
5424
- selectionMode,
5425
- targetFeatures,
5426
- status,
5427
- matchedFeature,
5428
- actions,
5429
- actionOptions,
5430
- contextVersion
5431
- };
5432
- }
5433
-
5434
- // src/commands/view.ts
5435
5572
  function resolveComponentOption2(options) {
5436
5573
  if (options.repo && options.component && options.repo.trim().toLowerCase() !== options.component.trim().toLowerCase()) {
5437
5574
  throw createCliError(
@@ -5489,8 +5626,9 @@ async function runView(featureName, options) {
5489
5626
  if (options.json) {
5490
5627
  const payload = {
5491
5628
  status: state.status,
5492
- reasonCode: toReasonCode2(state.status),
5629
+ reasonCode: toReasonCode(state.status),
5493
5630
  selectionMode: state.selectionMode,
5631
+ selectionFallback: state.selectionFallback,
5494
5632
  counts: {
5495
5633
  features: state.features.length,
5496
5634
  open: state.openFeatures.length,
@@ -5510,7 +5648,7 @@ async function runView(featureName, options) {
5510
5648
  }
5511
5649
  console.log();
5512
5650
  console.log(chalk6.bold("\u{1F4CA} Workflow View"));
5513
- console.log(chalk6.gray(`- Docs: ${path13.relative(cwd, config.docsDir)}`));
5651
+ console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, config.docsDir)}`));
5514
5652
  console.log(
5515
5653
  chalk6.gray(
5516
5654
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -5537,7 +5675,7 @@ async function runView(featureName, options) {
5537
5675
  }
5538
5676
  if (!state.matchedFeature) {
5539
5677
  console.log();
5540
- console.log(chalk6.blue(`Selection: ${state.status} (${toReasonCode2(state.status)})`));
5678
+ console.log(chalk6.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`));
5541
5679
  const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
5542
5680
  for (const f2 of rows) {
5543
5681
  const statusText = f2.completion.workflowDone ? chalk6.green("WORKFLOW_DONE") : f2.completion.implementationDone ? chalk6.cyan("DONE") : chalk6.yellow("IN_PROGRESS");
@@ -5725,16 +5863,18 @@ async function runFlow(featureName, options) {
5725
5863
  context: {
5726
5864
  before: {
5727
5865
  status: before.status,
5728
- reasonCode: toReasonCode2(before.status),
5866
+ reasonCode: toReasonCode(before.status),
5729
5867
  selectionMode: before.selectionMode,
5868
+ selectionFallback: before.selectionFallback,
5730
5869
  matchedFeature: before.matchedFeature,
5731
5870
  actionOptions: before.actionOptions,
5732
5871
  contextVersion: before.contextVersion
5733
5872
  },
5734
5873
  after: {
5735
5874
  status: after.status,
5736
- reasonCode: toReasonCode2(after.status),
5875
+ reasonCode: toReasonCode(after.status),
5737
5876
  selectionMode: after.selectionMode,
5877
+ selectionFallback: after.selectionFallback,
5738
5878
  matchedFeature: after.matchedFeature,
5739
5879
  actionOptions: after.actionOptions,
5740
5880
  contextVersion: after.contextVersion
@@ -5753,7 +5893,7 @@ async function runFlow(featureName, options) {
5753
5893
  console.log(chalk6.bold("\u{1F501} Flow Summary"));
5754
5894
  console.log(
5755
5895
  chalk6.gray(
5756
- `- Before: ${before.status} (${toReasonCode2(before.status)}) / After: ${after.status} (${toReasonCode2(after.status)})`
5896
+ `- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
5757
5897
  )
5758
5898
  );
5759
5899
  if (approvalResult && typeof approvalResult === "object") {
@@ -5865,7 +6005,7 @@ function ensureSections(body, sections, kind) {
5865
6005
  }
5866
6006
  function ensureDocsExist(docsDir, relativePaths) {
5867
6007
  const missing = relativePaths.filter(
5868
- (relativePath) => !fs2.existsSync(path13.join(docsDir, relativePath))
6008
+ (relativePath) => !fs2.existsSync(path6.join(docsDir, relativePath))
5869
6009
  );
5870
6010
  if (missing.length > 0) {
5871
6011
  throw createCliError(
@@ -5875,8 +6015,8 @@ function ensureDocsExist(docsDir, relativePaths) {
5875
6015
  }
5876
6016
  }
5877
6017
  function toBodyFilePath(raw, fallbackName) {
5878
- const selected = raw?.trim() || path13.join(os.tmpdir(), fallbackName);
5879
- return path13.resolve(selected);
6018
+ const selected = raw?.trim() || path6.join(os.tmpdir(), fallbackName);
6019
+ return path6.resolve(selected);
5880
6020
  }
5881
6021
  async function resolveFeatureOrThrow(featureName, options) {
5882
6022
  const config = await getConfig(process.cwd());
@@ -6049,7 +6189,7 @@ function ensureCleanWorktree(cwd) {
6049
6189
  }
6050
6190
  }
6051
6191
  function commitAndPushPath(cwd, absPath, message) {
6052
- const relativePath = path13.relative(cwd, absPath) || absPath;
6192
+ const relativePath = path6.relative(cwd, absPath) || absPath;
6053
6193
  const status = runProcessOrThrow(
6054
6194
  "git",
6055
6195
  ["status", "--porcelain=v1", "--", relativePath],
@@ -6177,7 +6317,7 @@ function githubCommand(program2) {
6177
6317
  options.bodyFile,
6178
6318
  `lee-spec-kit.issue.${feature.folderName}.md`
6179
6319
  );
6180
- await fs2.ensureDir(path13.dirname(bodyFile));
6320
+ await fs2.ensureDir(path6.dirname(bodyFile));
6181
6321
  await fs2.writeFile(bodyFile, body, "utf-8");
6182
6322
  let issueUrl;
6183
6323
  if (options.create) {
@@ -6275,7 +6415,7 @@ function githubCommand(program2) {
6275
6415
  options.bodyFile,
6276
6416
  `lee-spec-kit.pr.${feature.folderName}.md`
6277
6417
  );
6278
- await fs2.ensureDir(path13.dirname(bodyFile));
6418
+ await fs2.ensureDir(path6.dirname(bodyFile));
6279
6419
  await fs2.writeFile(bodyFile, body, "utf-8");
6280
6420
  const retryCount = toRetryCount(options.retry);
6281
6421
  let prUrl = options.pr?.trim() || "";
@@ -6313,7 +6453,7 @@ function githubCommand(program2) {
6313
6453
  }
6314
6454
  if (prUrl && options.syncTasks !== false) {
6315
6455
  const synced = syncTasksPrMetadata(
6316
- path13.join(config.docsDir, paths.tasksPath),
6456
+ path6.join(config.docsDir, paths.tasksPath),
6317
6457
  prUrl,
6318
6458
  "Review"
6319
6459
  );
@@ -6452,11 +6592,11 @@ ${version}
6452
6592
  }
6453
6593
  return `${ascii}${footer}`;
6454
6594
  }
6455
- var CACHE_FILE = path13.join(os.homedir(), ".lee-spec-kit-version-cache.json");
6595
+ var CACHE_FILE = path6.join(os.homedir(), ".lee-spec-kit-version-cache.json");
6456
6596
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
6457
6597
  function getCurrentVersion() {
6458
6598
  try {
6459
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
6599
+ const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6460
6600
  if (fs2.existsSync(packageJsonPath)) {
6461
6601
  const pkg = fs2.readJsonSync(packageJsonPath);
6462
6602
  return pkg.version;
@@ -6552,7 +6692,7 @@ function shouldCheckForUpdates() {
6552
6692
  if (shouldCheckForUpdates()) checkForUpdates();
6553
6693
  function getCliVersion() {
6554
6694
  try {
6555
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
6695
+ const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6556
6696
  if (fs2.existsSync(packageJsonPath)) {
6557
6697
  const pkg = fs2.readJsonSync(packageJsonPath);
6558
6698
  if (pkg?.version) return String(pkg.version);