lee-spec-kit 0.5.2 → 0.6.1

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 \uC815\uCC45 \uC548\uB0B4(\uD604\uC7AC Next Action \uC544\uB2D8): /docs/agents/agents.md \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)",
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 notice (not the current next action): see /docs/agents/agents.md (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)",
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")));
@@ -3923,6 +4169,7 @@ function getActionSummary(action) {
3923
4169
  if (action.category === "issue_create") return "Create and record issue";
3924
4170
  if (action.category === "branch_create") return "Create feature branch";
3925
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";
3926
4173
  if (action.category === "pr_status_update") return "Update PR status";
3927
4174
  if (action.category === "code_review") return "Process code review feedback";
3928
4175
  if (action.category === "task_execute") return "Proceed with task execution";
@@ -4160,7 +4407,7 @@ function getCommandExecutionLockPath(action, config) {
4160
4407
  if (action.scope === "docs") {
4161
4408
  return getDocsLockPath(config.docsDir);
4162
4409
  }
4163
- return path13.join(action.cwd, ".lee-spec-kit.project.lock");
4410
+ return path6.join(action.cwd, ".lee-spec-kit.project.lock");
4164
4411
  }
4165
4412
  function contextCommand(program2) {
4166
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(
@@ -4196,7 +4443,7 @@ function contextCommand(program2) {
4196
4443
  }
4197
4444
  );
4198
4445
  }
4199
- function getListLabel(f, stepsMap, lang, workflowPolicy) {
4446
+ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
4200
4447
  if (f.completion.implementationDone && !f.completion.workflowDone) {
4201
4448
  if (f.git.docsHasUncommittedChanges) {
4202
4449
  return tr(lang, "cli", "context.list.docsCommitNeeded");
@@ -4210,6 +4457,12 @@ function getListLabel(f, stepsMap, lang, workflowPolicy) {
4210
4457
  if (workflowPolicy.requirePr && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
4211
4458
  return tr(lang, "cli", "context.list.addPrMetadata");
4212
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
+ }
4213
4466
  if (workflowPolicy.requirePr && !f.pr.link) {
4214
4467
  return tr(lang, "cli", "context.list.recordPrLink");
4215
4468
  }
@@ -4244,6 +4497,7 @@ async function runContext(featureName, options) {
4244
4497
  const config = await getConfig(cwd);
4245
4498
  const lang = config?.lang ?? "en";
4246
4499
  const workflowPolicy = resolveWorkflowPolicy(config?.workflow);
4500
+ const prePrReviewPolicy = resolvePrePrReviewPolicy(config?.workflow);
4247
4501
  if (!config) {
4248
4502
  throw createCliError(
4249
4503
  "CONFIG_NOT_FOUND",
@@ -4311,8 +4565,9 @@ async function runContext(featureName, options) {
4311
4565
  primaryActionCategory: primaryAction?.action.category ?? null,
4312
4566
  primaryActionOperationType: primaryAction?.action.operationType ?? null,
4313
4567
  workflowPolicy,
4568
+ prePrReviewPolicy,
4314
4569
  checkPolicy: {
4315
- docPath: "/docs/agents/agents.md",
4570
+ docPath: "builtin://agents/policy",
4316
4571
  hint: tr(lang, "cli", "context.checkPolicyHint"),
4317
4572
  policyOnly: true,
4318
4573
  token: "<LABEL>",
@@ -4444,7 +4699,13 @@ async function runContext(featureName, options) {
4444
4699
  )
4445
4700
  );
4446
4701
  state.inProgressFeatures.forEach((f2) => {
4447
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4702
+ const stepName2 = getListLabel(
4703
+ f2,
4704
+ stepsMap,
4705
+ lang,
4706
+ workflowPolicy,
4707
+ prePrReviewPolicy
4708
+ );
4448
4709
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4449
4710
  console.log(
4450
4711
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4457,7 +4718,13 @@ async function runContext(featureName, options) {
4457
4718
  )
4458
4719
  );
4459
4720
  state.readyToCloseFeatures.forEach((f2) => {
4460
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4721
+ const stepName2 = getListLabel(
4722
+ f2,
4723
+ stepsMap,
4724
+ lang,
4725
+ workflowPolicy,
4726
+ prePrReviewPolicy
4727
+ );
4461
4728
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4462
4729
  console.log(
4463
4730
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4468,7 +4735,13 @@ async function runContext(featureName, options) {
4468
4735
  console.log(chalk6.blue(title));
4469
4736
  console.log();
4470
4737
  state.targetFeatures.forEach((f2) => {
4471
- const stepName2 = getListLabel(f2, stepsMap, lang, workflowPolicy);
4738
+ const stepName2 = getListLabel(
4739
+ f2,
4740
+ stepsMap,
4741
+ lang,
4742
+ workflowPolicy,
4743
+ prePrReviewPolicy
4744
+ );
4472
4745
  const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
4473
4746
  console.log(
4474
4747
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -4509,7 +4782,7 @@ async function runContext(featureName, options) {
4509
4782
  if (f.issueNumber) {
4510
4783
  console.log(` \u2022 Issue: #${f.issueNumber}`);
4511
4784
  }
4512
- console.log(` \u2022 Path: ${path13.relative(cwd, f.path)}`);
4785
+ console.log(` \u2022 Path: ${path6.relative(cwd, f.path)}`);
4513
4786
  if (f.git.projectBranch) {
4514
4787
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
4515
4788
  }
@@ -4735,7 +5008,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
4735
5008
  ]);
4736
5009
  function formatPath(cwd, p) {
4737
5010
  if (!p) return "";
4738
- return path13.isAbsolute(p) ? path13.relative(cwd, p) : p;
5011
+ return path6.isAbsolute(p) ? path6.relative(cwd, p) : p;
4739
5012
  }
4740
5013
  function detectPlaceholders(content) {
4741
5014
  const patterns = [
@@ -4878,7 +5151,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
4878
5151
  const placeholderContext = {
4879
5152
  projectName: config.projectName,
4880
5153
  featureName: f.slug,
4881
- featurePath: f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path),
5154
+ featurePath: f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path),
4882
5155
  repoType: f.type,
4883
5156
  featureNumber
4884
5157
  };
@@ -4888,7 +5161,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
4888
5161
  "tasks.md"
4889
5162
  ];
4890
5163
  for (const file of files) {
4891
- const fullPath = path13.join(f.path, file);
5164
+ const fullPath = path6.join(f.path, file);
4892
5165
  if (!await fs2.pathExists(fullPath)) continue;
4893
5166
  const original = await fs2.readFile(fullPath, "utf-8");
4894
5167
  let next = original;
@@ -4933,7 +5206,7 @@ async function checkDocsStructure(config, cwd) {
4933
5206
  const issues = [];
4934
5207
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
4935
5208
  for (const dir of requiredDirs) {
4936
- const p = path13.join(config.docsDir, dir);
5209
+ const p = path6.join(config.docsDir, dir);
4937
5210
  if (!await fs2.pathExists(p)) {
4938
5211
  issues.push({
4939
5212
  level: "error",
@@ -4943,7 +5216,7 @@ async function checkDocsStructure(config, cwd) {
4943
5216
  });
4944
5217
  }
4945
5218
  }
4946
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
5219
+ const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
4947
5220
  if (!await fs2.pathExists(configPath)) {
4948
5221
  issues.push({
4949
5222
  level: "warn",
@@ -4966,7 +5239,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4966
5239
  }
4967
5240
  const idMap = /* @__PURE__ */ new Map();
4968
5241
  for (const f of features) {
4969
- const rel = f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path);
5242
+ const rel = f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path);
4970
5243
  const id = f.id || "UNKNOWN";
4971
5244
  if (!idMap.has(id)) idMap.set(id, []);
4972
5245
  idMap.get(id).push(rel);
@@ -4974,7 +5247,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4974
5247
  if (!isInitialTemplateState) {
4975
5248
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
4976
5249
  for (const file of featureDocs) {
4977
- const p = path13.join(f.path, file);
5250
+ const p = path6.join(f.path, file);
4978
5251
  if (!await fs2.pathExists(p)) continue;
4979
5252
  const content = await fs2.readFile(p, "utf-8");
4980
5253
  const placeholders = detectPlaceholders(content);
@@ -4989,7 +5262,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
4989
5262
  });
4990
5263
  }
4991
5264
  if (decisionsPlaceholderMode !== "off") {
4992
- const decisionsPath = path13.join(f.path, "decisions.md");
5265
+ const decisionsPath = path6.join(f.path, "decisions.md");
4993
5266
  if (await fs2.pathExists(decisionsPath)) {
4994
5267
  const content = await fs2.readFile(decisionsPath, "utf-8");
4995
5268
  const placeholders = detectPlaceholders(content);
@@ -5018,7 +5291,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5018
5291
  level: "warn",
5019
5292
  code: "spec_status_unset",
5020
5293
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
5021
- path: formatPath(cwd, path13.join(f.path, "spec.md"))
5294
+ path: formatPath(cwd, path6.join(f.path, "spec.md"))
5022
5295
  });
5023
5296
  }
5024
5297
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -5026,7 +5299,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5026
5299
  level: "warn",
5027
5300
  code: "plan_status_unset",
5028
5301
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
5029
- path: formatPath(cwd, path13.join(f.path, "plan.md"))
5302
+ path: formatPath(cwd, path6.join(f.path, "plan.md"))
5030
5303
  });
5031
5304
  }
5032
5305
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -5034,7 +5307,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5034
5307
  level: "warn",
5035
5308
  code: "tasks_empty",
5036
5309
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
5037
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5310
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
5038
5311
  });
5039
5312
  }
5040
5313
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
@@ -5042,7 +5315,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5042
5315
  level: "warn",
5043
5316
  code: "tasks_doc_status_missing",
5044
5317
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
5045
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5318
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
5046
5319
  });
5047
5320
  }
5048
5321
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -5050,7 +5323,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5050
5323
  level: "warn",
5051
5324
  code: "tasks_doc_status_unset",
5052
5325
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
5053
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
5326
+ path: formatPath(cwd, path6.join(f.path, "tasks.md"))
5054
5327
  });
5055
5328
  }
5056
5329
  }
@@ -5074,7 +5347,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5074
5347
  level: "warn",
5075
5348
  code: "missing_feature_id",
5076
5349
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
5077
- path: formatPath(cwd, path13.join(config.docsDir, p))
5350
+ path: formatPath(cwd, path6.join(config.docsDir, p))
5078
5351
  });
5079
5352
  }
5080
5353
  return issues;
@@ -5196,7 +5469,7 @@ function doctorCommand(program2) {
5196
5469
  }
5197
5470
  console.log();
5198
5471
  console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
5199
- console.log(chalk6.gray(`- Docs: ${path13.relative(cwd, docsDir)}`));
5472
+ console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, docsDir)}`));
5200
5473
  console.log(chalk6.gray(`- Type: ${projectType}`));
5201
5474
  console.log(chalk6.gray(`- Lang: ${lang}`));
5202
5475
  console.log();
@@ -5375,7 +5648,7 @@ async function runView(featureName, options) {
5375
5648
  }
5376
5649
  console.log();
5377
5650
  console.log(chalk6.bold("\u{1F4CA} Workflow View"));
5378
- console.log(chalk6.gray(`- Docs: ${path13.relative(cwd, config.docsDir)}`));
5651
+ console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, config.docsDir)}`));
5379
5652
  console.log(
5380
5653
  chalk6.gray(
5381
5654
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -5732,7 +6005,7 @@ function ensureSections(body, sections, kind) {
5732
6005
  }
5733
6006
  function ensureDocsExist(docsDir, relativePaths) {
5734
6007
  const missing = relativePaths.filter(
5735
- (relativePath) => !fs2.existsSync(path13.join(docsDir, relativePath))
6008
+ (relativePath) => !fs2.existsSync(path6.join(docsDir, relativePath))
5736
6009
  );
5737
6010
  if (missing.length > 0) {
5738
6011
  throw createCliError(
@@ -5742,8 +6015,8 @@ function ensureDocsExist(docsDir, relativePaths) {
5742
6015
  }
5743
6016
  }
5744
6017
  function toBodyFilePath(raw, fallbackName) {
5745
- const selected = raw?.trim() || path13.join(os.tmpdir(), fallbackName);
5746
- return path13.resolve(selected);
6018
+ const selected = raw?.trim() || path6.join(os.tmpdir(), fallbackName);
6019
+ return path6.resolve(selected);
5747
6020
  }
5748
6021
  async function resolveFeatureOrThrow(featureName, options) {
5749
6022
  const config = await getConfig(process.cwd());
@@ -5916,7 +6189,7 @@ function ensureCleanWorktree(cwd) {
5916
6189
  }
5917
6190
  }
5918
6191
  function commitAndPushPath(cwd, absPath, message) {
5919
- const relativePath = path13.relative(cwd, absPath) || absPath;
6192
+ const relativePath = path6.relative(cwd, absPath) || absPath;
5920
6193
  const status = runProcessOrThrow(
5921
6194
  "git",
5922
6195
  ["status", "--porcelain=v1", "--", relativePath],
@@ -6044,7 +6317,7 @@ function githubCommand(program2) {
6044
6317
  options.bodyFile,
6045
6318
  `lee-spec-kit.issue.${feature.folderName}.md`
6046
6319
  );
6047
- await fs2.ensureDir(path13.dirname(bodyFile));
6320
+ await fs2.ensureDir(path6.dirname(bodyFile));
6048
6321
  await fs2.writeFile(bodyFile, body, "utf-8");
6049
6322
  let issueUrl;
6050
6323
  if (options.create) {
@@ -6142,7 +6415,7 @@ function githubCommand(program2) {
6142
6415
  options.bodyFile,
6143
6416
  `lee-spec-kit.pr.${feature.folderName}.md`
6144
6417
  );
6145
- await fs2.ensureDir(path13.dirname(bodyFile));
6418
+ await fs2.ensureDir(path6.dirname(bodyFile));
6146
6419
  await fs2.writeFile(bodyFile, body, "utf-8");
6147
6420
  const retryCount = toRetryCount(options.retry);
6148
6421
  let prUrl = options.pr?.trim() || "";
@@ -6180,7 +6453,7 @@ function githubCommand(program2) {
6180
6453
  }
6181
6454
  if (prUrl && options.syncTasks !== false) {
6182
6455
  const synced = syncTasksPrMetadata(
6183
- path13.join(config.docsDir, paths.tasksPath),
6456
+ path6.join(config.docsDir, paths.tasksPath),
6184
6457
  prUrl,
6185
6458
  "Review"
6186
6459
  );
@@ -6319,11 +6592,11 @@ ${version}
6319
6592
  }
6320
6593
  return `${ascii}${footer}`;
6321
6594
  }
6322
- 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");
6323
6596
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
6324
6597
  function getCurrentVersion() {
6325
6598
  try {
6326
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
6599
+ const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6327
6600
  if (fs2.existsSync(packageJsonPath)) {
6328
6601
  const pkg = fs2.readJsonSync(packageJsonPath);
6329
6602
  return pkg.version;
@@ -6419,7 +6692,7 @@ function shouldCheckForUpdates() {
6419
6692
  if (shouldCheckForUpdates()) checkForUpdates();
6420
6693
  function getCliVersion() {
6421
6694
  try {
6422
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
6695
+ const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6423
6696
  if (fs2.existsSync(packageJsonPath)) {
6424
6697
  const pkg = fs2.readJsonSync(packageJsonPath);
6425
6698
  if (pkg?.version) return String(pkg.version);