lee-spec-kit 0.6.16 → 0.6.18

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 path19 from 'path';
2
+ import path20 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
5
  import fs15 from 'fs-extra';
@@ -11,7 +11,7 @@ import os from 'os';
11
11
  import { createHash, randomUUID } from 'crypto';
12
12
 
13
13
  var getFilename = () => fileURLToPath(import.meta.url);
14
- var getDirname = () => path19.dirname(getFilename());
14
+ var getDirname = () => path20.dirname(getFilename());
15
15
  var __dirname$1 = /* @__PURE__ */ getDirname();
16
16
  async function copyTemplates(src, dest) {
17
17
  await fs15.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 = path19.dirname(__filename2);
45
+ var __dirname2 = path20.dirname(__filename2);
46
46
  function getTemplatesDir() {
47
- const rootDir = path19.resolve(__dirname2, "..");
48
- return path19.join(rootDir, "templates");
47
+ const rootDir = path20.resolve(__dirname2, "..");
48
+ return path20.join(rootDir, "templates");
49
49
  }
50
50
 
51
51
  // src/utils/i18n.ts
@@ -139,6 +139,7 @@ var I18N = {
139
139
  "context.suggestionCommandHint": "\uB77C\uBCA8 \uCC38\uACE0 \uBA85\uB839: {command}",
140
140
  "context.suggestionFinalPrompt": "\uD604\uC7AC \uCD94\uCC9C \uB77C\uBCA8: {labels}. \uC751\uB2F5\uC740 \uB77C\uBCA8 \uD1A0\uD070 \uD3EC\uD568 \uD615\uC2DD\uC73C\uB85C \uD574\uC8FC\uC138\uC694. (\uC608: {example}, `A \uC9C4\uD589\uD574`)",
141
141
  "context.suggestion.createFeature": "\uC0C8 Feature\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4",
142
+ "context.suggestion.runOnboard": "\uCD08\uAE30 \uC124\uC815 \uC810\uAC80(onboard)\uC744 \uC2E4\uD589\uD569\uB2C8\uB2E4",
142
143
  "context.suggestion.showDone": "\uC644\uB8CC\uB41C Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
143
144
  "context.suggestion.showAll": "\uC804\uCCB4 Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
144
145
  "context.suggestion.selectFeature": "\uC9C4\uD589\uD560 Feature\uB97C \uC120\uD0DD\uD574 \uC0C1\uC138 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC5FD\uB2C8\uB2E4",
@@ -202,6 +203,7 @@ var I18N = {
202
203
  "init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
203
204
  "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
204
205
  "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
206
+ "init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
205
207
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
206
208
  "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
207
209
  "init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
@@ -274,7 +276,7 @@ var I18N = {
274
276
  "github.operationPrMerge": "GitHub PR merge",
275
277
  "github.createIssueFailed": "GitHub issue \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
276
278
  "github.createPrFailed": "GitHub PR \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
277
- "github.mergeRequiresPr": "`--merge`\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 `--create` \uB610\uB294 `--pr <url|number>`\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
279
+ "github.mergeRequiresPr": "`--merge`\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 `--create`, `--pr <url|number>`, \uB610\uB294 tasks.md\uC758 PR \uB9C1\uD06C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
278
280
  "github.checkoutBaseAfterMergeFailed": "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 checkout\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
279
281
  "github.pullBaseAfterMergeFailed": "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 \uCD5C\uC2E0\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
280
282
  "github.issueDefaultTitle": "{slug} ({summary})",
@@ -431,7 +433,7 @@ var I18N = {
431
433
  reviewFixCommitIssueGuidance: 'PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D\uC744 \uC694\uC57D\uD574\uC57C \uD558\uBA70, \uD0DC\uC2A4\uD06C \uC81C\uBAA9\uC744 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694. \uC608: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9AC\uBDF0 \uC218\uC815 \uBC18\uC601 \uD30C\uC77C\uB9CC git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "fix(#{issueNumber}): <review-fix-summary>")` (`<review-fix-summary>`\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9\uC73C\uB85C \uC9C1\uC811 \uC791\uC131)',
432
434
  reviewFixCommitGuidance: 'PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D\uC744 \uC694\uC57D\uD574\uC57C \uD558\uBA70, \uD0DC\uC2A4\uD06C \uC81C\uBAA9\uC744 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694. \uC608: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9AC\uBDF0 \uC218\uC815 \uBC18\uC601 \uD30C\uC77C\uB9CC git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "fix(review): <review-fix-summary>")` (`<review-fix-summary>`\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9\uC73C\uB85C \uC9C1\uC811 \uC791\uC131)',
433
435
  standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
434
- createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
436
+ createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
435
437
  tasksAllDoneButNoChecklist: '\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uC791\uC131\uD558\uC138\uC694. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC5D0 \uAC80\uC99D \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0, \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uC138\uC694. \uCD5C\uC885 \uC2B9\uC778(OK)\uB3C4 \uBC18\uC601\uD558\uC138\uC694.',
436
438
  tasksAllDoneButChecklist: "\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uC758 \uB0A8\uC740 \uD56D\uBAA9\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uD604\uC7AC \uC9C4\uD589: ({checked}/{total}) \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uACE0 \uCD5C\uC885 \uC2B9\uC778(OK)\uC744 \uBC18\uC601\uD558\uC138\uC694.",
437
439
  finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD569\uB2C8\uB2E4: "{title}" ({done}/{total}) \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC',
@@ -441,7 +443,7 @@ var I18N = {
441
443
  taskCommitGateWarnProceed: "\u26A0\uFE0F \uD0DC\uC2A4\uD06C \uCEE4\uBC0B \uB2E8\uC704 \uC810\uAC80 \uACBD\uACE0: {reason}. \uD604\uC7AC\uB294 \uC9C4\uD589 \uAC00\uB2A5\uD558\uC9C0\uB9CC `1 \uD0DC\uC2A4\uD06C = 1 \uCEE4\uBC0B`\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
442
444
  taskCommitGateReasonNoTasksCommit: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
443
445
  taskCommitGateReasonTasksFileUnavailable: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uC774\uB825\uC744 \uD310\uB3C5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
444
- taskCommitGateReasonDoneCount: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uC810\uAC80 \uACB0\uACFC\uAC00 \uC608\uC0C1\uACFC \uB2E4\uB985\uB2C8\uB2E4 ({count})",
446
+ taskCommitGateReasonDoneCount: "\uCD5C\uC2E0 tasks.md \uCEE4\uBC0B\uC5D0\uC11C DONE \uC804\uD658\uC774 {count}\uAC74 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
445
447
  taskCommitGateReasonMismatchLastDone: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC774 \uC9C1\uC804 \uC644\uB8CC \uD0DC\uC2A4\uD06C\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
446
448
  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)",
447
449
  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)",
@@ -465,16 +467,21 @@ var I18N = {
465
467
  prCreateExecute: "\uD655\uC815\uB41C PR \uBCF8\uBB38\uC73C\uB85C PR\uC744 \uC0DD\uC131\uD558\uACE0, \uC0DD\uC131\uB41C PR \uB9C1\uD06C\uB97C tasks.md\uC758 PR \uD544\uB4DC\uC5D0 \uAE30\uB85D\uD558\uC138\uC694.",
466
468
  prCreateRequiredSequence: "PR \uC0DD\uC131\uC740 \uD544\uC218 2\uB2E8\uACC4\uC785\uB2C8\uB2E4: (1) PR \uBCF8\uBB38 \uD15C\uD50C\uB9BF \uC0DD\uC131/\uBCF4\uC644 + \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK), (2) PR \uC0DD\uC131 + tasks.md PR \uB9C1\uD06C \uAE30\uB85D. \uC704 \uC21C\uC11C\uB97C \uBAA8\uB450 \uC644\uB8CC\uD558\uC138\uC694.",
467
469
  prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815\uD558\uC138\uC694. (PR \uC0DD\uC131/\uB9AC\uBDF0 \uB2E8\uACC4\uC5D0\uC11C\uB294 Review\uB97C \uC720\uC9C0\uD569\uB2C8\uB2E4.)",
470
+ prReviewMergedSyncStatus: "\uC6D0\uACA9 PR\uC774 \uC774\uBBF8 \uBA38\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR/\uB9AC\uBDF0 \uADFC\uAC70 \uD544\uB4DC\uB3C4 \uCD5C\uC2E0 \uC0C1\uD0DC\uB85C \uD655\uC778)",
468
471
  prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uB294 \uB3D9\uC548 PR \uC0C1\uD0DC\uB294 Review\uB85C \uC720\uC9C0\uD558\uC138\uC694. \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD0DC\uC2A4\uD06C\uBA85\uC774 \uC544\uB2C8\uB77C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D \uC694\uC57D\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694. \uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
469
472
  prReviewResolve: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uBA3C\uC800 \uD655\uC778/\uBD84\uC11D\uD55C \uB4A4 \uD544\uC694\uD55C \uC218\uC815 \uC791\uC5C5\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uC9C4\uD589 \uC911\uC5D0\uB294 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC720\uC9C0\uD558\uC138\uC694. \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD0DC\uC2A4\uD06C\uBA85\uC774 \uC544\uB2C8\uB77C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D \uC694\uC57D\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694. `PR \uB9AC\uBDF0 Findings/Evidence`\uB97C \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uACE0, \uCEE4\uBC0B \uD6C4 \uC6D0\uACA9 \uBC18\uC601(push)\uB3C4 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK)\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589\uD558\uC138\uC694.",
470
- prReviewPush: '\uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC6D0\uACA9 PR \uBE0C\uB79C\uCE58\uC5D0 \uBC18\uC601\uD558\uB824\uBA74 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `cd "{projectGitCwd}" && git push`\uB97C \uC2E4\uD589\uD558\uC138\uC694.',
473
+ prReviewPush: 'cd "{projectGitCwd}" && git push',
471
474
  prReviewRemoteBlocked: "\uC6D0\uACA9 PR \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uACB0\uACFC \uC544\uC9C1 \uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: {reasons}. \uB9AC\uBDF0 \uCF54\uBA58\uD2B8/\uCCB4\uD06C \uC0C1\uD0DC\uB97C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.",
472
475
  prReviewRemoteReasonChangesRequested: "\uB9AC\uBDF0 \uC2B9\uC778 \uC0C1\uD0DC\uAC00 \uBCC0\uACBD \uC694\uCCAD \uB610\uB294 \uCD94\uAC00 \uB9AC\uBDF0 \uD544\uC694 \uC0C1\uD0DC\uC785\uB2C8\uB2E4",
476
+ prReviewRemoteReasonClosed: "PR\uC774 \uBA38\uC9C0\uB418\uC9C0 \uC54A\uC740 \uCC44 \uB2EB\uD600 \uC788\uC2B5\uB2C8\uB2E4 (reopen \uB610\uB294 \uC0C8 PR \uD544\uC694)",
473
477
  prReviewRemoteReasonChecksFailing: "\uC2E4\uD328\uD55C \uCCB4\uD06C\uAC00 {count}\uAC74 \uC788\uC2B5\uB2C8\uB2E4",
474
478
  prReviewRemoteReasonChecksPending: "\uB300\uAE30 \uC911\uC778 \uCCB4\uD06C\uAC00 {count}\uAC74 \uC788\uC2B5\uB2C8\uB2E4",
475
479
  prReviewRemoteReasonMergeBlocked: "\uBA38\uC9C0 \uC0C1\uD0DC\uAC00 `{status}`\uB85C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4",
480
+ prReviewRemoteReasonUnavailable: "\uC6D0\uACA9 PR \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (gh \uC778\uC99D/\uB124\uD2B8\uC6CC\uD06C/\uAD8C\uD55C \uD655\uC778 \uD544\uC694)",
476
481
  prReviewMerge: "\uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
482
+ prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
477
483
  prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815/\uC720\uC9C0\uD558\uC138\uC694.",
484
+ userRequestReplan: "\uD604\uC7AC \uB2E8\uACC4\uC640 \uBCC4\uAC1C\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC81C\uC548\uD55C \uC0C8 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC694\uAD6C\uC0AC\uD56D\uC744 \uC694\uC57D\uD574 tasks.md\uC5D0 \uCD94\uAC00\uD558\uAC70\uB098 \uBCC4\uB3C4 Feature\uB85C \uBD84\uB9AC\uD55C \uB4A4, \uBB38\uC11C \uC0C1\uD0DC\uB97C \uB9DE\uCD94\uACE0 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
478
485
  featureDone: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
479
486
  fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
480
487
  },
@@ -589,6 +596,7 @@ var I18N = {
589
596
  "context.suggestionCommandHint": "Reference command: {command}",
590
597
  "context.suggestionFinalPrompt": "Recommended labels now: {labels}. Please reply with a format that includes a label token. (e.g. {example}, `A proceed`)",
591
598
  "context.suggestion.createFeature": "Create a new feature",
599
+ "context.suggestion.runOnboard": "Run onboarding checks",
592
600
  "context.suggestion.showDone": "Show completed features",
593
601
  "context.suggestion.showAll": "Show all features",
594
602
  "context.suggestion.selectFeature": "Select a feature and open detailed context",
@@ -652,6 +660,7 @@ var I18N = {
652
660
  "init.log.nextStepsTitle": "Next steps:",
653
661
  "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
654
662
  "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
663
+ "init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
655
664
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
656
665
  "init.log.gitInit": "\u{1F4E6} Initializing Git...",
657
666
  "init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
@@ -724,7 +733,7 @@ var I18N = {
724
733
  "github.operationPrMerge": "GitHub PR merge",
725
734
  "github.createIssueFailed": "Failed to create GitHub issue",
726
735
  "github.createPrFailed": "Failed to create GitHub PR",
727
- "github.mergeRequiresPr": "`--merge` requires `--create` or `--pr <url|number>`.",
736
+ "github.mergeRequiresPr": "`--merge` requires `--create`, `--pr <url|number>`, or a PR link in tasks.md.",
728
737
  "github.checkoutBaseAfterMergeFailed": "Failed to checkout {base} after merge",
729
738
  "github.pullBaseAfterMergeFailed": "Failed to update {base} after merge",
730
739
  "github.issueDefaultTitle": "{slug} ({summary})",
@@ -881,7 +890,7 @@ var I18N = {
881
890
  reviewFixCommitIssueGuidance: 'Commit PR review fixes. The commit message must summarize review comments resolved in this commit, not reuse a task title. Example: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files that implement review fixes with git add [files], then run again." && exit 1 || git commit -m "fix(#{issueNumber}): <review-fix-summary>")` (replace `<review-fix-summary>` with what this commit actually resolves).',
882
891
  reviewFixCommitGuidance: 'Commit PR review fixes. The commit message must summarize review comments resolved in this commit, not reuse a task title. Example: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files that implement review fixes with git add [files], then run again." && exit 1 || git commit -m "fix(review): <review-fix-summary>")` (replace `<review-fix-summary>` with what this commit actually resolves).',
883
892
  standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
884
- createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
893
+ createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
885
894
  tasksAllDoneButNoChecklist: 'Create the completion checklist. Add verification items to the tasks.md "Completion Criteria" section, then mark satisfied items as [x] after user confirmation. Record final approval (OK) as well.',
886
895
  tasksAllDoneButChecklist: "Proceed with remaining completion checklist items. Current progress: ({checked}/{total}) Mark items as [x] only after user confirmation and real verification. Record final approval (OK) as well.",
887
896
  finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) Share outcome/verification + get OK before marking DONE',
@@ -891,7 +900,7 @@ var I18N = {
891
900
  taskCommitGateWarnProceed: "\u26A0\uFE0F Task commit boundary warning: {reason}. You may continue, but `1 task = 1 commit` is recommended.",
892
901
  taskCommitGateReasonNoTasksCommit: "No recent project code commit was found",
893
902
  taskCommitGateReasonTasksFileUnavailable: "Cannot read recent project code commit history",
894
- taskCommitGateReasonDoneCount: "Project commit gate check result is unexpected ({count})",
903
+ taskCommitGateReasonDoneCount: "DONE transitions detected in the latest tasks.md commit ({count})",
895
904
  taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
896
905
  prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
897
906
  prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
@@ -915,16 +924,21 @@ var I18N = {
915
924
  prCreateExecute: "Create the PR with the finalized body, then record the created PR link in tasks.md.",
916
925
  prCreateRequiredSequence: "PR creation is a required 2-step sequence: (1) generate/refine PR body template + explicit user OK, (2) create PR + record PR link in tasks.md. Complete both in order.",
917
926
  prFillStatus: "Set PR Status in tasks.md to Review. (Keep Review during PR creation/review stages.)",
927
+ prReviewMergedSyncStatus: "The remote PR is already merged. Update PR Status in tasks.md to Approved. (Also verify PR/review evidence fields are up to date.)",
918
928
  prResolveReview: "Keep PR Status as Review while addressing comments. For review-fix commits, use commit messages that summarize resolved review feedback (not task titles). Once ready to merge, get explicit user OK and run `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`. (On success, PR Status is synced to Approved.)",
919
929
  prReviewResolve: "First review/analyze the PR comments, then apply the required fixes. Keep PR Status as Review while addressing comments. For review-fix commits, use commit messages that summarize resolved review feedback (not task titles). Keep `PR Review Findings/Evidence` updated, then run push only after explicit user OK.",
920
- prReviewPush: 'To reflect review-fix commits on the PR head branch, get explicit user OK and run `cd "{projectGitCwd}" && git push`.',
930
+ prReviewPush: 'cd "{projectGitCwd}" && git push',
921
931
  prReviewRemoteBlocked: "Remote PR checks indicate this PR is not ready to merge yet: {reasons}. Resolve review comments/check statuses, then re-check.",
922
932
  prReviewRemoteReasonChangesRequested: "review decision is changes requested or additional review required",
933
+ prReviewRemoteReasonClosed: "PR is closed without merge (reopen or create a new PR)",
923
934
  prReviewRemoteReasonChecksFailing: "{count} failing check(s) detected",
924
935
  prReviewRemoteReasonChecksPending: "{count} pending check(s) detected",
925
936
  prReviewRemoteReasonMergeBlocked: "merge state is blocked (`{status}`)",
937
+ prReviewRemoteReasonUnavailable: "remote PR status could not be verified (check gh auth/network/permissions)",
926
938
  prReviewMerge: "When ready to merge, get explicit user OK and run `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`. (On success, PR Status is synced to Approved.)",
939
+ prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
927
940
  prRequestReview: "Request review and set/keep PR Status as Review.",
941
+ userRequestReplan: "You can pause this step and handle a newly requested user requirement first. Summarize it, add it to tasks.md or split it into a separate Feature, then align document statuses and rerun context.",
928
942
  featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
929
943
  fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
930
944
  },
@@ -1345,10 +1359,10 @@ function sleep(ms) {
1345
1359
  return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
1346
1360
  }
1347
1361
  function toScopeKey(value) {
1348
- return createHash("sha1").update(path19.resolve(value)).digest("hex").slice(0, 16);
1362
+ return createHash("sha1").update(path20.resolve(value)).digest("hex").slice(0, 16);
1349
1363
  }
1350
1364
  function getTempRuntimeDir(scopePath) {
1351
- return path19.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1365
+ return path20.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1352
1366
  }
1353
1367
  function resolveGitRuntimeDir(cwd) {
1354
1368
  try {
@@ -1362,38 +1376,38 @@ function resolveGitRuntimeDir(cwd) {
1362
1376
  }
1363
1377
  ).trim();
1364
1378
  if (!out) return null;
1365
- return path19.isAbsolute(out) ? out : path19.resolve(cwd, out);
1379
+ return path20.isAbsolute(out) ? out : path20.resolve(cwd, out);
1366
1380
  } catch {
1367
1381
  return null;
1368
1382
  }
1369
1383
  }
1370
1384
  function getRuntimeStateDir(cwd) {
1371
- const resolved = path19.resolve(cwd);
1385
+ const resolved = path20.resolve(cwd);
1372
1386
  return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
1373
1387
  }
1374
1388
  function getDocsLockPath(docsDir) {
1375
- return path19.join(
1389
+ return path20.join(
1376
1390
  getRuntimeStateDir(docsDir),
1377
1391
  "locks",
1378
1392
  `docs-${toScopeKey(docsDir)}.lock`
1379
1393
  );
1380
1394
  }
1381
1395
  function getInitLockPath(targetDir) {
1382
- return path19.join(
1383
- getRuntimeStateDir(path19.dirname(path19.resolve(targetDir))),
1396
+ return path20.join(
1397
+ getRuntimeStateDir(path20.dirname(path20.resolve(targetDir))),
1384
1398
  "locks",
1385
1399
  `init-${toScopeKey(targetDir)}.lock`
1386
1400
  );
1387
1401
  }
1388
1402
  function getApprovalTicketStorePath(docsDir) {
1389
- return path19.join(
1403
+ return path20.join(
1390
1404
  getRuntimeStateDir(docsDir),
1391
1405
  "tickets",
1392
1406
  `approval-${toScopeKey(docsDir)}.json`
1393
1407
  );
1394
1408
  }
1395
1409
  function getProjectExecutionLockPath(cwd) {
1396
- return path19.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1410
+ return path20.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1397
1411
  }
1398
1412
  async function isStaleLock(lockPath, staleMs) {
1399
1413
  try {
@@ -1434,7 +1448,7 @@ function isProcessAlive(pid) {
1434
1448
  }
1435
1449
  }
1436
1450
  async function tryAcquire(lockPath, owner) {
1437
- await fs15.ensureDir(path19.dirname(lockPath));
1451
+ await fs15.ensureDir(path20.dirname(lockPath));
1438
1452
  try {
1439
1453
  const fd = await fs15.open(lockPath, "wx");
1440
1454
  const payload = JSON.stringify(
@@ -1511,30 +1525,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
1511
1525
  "pr-template.md"
1512
1526
  ];
1513
1527
  var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1514
- var ENGINE_MANAGED_FEATURE_PATH = path19.join(
1528
+ var ENGINE_MANAGED_FEATURE_PATH = path20.join(
1515
1529
  "features",
1516
1530
  "feature-base"
1517
1531
  );
1518
1532
  async function pruneEngineManagedDocs(docsDir) {
1519
1533
  const removed = [];
1520
1534
  for (const file of ENGINE_MANAGED_AGENT_FILES) {
1521
- const target = path19.join(docsDir, "agents", file);
1535
+ const target = path20.join(docsDir, "agents", file);
1522
1536
  if (await fs15.pathExists(target)) {
1523
1537
  await fs15.remove(target);
1524
- removed.push(path19.relative(docsDir, target));
1538
+ removed.push(path20.relative(docsDir, target));
1525
1539
  }
1526
1540
  }
1527
1541
  for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1528
- const target = path19.join(docsDir, "agents", dir);
1542
+ const target = path20.join(docsDir, "agents", dir);
1529
1543
  if (await fs15.pathExists(target)) {
1530
1544
  await fs15.remove(target);
1531
- removed.push(path19.relative(docsDir, target));
1545
+ removed.push(path20.relative(docsDir, target));
1532
1546
  }
1533
1547
  }
1534
- const featureBasePath = path19.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1548
+ const featureBasePath = path20.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1535
1549
  if (await fs15.pathExists(featureBasePath)) {
1536
1550
  await fs15.remove(featureBasePath);
1537
- removed.push(path19.relative(docsDir, featureBasePath));
1551
+ removed.push(path20.relative(docsDir, featureBasePath));
1538
1552
  }
1539
1553
  return removed;
1540
1554
  }
@@ -1675,7 +1689,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
1675
1689
  }
1676
1690
  async function runInit(options) {
1677
1691
  const cwd = process.cwd();
1678
- const defaultName = path19.basename(cwd);
1692
+ const defaultName = path20.basename(cwd);
1679
1693
  let projectName = options.name || defaultName;
1680
1694
  let projectType = options.type;
1681
1695
  let components = parseComponentsOption(options.components);
@@ -1688,7 +1702,7 @@ async function runInit(options) {
1688
1702
  const componentProjectRoots = parseComponentProjectRootsOption(
1689
1703
  options.componentProjectRoots
1690
1704
  );
1691
- const targetDir = path19.resolve(cwd, options.dir || "./docs");
1705
+ const targetDir = path20.resolve(cwd, options.dir || "./docs");
1692
1706
  const skipPrompts = !!options.yes || !!options.nonInteractive;
1693
1707
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
1694
1708
  throw createCliError(
@@ -2077,7 +2091,7 @@ async function runInit(options) {
2077
2091
  );
2078
2092
  console.log();
2079
2093
  const templatesDir = getTemplatesDir();
2080
- const commonPath = path19.join(templatesDir, lang, "common");
2094
+ const commonPath = path20.join(templatesDir, lang, "common");
2081
2095
  if (!await fs15.pathExists(commonPath)) {
2082
2096
  throw new Error(
2083
2097
  tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
@@ -2085,11 +2099,11 @@ async function runInit(options) {
2085
2099
  }
2086
2100
  await copyTemplates(commonPath, targetDir);
2087
2101
  if (projectType === "multi") {
2088
- const featuresRoot = path19.join(targetDir, "features");
2102
+ const featuresRoot = path20.join(targetDir, "features");
2089
2103
  for (const component of components) {
2090
- const componentDir = path19.join(featuresRoot, component);
2104
+ const componentDir = path20.join(featuresRoot, component);
2091
2105
  await fs15.ensureDir(componentDir);
2092
- const readmePath = path19.join(componentDir, "README.md");
2106
+ const readmePath = path20.join(componentDir, "README.md");
2093
2107
  if (!await fs15.pathExists(readmePath)) {
2094
2108
  await fs15.writeFile(
2095
2109
  readmePath,
@@ -2118,7 +2132,7 @@ async function runInit(options) {
2118
2132
  workflow: {
2119
2133
  mode: workflowMode,
2120
2134
  codeDirtyScope: "auto",
2121
- taskCommitGate: "strict",
2135
+ taskCommitGate: "warn",
2122
2136
  prePrReview: {
2123
2137
  skills: ["code-review-excellence"],
2124
2138
  minorPolicy: "warn"
@@ -2142,7 +2156,7 @@ async function runInit(options) {
2142
2156
  config.projectRoot = projectRoot;
2143
2157
  }
2144
2158
  }
2145
- const configPath = path19.join(targetDir, ".lee-spec-kit.json");
2159
+ const configPath = path20.join(targetDir, ".lee-spec-kit.json");
2146
2160
  await fs15.writeJson(configPath, config, { spaces: 2 });
2147
2161
  console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
2148
2162
  console.log();
@@ -2152,6 +2166,7 @@ async function runInit(options) {
2152
2166
  chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
2153
2167
  );
2154
2168
  console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
2169
+ console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps3")));
2155
2170
  console.log();
2156
2171
  },
2157
2172
  { owner: "init" }
@@ -2159,7 +2174,8 @@ async function runInit(options) {
2159
2174
  }
2160
2175
  async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2161
2176
  try {
2162
- const runGit = (args, workdir) => {
2177
+ const gitWorkdir = docsRepo === "standalone" ? targetDir : cwd;
2178
+ const runGit2 = (args, workdir) => {
2163
2179
  execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
2164
2180
  };
2165
2181
  const getCachedStagedFiles = (workdir) => {
@@ -2205,14 +2221,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2205
2221
  }
2206
2222
  };
2207
2223
  try {
2208
- runGit(["rev-parse", "--is-inside-work-tree"], cwd);
2224
+ runGit2(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
2209
2225
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
2210
2226
  } catch {
2211
2227
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
2212
- runGit(["init"], cwd);
2228
+ runGit2(["init"], gitWorkdir);
2213
2229
  }
2214
- const relativePath = path19.relative(cwd, targetDir);
2215
- const stagedBeforeAdd = getCachedStagedFiles(cwd);
2230
+ const relativePath = docsRepo === "standalone" ? "." : path20.relative(cwd, targetDir);
2231
+ const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
2216
2232
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
2217
2233
  console.log(
2218
2234
  chalk6.yellow(
@@ -2223,8 +2239,8 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2223
2239
  console.log();
2224
2240
  return;
2225
2241
  }
2226
- if (relativePath !== "." && isPathIgnored(cwd, relativePath)) {
2227
- const repoRelativePath = toRepoRelativePath(cwd, relativePath);
2242
+ if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
2243
+ const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
2228
2244
  console.log(
2229
2245
  chalk6.yellow(
2230
2246
  tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
@@ -2242,14 +2258,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2242
2258
  console.log();
2243
2259
  return;
2244
2260
  }
2245
- runGit(["add", relativePath], cwd);
2246
- runGit(
2261
+ runGit2(["add", relativePath], gitWorkdir);
2262
+ runGit2(
2247
2263
  ["commit", "-m", "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)", "--", relativePath],
2248
- cwd
2264
+ gitWorkdir
2249
2265
  );
2250
2266
  if (docsRepo === "standalone" && pushDocs && docsRemote) {
2251
2267
  try {
2252
- runGit(["remote", "add", "origin", docsRemote], cwd);
2268
+ runGit2(["remote", "add", "origin", docsRemote], gitWorkdir);
2253
2269
  console.log(
2254
2270
  chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
2255
2271
  );
@@ -2268,17 +2284,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2268
2284
  }
2269
2285
  function getAncestorDirs(startDir) {
2270
2286
  const dirs = [];
2271
- let current = path19.resolve(startDir);
2287
+ let current = path20.resolve(startDir);
2272
2288
  while (true) {
2273
2289
  dirs.push(current);
2274
- const parent = path19.dirname(current);
2290
+ const parent = path20.dirname(current);
2275
2291
  if (parent === current) break;
2276
2292
  current = parent;
2277
2293
  }
2278
2294
  return dirs;
2279
2295
  }
2280
2296
  function hasWorkspaceBoundary(dir) {
2281
- return fs15.existsSync(path19.join(dir, "package.json")) || fs15.existsSync(path19.join(dir, ".git"));
2297
+ return fs15.existsSync(path20.join(dir, "package.json")) || fs15.existsSync(path20.join(dir, ".git"));
2282
2298
  }
2283
2299
  function getSearchBaseDirs(cwd) {
2284
2300
  const ancestors = getAncestorDirs(cwd);
@@ -2294,7 +2310,7 @@ function normalizeComponentKeys(value) {
2294
2310
  return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
2295
2311
  }
2296
2312
  async function inferComponentsFromFeaturesDir(docsDir) {
2297
- const featuresPath = path19.join(docsDir, "features");
2313
+ const featuresPath = path20.join(docsDir, "features");
2298
2314
  if (!await fs15.pathExists(featuresPath)) return [];
2299
2315
  const entries = await fs15.readdir(featuresPath, { withFileTypes: true });
2300
2316
  const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
@@ -2305,21 +2321,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
2305
2321
  async function getConfig(cwd) {
2306
2322
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2307
2323
  const baseDirs = [
2308
- ...explicitDocsDir ? [path19.resolve(explicitDocsDir)] : [],
2324
+ ...explicitDocsDir ? [path20.resolve(explicitDocsDir)] : [],
2309
2325
  ...getSearchBaseDirs(cwd)
2310
2326
  ];
2311
2327
  const visitedBaseDirs = /* @__PURE__ */ new Set();
2312
2328
  const visitedDocsDirs = /* @__PURE__ */ new Set();
2313
2329
  for (const baseDir of baseDirs) {
2314
- const resolvedBaseDir = path19.resolve(baseDir);
2330
+ const resolvedBaseDir = path20.resolve(baseDir);
2315
2331
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
2316
2332
  visitedBaseDirs.add(resolvedBaseDir);
2317
- const possibleDocsDirs = [path19.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2333
+ const possibleDocsDirs = [path20.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2318
2334
  for (const docsDir of possibleDocsDirs) {
2319
- const resolvedDocsDir = path19.resolve(docsDir);
2335
+ const resolvedDocsDir = path20.resolve(docsDir);
2320
2336
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
2321
2337
  visitedDocsDirs.add(resolvedDocsDir);
2322
- const configPath = path19.join(resolvedDocsDir, ".lee-spec-kit.json");
2338
+ const configPath = path20.join(resolvedDocsDir, ".lee-spec-kit.json");
2323
2339
  if (await fs15.pathExists(configPath)) {
2324
2340
  try {
2325
2341
  const configFile = await fs15.readJson(configPath);
@@ -2349,16 +2365,16 @@ async function getConfig(cwd) {
2349
2365
  } catch {
2350
2366
  }
2351
2367
  }
2352
- const agentsPath = path19.join(resolvedDocsDir, "agents");
2353
- const featuresPath = path19.join(resolvedDocsDir, "features");
2368
+ const agentsPath = path20.join(resolvedDocsDir, "agents");
2369
+ const featuresPath = path20.join(resolvedDocsDir, "features");
2354
2370
  if (await fs15.pathExists(agentsPath) && await fs15.pathExists(featuresPath)) {
2355
2371
  const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
2356
2372
  const projectType = inferredComponents.length > 0 ? "multi" : "single";
2357
2373
  const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
2358
2374
  const langProbeCandidates = [
2359
- path19.join(agentsPath, "custom.md"),
2360
- path19.join(agentsPath, "constitution.md"),
2361
- path19.join(agentsPath, "agents.md")
2375
+ path20.join(agentsPath, "custom.md"),
2376
+ path20.join(agentsPath, "constitution.md"),
2377
+ path20.join(agentsPath, "agents.md")
2362
2378
  ];
2363
2379
  let lang = "en";
2364
2380
  for (const candidate of langProbeCandidates) {
@@ -2424,13 +2440,13 @@ async function patchMarkdownIfExists(filePath, transform) {
2424
2440
  await fs15.writeFile(filePath, transform(content), "utf-8");
2425
2441
  }
2426
2442
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
2427
- await patchMarkdownIfExists(path19.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2443
+ await patchMarkdownIfExists(path20.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2428
2444
  await patchMarkdownIfExists(
2429
- path19.join(featureDir, "tasks.md"),
2445
+ path20.join(featureDir, "tasks.md"),
2430
2446
  (content) => sanitizeTasksForLocal(content, lang)
2431
2447
  );
2432
- await fs15.remove(path19.join(featureDir, "issue.md"));
2433
- await fs15.remove(path19.join(featureDir, "pr.md"));
2448
+ await fs15.remove(path20.join(featureDir, "issue.md"));
2449
+ await fs15.remove(path20.join(featureDir, "pr.md"));
2434
2450
  }
2435
2451
 
2436
2452
  // src/commands/feature.ts
@@ -2575,19 +2591,19 @@ async function runFeature(name, options) {
2575
2591
  }
2576
2592
  let featuresDir;
2577
2593
  if (projectType === "multi") {
2578
- featuresDir = path19.join(docsDir, "features", component);
2594
+ featuresDir = path20.join(docsDir, "features", component);
2579
2595
  } else {
2580
- featuresDir = path19.join(docsDir, "features");
2596
+ featuresDir = path20.join(docsDir, "features");
2581
2597
  }
2582
2598
  const featureFolderName = `${featureId}-${name}`;
2583
- const featureDir = path19.join(featuresDir, featureFolderName);
2599
+ const featureDir = path20.join(featuresDir, featureFolderName);
2584
2600
  if (await fs15.pathExists(featureDir)) {
2585
2601
  throw createCliError(
2586
2602
  "INVALID_ARGUMENT",
2587
2603
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
2588
2604
  );
2589
2605
  }
2590
- const featureBasePath = path19.join(
2606
+ const featureBasePath = path20.join(
2591
2607
  getTemplatesDir(),
2592
2608
  lang,
2593
2609
  "common",
@@ -2646,7 +2662,7 @@ async function runFeature(name, options) {
2646
2662
  featureName: name,
2647
2663
  component: projectType === "multi" ? component : void 0,
2648
2664
  featurePath: featureDir,
2649
- featurePathFromDocs: path19.relative(docsDir, featureDir)
2665
+ featurePathFromDocs: path20.relative(docsDir, featureDir)
2650
2666
  };
2651
2667
  },
2652
2668
  { owner: "feature" }
@@ -2658,9 +2674,9 @@ function sleep2(ms) {
2658
2674
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2659
2675
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2660
2676
  const candidates = [
2661
- ...explicitDocsDir ? [path19.resolve(explicitDocsDir)] : [],
2662
- path19.resolve(cwd, "docs"),
2663
- path19.resolve(cwd)
2677
+ ...explicitDocsDir ? [path20.resolve(explicitDocsDir)] : [],
2678
+ path20.resolve(cwd, "docs"),
2679
+ path20.resolve(cwd)
2664
2680
  ];
2665
2681
  const endAt = Date.now() + timeoutMs;
2666
2682
  while (Date.now() < endAt) {
@@ -2687,11 +2703,11 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2687
2703
  return getConfig(cwd);
2688
2704
  }
2689
2705
  async function getNextFeatureId(docsDir, projectType, components) {
2690
- const featuresDir = path19.join(docsDir, "features");
2706
+ const featuresDir = path20.join(docsDir, "features");
2691
2707
  let max = 0;
2692
2708
  const scanDirs = [];
2693
2709
  if (projectType === "multi") {
2694
- scanDirs.push(...components.map((component) => path19.join(featuresDir, component)));
2710
+ scanDirs.push(...components.map((component) => path20.join(featuresDir, component)));
2695
2711
  } else {
2696
2712
  scanDirs.push(featuresDir);
2697
2713
  }
@@ -2840,6 +2856,9 @@ function getPrReviewRemoteBlockReasons(feature, lang) {
2840
2856
  const remote = feature.pr.remote;
2841
2857
  if (!remote || !remote.available) return [];
2842
2858
  const reasons = [];
2859
+ if (remote.state === "CLOSED" && !remote.isMerged) {
2860
+ reasons.push(tr(lang, "messages", "prReviewRemoteReasonClosed"));
2861
+ }
2843
2862
  if (remote.hasBlockingReview) {
2844
2863
  reasons.push(tr(lang, "messages", "prReviewRemoteReasonChangesRequested"));
2845
2864
  }
@@ -2905,14 +2924,90 @@ function normalizeTaskTopic(value) {
2905
2924
  function normalizeCommitSubjectForGate(value) {
2906
2925
  return normalizeCommitTopicText(value).replace(/^[a-z]+(?:\([^)]*\))?!?:\s*/i, "").toLowerCase();
2907
2926
  }
2927
+ function toTaskKey(rawTitle) {
2928
+ const trimmed = normalizeCommitTopicText(rawTitle);
2929
+ if (!trimmed) return "";
2930
+ const idMatch = trimmed.match(/^(T-[A-Za-z0-9-]+)/i);
2931
+ if (idMatch) return idMatch[1].toUpperCase();
2932
+ return normalizeTaskTopic(trimmed).toLowerCase();
2933
+ }
2934
+ function countDoneTransitionsInLatestTasksCommit(feature) {
2935
+ const docsGitCwd = feature.git.docsGitCwd;
2936
+ const tasksRelativePath = normalizeGitRelativePath(
2937
+ path20.join(feature.docs.featurePathFromDocs, "tasks.md")
2938
+ );
2939
+ const diff = readGitText(docsGitCwd, [
2940
+ "diff",
2941
+ "--unified=0",
2942
+ "--no-color",
2943
+ "HEAD~1",
2944
+ "HEAD",
2945
+ "--",
2946
+ tasksRelativePath
2947
+ ]);
2948
+ if (diff === void 0) return 0;
2949
+ if (!diff.trim()) return 0;
2950
+ const removedByTask = /* @__PURE__ */ new Map();
2951
+ const addedByTask = /* @__PURE__ */ new Map();
2952
+ const parseTaskLine = (line) => {
2953
+ const match = line.match(/^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\s+(.+?)\s*$/i);
2954
+ if (!match) return null;
2955
+ const key = toTaskKey(match[2]);
2956
+ if (!key) return null;
2957
+ return {
2958
+ key,
2959
+ status: match[1].toUpperCase()
2960
+ };
2961
+ };
2962
+ for (const line of diff.split("\n")) {
2963
+ if (line.startsWith("---") || line.startsWith("+++")) continue;
2964
+ if (line.startsWith("-")) {
2965
+ const parsed = parseTaskLine(line.slice(1));
2966
+ if (!parsed) continue;
2967
+ const existing = removedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
2968
+ existing.add(parsed.status);
2969
+ removedByTask.set(parsed.key, existing);
2970
+ continue;
2971
+ }
2972
+ if (line.startsWith("+")) {
2973
+ const parsed = parseTaskLine(line.slice(1));
2974
+ if (!parsed) continue;
2975
+ const existing = addedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
2976
+ existing.add(parsed.status);
2977
+ addedByTask.set(parsed.key, existing);
2978
+ }
2979
+ }
2980
+ let doneTransitions = 0;
2981
+ for (const [taskKey, addedStatuses] of addedByTask.entries()) {
2982
+ if (!addedStatuses.has("DONE")) continue;
2983
+ const removedStatuses = removedByTask.get(taskKey);
2984
+ if (!removedStatuses) continue;
2985
+ const transitionedFromOpen = removedStatuses.has("TODO") || removedStatuses.has("DOING") || removedStatuses.has("REVIEW");
2986
+ if (transitionedFromOpen) {
2987
+ doneTransitions += 1;
2988
+ }
2989
+ }
2990
+ return doneTransitions;
2991
+ }
2908
2992
  function checkTaskCommitGate(feature) {
2993
+ const doneTransitions = countDoneTransitionsInLatestTasksCommit(feature);
2994
+ if (doneTransitions === 0) {
2995
+ return { pass: true, doneTransitions };
2996
+ }
2997
+ if (typeof doneTransitions === "number" && doneTransitions > 1) {
2998
+ return {
2999
+ pass: false,
3000
+ reason: "DONE_TRANSITIONS_COUNT",
3001
+ doneTransitions
3002
+ };
3003
+ }
2909
3004
  const projectGitCwd = feature.git.projectGitCwd;
2910
3005
  const lastDoneTopic = normalizeTaskTopic(feature.lastDoneTask?.title || "");
2911
3006
  if (!projectGitCwd || !lastDoneTopic) {
2912
3007
  return { pass: true };
2913
3008
  }
2914
3009
  const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
2915
- const relativeDocsDir = path19.relative(projectGitCwd, feature.git.docsGitCwd);
3010
+ const relativeDocsDir = path20.relative(projectGitCwd, feature.git.docsGitCwd);
2916
3011
  const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
2917
3012
  if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
2918
3013
  args.push(`:(exclude)${normalizedDocsDir}/**`);
@@ -2933,6 +3028,10 @@ function checkTaskCommitGate(feature) {
2933
3028
  }
2934
3029
  function getTaskCommitGateReasonText(lang, check) {
2935
3030
  switch (check.reason) {
3031
+ case "DONE_TRANSITIONS_COUNT":
3032
+ return tr(lang, "messages", "taskCommitGateReasonDoneCount", {
3033
+ count: check.doneTransitions || 0
3034
+ });
2936
3035
  case "NO_PROJECT_COMMIT":
2937
3036
  return tr(lang, "messages", "taskCommitGateReasonNoTasksCommit");
2938
3037
  case "PROJECT_LOG_UNAVAILABLE":
@@ -3746,6 +3845,16 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
3746
3845
  ];
3747
3846
  }
3748
3847
  if (f.pr.status === "Review") {
3848
+ if (f.pr.remote?.available && f.pr.remote.isMerged) {
3849
+ return [
3850
+ {
3851
+ type: "instruction",
3852
+ category: "pr_status_update",
3853
+ requiresUserCheck: true,
3854
+ message: tr(lang, "messages", "prReviewMergedSyncStatus")
3855
+ }
3856
+ ];
3857
+ }
3749
3858
  if (!f.docs.prReviewFindingsFieldExists) {
3750
3859
  return [
3751
3860
  {
@@ -3787,6 +3896,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
3787
3896
  ];
3788
3897
  }
3789
3898
  const remoteBlockReasons = getPrReviewRemoteBlockReasons(f, lang);
3899
+ const remoteUnavailable = workflowPolicy.mode === "github" && !!f.pr.link && (!f.pr.remote || !f.pr.remote.available);
3790
3900
  const actions = [
3791
3901
  {
3792
3902
  type: "instruction",
@@ -3814,16 +3924,41 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
3814
3924
  })
3815
3925
  });
3816
3926
  }
3817
- actions.push({
3818
- type: "instruction",
3819
- category: "code_review",
3820
- requiresUserCheck: true,
3821
- message: remoteBlockReasons.length > 0 ? tr(lang, "messages", "prReviewRemoteBlocked", {
3822
- reasons: remoteBlockReasons.join("; ")
3823
- }) : tr(lang, "messages", "prReviewMerge", {
3824
- featureRef: f.id || f.folderName
3825
- })
3826
- });
3927
+ if (remoteBlockReasons.length > 0 || remoteUnavailable) {
3928
+ const reasons = [...remoteBlockReasons];
3929
+ if (remoteUnavailable) {
3930
+ reasons.push(tr(lang, "messages", "prReviewRemoteReasonUnavailable"));
3931
+ }
3932
+ actions.push({
3933
+ type: "instruction",
3934
+ category: "code_review",
3935
+ requiresUserCheck: true,
3936
+ message: tr(lang, "messages", "prReviewRemoteBlocked", {
3937
+ reasons: reasons.join("; ")
3938
+ })
3939
+ });
3940
+ } else if (f.git.docsGitCwd) {
3941
+ actions.push({
3942
+ type: "command",
3943
+ category: "code_review",
3944
+ requiresUserCheck: true,
3945
+ operationType: "remote",
3946
+ scope: "docs",
3947
+ cwd: f.git.docsGitCwd,
3948
+ cmd: tr(lang, "messages", "prReviewMergeCommand", {
3949
+ featureRef: f.id || f.folderName
3950
+ })
3951
+ });
3952
+ } else {
3953
+ actions.push({
3954
+ type: "instruction",
3955
+ category: "code_review",
3956
+ requiresUserCheck: true,
3957
+ message: tr(lang, "messages", "prReviewMerge", {
3958
+ featureRef: f.id || f.folderName
3959
+ })
3960
+ });
3961
+ }
3827
3962
  return actions;
3828
3963
  }
3829
3964
  return [
@@ -3901,14 +4036,32 @@ function applyApprovalPolicy(step, actions, approval) {
3901
4036
  return { ...a, requiresUserCheck };
3902
4037
  });
3903
4038
  }
4039
+ function withUserRequestReplanOption(actions, lang) {
4040
+ if (actions.some((action) => action.category === "user_request_replan")) {
4041
+ return actions;
4042
+ }
4043
+ return [
4044
+ ...actions,
4045
+ {
4046
+ type: "instruction",
4047
+ category: "user_request_replan",
4048
+ requiresUserCheck: true,
4049
+ message: tr(lang, "messages", "userRequestReplan")
4050
+ }
4051
+ ];
4052
+ }
3904
4053
  function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
3905
4054
  const ordered = [...stepDefinitions].sort((a, b) => a.step - b.step);
3906
4055
  for (const definition of ordered) {
3907
4056
  if (!definition.current) continue;
3908
4057
  if (definition.current.when(feature)) {
4058
+ const currentActions = withUserRequestReplanOption(
4059
+ definition.current.actions(feature),
4060
+ lang
4061
+ );
3909
4062
  const actions = applyApprovalPolicy(
3910
4063
  definition.step,
3911
- definition.current.actions(feature),
4064
+ currentActions,
3912
4065
  approval
3913
4066
  );
3914
4067
  return {
@@ -4034,6 +4187,7 @@ function isGitPathIgnored(cwd, relativePath) {
4034
4187
  return void 0;
4035
4188
  }
4036
4189
  }
4190
+ var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
4037
4191
  function getGitTopLevel(cwd) {
4038
4192
  try {
4039
4193
  return execSync("git rev-parse --show-toplevel", {
@@ -4045,6 +4199,52 @@ function getGitTopLevel(cwd) {
4045
4199
  return null;
4046
4200
  }
4047
4201
  }
4202
+ function listGitWorktrees(cwd) {
4203
+ const topLevel = getGitTopLevel(cwd) || cwd;
4204
+ const cacheKey = path20.resolve(topLevel);
4205
+ const cached = GIT_WORKTREE_CACHE.get(cacheKey);
4206
+ if (cached) return cached;
4207
+ try {
4208
+ const out = execFileSync("git", ["worktree", "list", "--porcelain"], {
4209
+ cwd: topLevel,
4210
+ encoding: "utf-8",
4211
+ stdio: ["ignore", "pipe", "pipe"]
4212
+ });
4213
+ const entries = [];
4214
+ let current = null;
4215
+ for (const rawLine of out.split("\n")) {
4216
+ const line = rawLine.trim();
4217
+ if (!line) {
4218
+ if (current?.path) entries.push(current);
4219
+ current = null;
4220
+ continue;
4221
+ }
4222
+ if (line.startsWith("worktree ")) {
4223
+ if (current?.path) entries.push(current);
4224
+ current = { path: line.slice("worktree ".length).trim() };
4225
+ continue;
4226
+ }
4227
+ if (line.startsWith("branch ")) {
4228
+ if (!current) continue;
4229
+ const fullRef = line.slice("branch ".length).trim();
4230
+ current.branch = fullRef.replace(/^refs\/heads\//, "");
4231
+ }
4232
+ }
4233
+ if (current?.path) entries.push(current);
4234
+ GIT_WORKTREE_CACHE.set(cacheKey, entries);
4235
+ return entries;
4236
+ } catch {
4237
+ return void 0;
4238
+ }
4239
+ }
4240
+ function findWorktreePathForBranch(cwd, branchName) {
4241
+ const target = branchName.trim();
4242
+ if (!target) return void 0;
4243
+ const entries = listGitWorktrees(cwd);
4244
+ if (!entries) return void 0;
4245
+ const match = entries.find((entry) => entry.branch === target);
4246
+ return match?.path;
4247
+ }
4048
4248
  function resolveProjectGitCwd(config, repo, lang = config.lang ?? DEFAULT_LANG) {
4049
4249
  const docsRepo = config.docsRepo;
4050
4250
  if (docsRepo !== "standalone") {
@@ -4198,13 +4398,13 @@ function parsePrLink(value) {
4198
4398
  return trimmed;
4199
4399
  }
4200
4400
  function normalizeGitPath(value) {
4201
- return value.split(path19.sep).join("/");
4401
+ return value.split(path20.sep).join("/");
4202
4402
  }
4203
4403
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
4204
- const relativeDocsDir = path19.relative(projectGitCwd, docsDir);
4404
+ const relativeDocsDir = path20.relative(projectGitCwd, docsDir);
4205
4405
  if (!relativeDocsDir) return [];
4206
- if (path19.isAbsolute(relativeDocsDir)) return [];
4207
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path19.sep}`)) {
4406
+ if (path20.isAbsolute(relativeDocsDir)) return [];
4407
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path20.sep}`)) {
4208
4408
  return [];
4209
4409
  }
4210
4410
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
@@ -4226,6 +4426,27 @@ function uniqueNormalizedPaths(values) {
4226
4426
  var PROJECT_DIRTY_STATUS_CACHE = /* @__PURE__ */ new Map();
4227
4427
  var COMPONENT_STATUS_PATH_CACHE = /* @__PURE__ */ new Map();
4228
4428
  var PR_REMOTE_STATUS_CACHE = /* @__PURE__ */ new Map();
4429
+ var FEATURE_WORKTREE_CACHE = /* @__PURE__ */ new Map();
4430
+ function resolveFeatureWorktreePath(projectGitCwd, issueNumber, slug, folderName) {
4431
+ const expectedBranches = [
4432
+ `feat/${issueNumber}-${slug}`,
4433
+ `feat/${issueNumber}-${folderName}`
4434
+ ];
4435
+ for (const branchName of expectedBranches) {
4436
+ const cacheKey = `${projectGitCwd}::${branchName}`;
4437
+ let foundPath = FEATURE_WORKTREE_CACHE.get(cacheKey);
4438
+ if (typeof foundPath === "undefined") {
4439
+ foundPath = findWorktreePathForBranch(projectGitCwd, branchName) || null;
4440
+ FEATURE_WORKTREE_CACHE.set(cacheKey, foundPath);
4441
+ }
4442
+ if (!foundPath) continue;
4443
+ return {
4444
+ cwd: foundPath,
4445
+ branch: getCurrentBranch(foundPath) || branchName
4446
+ };
4447
+ }
4448
+ return void 0;
4449
+ }
4229
4450
  function toUpperToken(value) {
4230
4451
  if (typeof value !== "string") return void 0;
4231
4452
  const normalized = value.trim().toUpperCase();
@@ -4268,7 +4489,7 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
4268
4489
  "view",
4269
4490
  prRef,
4270
4491
  "--json",
4271
- "reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
4492
+ "state,mergedAt,reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
4272
4493
  ],
4273
4494
  {
4274
4495
  cwd: projectGitCwd,
@@ -4283,6 +4504,9 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
4283
4504
  return null;
4284
4505
  }
4285
4506
  const parsed = JSON.parse(raw);
4507
+ const state = toUpperToken(parsed.state);
4508
+ const mergedAt = typeof parsed.mergedAt === "string" && parsed.mergedAt.trim().length > 0 ? parsed.mergedAt.trim() : void 0;
4509
+ const isMerged = state === "MERGED" || !!mergedAt;
4286
4510
  const reviewDecision = toUpperToken(parsed.reviewDecision);
4287
4511
  const mergeStateStatus = toUpperToken(parsed.mergeStateStatus);
4288
4512
  const isDraft = parsed.isDraft === true;
@@ -4297,11 +4521,14 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
4297
4521
  const remote = {
4298
4522
  source: "gh",
4299
4523
  available: true,
4524
+ state,
4525
+ mergedAt,
4526
+ isMerged,
4300
4527
  reviewDecision,
4301
4528
  mergeStateStatus,
4302
4529
  isDraft,
4303
4530
  hasBlockingReview: reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED",
4304
- mergeBlocked: isDraft || isMergeBlockedState(mergeStateStatus),
4531
+ mergeBlocked: !isMerged && (isDraft || isMergeBlockedState(mergeStateStatus)),
4305
4532
  failingChecks,
4306
4533
  pendingChecks
4307
4534
  };
@@ -4325,10 +4552,10 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
4325
4552
  const normalizedCandidates = uniqueNormalizedPaths(
4326
4553
  candidates.map((candidate) => {
4327
4554
  if (!candidate) return "";
4328
- if (!path19.isAbsolute(candidate)) return candidate;
4329
- const relative = path19.relative(projectGitCwd, candidate);
4555
+ if (!path20.isAbsolute(candidate)) return candidate;
4556
+ const relative = path20.relative(projectGitCwd, candidate);
4330
4557
  if (!relative) return "";
4331
- if (relative === ".." || relative.startsWith(`..${path19.sep}`)) return "";
4558
+ if (relative === ".." || relative.startsWith(`..${path20.sep}`)) return "";
4332
4559
  return relative;
4333
4560
  }).filter(Boolean)
4334
4561
  );
@@ -4341,7 +4568,7 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
4341
4568
  if (cached) return [...cached];
4342
4569
  const existing = [];
4343
4570
  for (const candidate of normalizedCandidates) {
4344
- if (await fs15.pathExists(path19.join(projectGitCwd, candidate))) {
4571
+ if (await fs15.pathExists(path20.join(projectGitCwd, candidate))) {
4345
4572
  existing.push(candidate);
4346
4573
  }
4347
4574
  }
@@ -4430,15 +4657,15 @@ async function parseFeature(featurePath, type, context, options) {
4430
4657
  const lang = options.lang;
4431
4658
  const workflowPolicy = resolveWorkflowPolicy(options.workflow);
4432
4659
  const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
4433
- const folderName = path19.basename(featurePath);
4660
+ const folderName = path20.basename(featurePath);
4434
4661
  const match = folderName.match(/^(F\d+)-(.+)$/);
4435
4662
  const id = match?.[1];
4436
4663
  const slug = match?.[2] || folderName;
4437
- const specPath = path19.join(featurePath, "spec.md");
4438
- const planPath = path19.join(featurePath, "plan.md");
4439
- const tasksPath = path19.join(featurePath, "tasks.md");
4440
- const issueDocPath = path19.join(featurePath, "issue.md");
4441
- const prDocPath = path19.join(featurePath, "pr.md");
4664
+ const specPath = path20.join(featurePath, "spec.md");
4665
+ const planPath = path20.join(featurePath, "plan.md");
4666
+ const tasksPath = path20.join(featurePath, "tasks.md");
4667
+ const issueDocPath = path20.join(featurePath, "issue.md");
4668
+ const prDocPath = path20.join(featurePath, "pr.md");
4442
4669
  let specStatus;
4443
4670
  let issueNumber;
4444
4671
  const specExists = await fs15.pathExists(specPath);
@@ -4449,6 +4676,30 @@ async function parseFeature(featurePath, type, context, options) {
4449
4676
  const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
4450
4677
  issueNumber = parseIssueNumber(issueValue);
4451
4678
  }
4679
+ let effectiveProjectGitCwd = context.projectGitCwd;
4680
+ let effectiveProjectBranch = context.projectBranch;
4681
+ let effectiveProjectBranchAvailable = context.projectBranchAvailable;
4682
+ if (effectiveProjectGitCwd && issueNumber) {
4683
+ const alreadyExpected = isExpectedFeatureBranch(
4684
+ effectiveProjectBranch,
4685
+ issueNumber,
4686
+ slug,
4687
+ folderName
4688
+ );
4689
+ if (!alreadyExpected) {
4690
+ const worktree = resolveFeatureWorktreePath(
4691
+ effectiveProjectGitCwd,
4692
+ issueNumber,
4693
+ slug,
4694
+ folderName
4695
+ );
4696
+ if (worktree) {
4697
+ effectiveProjectGitCwd = worktree.cwd;
4698
+ effectiveProjectBranch = worktree.branch;
4699
+ effectiveProjectBranchAvailable = true;
4700
+ }
4701
+ }
4702
+ }
4452
4703
  let planStatus;
4453
4704
  const planExists = await fs15.pathExists(planPath);
4454
4705
  if (planExists) {
@@ -4588,20 +4839,20 @@ async function parseFeature(featurePath, type, context, options) {
4588
4839
  prDocPrFieldExists = hasAnySpecKey(content, ["PR", "Pull Request"]);
4589
4840
  prDocReviewStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
4590
4841
  }
4591
- if (workflowPolicy.requireReview && prStatus === "Review" && prLink && context.projectGitCwd) {
4592
- prRemote = resolvePrRemoteStatus(prLink, context.projectGitCwd) || void 0;
4842
+ if (workflowPolicy.requireReview && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
4843
+ prRemote = resolvePrRemoteStatus(prLink, effectiveProjectGitCwd) || void 0;
4593
4844
  }
4594
4845
  const warnings = [];
4595
- if (context.projectBranchAvailable === false) {
4846
+ if (effectiveProjectBranchAvailable === false) {
4596
4847
  warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
4597
4848
  }
4598
4849
  const onExpectedBranch = isExpectedFeatureBranch(
4599
- context.projectBranch,
4850
+ effectiveProjectBranch,
4600
4851
  issueNumber,
4601
4852
  slug,
4602
4853
  folderName
4603
4854
  );
4604
- const relativeFeaturePathFromDocs = path19.relative(context.docsDir, featurePath);
4855
+ const relativeFeaturePathFromDocs = path20.relative(context.docsDir, featurePath);
4605
4856
  const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
4606
4857
  const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
4607
4858
  let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
@@ -4625,10 +4876,10 @@ async function parseFeature(featurePath, type, context, options) {
4625
4876
  }
4626
4877
  let projectHasUncommittedChanges = typeof context.projectHasUncommittedChanges === "boolean" ? context.projectHasUncommittedChanges : false;
4627
4878
  let projectStatusUnavailable = !!context.projectStatusUnavailable;
4628
- if (typeof context.projectHasUncommittedChanges !== "boolean" && context.projectGitCwd) {
4879
+ if (typeof context.projectHasUncommittedChanges !== "boolean" && effectiveProjectGitCwd) {
4629
4880
  const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
4630
4881
  const projectCacheKey = JSON.stringify({
4631
- projectGitCwd: context.projectGitCwd,
4882
+ projectGitCwd: effectiveProjectGitCwd,
4632
4883
  docsDir: context.docsDir,
4633
4884
  type,
4634
4885
  dirtyScopePolicy,
@@ -4642,19 +4893,19 @@ async function parseFeature(featurePath, type, context, options) {
4642
4893
  let projectStatusPaths = [];
4643
4894
  if (dirtyScopePolicy === "component" && type !== "single") {
4644
4895
  const componentStatusPaths = await resolveComponentStatusPaths(
4645
- context.projectGitCwd,
4896
+ effectiveProjectGitCwd,
4646
4897
  type,
4647
4898
  options.workflow
4648
4899
  );
4649
- projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(context.projectGitCwd, context.docsDir);
4900
+ projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(effectiveProjectGitCwd, context.docsDir);
4650
4901
  } else {
4651
4902
  projectStatusPaths = resolveProjectStatusPaths(
4652
- context.projectGitCwd,
4903
+ effectiveProjectGitCwd,
4653
4904
  context.docsDir
4654
4905
  );
4655
4906
  }
4656
4907
  const projectStatus = getGitStatusPorcelain(
4657
- context.projectGitCwd,
4908
+ effectiveProjectGitCwd,
4658
4909
  projectStatusPaths
4659
4910
  );
4660
4911
  projectStatusUnavailable = projectStatus === void 0;
@@ -4823,10 +5074,10 @@ async function parseFeature(featurePath, type, context, options) {
4823
5074
  pr: { link: prLink, status: prStatus, remote: prRemote },
4824
5075
  git: {
4825
5076
  docsBranch: context.docsBranch,
4826
- projectBranch: context.projectBranch,
4827
- projectBranchAvailable: context.projectBranchAvailable,
5077
+ projectBranch: effectiveProjectBranch,
5078
+ projectBranchAvailable: effectiveProjectBranchAvailable,
4828
5079
  docsGitCwd: context.docsGitCwd,
4829
- projectGitCwd: context.projectGitCwd,
5080
+ projectGitCwd: effectiveProjectGitCwd,
4830
5081
  onExpectedBranch,
4831
5082
  docsEverCommitted,
4832
5083
  docsHasUncommittedChanges,
@@ -4999,7 +5250,7 @@ async function scanFeatures(config) {
4999
5250
  }
5000
5251
  }
5001
5252
  const relativeFeaturePaths = allFeatureDirs.map(
5002
- (dir) => normalizeRelPath(path19.relative(config.docsDir, dir))
5253
+ (dir) => normalizeRelPath(path20.relative(config.docsDir, dir))
5003
5254
  );
5004
5255
  const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
5005
5256
  const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
@@ -5010,7 +5261,7 @@ async function scanFeatures(config) {
5010
5261
  const parsed = await Promise.all(
5011
5262
  target.dirs.map(async (dir) => {
5012
5263
  const relativeFeaturePathFromDocs = normalizeRelPath(
5013
- path19.relative(config.docsDir, dir)
5264
+ path20.relative(config.docsDir, dir)
5014
5265
  );
5015
5266
  const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
5016
5267
  return parseFeature(
@@ -5079,13 +5330,13 @@ async function runStatus(options) {
5079
5330
  );
5080
5331
  }
5081
5332
  const { docsDir, projectType, projectName, lang } = config;
5082
- const featuresDir = path19.join(docsDir, "features");
5333
+ const featuresDir = path20.join(docsDir, "features");
5083
5334
  const scan = await scanFeatures(config);
5084
5335
  const features = [];
5085
5336
  const idMap = /* @__PURE__ */ new Map();
5086
5337
  for (const f of scan.features) {
5087
5338
  const id = f.id || "UNKNOWN";
5088
- const relPath = path19.relative(docsDir, f.path);
5339
+ const relPath = path20.relative(docsDir, f.path);
5089
5340
  if (!idMap.has(id)) idMap.set(id, []);
5090
5341
  idMap.get(id).push(relPath);
5091
5342
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -5166,7 +5417,7 @@ async function runStatus(options) {
5166
5417
  }
5167
5418
  console.log();
5168
5419
  if (options.write) {
5169
- const outputPath = path19.join(featuresDir, "status.md");
5420
+ const outputPath = path20.join(featuresDir, "status.md");
5170
5421
  const date = getLocalDateString();
5171
5422
  const content = [
5172
5423
  "# Feature Status",
@@ -5194,7 +5445,7 @@ function escapeRegExp2(value) {
5194
5445
  }
5195
5446
  async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
5196
5447
  try {
5197
- const specPath = path19.join(featureDir, "spec.md");
5448
+ const specPath = path20.join(featureDir, "spec.md");
5198
5449
  if (!await fs15.pathExists(specPath)) return fallbackSlug;
5199
5450
  const content = await fs15.readFile(specPath, "utf-8");
5200
5451
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
@@ -5276,10 +5527,10 @@ async function runUpdate(options) {
5276
5527
  console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
5277
5528
  }
5278
5529
  if (agentsMode === "all") {
5279
- const commonAgentsBase = path19.join(templatesDir, lang, "common", "agents");
5280
- const targetAgentsBase = path19.join(docsDir, "agents");
5281
- const commonAgents = agentsMode === "skills" ? path19.join(commonAgentsBase, "skills") : commonAgentsBase;
5282
- const targetAgents = agentsMode === "skills" ? path19.join(targetAgentsBase, "skills") : targetAgentsBase;
5530
+ const commonAgentsBase = path20.join(templatesDir, lang, "common", "agents");
5531
+ const targetAgentsBase = path20.join(docsDir, "agents");
5532
+ const commonAgents = commonAgentsBase;
5533
+ const targetAgents = targetAgentsBase;
5283
5534
  const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
5284
5535
  const projectName = config.projectName ?? "{{projectName}}";
5285
5536
  const commonReplacements = {
@@ -5306,7 +5557,7 @@ async function runUpdate(options) {
5306
5557
  }
5307
5558
  console.log(
5308
5559
  chalk6.green(
5309
- ` \u2705 ${agentsMode === "skills" ? tr(lang, "cli", "update.skillsUpdated") : tr(lang, "cli", "update.agentsUpdated")}`
5560
+ ` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
5310
5561
  )
5311
5562
  );
5312
5563
  }
@@ -5359,7 +5610,7 @@ function normalizeSkillList2(raw) {
5359
5610
  return [...deduped];
5360
5611
  }
5361
5612
  async function backfillMissingConfigDefaults(docsDir) {
5362
- const configPath = path19.join(docsDir, ".lee-spec-kit.json");
5613
+ const configPath = path20.join(docsDir, ".lee-spec-kit.json");
5363
5614
  if (!await fs15.pathExists(configPath)) {
5364
5615
  return { changed: false, changedPaths: [] };
5365
5616
  }
@@ -5380,7 +5631,7 @@ async function backfillMissingConfigDefaults(docsDir) {
5380
5631
  const workflow = raw.workflow;
5381
5632
  setIfMissing(workflow, "mode", "github", "workflow.mode");
5382
5633
  setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
5383
- setIfMissing(workflow, "taskCommitGate", "strict", "workflow.taskCommitGate");
5634
+ setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
5384
5635
  if (!isPlainObject(workflow.prePrReview)) {
5385
5636
  workflow.prePrReview = {};
5386
5637
  changedPaths.push("workflow.prePrReview");
@@ -5432,8 +5683,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
5432
5683
  const files = await fs15.readdir(sourceDir);
5433
5684
  let updatedCount = 0;
5434
5685
  for (const file of files) {
5435
- const sourcePath = path19.join(sourceDir, file);
5436
- const targetPath = path19.join(targetDir, file);
5686
+ const sourcePath = path20.join(sourceDir, file);
5687
+ const targetPath = path20.join(targetDir, file);
5437
5688
  const stat = await fs15.stat(sourcePath);
5438
5689
  if (stat.isFile()) {
5439
5690
  if (protectedFiles.has(file)) {
@@ -5516,7 +5767,7 @@ function extractPorcelainPaths(line) {
5516
5767
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
5517
5768
  const top = getGitTopLevel2(docsDir);
5518
5769
  if (!top) return null;
5519
- const rel = path19.relative(top, docsDir) || ".";
5770
+ const rel = path20.relative(top, docsDir) || ".";
5520
5771
  try {
5521
5772
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
5522
5773
  cwd: top,
@@ -5528,7 +5779,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
5528
5779
  }
5529
5780
  const ignoredRelPaths = new Set(
5530
5781
  ignoredAbsPaths.map(
5531
- (absPath) => normalizeGitPath2(path19.relative(top, absPath) || ".")
5782
+ (absPath) => normalizeGitPath2(path20.relative(top, absPath) || ".")
5532
5783
  )
5533
5784
  );
5534
5785
  const filtered = output.split("\n").filter((line) => {
@@ -5585,7 +5836,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
5585
5836
  }
5586
5837
  async function runConfig(options) {
5587
5838
  const cwd = process.cwd();
5588
- const targetCwd = options.dir ? path19.resolve(cwd, options.dir) : cwd;
5839
+ const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
5589
5840
  const config = await getConfig(targetCwd);
5590
5841
  if (!config) {
5591
5842
  throw createCliError(
@@ -5593,7 +5844,7 @@ async function runConfig(options) {
5593
5844
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
5594
5845
  );
5595
5846
  }
5596
- const configPath = path19.join(config.docsDir, ".lee-spec-kit.json");
5847
+ const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
5597
5848
  if (!options.projectRoot) {
5598
5849
  console.log();
5599
5850
  console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -5609,6 +5860,7 @@ async function runConfig(options) {
5609
5860
  console.log();
5610
5861
  return;
5611
5862
  }
5863
+ const projectRoot = options.projectRoot;
5612
5864
  await withFileLock(
5613
5865
  getDocsLockPath(config.docsDir),
5614
5866
  async () => {
@@ -5662,13 +5914,13 @@ async function runConfig(options) {
5662
5914
  }
5663
5915
  assertAllowedComponent(targetComponent, components);
5664
5916
  const currentRoot = typeof configFile.projectRoot === "object" && configFile.projectRoot ? configFile.projectRoot : {};
5665
- currentRoot[targetComponent] = options.projectRoot;
5917
+ currentRoot[targetComponent] = projectRoot;
5666
5918
  configFile.projectRoot = currentRoot;
5667
5919
  console.log(
5668
5920
  chalk6.green(
5669
5921
  tr(config.lang, "cli", "config.projectRootSet", {
5670
5922
  repo: targetComponent.toUpperCase(),
5671
- path: options.projectRoot
5923
+ path: projectRoot
5672
5924
  })
5673
5925
  )
5674
5926
  );
@@ -5679,11 +5931,11 @@ async function runConfig(options) {
5679
5931
  "`--component` is only valid for multi projectRoot updates."
5680
5932
  );
5681
5933
  }
5682
- configFile.projectRoot = options.projectRoot;
5934
+ configFile.projectRoot = projectRoot;
5683
5935
  console.log(
5684
5936
  chalk6.green(
5685
5937
  tr(config.lang, "cli", "config.projectRootSetSingle", {
5686
- path: options.projectRoot
5938
+ path: projectRoot
5687
5939
  })
5688
5940
  )
5689
5941
  );
@@ -5752,6 +6004,7 @@ function getActionSummary(action) {
5752
6004
  if (action.category === "pre_pr_review") return "Run pre-PR self review";
5753
6005
  if (action.category === "pr_status_update") return "Update PR status";
5754
6006
  if (action.category === "code_review") return "Process code review feedback";
6007
+ if (action.category === "user_request_replan") return "Handle a new user request first";
5755
6008
  if (action.category === "task_execute") return "Proceed with task execution";
5756
6009
  if (action.category === "review_fix_commit") return "Commit review feedback fixes";
5757
6010
  if (action.category === "feature_done") return "Feature is complete";
@@ -5959,42 +6212,42 @@ var BUILTIN_DOC_DEFINITIONS = [
5959
6212
  {
5960
6213
  id: "agents",
5961
6214
  title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
5962
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "agents.md")
6215
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "agents.md")
5963
6216
  },
5964
6217
  {
5965
6218
  id: "git-workflow",
5966
6219
  title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
5967
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "git-workflow.md")
6220
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "git-workflow.md")
5968
6221
  },
5969
6222
  {
5970
6223
  id: "issue-doc",
5971
6224
  title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
5972
- relativePath: (_, lang) => path19.join(lang, "common", "features", "feature-base", "issue.md")
6225
+ relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "issue.md")
5973
6226
  },
5974
6227
  {
5975
6228
  id: "pr-doc",
5976
6229
  title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
5977
- relativePath: (_, lang) => path19.join(lang, "common", "features", "feature-base", "pr.md")
6230
+ relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "pr.md")
5978
6231
  },
5979
6232
  {
5980
6233
  id: "create-feature",
5981
6234
  title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
5982
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "skills", "create-feature.md")
6235
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-feature.md")
5983
6236
  },
5984
6237
  {
5985
6238
  id: "execute-task",
5986
6239
  title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
5987
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "skills", "execute-task.md")
6240
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "execute-task.md")
5988
6241
  },
5989
6242
  {
5990
6243
  id: "create-issue",
5991
6244
  title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
5992
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "skills", "create-issue.md")
6245
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-issue.md")
5993
6246
  },
5994
6247
  {
5995
6248
  id: "create-pr",
5996
6249
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
5997
- relativePath: (_, lang) => path19.join(lang, "common", "agents", "skills", "create-pr.md")
6250
+ relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-pr.md")
5998
6251
  }
5999
6252
  ];
6000
6253
  var DOC_FOLLOWUPS = {
@@ -6030,7 +6283,8 @@ var CATEGORY_DOC_MAP = {
6030
6283
  pre_pr_review: ["create-pr"],
6031
6284
  pr_create: ["create-pr", "pr-doc", "git-workflow"],
6032
6285
  pr_status_update: ["create-pr"],
6033
- code_review: ["create-pr"]
6286
+ code_review: ["create-pr"],
6287
+ user_request_replan: ["agents", "execute-task"]
6034
6288
  };
6035
6289
  function getBuiltinDocIds() {
6036
6290
  return BUILTIN_DOC_DEFINITIONS.map((doc) => doc.id);
@@ -6081,7 +6335,7 @@ function listBuiltinDocs(projectType, lang) {
6081
6335
  id: doc.id,
6082
6336
  title: doc.title[lang],
6083
6337
  relativePath,
6084
- absolutePath: path19.join(templatesDir, relativePath)
6338
+ absolutePath: path20.join(templatesDir, relativePath)
6085
6339
  };
6086
6340
  });
6087
6341
  }
@@ -6130,7 +6384,7 @@ function parseApprovalLabel(input, validLabels) {
6130
6384
  function getApprovalTicketPaths(config) {
6131
6385
  return {
6132
6386
  runtimePath: getApprovalTicketStorePath(config.docsDir),
6133
- legacyPath: path19.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
6387
+ legacyPath: path20.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
6134
6388
  };
6135
6389
  }
6136
6390
  function getApprovalSessionId() {
@@ -6158,7 +6412,7 @@ async function loadApprovalTicketStore(storePath) {
6158
6412
  }
6159
6413
  }
6160
6414
  async function saveApprovalTicketStore(storePath, payload) {
6161
- await fs15.ensureDir(path19.dirname(storePath));
6415
+ await fs15.ensureDir(path20.dirname(storePath));
6162
6416
  await fs15.writeJson(storePath, payload, { spaces: 2 });
6163
6417
  }
6164
6418
  async function resolveApprovalTicketStoreAndPath(config, nowMs) {
@@ -6376,9 +6630,14 @@ function buildSuggestionOptions(lang, state, projectType, selectedComponent) {
6376
6630
  const showDoneCommand = `npx lee-spec-kit context --done${componentArg}`;
6377
6631
  const showAllCommand = `npx lee-spec-kit context --all${componentArg}`;
6378
6632
  const showOpenCommand = `npx lee-spec-kit context${componentArg}`;
6633
+ const runOnboardCommand = "npx lee-spec-kit onboard --strict";
6379
6634
  const rawSuggestions = [];
6380
6635
  switch (state.status) {
6381
6636
  case "no_features":
6637
+ rawSuggestions.push({
6638
+ summary: tr(lang, "cli", "context.suggestion.runOnboard"),
6639
+ command: runOnboardCommand
6640
+ });
6382
6641
  rawSuggestions.push({
6383
6642
  summary: tr(lang, "cli", "context.suggestion.createFeature"),
6384
6643
  command: createFeatureCommand
@@ -6487,7 +6746,10 @@ function getCommandExecutionLockPath(action, config) {
6487
6746
  return getProjectExecutionLockPath(action.cwd);
6488
6747
  }
6489
6748
  function contextCommand(program2) {
6490
- program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").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(
6749
+ program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
6750
+ "--json-compact",
6751
+ "Output compact JSON for agents (implies --json, reduced duplication)"
6752
+ ).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(
6491
6753
  "--approve <reply>",
6492
6754
  "Approve one labeled option (examples: A, A OK, A proceed, A \uC9C4\uD589\uD574)"
6493
6755
  ).option(
@@ -6508,7 +6770,7 @@ function contextCommand(program2) {
6508
6770
  const lang = config?.lang ?? DEFAULT_LANG;
6509
6771
  const cliError = toCliError(error);
6510
6772
  const suggestions = getCliErrorSuggestions(cliError.code, lang);
6511
- if (options.json) {
6773
+ if (options.json || options.jsonCompact) {
6512
6774
  console.log(
6513
6775
  JSON.stringify({
6514
6776
  status: "error",
@@ -6600,6 +6862,111 @@ function getMultipleFeaturesRecommendation(projectType, selectedComponent) {
6600
6862
  }
6601
6863
  return "Multiple features detected across components. Please specify feature name (slug | F001 | F001-slug) or use --component.";
6602
6864
  }
6865
+ function getFeatureRef(feature) {
6866
+ return feature.folderName || `${feature.type}:${feature.slug}`;
6867
+ }
6868
+ function toCompactFeature(feature) {
6869
+ if (!feature) return null;
6870
+ return {
6871
+ ref: getFeatureRef(feature),
6872
+ id: feature.id ?? null,
6873
+ slug: feature.slug,
6874
+ folderName: feature.folderName,
6875
+ type: feature.type,
6876
+ path: feature.path,
6877
+ currentStep: feature.currentStep,
6878
+ nextAction: feature.nextAction,
6879
+ completion: feature.completion,
6880
+ specStatus: feature.specStatus,
6881
+ planStatus: feature.planStatus,
6882
+ tasks: feature.tasks,
6883
+ prePrReview: {
6884
+ status: feature.prePrReview.status,
6885
+ findings: feature.prePrReview.findings,
6886
+ evidenceProvided: feature.prePrReview.evidenceProvided
6887
+ },
6888
+ prReview: {
6889
+ findings: feature.prReview.findings,
6890
+ evidenceProvided: feature.prReview.evidenceProvided
6891
+ },
6892
+ pr: {
6893
+ link: feature.pr.link,
6894
+ status: feature.pr.status,
6895
+ remote: feature.pr.remote
6896
+ },
6897
+ git: {
6898
+ docsBranch: feature.git.docsBranch,
6899
+ projectBranch: feature.git.projectBranch,
6900
+ projectBranchAvailable: feature.git.projectBranchAvailable,
6901
+ onExpectedBranch: feature.git.onExpectedBranch,
6902
+ docsEverCommitted: feature.git.docsEverCommitted,
6903
+ docsHasUncommittedChanges: feature.git.docsHasUncommittedChanges,
6904
+ projectHasUncommittedChanges: feature.git.projectHasUncommittedChanges,
6905
+ docsPathIgnored: feature.git.docsPathIgnored
6906
+ },
6907
+ docs: {
6908
+ specExists: feature.docs.specExists,
6909
+ planExists: feature.docs.planExists,
6910
+ tasksExists: feature.docs.tasksExists,
6911
+ issueDocIssueFieldExists: feature.docs.issueDocIssueFieldExists,
6912
+ prDocPrFieldExists: feature.docs.prDocPrFieldExists,
6913
+ prDocReviewStatusFieldExists: feature.docs.prDocReviewStatusFieldExists,
6914
+ prFieldExists: feature.docs.prFieldExists,
6915
+ prStatusFieldExists: feature.docs.prStatusFieldExists,
6916
+ prePrReviewFieldExists: feature.docs.prePrReviewFieldExists,
6917
+ prePrFindingsFieldExists: feature.docs.prePrFindingsFieldExists,
6918
+ prePrEvidenceFieldExists: feature.docs.prePrEvidenceFieldExists,
6919
+ prReviewFindingsFieldExists: feature.docs.prReviewFindingsFieldExists,
6920
+ prReviewEvidenceFieldExists: feature.docs.prReviewEvidenceFieldExists
6921
+ },
6922
+ warnings: feature.warnings
6923
+ };
6924
+ }
6925
+ function toCompactActionOption(option) {
6926
+ const base = {
6927
+ label: option.label,
6928
+ summary: option.summary,
6929
+ detail: option.detail,
6930
+ approvalPrompt: option.approvalPrompt,
6931
+ actionType: option.action.type,
6932
+ category: option.action.category,
6933
+ operationType: option.action.operationType,
6934
+ requiresUserCheck: !!option.action.requiresUserCheck
6935
+ };
6936
+ if (option.action.type === "command") {
6937
+ base.scope = option.action.scope;
6938
+ base.cwd = option.action.cwd;
6939
+ base.cmd = option.action.cmd;
6940
+ return base;
6941
+ }
6942
+ base.message = option.action.message;
6943
+ return base;
6944
+ }
6945
+ function toCompactSuggestionOption(option) {
6946
+ return {
6947
+ label: option.label,
6948
+ summary: option.summary,
6949
+ command: option.command
6950
+ };
6951
+ }
6952
+ function resolveContextRecommendation(state, projectType, selectedComponent) {
6953
+ if (state.status === "multiple_active") {
6954
+ return getMultipleFeaturesRecommendation(projectType, selectedComponent);
6955
+ }
6956
+ if (state.status === "no_features") {
6957
+ return "No features found. Run onboarding checks first, then create a feature.";
6958
+ }
6959
+ if (state.status === "no_open") {
6960
+ return "No open features found. Use `context --done` to inspect completed features.";
6961
+ }
6962
+ if (state.status === "no_match") {
6963
+ return "No features found.";
6964
+ }
6965
+ if (state.targetFeatures.length === 1) {
6966
+ return state.targetFeatures[0].nextAction;
6967
+ }
6968
+ return "No matched feature.";
6969
+ }
6603
6970
  async function runContext(featureName, options) {
6604
6971
  const cwd = process.cwd();
6605
6972
  const config = await getConfig(cwd);
@@ -6659,7 +7026,8 @@ async function runContext(featureName, options) {
6659
7026
  );
6660
7027
  return;
6661
7028
  }
6662
- if (options.json) {
7029
+ const jsonMode = !!options.json || !!options.jsonCompact;
7030
+ if (jsonMode) {
6663
7031
  const primaryAction = state.actionOptions[0] ?? null;
6664
7032
  const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
6665
7033
  const approveCommand = buildApprovalCommand(
@@ -6674,6 +7042,76 @@ async function runContext(featureName, options) {
6674
7042
  selectedComponent,
6675
7043
  true
6676
7044
  );
7045
+ const recommendation = resolveContextRecommendation(
7046
+ state,
7047
+ config.projectType,
7048
+ selectedComponent
7049
+ );
7050
+ if (options.jsonCompact) {
7051
+ const compactResult = {
7052
+ schema: "context.v2.compact",
7053
+ status: state.status,
7054
+ reasonCode: toReasonCode(state.status),
7055
+ selectionMode: state.selectionMode,
7056
+ selectionFallback: state.selectionFallback,
7057
+ branches: state.branches,
7058
+ warnings: state.warnings,
7059
+ contextVersion: state.contextVersion,
7060
+ matchedFeature: toCompactFeature(state.matchedFeature),
7061
+ candidateRefs: state.targetFeatures.length > 1 ? state.targetFeatures.map((feature) => getFeatureRef(feature)) : [],
7062
+ completedCandidateRefs: state.selectionMode === "open" ? state.doneFeatures.map((feature) => getFeatureRef(feature)) : [],
7063
+ openCandidateRefs: state.selectionMode === "open" ? state.openFeatures.map((feature) => getFeatureRef(feature)) : [],
7064
+ inProgressCandidateRefs: state.selectionMode === "open" ? state.inProgressFeatures.map((feature) => getFeatureRef(feature)) : [],
7065
+ readyToCloseCandidateRefs: state.selectionMode === "open" ? state.readyToCloseFeatures.map((feature) => getFeatureRef(feature)) : [],
7066
+ actionOptions: state.actionOptions.map((option) => toCompactActionOption(option)),
7067
+ suggestionOptions: suggestionOptions.map(
7068
+ (option) => toCompactSuggestionOption(option)
7069
+ ),
7070
+ primaryActionLabel: primaryAction?.label ?? null,
7071
+ workflowPolicy,
7072
+ taskCommitGatePolicy,
7073
+ prePrReviewPolicy,
7074
+ checkPolicy: {
7075
+ docPath: "builtin://agents/policy",
7076
+ token: "<LABEL>",
7077
+ acceptedTokens: ["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."],
7078
+ tokenPattern: "^.*\\b([A-Z]+)\\b.*$",
7079
+ validLabels: state.actionOptions.map((o) => o.label),
7080
+ oneApprovalPerAction: true,
7081
+ requireFreshContext: true,
7082
+ contextVersion: state.contextVersion,
7083
+ config: config.approval ?? { mode: "builtin" }
7084
+ },
7085
+ approvalRequest: {
7086
+ finalPrompt: finalApprovalPrompt,
7087
+ userFacingLines: [
7088
+ ...state.actionOptions.map((o) => o.approvalPrompt),
7089
+ finalApprovalPrompt
7090
+ ].filter((line) => line.length > 0),
7091
+ labels: state.actionOptions.map((o) => o.label),
7092
+ approveCommand,
7093
+ executeCommand,
7094
+ executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
7095
+ },
7096
+ suggestionRequest: {
7097
+ finalPrompt: suggestionFinalPrompt,
7098
+ userFacingLines: [
7099
+ ...suggestionOptions.map((o) => `${o.label}: ${o.summary}`),
7100
+ suggestionFinalPrompt
7101
+ ].filter((line) => line.length > 0),
7102
+ labels: suggestionOptions.map((o) => o.label)
7103
+ },
7104
+ prPolicy: {
7105
+ screenshots: {
7106
+ upload: config.pr?.screenshots?.upload ?? false
7107
+ }
7108
+ },
7109
+ requiredDocs,
7110
+ recommendation
7111
+ };
7112
+ console.log(JSON.stringify(compactResult, null, 2));
7113
+ return;
7114
+ }
6677
7115
  const result = {
6678
7116
  status: state.status,
6679
7117
  reasonCode: toReasonCode(state.status),
@@ -6765,24 +7203,8 @@ async function runContext(featureName, options) {
6765
7203
  }
6766
7204
  },
6767
7205
  requiredDocs,
6768
- recommendation: ""
7206
+ recommendation
6769
7207
  };
6770
- if (result.status === "multiple_active") {
6771
- result.recommendation = getMultipleFeaturesRecommendation(
6772
- config.projectType,
6773
- selectedComponent
6774
- );
6775
- } else if (result.status === "no_features") {
6776
- result.recommendation = "No features found. Create a feature first.";
6777
- } else if (result.status === "no_open") {
6778
- result.recommendation = "No open features found. Use `context --done` to inspect completed features.";
6779
- } else if (result.status === "no_match") {
6780
- result.recommendation = "No features found.";
6781
- } else if (state.targetFeatures.length === 1) {
6782
- result.recommendation = state.targetFeatures[0].nextAction;
6783
- } else {
6784
- result.recommendation = "No matched feature.";
6785
- }
6786
7208
  console.log(JSON.stringify(result, null, 2));
6787
7209
  return;
6788
7210
  }
@@ -6956,7 +7378,7 @@ async function runContext(featureName, options) {
6956
7378
  if (f.issueNumber) {
6957
7379
  console.log(` \u2022 Issue: #${f.issueNumber}`);
6958
7380
  }
6959
- console.log(` \u2022 Path: ${path19.relative(cwd, f.path)}`);
7381
+ console.log(` \u2022 Path: ${path20.relative(cwd, f.path)}`);
6960
7382
  if (f.git.projectBranch) {
6961
7383
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
6962
7384
  }
@@ -7055,6 +7477,7 @@ async function runContext(featureName, options) {
7055
7477
  async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
7056
7478
  const approval = options.approve || "";
7057
7479
  const ticketToken = (options.ticket || "").trim();
7480
+ const jsonMode = !!options.json || !!options.jsonCompact;
7058
7481
  let parsedLabel = null;
7059
7482
  if (state.status !== "single_matched" || !state.matchedFeature) {
7060
7483
  throw createCliError(
@@ -7115,7 +7538,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7115
7538
  label: parsedLabel,
7116
7539
  featureRef
7117
7540
  }) : null;
7118
- if (options.json) {
7541
+ if (jsonMode) {
7119
7542
  console.log(
7120
7543
  JSON.stringify(
7121
7544
  {
@@ -7194,7 +7617,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7194
7617
  `Approved label "${parsedLabel}" is instruction-only. Re-run without \`--execute\` or pick a command option.`
7195
7618
  );
7196
7619
  }
7197
- if (options.json) {
7620
+ if (jsonMode) {
7198
7621
  console.log(
7199
7622
  JSON.stringify(
7200
7623
  {
@@ -7219,7 +7642,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7219
7642
  console.log();
7220
7643
  return;
7221
7644
  }
7222
- if (!options.json) {
7645
+ if (!jsonMode) {
7223
7646
  console.log();
7224
7647
  console.log(chalk6.blue(`\u25B6 Executing option ${parsedLabel}...`));
7225
7648
  console.log(chalk6.gray(` ${selectedAction.cmd}`));
@@ -7231,12 +7654,12 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7231
7654
  lockPath,
7232
7655
  async () => executeCommandAction(
7233
7656
  selectedAction.cmd,
7234
- !!options.json,
7657
+ jsonMode,
7235
7658
  selectedAction.cwd
7236
7659
  ),
7237
7660
  { owner: `context-execute:${selectedAction.scope}` }
7238
7661
  );
7239
- if (options.json) {
7662
+ if (jsonMode) {
7240
7663
  console.log(
7241
7664
  JSON.stringify(
7242
7665
  {
@@ -7285,7 +7708,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
7285
7708
  ]);
7286
7709
  function formatPath(cwd, p) {
7287
7710
  if (!p) return "";
7288
- return path19.isAbsolute(p) ? path19.relative(cwd, p) : p;
7711
+ return path20.isAbsolute(p) ? path20.relative(cwd, p) : p;
7289
7712
  }
7290
7713
  function detectPlaceholders(content) {
7291
7714
  const patterns = [
@@ -7434,7 +7857,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
7434
7857
  const placeholderContext = {
7435
7858
  projectName: config.projectName,
7436
7859
  featureName: f.slug,
7437
- featurePath: f.docs.featurePathFromDocs || path19.relative(config.docsDir, f.path),
7860
+ featurePath: f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path),
7438
7861
  repoType: f.type,
7439
7862
  featureNumber
7440
7863
  };
@@ -7444,7 +7867,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
7444
7867
  "tasks.md"
7445
7868
  ];
7446
7869
  for (const file of files) {
7447
- const fullPath = path19.join(f.path, file);
7870
+ const fullPath = path20.join(f.path, file);
7448
7871
  if (!await fs15.pathExists(fullPath)) continue;
7449
7872
  const original = await fs15.readFile(fullPath, "utf-8");
7450
7873
  let next = original;
@@ -7489,7 +7912,7 @@ async function checkDocsStructure(config, cwd) {
7489
7912
  const issues = [];
7490
7913
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
7491
7914
  for (const dir of requiredDirs) {
7492
- const p = path19.join(config.docsDir, dir);
7915
+ const p = path20.join(config.docsDir, dir);
7493
7916
  if (!await fs15.pathExists(p)) {
7494
7917
  issues.push({
7495
7918
  level: "error",
@@ -7499,7 +7922,7 @@ async function checkDocsStructure(config, cwd) {
7499
7922
  });
7500
7923
  }
7501
7924
  }
7502
- const configPath = path19.join(config.docsDir, ".lee-spec-kit.json");
7925
+ const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
7503
7926
  if (!await fs15.pathExists(configPath)) {
7504
7927
  issues.push({
7505
7928
  level: "warn",
@@ -7522,7 +7945,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7522
7945
  }
7523
7946
  const idMap = /* @__PURE__ */ new Map();
7524
7947
  for (const f of features) {
7525
- const rel = f.docs.featurePathFromDocs || path19.relative(config.docsDir, f.path);
7948
+ const rel = f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path);
7526
7949
  const id = f.id || "UNKNOWN";
7527
7950
  if (!idMap.has(id)) idMap.set(id, []);
7528
7951
  idMap.get(id).push(rel);
@@ -7530,7 +7953,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7530
7953
  if (!isInitialTemplateState) {
7531
7954
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
7532
7955
  for (const file of featureDocs) {
7533
- const p = path19.join(f.path, file);
7956
+ const p = path20.join(f.path, file);
7534
7957
  if (!await fs15.pathExists(p)) continue;
7535
7958
  const content = await fs15.readFile(p, "utf-8");
7536
7959
  const placeholders = detectPlaceholders(content);
@@ -7545,7 +7968,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7545
7968
  });
7546
7969
  }
7547
7970
  if (decisionsPlaceholderMode !== "off") {
7548
- const decisionsPath = path19.join(f.path, "decisions.md");
7971
+ const decisionsPath = path20.join(f.path, "decisions.md");
7549
7972
  if (await fs15.pathExists(decisionsPath)) {
7550
7973
  const content = await fs15.readFile(decisionsPath, "utf-8");
7551
7974
  const placeholders = detectPlaceholders(content);
@@ -7574,7 +7997,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7574
7997
  level: "warn",
7575
7998
  code: "spec_status_unset",
7576
7999
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
7577
- path: formatPath(cwd, path19.join(f.path, "spec.md"))
8000
+ path: formatPath(cwd, path20.join(f.path, "spec.md"))
7578
8001
  });
7579
8002
  }
7580
8003
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -7582,7 +8005,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7582
8005
  level: "warn",
7583
8006
  code: "plan_status_unset",
7584
8007
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
7585
- path: formatPath(cwd, path19.join(f.path, "plan.md"))
8008
+ path: formatPath(cwd, path20.join(f.path, "plan.md"))
7586
8009
  });
7587
8010
  }
7588
8011
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -7590,7 +8013,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7590
8013
  level: "warn",
7591
8014
  code: "tasks_empty",
7592
8015
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
7593
- path: formatPath(cwd, path19.join(f.path, "tasks.md"))
8016
+ path: formatPath(cwd, path20.join(f.path, "tasks.md"))
7594
8017
  });
7595
8018
  }
7596
8019
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
@@ -7598,7 +8021,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7598
8021
  level: "warn",
7599
8022
  code: "tasks_doc_status_missing",
7600
8023
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
7601
- path: formatPath(cwd, path19.join(f.path, "tasks.md"))
8024
+ path: formatPath(cwd, path20.join(f.path, "tasks.md"))
7602
8025
  });
7603
8026
  }
7604
8027
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -7606,7 +8029,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7606
8029
  level: "warn",
7607
8030
  code: "tasks_doc_status_unset",
7608
8031
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
7609
- path: formatPath(cwd, path19.join(f.path, "tasks.md"))
8032
+ path: formatPath(cwd, path20.join(f.path, "tasks.md"))
7610
8033
  });
7611
8034
  }
7612
8035
  }
@@ -7630,7 +8053,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
7630
8053
  level: "warn",
7631
8054
  code: "missing_feature_id",
7632
8055
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
7633
- path: formatPath(cwd, path19.join(config.docsDir, p))
8056
+ path: formatPath(cwd, path20.join(config.docsDir, p))
7634
8057
  });
7635
8058
  }
7636
8059
  return issues;
@@ -7752,7 +8175,7 @@ function doctorCommand(program2) {
7752
8175
  }
7753
8176
  console.log();
7754
8177
  console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
7755
- console.log(chalk6.gray(`- Docs: ${path19.relative(cwd, docsDir)}`));
8178
+ console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, docsDir)}`));
7756
8179
  console.log(chalk6.gray(`- Type: ${projectType}`));
7757
8180
  console.log(chalk6.gray(`- Lang: ${lang}`));
7758
8181
  console.log();
@@ -7925,7 +8348,7 @@ async function runView(featureName, options) {
7925
8348
  }
7926
8349
  console.log();
7927
8350
  console.log(chalk6.bold("\u{1F4CA} Workflow View"));
7928
- console.log(chalk6.gray(`- Docs: ${path19.relative(cwd, config.docsDir)}`));
8351
+ console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, config.docsDir)}`));
7929
8352
  console.log(
7930
8353
  chalk6.gray(
7931
8354
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -8298,25 +8721,25 @@ function tg(lang, key, vars = {}) {
8298
8721
  }
8299
8722
  function detectGithubCliLangSync(cwd) {
8300
8723
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
8301
- const startDirs = [explicitDocsDir ? path19.resolve(explicitDocsDir) : "", path19.resolve(cwd)].filter(Boolean);
8724
+ const startDirs = [explicitDocsDir ? path20.resolve(explicitDocsDir) : "", path20.resolve(cwd)].filter(Boolean);
8302
8725
  const scanOrder = [];
8303
8726
  const seen = /* @__PURE__ */ new Set();
8304
8727
  for (const start of startDirs) {
8305
8728
  let current = start;
8306
8729
  while (true) {
8307
- const abs = path19.resolve(current);
8730
+ const abs = path20.resolve(current);
8308
8731
  if (!seen.has(abs)) {
8309
8732
  scanOrder.push(abs);
8310
8733
  seen.add(abs);
8311
8734
  }
8312
- const parent = path19.dirname(abs);
8735
+ const parent = path20.dirname(abs);
8313
8736
  if (parent === abs) break;
8314
8737
  current = parent;
8315
8738
  }
8316
8739
  }
8317
8740
  for (const base of scanOrder) {
8318
- for (const docsDir of [path19.join(base, "docs"), base]) {
8319
- const configPath = path19.join(docsDir, ".lee-spec-kit.json");
8741
+ for (const docsDir of [path20.join(base, "docs"), base]) {
8742
+ const configPath = path20.join(docsDir, ".lee-spec-kit.json");
8320
8743
  if (fs15.existsSync(configPath)) {
8321
8744
  try {
8322
8745
  const parsed = fs15.readJsonSync(configPath);
@@ -8324,11 +8747,11 @@ function detectGithubCliLangSync(cwd) {
8324
8747
  } catch {
8325
8748
  }
8326
8749
  }
8327
- const agentsPath = path19.join(docsDir, "agents");
8328
- const featuresPath = path19.join(docsDir, "features");
8750
+ const agentsPath = path20.join(docsDir, "agents");
8751
+ const featuresPath = path20.join(docsDir, "features");
8329
8752
  if (!fs15.existsSync(agentsPath) || !fs15.existsSync(featuresPath)) continue;
8330
8753
  for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
8331
- const file = path19.join(agentsPath, probe);
8754
+ const file = path20.join(agentsPath, probe);
8332
8755
  if (!fs15.existsSync(file)) continue;
8333
8756
  try {
8334
8757
  const content = fs15.readFileSync(file, "utf-8");
@@ -8426,7 +8849,7 @@ function ensureSections(body, sections, kind, lang) {
8426
8849
  }
8427
8850
  function ensureDocsExist(docsDir, relativePaths, lang) {
8428
8851
  const missing = relativePaths.filter(
8429
- (relativePath) => !fs15.existsSync(path19.join(docsDir, relativePath))
8852
+ (relativePath) => !fs15.existsSync(path20.join(docsDir, relativePath))
8430
8853
  );
8431
8854
  if (missing.length > 0) {
8432
8855
  throw createCliError(
@@ -8436,13 +8859,13 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
8436
8859
  }
8437
8860
  }
8438
8861
  function buildDefaultBodyFileName(kind, docsDir, component) {
8439
- const key = `${path19.resolve(docsDir)}::${component.trim().toLowerCase()}`;
8862
+ const key = `${path20.resolve(docsDir)}::${component.trim().toLowerCase()}`;
8440
8863
  const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
8441
8864
  return `lee-spec-kit.${digest}.${kind}.md`;
8442
8865
  }
8443
8866
  function toBodyFilePath(raw, kind, docsDir, component) {
8444
- const selected = raw?.trim() || path19.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
8445
- return path19.resolve(selected);
8867
+ const selected = raw?.trim() || path20.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
8868
+ return path20.resolve(selected);
8446
8869
  }
8447
8870
  function toProjectRootDocsPath(relativePathFromDocs) {
8448
8871
  const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -8535,6 +8958,10 @@ function resolveGithubDocsCwd(config, feature) {
8535
8958
  if (docsGitCwd) return docsGitCwd;
8536
8959
  return config.docsDir;
8537
8960
  }
8961
+ function shouldPushDocsSync(config) {
8962
+ if (config.docsRepo !== "standalone") return true;
8963
+ return config.pushDocs === true;
8964
+ }
8538
8965
  function getFeatureDocPaths(feature) {
8539
8966
  const featurePathFromDocs = feature.docs.featurePathFromDocs;
8540
8967
  return {
@@ -9257,8 +9684,8 @@ function ensureCleanWorktree(cwd, lang) {
9257
9684
  );
9258
9685
  }
9259
9686
  }
9260
- function commitAndPushPath(cwd, absPath, message, lang) {
9261
- const relativePath = path19.relative(cwd, absPath) || absPath;
9687
+ function commitAndPushPath(cwd, absPath, message, lang, options) {
9688
+ const relativePath = path20.relative(cwd, absPath) || absPath;
9262
9689
  const status = runProcessOrThrow(
9263
9690
  "git",
9264
9691
  ["status", "--porcelain=v1", "--", relativePath],
@@ -9268,6 +9695,7 @@ function commitAndPushPath(cwd, absPath, message, lang) {
9268
9695
  if (status.stdout.trim().length === 0) return;
9269
9696
  runProcessOrThrow("git", ["add", "--", relativePath], cwd, tg(lang, "stageFileFailed"));
9270
9697
  runProcessOrThrow("git", ["commit", "-m", message], cwd, tg(lang, "commitSyncFailed"));
9698
+ if (options?.pushToOrigin === false) return;
9271
9699
  const branch = gitCurrentBranch(cwd, lang);
9272
9700
  runProcessOrThrow(
9273
9701
  "git",
@@ -9390,9 +9818,9 @@ function githubCommand(program2) {
9390
9818
  [paths.specPath, paths.planPath, paths.tasksPath],
9391
9819
  config.lang
9392
9820
  );
9393
- const specContent = await fs15.readFile(path19.join(config.docsDir, paths.specPath), "utf-8");
9394
- const planContent = await fs15.readFile(path19.join(config.docsDir, paths.planPath), "utf-8");
9395
- const tasksContent = await fs15.readFile(path19.join(config.docsDir, paths.tasksPath), "utf-8");
9821
+ const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
9822
+ const planContent = await fs15.readFile(path20.join(config.docsDir, paths.planPath), "utf-8");
9823
+ const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
9396
9824
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
9397
9825
  const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
9398
9826
  slug: feature.slug,
@@ -9430,7 +9858,7 @@ function githubCommand(program2) {
9430
9858
  config.lang
9431
9859
  );
9432
9860
  } else {
9433
- await fs15.ensureDir(path19.dirname(bodyFile));
9861
+ await fs15.ensureDir(path20.dirname(bodyFile));
9434
9862
  await fs15.writeFile(bodyFile, generatedBody, "utf-8");
9435
9863
  }
9436
9864
  let issueUrl;
@@ -9529,10 +9957,10 @@ function githubCommand(program2) {
9529
9957
  const labels = parseLabels(options.labels, config.lang);
9530
9958
  const paths = getFeatureDocPaths(feature);
9531
9959
  ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
9532
- const specContent = await fs15.readFile(path19.join(config.docsDir, paths.specPath), "utf-8");
9533
- const planPath = path19.join(config.docsDir, paths.planPath);
9960
+ const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
9961
+ const planPath = path20.join(config.docsDir, paths.planPath);
9534
9962
  const planContent = await fs15.pathExists(planPath) ? await fs15.readFile(planPath, "utf-8") : "";
9535
- const tasksContent = await fs15.readFile(path19.join(config.docsDir, paths.tasksPath), "utf-8");
9963
+ const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
9536
9964
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
9537
9965
  const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
9538
9966
  issue: feature.issueNumber,
@@ -9575,13 +10003,14 @@ function githubCommand(program2) {
9575
10003
  config.lang
9576
10004
  );
9577
10005
  } else {
9578
- await fs15.ensureDir(path19.dirname(bodyFile));
10006
+ await fs15.ensureDir(path20.dirname(bodyFile));
9579
10007
  await fs15.writeFile(bodyFile, generatedBody, "utf-8");
9580
10008
  }
9581
10009
  const retryCount = toRetryCount(options.retry, config.lang);
9582
10010
  let prUrl = options.pr?.trim() || "";
9583
10011
  let mergedAttempts;
9584
10012
  let syncChanged = false;
10013
+ const pushDocsSync = shouldPushDocsSync(config);
9585
10014
  if (options.create) {
9586
10015
  const projectGitCwd = resolveGithubProjectCwd(config, feature);
9587
10016
  ensureNoTodoPlaceholders(body, tg(config.lang, "kindPr"), config.lang);
@@ -9614,6 +10043,9 @@ function githubCommand(program2) {
9614
10043
  );
9615
10044
  prUrl = created.stdout.trim();
9616
10045
  }
10046
+ if (!prUrl && options.merge) {
10047
+ prUrl = (feature.pr.link || "").trim();
10048
+ }
9617
10049
  if (!prUrl && options.merge) {
9618
10050
  throw createCliError(
9619
10051
  "INVALID_ARGUMENT",
@@ -9629,7 +10061,7 @@ function githubCommand(program2) {
9629
10061
  }
9630
10062
  if (prUrl && options.syncTasks !== false) {
9631
10063
  const synced = syncTasksPrMetadata(
9632
- path19.join(config.docsDir, paths.tasksPath),
10064
+ path20.join(config.docsDir, paths.tasksPath),
9633
10065
  prUrl,
9634
10066
  "Review",
9635
10067
  config.lang
@@ -9648,7 +10080,8 @@ function githubCommand(program2) {
9648
10080
  docsGitCwd,
9649
10081
  synced.path,
9650
10082
  message,
9651
- config.lang
10083
+ config.lang,
10084
+ { pushToOrigin: pushDocsSync }
9652
10085
  );
9653
10086
  }
9654
10087
  }
@@ -9676,7 +10109,7 @@ function githubCommand(program2) {
9676
10109
  );
9677
10110
  if (prUrl && options.syncTasks !== false) {
9678
10111
  const mergedSync = syncTasksPrMetadata(
9679
- path19.join(config.docsDir, paths.tasksPath),
10112
+ path20.join(config.docsDir, paths.tasksPath),
9680
10113
  prUrl,
9681
10114
  "Approved",
9682
10115
  config.lang
@@ -9694,7 +10127,8 @@ function githubCommand(program2) {
9694
10127
  docsGitCwd,
9695
10128
  mergedSync.path,
9696
10129
  message,
9697
- config.lang
10130
+ config.lang,
10131
+ { pushToOrigin: pushDocsSync }
9698
10132
  );
9699
10133
  }
9700
10134
  }
@@ -9881,7 +10315,7 @@ function docsCommand(program2) {
9881
10315
  );
9882
10316
  return;
9883
10317
  }
9884
- const relativeFromCwd = path19.relative(process.cwd(), loaded.entry.absolutePath);
10318
+ const relativeFromCwd = path20.relative(process.cwd(), loaded.entry.absolutePath);
9885
10319
  console.log();
9886
10320
  console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
9887
10321
  console.log(
@@ -9959,7 +10393,7 @@ function detectCommand(program2) {
9959
10393
  }
9960
10394
  async function runDetect(options) {
9961
10395
  const cwd = process.cwd();
9962
- const targetCwd = options.dir ? path19.resolve(cwd, options.dir) : cwd;
10396
+ const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
9963
10397
  const config = await getConfig(targetCwd);
9964
10398
  const detected = !!config;
9965
10399
  const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
@@ -9986,7 +10420,7 @@ async function runDetect(options) {
9986
10420
  );
9987
10421
  return;
9988
10422
  }
9989
- const configPath2 = path19.join(config.docsDir, ".lee-spec-kit.json");
10423
+ const configPath2 = path20.join(config.docsDir, ".lee-spec-kit.json");
9990
10424
  const configFilePresent2 = await fs15.pathExists(configPath2);
9991
10425
  const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
9992
10426
  console.log(
@@ -10020,7 +10454,7 @@ async function runDetect(options) {
10020
10454
  console.log();
10021
10455
  return;
10022
10456
  }
10023
- const configPath = path19.join(config.docsDir, ".lee-spec-kit.json");
10457
+ const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
10024
10458
  const configFilePresent = await fs15.pathExists(configPath);
10025
10459
  const detectionSource = configFilePresent ? "config" : "heuristic";
10026
10460
  console.log(chalk6.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
@@ -10054,6 +10488,459 @@ async function runDetect(options) {
10054
10488
  }
10055
10489
  console.log();
10056
10490
  }
10491
+ function t(lang, ko, en) {
10492
+ return lang === "ko" ? ko : en;
10493
+ }
10494
+ function quotePath(value) {
10495
+ return `"${value.replace(/"/g, '\\"')}"`;
10496
+ }
10497
+ function toSlug(value) {
10498
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
10499
+ }
10500
+ function runGit(args, cwd) {
10501
+ try {
10502
+ return execFileSync("git", args, {
10503
+ cwd,
10504
+ encoding: "utf-8",
10505
+ stdio: ["ignore", "pipe", "pipe"]
10506
+ }).trim();
10507
+ } catch {
10508
+ return void 0;
10509
+ }
10510
+ }
10511
+ function isGitRepo(cwd) {
10512
+ return runGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
10513
+ }
10514
+ function hasHeadCommit(cwd) {
10515
+ return !!runGit(["rev-parse", "--verify", "HEAD"], cwd);
10516
+ }
10517
+ function getOriginUrl(cwd) {
10518
+ const out = runGit(["remote", "get-url", "origin"], cwd);
10519
+ return out || void 0;
10520
+ }
10521
+ function hasTemplateMarkers(content) {
10522
+ const patterns = [
10523
+ /\{\{projectName\}\}/,
10524
+ /\{\{date\}\}/,
10525
+ /\(Write your project mission here\)/,
10526
+ /\(Write project-specific architecture principles here/i,
10527
+ /\(Write project code quality standards here/i,
10528
+ /\(Write project security principles here/i,
10529
+ /\(Write your project-specific rules here\)/,
10530
+ /\(Override default rules or add additional rules here\)/,
10531
+ /\(Write project-specific workflows here\)/,
10532
+ /\(Write other rules here\)/,
10533
+ /\(프로젝트의 미션을 작성하세요\)/,
10534
+ /\(프로젝트별 아키텍처 원칙을 작성하세요/,
10535
+ /\(프로젝트의 코드 품질 기준을 작성하세요/,
10536
+ /\(프로젝트의 보안 원칙을 작성하세요/,
10537
+ /\(여기에 프로젝트만의 규칙을 작성하세요\)/,
10538
+ /\(기본 규칙을 오버라이드하거나 추가 규칙을 작성하세요\)/,
10539
+ /\(프로젝트만의 워크플로우가 있다면 작성하세요\)/,
10540
+ /\(기타 규칙을 작성하세요\)/
10541
+ ];
10542
+ return patterns.some((pattern) => pattern.test(content));
10543
+ }
10544
+ async function countFeatureDirs(docsDir, projectType) {
10545
+ const pattern = projectType === "single" ? "features/*/" : "features/*/*/";
10546
+ const dirs = await glob(pattern, {
10547
+ cwd: docsDir,
10548
+ absolute: false,
10549
+ ignore: ["**/feature-base/**"]
10550
+ });
10551
+ return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
10552
+ const base = path20.posix.basename(value);
10553
+ return !!base && base !== "feature-base";
10554
+ }).length;
10555
+ }
10556
+ async function hasUserPrdFile(prdDir) {
10557
+ if (!await fs15.pathExists(prdDir)) return false;
10558
+ const files = await glob("**/*.md", {
10559
+ cwd: prdDir,
10560
+ nodir: true,
10561
+ absolute: false,
10562
+ ignore: ["**/node_modules/**"]
10563
+ });
10564
+ return files.some((relativePath) => path20.basename(relativePath).toLowerCase() !== "readme.md");
10565
+ }
10566
+ function finalizeChecks(checks) {
10567
+ const summary = checks.reduce(
10568
+ (acc, check) => {
10569
+ acc[check.status] += 1;
10570
+ return acc;
10571
+ },
10572
+ { ok: 0, warn: 0, block: 0 }
10573
+ );
10574
+ const status = summary.block > 0 ? "blocked" : summary.warn > 0 ? "needs_action" : "ready";
10575
+ return { checks, summary, status };
10576
+ }
10577
+ function printOnboardResult(lang, result) {
10578
+ console.log();
10579
+ console.log(chalk6.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks")));
10580
+ for (const check of result.checks) {
10581
+ const mark = check.status === "ok" ? chalk6.green("\u2705") : check.status === "warn" ? chalk6.yellow("\u26A0\uFE0F") : chalk6.red("\u274C");
10582
+ const level = check.status === "ok" ? chalk6.green("OK") : check.status === "warn" ? chalk6.yellow("WARN") : chalk6.red("BLOCK");
10583
+ console.log(`${mark} [${level}] ${check.title}`);
10584
+ console.log(` ${check.message}`);
10585
+ if (check.path) console.log(chalk6.gray(` path: ${check.path}`));
10586
+ if (check.suggestedCommand) {
10587
+ console.log(chalk6.gray(` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`));
10588
+ }
10589
+ }
10590
+ console.log();
10591
+ console.log(
10592
+ chalk6.bold(
10593
+ t(
10594
+ lang,
10595
+ `\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
10596
+ `Summary: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`
10597
+ )
10598
+ )
10599
+ );
10600
+ if (result.status === "ready") {
10601
+ console.log(chalk6.green(t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")));
10602
+ } else if (result.status === "needs_action") {
10603
+ console.log(chalk6.yellow(t(lang, "\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", "Some onboarding actions are required.")));
10604
+ } else {
10605
+ console.log(chalk6.red(t(lang, "\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "Onboarding is blocked by required setup.")));
10606
+ }
10607
+ console.log();
10608
+ }
10609
+ async function runOnboardChecks(config) {
10610
+ const lang = config.lang;
10611
+ const checks = [];
10612
+ const docsDir = config.docsDir;
10613
+ const docsGitReady = isGitRepo(docsDir);
10614
+ if (!docsGitReady) {
10615
+ checks.push({
10616
+ id: "docs_git_repo",
10617
+ status: "block",
10618
+ title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
10619
+ message: t(
10620
+ lang,
10621
+ "docs \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
10622
+ "Docs directory is not a git repository."
10623
+ ),
10624
+ path: docsDir,
10625
+ suggestedCommand: `git -C ${quotePath(docsDir)} init`
10626
+ });
10627
+ } else {
10628
+ checks.push({
10629
+ id: "docs_git_repo",
10630
+ status: "ok",
10631
+ title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
10632
+ message: t(lang, "docs Git \uB808\uD3EC\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Docs git repository is available."),
10633
+ path: docsDir
10634
+ });
10635
+ if (!hasHeadCommit(docsDir)) {
10636
+ checks.push({
10637
+ id: "docs_initial_commit",
10638
+ status: "warn",
10639
+ title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
10640
+ message: t(
10641
+ lang,
10642
+ "docs \uCCAB \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD08\uAE30 \uC124\uC815 \uCEE4\uBC0B\uC744 \uBA3C\uC800 \uC0DD\uC131\uD558\uC138\uC694.",
10643
+ "No initial commit found in docs repo. Create an initial setup commit first."
10644
+ ),
10645
+ path: docsDir,
10646
+ suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard setup"`
10647
+ });
10648
+ } else {
10649
+ checks.push({
10650
+ id: "docs_initial_commit",
10651
+ status: "ok",
10652
+ title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
10653
+ message: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.", "Initial commit exists in docs repo."),
10654
+ path: docsDir
10655
+ });
10656
+ }
10657
+ const docsDirty = runGit(["status", "--porcelain=v1"], docsDir);
10658
+ if (docsDirty === void 0) {
10659
+ checks.push({
10660
+ id: "docs_worktree",
10661
+ status: "warn",
10662
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10663
+ message: t(
10664
+ lang,
10665
+ "docs \uBCC0\uACBD \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
10666
+ "Unable to read docs worktree status."
10667
+ ),
10668
+ path: docsDir
10669
+ });
10670
+ } else if (docsDirty.trim().length > 0) {
10671
+ checks.push({
10672
+ id: "docs_worktree",
10673
+ status: "warn",
10674
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10675
+ message: t(
10676
+ lang,
10677
+ "\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 docs \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC2B5\uB2C8\uB2E4.",
10678
+ "Uncommitted docs changes were found."
10679
+ ),
10680
+ path: docsDir,
10681
+ suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard updates"`
10682
+ });
10683
+ } else {
10684
+ checks.push({
10685
+ id: "docs_worktree",
10686
+ status: "ok",
10687
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10688
+ message: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC\uAC00 \uAE68\uB057\uD569\uB2C8\uB2E4.", "Docs worktree is clean."),
10689
+ path: docsDir
10690
+ });
10691
+ }
10692
+ }
10693
+ const constitutionPath = path20.join(docsDir, "agents", "constitution.md");
10694
+ if (!await fs15.pathExists(constitutionPath)) {
10695
+ checks.push({
10696
+ id: "constitution_exists",
10697
+ status: "block",
10698
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10699
+ message: t(
10700
+ lang,
10701
+ "`agents/constitution.md` \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
10702
+ "`agents/constitution.md` is missing."
10703
+ ),
10704
+ path: constitutionPath,
10705
+ suggestedCommand: `npx lee-spec-kit update --agents`
10706
+ });
10707
+ } else {
10708
+ const content = await fs15.readFile(constitutionPath, "utf-8");
10709
+ if (hasTemplateMarkers(content)) {
10710
+ checks.push({
10711
+ id: "constitution_filled",
10712
+ status: "block",
10713
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10714
+ message: t(
10715
+ lang,
10716
+ "Constitution\uC5D0 \uD15C\uD50C\uB9BF placeholder\uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC73C\uB85C \uBA3C\uC800 \uC791\uC131\uD558\uC138\uC694.",
10717
+ "Constitution still contains template placeholders. Fill project-specific content first."
10718
+ ),
10719
+ path: constitutionPath
10720
+ });
10721
+ } else {
10722
+ checks.push({
10723
+ id: "constitution_filled",
10724
+ status: "ok",
10725
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10726
+ message: t(lang, "Constitution\uC774 \uC791\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Constitution looks filled."),
10727
+ path: constitutionPath
10728
+ });
10729
+ }
10730
+ }
10731
+ const customPath = path20.join(docsDir, "agents", "custom.md");
10732
+ if (await fs15.pathExists(customPath)) {
10733
+ const content = await fs15.readFile(customPath, "utf-8");
10734
+ if (hasTemplateMarkers(content)) {
10735
+ checks.push({
10736
+ id: "custom_optional",
10737
+ status: "warn",
10738
+ title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
10739
+ message: t(
10740
+ lang,
10741
+ "`agents/custom.md`\uB294 \uC120\uD0DD \uD56D\uBAA9\uC774\uC9C0\uB9CC, \uD604\uC7AC \uD15C\uD50C\uB9BF \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD544\uC694\uD558\uBA74 \uADDC\uCE59\uC744 \uC791\uC131\uD558\uC138\uC694.",
10742
+ "`agents/custom.md` is optional, but it still looks like template content. Fill it if your project needs custom rules."
10743
+ ),
10744
+ path: customPath
10745
+ });
10746
+ } else {
10747
+ checks.push({
10748
+ id: "custom_optional",
10749
+ status: "ok",
10750
+ title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
10751
+ message: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C\uAC00 \uAD6C\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Custom rules doc looks configured."),
10752
+ path: customPath
10753
+ });
10754
+ }
10755
+ }
10756
+ const prdDir = path20.join(docsDir, "prd");
10757
+ const featureCount = await countFeatureDirs(docsDir, config.projectType);
10758
+ const prdReady = await hasUserPrdFile(prdDir);
10759
+ if (!prdReady) {
10760
+ checks.push({
10761
+ id: "prd_ready",
10762
+ status: featureCount === 0 ? "block" : "warn",
10763
+ title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
10764
+ message: featureCount === 0 ? t(
10765
+ lang,
10766
+ "PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Feature \uC0DD\uC131 \uC804\uC5D0 PRD\uBD80\uD130 \uC791\uC131\uD558\uC138\uC694.",
10767
+ "PRD is empty. Write PRD first before creating features."
10768
+ ) : t(
10769
+ lang,
10770
+ "PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 Feature\uAC00 \uC788\uB2E4\uBA74 PRD\uB97C \uBCF4\uAC15\uD558\uC138\uC694.",
10771
+ "PRD is empty. If features already exist, fill PRD as soon as possible."
10772
+ ),
10773
+ path: prdDir,
10774
+ suggestedCommand: `touch ${quotePath(path20.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
10775
+ });
10776
+ } else {
10777
+ checks.push({
10778
+ id: "prd_ready",
10779
+ status: "ok",
10780
+ title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
10781
+ message: t(lang, "PRD \uBB38\uC11C\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "PRD document is present."),
10782
+ path: prdDir
10783
+ });
10784
+ }
10785
+ const workflowPolicy = resolveWorkflowPolicy(config.workflow);
10786
+ if (workflowPolicy.mode === "github") {
10787
+ const projectKeys = config.projectType === "multi" ? resolveProjectComponents(config.projectType, config.components) : ["single"];
10788
+ for (const key of projectKeys) {
10789
+ const resolved = resolveProjectGitCwd(config, key, lang);
10790
+ const title = t(
10791
+ lang,
10792
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0 (${key})` : "\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0",
10793
+ config.projectType === "multi" ? `Project git connectivity (${key})` : "Project git connectivity"
10794
+ );
10795
+ if (resolved.warning || !resolved.cwd) {
10796
+ checks.push({
10797
+ id: `project_git_${key}`,
10798
+ status: "block",
10799
+ title,
10800
+ message: resolved.warning || t(
10801
+ lang,
10802
+ "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
10803
+ "Project repository path could not be resolved."
10804
+ )
10805
+ });
10806
+ continue;
10807
+ }
10808
+ if (!isGitRepo(resolved.cwd)) {
10809
+ checks.push({
10810
+ id: `project_git_${key}`,
10811
+ status: "block",
10812
+ title,
10813
+ message: t(
10814
+ lang,
10815
+ "\uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
10816
+ "Project path is not a git repository."
10817
+ ),
10818
+ path: resolved.cwd,
10819
+ suggestedCommand: `git -C ${quotePath(resolved.cwd)} init`
10820
+ });
10821
+ continue;
10822
+ }
10823
+ const origin = getOriginUrl(resolved.cwd);
10824
+ if (!origin) {
10825
+ checks.push({
10826
+ id: `project_origin_${key}`,
10827
+ status: "block",
10828
+ title: t(
10829
+ lang,
10830
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
10831
+ config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
10832
+ ),
10833
+ message: t(
10834
+ lang,
10835
+ "GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC704\uD574 \uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC758 origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
10836
+ "Project repo origin remote is required for github workflow."
10837
+ ),
10838
+ path: resolved.cwd,
10839
+ suggestedCommand: `git -C ${quotePath(resolved.cwd)} remote add origin <git-url>`
10840
+ });
10841
+ } else {
10842
+ checks.push({
10843
+ id: `project_origin_${key}`,
10844
+ status: "ok",
10845
+ title: t(
10846
+ lang,
10847
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
10848
+ config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
10849
+ ),
10850
+ message: t(
10851
+ lang,
10852
+ `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`,
10853
+ `origin is configured: ${origin}`
10854
+ ),
10855
+ path: resolved.cwd
10856
+ });
10857
+ }
10858
+ }
10859
+ }
10860
+ if (config.docsRepo === "standalone" && config.pushDocs) {
10861
+ const origin = getOriginUrl(docsDir);
10862
+ if (!origin) {
10863
+ checks.push({
10864
+ id: "docs_origin",
10865
+ status: "block",
10866
+ title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
10867
+ message: t(
10868
+ lang,
10869
+ "standalone + pushDocs=true \uC124\uC815\uC5D0\uC11C\uB294 docs origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
10870
+ "docs origin remote is required when standalone + pushDocs=true."
10871
+ ),
10872
+ path: docsDir,
10873
+ suggestedCommand: `git -C ${quotePath(docsDir)} remote add origin <docs-git-url>`
10874
+ });
10875
+ } else {
10876
+ checks.push({
10877
+ id: "docs_origin",
10878
+ status: "ok",
10879
+ title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
10880
+ message: t(lang, `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`, `origin is configured: ${origin}`),
10881
+ path: docsDir
10882
+ });
10883
+ }
10884
+ }
10885
+ return finalizeChecks(checks);
10886
+ }
10887
+ function onboardCommand(program2) {
10888
+ program2.command("onboard").description("Run onboarding checks for initial setup").option("--json", "Output in JSON format for agents").option("--strict", "Exit with code 1 when WARN/BLOCK exists").action(async (options) => {
10889
+ try {
10890
+ await runOnboard(options);
10891
+ } catch (error) {
10892
+ const config = await getConfig(process.cwd());
10893
+ const lang = config?.lang ?? DEFAULT_LANG;
10894
+ const cliError = toCliError(error);
10895
+ const suggestions = getCliErrorSuggestions(cliError.code, lang);
10896
+ if (options.json) {
10897
+ console.log(
10898
+ JSON.stringify({
10899
+ status: "error",
10900
+ reasonCode: cliError.code,
10901
+ error: cliError.message,
10902
+ suggestions
10903
+ })
10904
+ );
10905
+ } else {
10906
+ console.error(
10907
+ chalk6.red(tr(lang, "cli", "common.errorLabel")),
10908
+ chalk6.red(`[${cliError.code}] ${cliError.message}`)
10909
+ );
10910
+ printCliErrorSuggestions(suggestions, lang);
10911
+ }
10912
+ process.exit(1);
10913
+ }
10914
+ });
10915
+ }
10916
+ async function runOnboard(options) {
10917
+ const config = await getConfig(process.cwd());
10918
+ if (!config) {
10919
+ throw createCliError(
10920
+ "CONFIG_NOT_FOUND",
10921
+ tr(DEFAULT_LANG, "cli", "common.configNotFound")
10922
+ );
10923
+ }
10924
+ const lang = config.lang;
10925
+ const result = await runOnboardChecks(config);
10926
+ if (options.json) {
10927
+ const payload = {
10928
+ status: "ok",
10929
+ reasonCode: result.status === "ready" ? "ONBOARD_READY" : result.status === "needs_action" ? "ONBOARD_NEEDS_ACTION" : "ONBOARD_BLOCKED",
10930
+ docsDir: config.docsDir,
10931
+ docsRepo: config.docsRepo || "embedded",
10932
+ workflow: resolveWorkflowPolicy(config.workflow),
10933
+ summary: result.summary,
10934
+ checks: result.checks
10935
+ };
10936
+ console.log(JSON.stringify(payload, null, 2));
10937
+ } else {
10938
+ printOnboardResult(lang, result);
10939
+ }
10940
+ if (options.strict && (result.summary.warn > 0 || result.summary.block > 0)) {
10941
+ process.exitCode = 1;
10942
+ }
10943
+ }
10057
10944
  function isBannerDisabled() {
10058
10945
  const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
10059
10946
  return v === "1";
@@ -10097,11 +10984,11 @@ ${version}
10097
10984
  }
10098
10985
  return `${ascii}${footer}`;
10099
10986
  }
10100
- var CACHE_FILE = path19.join(os.homedir(), ".lee-spec-kit-version-cache.json");
10987
+ var CACHE_FILE = path20.join(os.homedir(), ".lee-spec-kit-version-cache.json");
10101
10988
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
10102
10989
  function getCurrentVersion() {
10103
10990
  try {
10104
- const packageJsonPath = path19.join(__dirname$1, "..", "package.json");
10991
+ const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
10105
10992
  if (fs15.existsSync(packageJsonPath)) {
10106
10993
  const pkg = fs15.readJsonSync(packageJsonPath);
10107
10994
  return pkg.version;
@@ -10205,7 +11092,7 @@ function shouldCheckForUpdates() {
10205
11092
  if (shouldCheckForUpdates()) checkForUpdates();
10206
11093
  function getCliVersion() {
10207
11094
  try {
10208
- const packageJsonPath = path19.join(__dirname$1, "..", "package.json");
11095
+ const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
10209
11096
  if (fs15.existsSync(packageJsonPath)) {
10210
11097
  const pkg = fs15.readJsonSync(packageJsonPath);
10211
11098
  if (pkg?.version) return String(pkg.version);
@@ -10233,4 +11120,5 @@ flowCommand(program);
10233
11120
  githubCommand(program);
10234
11121
  docsCommand(program);
10235
11122
  detectCommand(program);
11123
+ onboardCommand(program);
10236
11124
  await program.parseAsync();