lee-spec-kit 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.en.md +16 -9
  2. package/README.md +16 -9
  3. package/dist/index.js +737 -366
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/en/common/README.md +7 -0
  7. package/templates/en/common/agents/agents.md +5 -3
  8. package/templates/en/common/agents/issue-template.md +1 -5
  9. package/templates/en/common/agents/pr-template.md +2 -11
  10. package/templates/en/common/agents/skills/create-feature.md +3 -0
  11. package/templates/en/common/agents/skills/create-issue.md +4 -7
  12. package/templates/en/common/agents/skills/create-pr.md +4 -8
  13. package/templates/en/common/agents/skills/execute-task.md +3 -0
  14. package/templates/en/common/features/README.md +19 -0
  15. package/templates/en/common/features/feature-base/decisions.md +1 -0
  16. package/templates/en/common/features/feature-base/plan.md +1 -0
  17. package/templates/en/common/features/feature-base/tasks.md +1 -0
  18. package/templates/ko/common/README.md +7 -0
  19. package/templates/ko/common/agents/agents.md +5 -3
  20. package/templates/ko/common/agents/issue-template.md +1 -5
  21. package/templates/ko/common/agents/pr-template.md +2 -11
  22. package/templates/ko/common/agents/skills/create-feature.md +3 -0
  23. package/templates/ko/common/agents/skills/create-issue.md +4 -7
  24. package/templates/ko/common/agents/skills/create-pr.md +4 -8
  25. package/templates/ko/common/agents/skills/execute-task.md +4 -0
  26. package/templates/ko/common/features/README.md +19 -0
  27. package/templates/ko/common/features/feature-base/decisions.md +1 -0
  28. package/templates/ko/common/features/feature-base/plan.md +1 -0
  29. package/templates/ko/common/features/feature-base/tasks.md +1 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import path13 from 'path';
2
+ import path15 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
5
  import fs from 'fs-extra';
@@ -8,11 +8,11 @@ import chalk9 from 'chalk';
8
8
  import { spawn, spawnSync, execFileSync, execSync } from 'child_process';
9
9
  import os from 'os';
10
10
  import { createHash, randomUUID } from 'crypto';
11
- import fs10 from 'fs';
11
+ import fs12 from 'fs';
12
12
  import { Buffer as Buffer$1 } from 'buffer';
13
13
 
14
14
  var getFilename = () => fileURLToPath(import.meta.url);
15
- var getDirname = () => path13.dirname(getFilename());
15
+ var getDirname = () => path15.dirname(getFilename());
16
16
  var __dirname$1 = /* @__PURE__ */ getDirname();
17
17
  async function walkFiles(fsAdapter, rootDir, options = {}) {
18
18
  const out = [];
@@ -25,7 +25,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
25
25
  async function visit(current) {
26
26
  const entries = await fsAdapter.readdir(current);
27
27
  for (const entryName of entries) {
28
- const absolute = path13.join(current, entryName);
28
+ const absolute = path15.join(current, entryName);
29
29
  const stat = await fsAdapter.stat(absolute);
30
30
  if (stat.isDirectory()) {
31
31
  if (ignored.has(entryName.trim().toLowerCase())) continue;
@@ -34,7 +34,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
34
34
  }
35
35
  if (!stat.isFile()) continue;
36
36
  if (normalizedExtensions.size > 0) {
37
- const ext = path13.extname(entryName).toLowerCase();
37
+ const ext = path15.extname(entryName).toLowerCase();
38
38
  if (!normalizedExtensions.has(ext)) continue;
39
39
  }
40
40
  out.push(absolute);
@@ -50,7 +50,7 @@ async function listSubdirectories(fsAdapter, rootDir) {
50
50
  const entries = await fsAdapter.readdir(rootDir);
51
51
  const dirs = [];
52
52
  for (const entryName of entries) {
53
- const absolute = path13.join(rootDir, entryName);
53
+ const absolute = path15.join(rootDir, entryName);
54
54
  const stat = await fsAdapter.stat(absolute);
55
55
  if (stat.isDirectory()) {
56
56
  dirs.push(absolute);
@@ -151,10 +151,10 @@ var DefaultFileSystemAdapter = class {
151
151
  }
152
152
  };
153
153
  var __filename2 = fileURLToPath(import.meta.url);
154
- var __dirname2 = path13.dirname(__filename2);
154
+ var __dirname2 = path15.dirname(__filename2);
155
155
  function getTemplatesDir() {
156
- const rootDir = path13.resolve(__dirname2, "..");
157
- return path13.join(rootDir, "templates");
156
+ const rootDir = path15.resolve(__dirname2, "..");
157
+ return path15.join(rootDir, "templates");
158
158
  }
159
159
 
160
160
  // src/utils/locales/ko/cli.ts
@@ -259,6 +259,7 @@ var koCli = {
259
259
  "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
260
260
  "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
261
261
  "init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
262
+ "init.log.nextSteps4": " 4. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 AGENTS.md \uC5C6\uC774 Codex\uB97C \uC4F4\uB2E4\uBA74 bootstrap \uC124\uCE58: npx lee-spec-kit setup codex-bootstrap",
262
263
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
263
264
  "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
264
265
  "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)',
@@ -277,6 +278,10 @@ var koCli = {
277
278
  "idea.nextSteps1": " 1. \uBC94\uC704, PRD Refs, \uC2B9\uACA9 \uBA54\uBAA8\uB97C \uC791\uC131",
278
279
  "idea.nextSteps2": " 2. Feature\uB85C \uC2B9\uACA9: npx lee-spec-kit feature <name> --idea {ideaId}",
279
280
  "idea.nextSteps3": " 3. Feature\uB85C \uB9CC\uB4E4\uC9C0 \uC54A\uC744 \uACBD\uC6B0 Dropped\uB85C \uD45C\uC2DC",
281
+ "setup.codexBootstrapInstalled": "\u2705 Codex bootstrap \uC124\uCE58 \uC644\uB8CC: {path}",
282
+ "setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: {path}",
283
+ "setup.codexBootstrapRemoved": "\u2705 Codex bootstrap \uC81C\uAC70 \uC644\uB8CC: {path}",
284
+ "setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
280
285
  "github.cmdGithubDescription": "GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uB3C4\uC6B0\uBBF8 (issue/pr \uBCF8\uBB38 \uD15C\uD50C\uB9BF \uC0DD\uC131, \uAC80\uC99D, merge \uC7AC\uC2DC\uB3C4)",
281
286
  "github.cmdIssueDescription": "feature \uBB38\uC11C \uAE30\uBC18 GitHub issue \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131",
282
287
  "github.cmdPrDescription": "GitHub PR \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131 + tasks \uB3D9\uAE30\uD654 + merge \uC7AC\uC2DC\uB3C4",
@@ -304,8 +309,11 @@ var koCli = {
304
309
  "github.labelsRequired": "\uCD5C\uC18C 1\uAC1C \uB77C\uBCA8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. `--labels enhancement`\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
305
310
  "github.approvalRequired": "{operation}\uC740(\uB294) \uC0AC\uC6A9\uC790 \uBA85\uC2DC \uC2B9\uC778 \uD6C4\uC5D0\uB9CC \uC2E4\uD589\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uD68D \uACF5\uC720 \uD6C4 `--confirm OK`\uB85C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
306
311
  "github.ghCommandFailed": "GitHub CLI \uBA85\uB839 \uC2E4\uD589\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
312
+ "github.issueLookupFailed": "GitHub issue \uC874\uC7AC \uC5EC\uBD80 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
307
313
  "github.ghEmptyJson": "GitHub CLI JSON \uCD9C\uB825\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
308
314
  "github.ghInvalidJson": "GitHub CLI JSON \uD30C\uC2F1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: {snippet}",
315
+ "github.invalidIssueReference": "Issue \uD544\uB4DC\uAC00 \uC62C\uBC14\uB978 GitHub issue reference \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4: {value}. `#123` \uAC19\uC740 \uC2E4\uC81C issue \uBC88\uD638\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
316
+ "github.issueNotFound": "GitHub issue {issue} \uB97C \uD604\uC7AC repository context\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uAC70\uB098 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
309
317
  "github.sectionsMissing": "{kind} \uBCF8\uBB38\uC5D0 \uD544\uC218 \uC139\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {sections}",
310
318
  "github.todoPlaceholdersRemain": "{kind} \uBCF8\uBB38\uC5D0 TODO \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uBAA9\uD45C/\uC644\uB8CC \uAE30\uC900 \uB4F1\uC744 \uCC44\uC6B4 \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
311
319
  "github.artifactModeInvalid": "`--{kind}` \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {value}. \uD5C8\uC6A9\uAC12: auto,on,off",
@@ -538,12 +546,12 @@ var koContext = {
538
546
  "context.actionDetail.featureScopeSplitKeep": "\uBD84\uD560 \uAC00\uC774\uB4DC\uB97C \uD655\uC778\uD55C \uB4A4 \uD604\uC7AC \uC774\uC288 \uBC94\uC704\uB97C \uC720\uC9C0\uD558\uACE0 \uC9C4\uD589\uD558\uC138\uC694",
539
547
  "context.actionDetail.featureScopeSplitTwo": "\uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C 2\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uC138\uC694",
540
548
  "context.actionDetail.featureScopeSplitFour": "\uAE30\uC900 \uAE30\uBC18\uC73C\uB85C 4\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB300\uB85C PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
541
- "context.actionDetail.worktreeCleanup": "\uC644\uB8CC\uB41C feature worktree\uB97C \uC815\uB9AC\uD558\uC138\uC694",
549
+ "context.actionDetail.worktreeCleanup": "feature\uB97C \uB05D\uB0B4\uB824\uBA74 worktree\uB97C \uC815\uB9AC\uD558\uC138\uC694",
542
550
  "context.actionDetail.prMetadataMigrate": "tasks.md\uC758 PR \uD56D\uBAA9 \uD615\uC2DD\uC744 \uCD5C\uC2E0 \uD15C\uD50C\uB9BF\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
543
551
  "context.actionDetail.prMetadataMigratePrFields": "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
544
552
  "context.actionDetail.prMetadataMigratePrePrReviewField": "tasks.md\uC5D0 PR \uC804 \uB9AC\uBDF0 \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
545
553
  "context.actionDetail.userRequestReplan": "\uC0C8 \uC0AC\uC6A9\uC790 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694",
546
- "context.actionDetail.featureDone": "\uC774 Feature\uC758 \uC644\uB8CC \uC870\uAC74\uC774 \uBAA8\uB450 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
554
+ "context.actionDetail.featureDone": "\uC774 Feature\uAC00 \uC644\uC804\uD788 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
547
555
  "context.actionDetail.fallback": "\uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694",
548
556
  "context.suggestion.createFeature": "\uC0C8 Feature\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4",
549
557
  "context.suggestion.runOnboard": "\uCD08\uAE30 \uC124\uC815 \uC810\uAC80(onboard)\uC744 \uC2E4\uD589\uD569\uB2C8\uB2E4",
@@ -557,6 +565,7 @@ var koContext = {
557
565
  "context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 git-workflow \uAC00\uC774\uB4DC\uB97C \uAE30\uC900\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
558
566
  "context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
559
567
  "context.list.projectCommitNeeded": "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694",
568
+ "context.list.cleanupPending": "feature \uC885\uB8CC\uB97C \uC704\uD574 worktree \uC815\uB9AC",
560
569
  "context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
561
570
  "context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
562
571
  "context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
@@ -594,7 +603,7 @@ var koSteps = {
594
603
  prePrReview: "Pre-PR \uB9AC\uBDF0",
595
604
  prCreate: "PR \uC0DD\uC131",
596
605
  codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
597
- featureDone: "Feature \uC644\uB8CC"
606
+ featureDone: "Feature \uB9C8\uBB34\uB9AC"
598
607
  };
599
608
 
600
609
  // src/utils/locales/ko/messages.ts
@@ -674,7 +683,7 @@ var koMessages = {
674
683
  featureScopeSplitTwo: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) \uAD8C\uC7A5 \uADDC\uCE59\uC0C1 40~79 \uD0DC\uC2A4\uD06C\uC774\uBA74\uC11C \uD558\uB4DC \uAE30\uC900 \uBBF8\uB9CC\uC774\uBA74 2\uAC1C \uC774\uC288 \uBD84\uD560\uC774 \uAE30\uBCF8\uC785\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C \uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. \uAC01 \uC774\uC288\uC5D0\uB294 \uB2E4\uC74C \uD15C\uD50C\uB9BF\uC744 \uAE30\uB85D\uD558\uC138\uC694: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
675
684
  featureScopeSplitFour: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}\uC774\uBA74 4\uAC1C \uC774\uC288 \uBD84\uD560\uC744 \uAC15\uD558\uAC8C \uAD8C\uC7A5\uD569\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C 4\uAC1C\uC758 \uC5F0\uAD00 \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB97C \uBA85\uC2DC\uD55C \uB4A4 PR\uC744 \uC21C\uCC28 \uBA38\uC9C0\uD558\uC138\uC694. \uAC01 \uC774\uC288 \uD15C\uD50C\uB9BF: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
676
685
  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.",
677
- 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.",
686
+ featureDone: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uB85C\uCEEC \uC815\uB9AC\uAE4C\uC9C0 \uBAA8\uB450 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uC804\uD788 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
678
687
  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."
679
688
  };
680
689
 
@@ -828,6 +837,7 @@ var enCli = {
828
837
  "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
829
838
  "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
830
839
  "init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
840
+ "init.log.nextSteps4": " 4. If you use Codex without repo-root AGENTS.md, install bootstrap: npx lee-spec-kit setup codex-bootstrap",
831
841
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
832
842
  "init.log.gitInit": "\u{1F4E6} Initializing Git...",
833
843
  "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.)',
@@ -846,6 +856,10 @@ var enCli = {
846
856
  "idea.nextSteps1": " 1. Fill scope, PRD refs, and promotion notes",
847
857
  "idea.nextSteps2": " 2. Promote it with: npx lee-spec-kit feature <name> --idea {ideaId}",
848
858
  "idea.nextSteps3": " 3. Mark it dropped if it should not become a feature",
859
+ "setup.codexBootstrapInstalled": "\u2705 Codex bootstrap installed: {path}",
860
+ "setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap already installed: {path}",
861
+ "setup.codexBootstrapRemoved": "\u2705 Codex bootstrap removed: {path}",
862
+ "setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap is already absent: {path}",
849
863
  "github.cmdGithubDescription": "GitHub workflow helpers (issue/pr templates, validation, merge retry)",
850
864
  "github.cmdIssueDescription": "Generate/create GitHub issue body from feature docs with validation",
851
865
  "github.cmdPrDescription": "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry",
@@ -873,8 +887,11 @@ var enCli = {
873
887
  "github.labelsRequired": "At least one label is required. Use `--labels enhancement`.",
874
888
  "github.approvalRequired": "{operation} requires explicit user approval. Re-run with `--confirm OK` after sharing the plan with the user.",
875
889
  "github.ghCommandFailed": "GitHub CLI command failed",
890
+ "github.issueLookupFailed": "Failed to verify GitHub issue",
876
891
  "github.ghEmptyJson": "GitHub CLI returned empty JSON output.",
877
892
  "github.ghInvalidJson": "GitHub CLI returned invalid JSON: {snippet}",
893
+ "github.invalidIssueReference": "Issue field is not a valid GitHub issue reference: {value}. Use a real issue number such as `#123`.",
894
+ "github.issueNotFound": "GitHub issue {issue} was not found or is not accessible from the current repository context.",
878
895
  "github.sectionsMissing": "{kind} body is missing required sections: {sections}",
879
896
  "github.todoPlaceholdersRemain": "{kind} body still contains TODO placeholders. Fill goals/completion criteria before creating remotely.",
880
897
  "github.artifactModeInvalid": "Invalid value for `--{kind}`: {value}. Allowed: auto,on,off",
@@ -1107,12 +1124,12 @@ var enContext = {
1107
1124
  "context.actionDetail.featureScopeSplitKeep": "Keep current issue scope and continue (after split-guide check)",
1108
1125
  "context.actionDetail.featureScopeSplitTwo": "Split into 2 linked issues using coupling/file-overlap/test/deploy criteria",
1109
1126
  "context.actionDetail.featureScopeSplitFour": "Split into 4 linked issues (criteria-based) and merge PRs in dependency order",
1110
- "context.actionDetail.worktreeCleanup": "Clean up the completed feature worktree",
1127
+ "context.actionDetail.worktreeCleanup": "Clean up the feature worktree to finish this feature",
1111
1128
  "context.actionDetail.prMetadataMigrate": "Update tasks.md PR fields to the latest template format",
1112
1129
  "context.actionDetail.prMetadataMigratePrFields": "Update tasks.md with PR/PR Status fields",
1113
1130
  "context.actionDetail.prMetadataMigratePrePrReviewField": "Add Pre-PR Review field in tasks.md",
1114
1131
  "context.actionDetail.userRequestReplan": "Handle the new user request first and re-run context",
1115
- "context.actionDetail.featureDone": "All completion checks are satisfied for this feature",
1132
+ "context.actionDetail.featureDone": "This feature is fully complete",
1116
1133
  "context.actionDetail.fallback": "Verify current status and re-run context",
1117
1134
  "context.suggestion.createFeature": "Create a new feature",
1118
1135
  "context.suggestion.runOnboard": "Run onboarding checks",
@@ -1126,6 +1143,7 @@ var enContext = {
1126
1143
  "context.tipDocsCommitRules": "Check commit message rules against the git-workflow guide.",
1127
1144
  "context.list.docsCommitNeeded": "Commit docs changes",
1128
1145
  "context.list.projectCommitNeeded": "Commit project code changes",
1146
+ "context.list.cleanupPending": "Clean up the feature worktree to finish",
1129
1147
  "context.list.issueNumberNeeded": "Fill issue number in docs",
1130
1148
  "context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
1131
1149
  "context.list.recordPrLink": "Record PR link",
@@ -1163,7 +1181,7 @@ var enSteps = {
1163
1181
  prePrReview: "Pre-PR review",
1164
1182
  prCreate: "Create PR",
1165
1183
  codeReview: "Code review",
1166
- featureDone: "Feature done"
1184
+ featureDone: "Finalize feature"
1167
1185
  };
1168
1186
 
1169
1187
  // src/utils/locales/en/messages.ts
@@ -1243,7 +1261,7 @@ var enMessages = {
1243
1261
  featureScopeSplitTwo: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Recommendation rule: 40~79 tasks and below the hard threshold usually maps to 2 issues. Follow `{guideCommand}` and split by coupling/file-overlap/test/deploy criteria. For each new issue, document this template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1244
1262
  featureScopeSplitFour: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Hard recommendation is 4 issues when tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}. Follow `{guideCommand}` and split into 4 linked issues with explicit dependency order and sequential PR merges. Use the per-issue template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1245
1263
  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.",
1246
- featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
1264
+ featureDone: "All workflow requirements and local cleanup are complete. This feature is fully finished.",
1247
1265
  fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
1248
1266
  };
1249
1267
 
@@ -1741,10 +1759,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
1741
1759
  var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
1742
1760
  var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
1743
1761
  function toScopeKey(value) {
1744
- return createHash("sha1").update(path13.resolve(value)).digest("hex").slice(0, 16);
1762
+ return createHash("sha1").update(path15.resolve(value)).digest("hex").slice(0, 16);
1745
1763
  }
1746
1764
  function getTempRuntimeDir(scopePath) {
1747
- return path13.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1765
+ return path15.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1748
1766
  }
1749
1767
  function resolveGitRuntimeDir(cwd) {
1750
1768
  try {
@@ -1758,38 +1776,38 @@ function resolveGitRuntimeDir(cwd) {
1758
1776
  }
1759
1777
  ).trim();
1760
1778
  if (!out) return null;
1761
- return path13.isAbsolute(out) ? out : path13.resolve(cwd, out);
1779
+ return path15.isAbsolute(out) ? out : path15.resolve(cwd, out);
1762
1780
  } catch {
1763
1781
  return null;
1764
1782
  }
1765
1783
  }
1766
1784
  function getRuntimeStateDir(cwd) {
1767
- const resolved = path13.resolve(cwd);
1785
+ const resolved = path15.resolve(cwd);
1768
1786
  return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
1769
1787
  }
1770
1788
  function getDocsLockPath(docsDir) {
1771
- return path13.join(
1789
+ return path15.join(
1772
1790
  getRuntimeStateDir(docsDir),
1773
1791
  "locks",
1774
1792
  `docs-${toScopeKey(docsDir)}.lock`
1775
1793
  );
1776
1794
  }
1777
1795
  function getInitLockPath(targetDir) {
1778
- return path13.join(
1779
- getRuntimeStateDir(path13.dirname(path13.resolve(targetDir))),
1796
+ return path15.join(
1797
+ getRuntimeStateDir(path15.dirname(path15.resolve(targetDir))),
1780
1798
  "locks",
1781
1799
  `init-${toScopeKey(targetDir)}.lock`
1782
1800
  );
1783
1801
  }
1784
1802
  function getApprovalTicketStorePath(docsDir) {
1785
- return path13.join(
1803
+ return path15.join(
1786
1804
  getRuntimeStateDir(docsDir),
1787
1805
  "tickets",
1788
1806
  `approval-${toScopeKey(docsDir)}.json`
1789
1807
  );
1790
1808
  }
1791
1809
  function getProjectExecutionLockPath(cwd) {
1792
- return path13.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1810
+ return path15.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1793
1811
  }
1794
1812
  async function isStaleLock(lockPath, staleMs) {
1795
1813
  try {
@@ -1830,7 +1848,7 @@ function isProcessAlive(pid) {
1830
1848
  }
1831
1849
  }
1832
1850
  async function tryAcquire(lockPath, owner) {
1833
- await fs.ensureDir(path13.dirname(lockPath));
1851
+ await fs.ensureDir(path15.dirname(lockPath));
1834
1852
  try {
1835
1853
  const fd = await fs.open(lockPath, "wx");
1836
1854
  const payload = JSON.stringify(
@@ -1907,30 +1925,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
1907
1925
  "pr-template.md"
1908
1926
  ];
1909
1927
  var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1910
- var ENGINE_MANAGED_FEATURE_PATH = path13.join(
1928
+ var ENGINE_MANAGED_FEATURE_PATH = path15.join(
1911
1929
  "features",
1912
1930
  "feature-base"
1913
1931
  );
1914
1932
  async function pruneEngineManagedDocs(docsDir) {
1915
1933
  const removed = [];
1916
1934
  for (const file of ENGINE_MANAGED_AGENT_FILES) {
1917
- const target = path13.join(docsDir, "agents", file);
1935
+ const target = path15.join(docsDir, "agents", file);
1918
1936
  if (await fs.pathExists(target)) {
1919
1937
  await fs.remove(target);
1920
- removed.push(path13.relative(docsDir, target));
1938
+ removed.push(path15.relative(docsDir, target));
1921
1939
  }
1922
1940
  }
1923
1941
  for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1924
- const target = path13.join(docsDir, "agents", dir);
1942
+ const target = path15.join(docsDir, "agents", dir);
1925
1943
  if (await fs.pathExists(target)) {
1926
1944
  await fs.remove(target);
1927
- removed.push(path13.relative(docsDir, target));
1945
+ removed.push(path15.relative(docsDir, target));
1928
1946
  }
1929
1947
  }
1930
- const featureBasePath = path13.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1948
+ const featureBasePath = path15.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1931
1949
  if (await fs.pathExists(featureBasePath)) {
1932
1950
  await fs.remove(featureBasePath);
1933
- removed.push(path13.relative(docsDir, featureBasePath));
1951
+ removed.push(path15.relative(docsDir, featureBasePath));
1934
1952
  }
1935
1953
  return removed;
1936
1954
  }
@@ -2011,7 +2029,7 @@ Auto-run continuity (main/sub-agent orchestration):
2011
2029
  3. Else run \`npx lee-spec-kit context --json-compact\` (fallback: \`--json\`) and continue from current \`actionOptions\`/\`autoRun\`.
2012
2030
  - Pause and report to user only when:
2013
2031
  - \`approvalRequest.required === true\`, or
2014
- - \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\` or \`AUTO_MANUAL_REQUIRED\`, or
2032
+ - \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\`, \`AUTO_DELEGATED_HANDOFF\`, or \`AUTO_MANUAL_REQUIRED\`, or
2015
2033
  - command execution fails (non-zero/error), or
2016
2034
  - user explicitly asks to pause.
2017
2035
 
@@ -2081,6 +2099,101 @@ async function upsertLeeSpecKitAgentsMd(filePath, options) {
2081
2099
  await fs.writeFile(filePath, next, "utf-8");
2082
2100
  return { changed: true, action: "appended" };
2083
2101
  }
2102
+ var LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN = "# lee-spec-kit:codex-bootstrap:begin";
2103
+ var LEE_SPEC_KIT_CODEX_BOOTSTRAP_END = "# lee-spec-kit:codex-bootstrap:end";
2104
+ var REQUIRED_FALLBACK = "docs/AGENTS.md";
2105
+ var REQUIRED_COMPACT_LINES = [
2106
+ "Preserve any instructions loaded from ./docs/AGENTS.md in the compacted summary.",
2107
+ "After context compression/reset, read ./docs/AGENTS.md again before resuming project-specific work."
2108
+ ];
2109
+ function renderManagedSegment2() {
2110
+ return [
2111
+ LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN,
2112
+ `project_doc_fallback_filenames = ["${REQUIRED_FALLBACK}"]`,
2113
+ 'compact_prompt = """',
2114
+ ...REQUIRED_COMPACT_LINES,
2115
+ '"""',
2116
+ LEE_SPEC_KIT_CODEX_BOOTSTRAP_END
2117
+ ].join("\n");
2118
+ }
2119
+ function renderManagedBlock2() {
2120
+ return `${renderManagedSegment2()}
2121
+
2122
+ `;
2123
+ }
2124
+ function getCodexHome() {
2125
+ const explicit = String(process.env.CODEX_HOME || "").trim();
2126
+ if (explicit) return explicit;
2127
+ return path15.join(os.homedir(), ".codex");
2128
+ }
2129
+ function getCodexConfigPath() {
2130
+ return path15.join(getCodexHome(), "config.toml");
2131
+ }
2132
+ function contentIncludesRequiredBootstrap(content) {
2133
+ return content.includes(REQUIRED_FALLBACK) && REQUIRED_COMPACT_LINES.every((line) => content.includes(line));
2134
+ }
2135
+ function hasConflictingTopLevelKey(content, key) {
2136
+ const keyPattern = new RegExp(`^\\s*${key}\\s*=`, "m");
2137
+ return keyPattern.test(content);
2138
+ }
2139
+ async function hasLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
2140
+ if (!await fs.pathExists(filePath)) return false;
2141
+ const content = await fs.readFile(filePath, "utf-8");
2142
+ return content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN) && content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END) || contentIncludesRequiredBootstrap(content);
2143
+ }
2144
+ async function upsertLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
2145
+ const block = renderManagedBlock2();
2146
+ const segment = renderManagedSegment2();
2147
+ await fs.ensureDir(path15.dirname(filePath));
2148
+ const exists = await fs.pathExists(filePath);
2149
+ if (!exists) {
2150
+ await fs.writeFile(filePath, block, "utf-8");
2151
+ return { changed: true, action: "created", filePath };
2152
+ }
2153
+ const current = await fs.readFile(filePath, "utf-8");
2154
+ const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);
2155
+ const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);
2156
+ if (beginIndex !== -1 && endIndex !== -1 && beginIndex <= endIndex) {
2157
+ const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
2158
+ const next2 = `${current.slice(0, beginIndex)}${segment}${current.slice(replaceEnd)}`;
2159
+ if (next2 === current) {
2160
+ return { changed: false, action: "noop", filePath };
2161
+ }
2162
+ await fs.writeFile(filePath, next2, "utf-8");
2163
+ return { changed: true, action: "updated", filePath };
2164
+ }
2165
+ if (hasConflictingTopLevelKey(current, "project_doc_fallback_filenames") || hasConflictingTopLevelKey(current, "compact_prompt")) {
2166
+ if (contentIncludesRequiredBootstrap(current)) {
2167
+ return { changed: false, action: "noop", filePath };
2168
+ }
2169
+ throw new Error(
2170
+ `Codex config already defines project_doc_fallback_filenames or compact_prompt outside lee-spec-kit managed block: ${filePath}`
2171
+ );
2172
+ }
2173
+ let next = current;
2174
+ if (next.length > 0 && !next.endsWith("\n")) next += "\n";
2175
+ if (next.trim().length > 0 && !next.endsWith("\n\n")) next += "\n";
2176
+ next += block;
2177
+ await fs.writeFile(filePath, next, "utf-8");
2178
+ return { changed: true, action: "appended", filePath };
2179
+ }
2180
+ async function removeLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
2181
+ if (!await fs.pathExists(filePath)) {
2182
+ return { changed: false, filePath };
2183
+ }
2184
+ const current = await fs.readFile(filePath, "utf-8");
2185
+ const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);
2186
+ const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);
2187
+ if (beginIndex === -1 || endIndex === -1 || beginIndex > endIndex) {
2188
+ return { changed: false, filePath };
2189
+ }
2190
+ const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
2191
+ let next = `${current.slice(0, beginIndex)}${current.slice(replaceEnd)}`;
2192
+ next = next.replace(/\n{3,}/g, "\n\n").trimEnd();
2193
+ if (next.length > 0) next += "\n";
2194
+ await fs.writeFile(filePath, next, "utf-8");
2195
+ return { changed: true, filePath };
2196
+ }
2084
2197
 
2085
2198
  // src/utils/init/options.ts
2086
2199
  function parseStandaloneMultiProjectRootJson(raw) {
@@ -2229,7 +2342,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
2229
2342
  }
2230
2343
  async function runInit(options) {
2231
2344
  const cwd = process.cwd();
2232
- const defaultName = path13.basename(cwd);
2345
+ const defaultName = path15.basename(cwd);
2233
2346
  let projectName = options.name || defaultName;
2234
2347
  let projectType = options.type;
2235
2348
  let components = parseComponentsOption(options.components);
@@ -2240,7 +2353,7 @@ async function runInit(options) {
2240
2353
  let docsRemote = options.docsRemote;
2241
2354
  let projectRoot;
2242
2355
  const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
2243
- const targetDir = path13.resolve(cwd, options.dir || "./docs");
2356
+ const targetDir = path15.resolve(cwd, options.dir || "./docs");
2244
2357
  const skipPrompts = !!options.yes || !!options.nonInteractive;
2245
2358
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
2246
2359
  throw createCliError(
@@ -2637,7 +2750,7 @@ async function runInit(options) {
2637
2750
  );
2638
2751
  console.log();
2639
2752
  const templatesDir = getTemplatesDir();
2640
- const commonPath = path13.join(templatesDir, lang, "common");
2753
+ const commonPath = path15.join(templatesDir, lang, "common");
2641
2754
  if (!await fs.pathExists(commonPath)) {
2642
2755
  throw new Error(
2643
2756
  tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
@@ -2646,11 +2759,11 @@ async function runInit(options) {
2646
2759
  const fsAdapter = new DefaultFileSystemAdapter();
2647
2760
  await copyTemplates(fsAdapter, commonPath, targetDir);
2648
2761
  if (projectType === "multi") {
2649
- const featuresRoot = path13.join(targetDir, "features");
2762
+ const featuresRoot = path15.join(targetDir, "features");
2650
2763
  for (const component of components) {
2651
- const componentDir = path13.join(featuresRoot, component);
2764
+ const componentDir = path15.join(featuresRoot, component);
2652
2765
  await fs.ensureDir(componentDir);
2653
- const readmePath = path13.join(componentDir, "README.md");
2766
+ const readmePath = path15.join(componentDir, "README.md");
2654
2767
  if (!await fs.pathExists(readmePath)) {
2655
2768
  await fs.writeFile(
2656
2769
  readmePath,
@@ -2711,20 +2824,20 @@ async function runInit(options) {
2711
2824
  config.projectRoot = projectRoot;
2712
2825
  }
2713
2826
  }
2714
- const configPath = path13.join(targetDir, ".lee-spec-kit.json");
2827
+ const configPath = path15.join(targetDir, ".lee-spec-kit.json");
2715
2828
  await fs.writeJson(configPath, config, { spaces: 2 });
2716
2829
  const extraCommitPathsAbs = [];
2717
2830
  try {
2718
2831
  if (docsRepo === "embedded") {
2719
2832
  const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
2720
- const agentsMdPath = path13.join(repoRoot, "AGENTS.md");
2833
+ const agentsMdPath = path15.join(repoRoot, "AGENTS.md");
2721
2834
  const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
2722
2835
  lang,
2723
2836
  docsRepo
2724
2837
  });
2725
2838
  if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
2726
2839
  } else {
2727
- await upsertLeeSpecKitAgentsMd(path13.join(targetDir, "AGENTS.md"), {
2840
+ await upsertLeeSpecKitAgentsMd(path15.join(targetDir, "AGENTS.md"), {
2728
2841
  lang,
2729
2842
  docsRepo
2730
2843
  });
@@ -2734,16 +2847,16 @@ async function runInit(options) {
2734
2847
  } else if (projectRoot && typeof projectRoot === "object") {
2735
2848
  roots.push(...Object.values(projectRoot));
2736
2849
  }
2737
- const resolvedCwd = path13.resolve(cwd);
2850
+ const resolvedCwd = path15.resolve(cwd);
2738
2851
  for (const raw of roots) {
2739
2852
  const value = String(raw || "").trim();
2740
2853
  if (!value) continue;
2741
- const abs = path13.resolve(cwd, value);
2742
- if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path13.sep}`)) {
2854
+ const abs = path15.resolve(cwd, value);
2855
+ if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path15.sep}`)) {
2743
2856
  if (await fs.pathExists(abs)) {
2744
2857
  const stat = await fs.stat(abs);
2745
2858
  if (stat.isDirectory()) {
2746
- await upsertLeeSpecKitAgentsMd(path13.join(abs, "AGENTS.md"), {
2859
+ await upsertLeeSpecKitAgentsMd(path15.join(abs, "AGENTS.md"), {
2747
2860
  lang,
2748
2861
  docsRepo
2749
2862
  });
@@ -2773,6 +2886,9 @@ async function runInit(options) {
2773
2886
  );
2774
2887
  console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps2")));
2775
2888
  console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps3")));
2889
+ if (!await hasLeeSpecKitCodexBootstrap()) {
2890
+ console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps4")));
2891
+ }
2776
2892
  console.log();
2777
2893
  },
2778
2894
  { owner: "init" }
@@ -2833,7 +2949,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
2833
2949
  console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
2834
2950
  runGitOrThrow(["init"], gitWorkdir);
2835
2951
  }
2836
- const relativePath = docsRepo === "standalone" ? "." : path13.relative(gitWorkdir, targetDir);
2952
+ const relativePath = docsRepo === "standalone" ? "." : path15.relative(gitWorkdir, targetDir);
2837
2953
  const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
2838
2954
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
2839
2955
  console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
@@ -2860,7 +2976,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
2860
2976
  console.log();
2861
2977
  return;
2862
2978
  }
2863
- const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path13.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
2979
+ const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path15.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
2864
2980
  const pathsToStage = [relativePath, ...extraRelativePaths];
2865
2981
  for (const p of pathsToStage) {
2866
2982
  runGitOrThrow(["add", p], gitWorkdir);
@@ -2896,17 +3012,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
2896
3012
  }
2897
3013
  function getAncestorDirs(startDir) {
2898
3014
  const dirs = [];
2899
- let current = path13.resolve(startDir);
3015
+ let current = path15.resolve(startDir);
2900
3016
  while (true) {
2901
3017
  dirs.push(current);
2902
- const parent = path13.dirname(current);
3018
+ const parent = path15.dirname(current);
2903
3019
  if (parent === current) break;
2904
3020
  current = parent;
2905
3021
  }
2906
3022
  return dirs;
2907
3023
  }
2908
3024
  function hasWorkspaceBoundary(dir) {
2909
- return fs.existsSync(path13.join(dir, "package.json")) || fs.existsSync(path13.join(dir, ".git"));
3025
+ return fs.existsSync(path15.join(dir, "package.json")) || fs.existsSync(path15.join(dir, ".git"));
2910
3026
  }
2911
3027
  function getSearchBaseDirs(cwd) {
2912
3028
  const ancestors = getAncestorDirs(cwd);
@@ -2922,7 +3038,7 @@ function normalizeComponentKeys(value) {
2922
3038
  return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
2923
3039
  }
2924
3040
  async function inferComponentsFromFeaturesDir(docsDir) {
2925
- const featuresPath = path13.join(docsDir, "features");
3041
+ const featuresPath = path15.join(docsDir, "features");
2926
3042
  if (!await fs.pathExists(featuresPath)) return [];
2927
3043
  const entries = await fs.readdir(featuresPath, { withFileTypes: true });
2928
3044
  const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
@@ -2933,21 +3049,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
2933
3049
  async function getConfig(cwd) {
2934
3050
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2935
3051
  const baseDirs = [
2936
- ...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
3052
+ ...explicitDocsDir ? [path15.resolve(explicitDocsDir)] : [],
2937
3053
  ...getSearchBaseDirs(cwd)
2938
3054
  ];
2939
3055
  const visitedBaseDirs = /* @__PURE__ */ new Set();
2940
3056
  const visitedDocsDirs = /* @__PURE__ */ new Set();
2941
3057
  for (const baseDir of baseDirs) {
2942
- const resolvedBaseDir = path13.resolve(baseDir);
3058
+ const resolvedBaseDir = path15.resolve(baseDir);
2943
3059
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
2944
3060
  visitedBaseDirs.add(resolvedBaseDir);
2945
- const possibleDocsDirs = [path13.join(resolvedBaseDir, "docs"), resolvedBaseDir];
3061
+ const possibleDocsDirs = [path15.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2946
3062
  for (const docsDir of possibleDocsDirs) {
2947
- const resolvedDocsDir = path13.resolve(docsDir);
3063
+ const resolvedDocsDir = path15.resolve(docsDir);
2948
3064
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
2949
3065
  visitedDocsDirs.add(resolvedDocsDir);
2950
- const configPath = path13.join(resolvedDocsDir, ".lee-spec-kit.json");
3066
+ const configPath = path15.join(resolvedDocsDir, ".lee-spec-kit.json");
2951
3067
  if (await fs.pathExists(configPath)) {
2952
3068
  try {
2953
3069
  const configFile = await fs.readJson(configPath);
@@ -2977,16 +3093,16 @@ async function getConfig(cwd) {
2977
3093
  } catch {
2978
3094
  }
2979
3095
  }
2980
- const agentsPath = path13.join(resolvedDocsDir, "agents");
2981
- const featuresPath = path13.join(resolvedDocsDir, "features");
3096
+ const agentsPath = path15.join(resolvedDocsDir, "agents");
3097
+ const featuresPath = path15.join(resolvedDocsDir, "features");
2982
3098
  if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
2983
3099
  const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
2984
3100
  const projectType = inferredComponents.length > 0 ? "multi" : "single";
2985
3101
  const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
2986
3102
  const langProbeCandidates = [
2987
- path13.join(agentsPath, "custom.md"),
2988
- path13.join(agentsPath, "constitution.md"),
2989
- path13.join(agentsPath, "agents.md")
3103
+ path15.join(agentsPath, "custom.md"),
3104
+ path15.join(agentsPath, "constitution.md"),
3105
+ path15.join(agentsPath, "agents.md")
2990
3106
  ];
2991
3107
  let lang = "en";
2992
3108
  for (const candidate of langProbeCandidates) {
@@ -3057,13 +3173,85 @@ async function patchMarkdownIfExists(filePath, transform) {
3057
3173
  await fs.writeFile(filePath, transform(content), "utf-8");
3058
3174
  }
3059
3175
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
3060
- await patchMarkdownIfExists(path13.join(featureDir, "spec.md"), sanitizeSpecForLocal);
3176
+ await patchMarkdownIfExists(path15.join(featureDir, "spec.md"), sanitizeSpecForLocal);
3061
3177
  await patchMarkdownIfExists(
3062
- path13.join(featureDir, "tasks.md"),
3178
+ path15.join(featureDir, "tasks.md"),
3063
3179
  (content) => sanitizeTasksForLocal(content, lang)
3064
3180
  );
3065
- await fs.remove(path13.join(featureDir, "issue.md"));
3066
- await fs.remove(path13.join(featureDir, "pr.md"));
3181
+ await fs.remove(path15.join(featureDir, "issue.md"));
3182
+ await fs.remove(path15.join(featureDir, "pr.md"));
3183
+ }
3184
+ var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
3185
+ var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
3186
+ function extractExplicitIdeaRef(requestText) {
3187
+ const pathMatch = requestText.match(IDEA_PATH_PATTERN);
3188
+ if (pathMatch) return pathMatch[0];
3189
+ const refMatch = requestText.match(IDEA_REF_PATTERN);
3190
+ if (refMatch) return refMatch[1];
3191
+ return null;
3192
+ }
3193
+ async function resolveIdeaReference(docsDir, ref, lang) {
3194
+ const ideasDir = path15.join(docsDir, "ideas");
3195
+ const trimmedRef = ref.trim();
3196
+ if (!trimmedRef) {
3197
+ throw createCliError(
3198
+ "INVALID_ARGUMENT",
3199
+ tr(lang, "cli", "feature.ideaNotFound", { ref })
3200
+ );
3201
+ }
3202
+ if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
3203
+ const candidate = path15.resolve(process.cwd(), trimmedRef);
3204
+ if (await fs.pathExists(candidate)) {
3205
+ return { path: candidate };
3206
+ }
3207
+ throw createCliError(
3208
+ "INVALID_ARGUMENT",
3209
+ tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3210
+ );
3211
+ }
3212
+ if (!await fs.pathExists(ideasDir)) {
3213
+ throw createCliError(
3214
+ "INVALID_ARGUMENT",
3215
+ tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3216
+ );
3217
+ }
3218
+ const entries = await fs.readdir(ideasDir, { withFileTypes: true });
3219
+ const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
3220
+ const exactName = `${trimmedRef}.md`;
3221
+ if (files.includes(exactName)) {
3222
+ return { path: path15.join(ideasDir, exactName) };
3223
+ }
3224
+ const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
3225
+ if (byId.length === 1) {
3226
+ return { path: path15.join(ideasDir, byId[0]) };
3227
+ }
3228
+ if (byId.length > 1) {
3229
+ throw createCliError(
3230
+ "INVALID_ARGUMENT",
3231
+ tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
3232
+ );
3233
+ }
3234
+ throw createCliError(
3235
+ "INVALID_ARGUMENT",
3236
+ tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3237
+ );
3238
+ }
3239
+ async function readIdeaMetadataValue(ideaPath, label) {
3240
+ const content = await fs.readFile(ideaPath, "utf-8");
3241
+ const pattern = new RegExp(`^- \\*\\*${escapeRegExp(label)}\\*\\*:\\s*(.+)$`, "m");
3242
+ const match = content.match(pattern);
3243
+ if (!match) return null;
3244
+ const value = match[1].trim();
3245
+ return value.length > 0 ? value : null;
3246
+ }
3247
+ async function deriveFeatureNameFromIdea(ideaPath) {
3248
+ const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
3249
+ if (ideaName && ideaName !== "-") return ideaName;
3250
+ const basename = path15.basename(ideaPath, ".md");
3251
+ return basename.replace(/^I\d{3,}-/, "");
3252
+ }
3253
+ function escapeRegExp(value) {
3254
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3067
3255
  }
3068
3256
 
3069
3257
  // src/commands/feature.ts
@@ -3215,19 +3403,19 @@ async function runFeature(name, options) {
3215
3403
  }
3216
3404
  let featuresDir;
3217
3405
  if (projectType === "multi") {
3218
- featuresDir = path13.join(docsDir, "features", component);
3406
+ featuresDir = path15.join(docsDir, "features", component);
3219
3407
  } else {
3220
- featuresDir = path13.join(docsDir, "features");
3408
+ featuresDir = path15.join(docsDir, "features");
3221
3409
  }
3222
3410
  const featureFolderName = `${featureId}-${name}`;
3223
- const featureDir = path13.join(featuresDir, featureFolderName);
3411
+ const featureDir = path15.join(featuresDir, featureFolderName);
3224
3412
  if (await fs.pathExists(featureDir)) {
3225
3413
  throw createCliError(
3226
3414
  "INVALID_ARGUMENT",
3227
3415
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
3228
3416
  );
3229
3417
  }
3230
- const featureBasePath = path13.join(
3418
+ const featureBasePath = path15.join(
3231
3419
  getTemplatesDir(),
3232
3420
  lang,
3233
3421
  "common",
@@ -3274,8 +3462,8 @@ async function runFeature(name, options) {
3274
3462
  await replaceInFiles(fsAdapter, featureDir, replacements);
3275
3463
  if (linkedIdea) {
3276
3464
  await stampIdeaReferenceInSpec(
3277
- path13.join(featureDir, "spec.md"),
3278
- path13.relative(featureDir, linkedIdea.path)
3465
+ path15.join(featureDir, "spec.md"),
3466
+ path15.relative(featureDir, linkedIdea.path)
3279
3467
  );
3280
3468
  await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
3281
3469
  }
@@ -3303,58 +3491,12 @@ async function runFeature(name, options) {
3303
3491
  featureName: name,
3304
3492
  component: projectType === "multi" ? component : void 0,
3305
3493
  featurePath: featureDir,
3306
- featurePathFromDocs: path13.relative(docsDir, featureDir)
3494
+ featurePathFromDocs: path15.relative(docsDir, featureDir)
3307
3495
  };
3308
3496
  },
3309
3497
  { owner: "feature" }
3310
3498
  );
3311
3499
  }
3312
- async function resolveIdeaReference(docsDir, ref, lang) {
3313
- const ideasDir = path13.join(docsDir, "ideas");
3314
- const trimmedRef = ref.trim();
3315
- if (!trimmedRef) {
3316
- throw createCliError(
3317
- "INVALID_ARGUMENT",
3318
- tr(lang, "cli", "feature.ideaNotFound", { ref })
3319
- );
3320
- }
3321
- if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
3322
- const candidate = path13.resolve(process.cwd(), trimmedRef);
3323
- if (await fs.pathExists(candidate)) {
3324
- return { path: candidate };
3325
- }
3326
- throw createCliError(
3327
- "INVALID_ARGUMENT",
3328
- tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3329
- );
3330
- }
3331
- if (!await fs.pathExists(ideasDir)) {
3332
- throw createCliError(
3333
- "INVALID_ARGUMENT",
3334
- tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3335
- );
3336
- }
3337
- const entries = await fs.readdir(ideasDir, { withFileTypes: true });
3338
- const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
3339
- const exactName = `${trimmedRef}.md`;
3340
- if (files.includes(exactName)) {
3341
- return { path: path13.join(ideasDir, exactName) };
3342
- }
3343
- const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
3344
- if (byId.length === 1) {
3345
- return { path: path13.join(ideasDir, byId[0]) };
3346
- }
3347
- if (byId.length > 1) {
3348
- throw createCliError(
3349
- "INVALID_ARGUMENT",
3350
- tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
3351
- );
3352
- }
3353
- throw createCliError(
3354
- "INVALID_ARGUMENT",
3355
- tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
3356
- );
3357
- }
3358
3500
  async function stampIdeaReferenceInSpec(specPath, relativeIdeaPath) {
3359
3501
  const normalizedPath = relativeIdeaPath.replace(/\\/g, "/");
3360
3502
  const ideaLine = `- Idea: \`${normalizedPath}\``;
@@ -3385,7 +3527,7 @@ async function markIdeaAsFeatureized(ideaPath, featureFolderName) {
3385
3527
  await fs.writeFile(ideaPath, content, "utf-8");
3386
3528
  }
3387
3529
  function replaceOrAppendIdeaMetadata(content, label, value) {
3388
- const pattern = new RegExp(`^- \\*\\*${escapeRegExp(label)}\\*\\*:.*$`, "m");
3530
+ const pattern = new RegExp(`^- \\*\\*${escapeRegExp2(label)}\\*\\*:.*$`, "m");
3389
3531
  const line = `- **${label}**: ${value}`;
3390
3532
  if (pattern.test(content)) {
3391
3533
  return content.replace(pattern, line);
@@ -3403,15 +3545,15 @@ ${heading}
3403
3545
  ${line}
3404
3546
  `;
3405
3547
  }
3406
- function escapeRegExp(value) {
3548
+ function escapeRegExp2(value) {
3407
3549
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3408
3550
  }
3409
3551
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
3410
3552
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
3411
3553
  const candidates = [
3412
- ...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
3413
- path13.resolve(cwd, "docs"),
3414
- path13.resolve(cwd)
3554
+ ...explicitDocsDir ? [path15.resolve(explicitDocsDir)] : [],
3555
+ path15.resolve(cwd, "docs"),
3556
+ path15.resolve(cwd)
3415
3557
  ];
3416
3558
  const endAt = Date.now() + timeoutMs;
3417
3559
  while (Date.now() < endAt) {
@@ -3438,12 +3580,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
3438
3580
  return getConfig(cwd);
3439
3581
  }
3440
3582
  async function getNextFeatureId(docsDir, projectType, components) {
3441
- const featuresDir = path13.join(docsDir, "features");
3583
+ const featuresDir = path15.join(docsDir, "features");
3442
3584
  let max = 0;
3443
3585
  const scanDirs = [];
3444
3586
  if (projectType === "multi") {
3445
3587
  scanDirs.push(
3446
- ...components.map((component) => path13.join(featuresDir, component))
3588
+ ...components.map((component) => path15.join(featuresDir, component))
3447
3589
  );
3448
3590
  } else {
3449
3591
  scanDirs.push(featuresDir);
@@ -3544,16 +3686,16 @@ async function runIdea(name, options) {
3544
3686
  getDocsLockPath(docsDir),
3545
3687
  async () => {
3546
3688
  const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
3547
- const ideasDir = path13.join(docsDir, "ideas");
3689
+ const ideasDir = path15.join(docsDir, "ideas");
3548
3690
  const ideaFileName = `${ideaId}-${name}.md`;
3549
- const ideaPath = path13.join(ideasDir, ideaFileName);
3691
+ const ideaPath = path15.join(ideasDir, ideaFileName);
3550
3692
  if (await fs.pathExists(ideaPath)) {
3551
3693
  throw createCliError(
3552
3694
  "INVALID_ARGUMENT",
3553
3695
  tr(lang, "cli", "idea.fileExists", { path: ideaPath })
3554
3696
  );
3555
3697
  }
3556
- const templatePath = path13.join(
3698
+ const templatePath = path15.join(
3557
3699
  getTemplatesDir(),
3558
3700
  lang,
3559
3701
  "common",
@@ -3591,7 +3733,7 @@ async function runIdea(name, options) {
3591
3733
  ideaName: name,
3592
3734
  component: component || void 0,
3593
3735
  ideaPath,
3594
- ideaPathFromDocs: path13.relative(docsDir, ideaPath)
3736
+ ideaPathFromDocs: path15.relative(docsDir, ideaPath)
3595
3737
  };
3596
3738
  },
3597
3739
  { owner: "idea" }
@@ -3609,7 +3751,7 @@ function applyIdeaTemplate(template, values) {
3609
3751
  return template.replaceAll("{idea-id}", values.ideaId).replaceAll("{idea-name}", values.name).replaceAll("{YYYY-MM-DD}", values.created).replaceAll("{{description}}", values.description).replaceAll("{component}", values.component);
3610
3752
  }
3611
3753
  async function getNextIdeaId(docsDir) {
3612
- const ideasDir = path13.join(docsDir, "ideas");
3754
+ const ideasDir = path15.join(docsDir, "ideas");
3613
3755
  let max = 0;
3614
3756
  if (await fs.pathExists(ideasDir)) {
3615
3757
  const entries = await fs.readdir(ideasDir, { withFileTypes: true });
@@ -4048,7 +4190,7 @@ function isNonNegativeIntegerValue(value) {
4048
4190
  }
4049
4191
  function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
4050
4192
  try {
4051
- const raw = fs10.readFileSync(evidencePath, "utf-8");
4193
+ const raw = fs12.readFileSync(evidencePath, "utf-8");
4052
4194
  const parsed = JSON.parse(raw);
4053
4195
  const evidenceFeature = (parsed.feature || "").toString().trim();
4054
4196
  if (evidenceFeature && evidenceFeature !== feature.folderName) {
@@ -4064,31 +4206,31 @@ function resolvePrePrReviewEvidencePath(feature) {
4064
4206
  const candidates = [];
4065
4207
  const explicit = (feature.prePrReview.evidence || "").trim();
4066
4208
  if (explicit && explicit !== "-") {
4067
- if (path13.isAbsolute(explicit)) {
4209
+ if (path15.isAbsolute(explicit)) {
4068
4210
  candidates.push(explicit);
4069
4211
  } else {
4070
- candidates.push(path13.resolve(feature.path, explicit));
4071
- candidates.push(path13.resolve(docsRoot, explicit));
4212
+ candidates.push(path15.resolve(feature.path, explicit));
4213
+ candidates.push(path15.resolve(docsRoot, explicit));
4072
4214
  const normalizedExplicit = explicit.replace(/\\/g, "/");
4073
4215
  if (normalizedExplicit.startsWith("docs/")) {
4074
4216
  const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
4075
4217
  if (withoutDocsPrefix) {
4076
- candidates.push(path13.resolve(docsRoot, withoutDocsPrefix));
4218
+ candidates.push(path15.resolve(docsRoot, withoutDocsPrefix));
4077
4219
  }
4078
4220
  }
4079
4221
  }
4080
4222
  }
4081
- candidates.push(path13.join(feature.path, "review-trace.json"));
4082
- candidates.push(path13.join(docsRoot, "review-trace.json"));
4223
+ candidates.push(path15.join(feature.path, "review-trace.json"));
4224
+ candidates.push(path15.join(docsRoot, "review-trace.json"));
4083
4225
  const seen = /* @__PURE__ */ new Set();
4084
4226
  for (const candidate of candidates) {
4085
- const abs = path13.resolve(candidate);
4227
+ const abs = path15.resolve(candidate);
4086
4228
  if (seen.has(abs)) continue;
4087
4229
  seen.add(abs);
4088
- if (!fs10.existsSync(abs)) continue;
4230
+ if (!fs12.existsSync(abs)) continue;
4089
4231
  if (!abs.toLowerCase().endsWith(".json")) continue;
4090
4232
  if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
4091
- const rel = path13.relative(docsRoot, abs).replace(/\\/g, "/");
4233
+ const rel = path15.relative(docsRoot, abs).replace(/\\/g, "/");
4092
4234
  if (rel && !rel.startsWith("../")) {
4093
4235
  return rel;
4094
4236
  }
@@ -4129,8 +4271,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
4129
4271
  }
4130
4272
  function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
4131
4273
  if (!projectGitCwd) return null;
4132
- const normalized = path13.resolve(projectGitCwd);
4133
- const marker = `${path13.sep}.worktrees${path13.sep}`;
4274
+ const normalized = path15.resolve(projectGitCwd);
4275
+ const marker = `${path15.sep}.worktrees${path15.sep}`;
4134
4276
  const markerIndex = normalized.lastIndexOf(marker);
4135
4277
  if (markerIndex <= 0) return null;
4136
4278
  const projectRoot = normalized.slice(0, markerIndex);
@@ -4140,6 +4282,10 @@ function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
4140
4282
  worktreePath: normalized
4141
4283
  };
4142
4284
  }
4285
+ function resolveFeatureWorktreeCleanupPaths(feature) {
4286
+ const cleanupCandidate = feature.git.projectInManagedWorktree ? feature.git.projectGitCwd : feature.git.expectedWorktreePath;
4287
+ return resolveManagedWorktreeCleanupPaths(cleanupCandidate);
4288
+ }
4143
4289
  function shouldBlockTaskCommitGate(policy, check) {
4144
4290
  if (policy !== "strict") return false;
4145
4291
  return !check.pass;
@@ -4173,7 +4319,7 @@ function toTaskKey(rawTitle) {
4173
4319
  function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
4174
4320
  const docsGitCwd = feature.git.docsGitCwd;
4175
4321
  const tasksRelativePath = normalizeGitRelativePath(
4176
- path13.join(feature.docs.featurePathFromDocs, "tasks.md")
4322
+ path15.join(feature.docs.featurePathFromDocs, "tasks.md")
4177
4323
  );
4178
4324
  const diff = readGitText(ctx, docsGitCwd, [
4179
4325
  "diff",
@@ -4248,7 +4394,7 @@ function checkTaskCommitGate(ctx, feature) {
4248
4394
  return { pass: true };
4249
4395
  }
4250
4396
  const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
4251
- const relativeDocsDir = path13.relative(projectGitCwd, feature.git.docsGitCwd);
4397
+ const relativeDocsDir = path15.relative(projectGitCwd, feature.git.docsGitCwd);
4252
4398
  const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
4253
4399
  if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
4254
4400
  args.push(`:(exclude)${normalizedDocsDir}/**`);
@@ -5458,35 +5604,36 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
5458
5604
  step: 15,
5459
5605
  name: tr(lang, "steps", "featureDone"),
5460
5606
  checklist: {
5461
- done: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy)
5607
+ done: (f) => f.completion.workflowDone
5462
5608
  },
5463
5609
  current: {
5464
5610
  when: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy),
5465
5611
  actions: (f) => {
5466
- const actions = [
5612
+ if (f.completion.cleanupPending) {
5613
+ const cleanupPaths = resolveFeatureWorktreeCleanupPaths(f);
5614
+ if (cleanupPaths) {
5615
+ return [
5616
+ {
5617
+ type: "command",
5618
+ category: "worktree_cleanup",
5619
+ requiresUserCheck: true,
5620
+ scope: "project",
5621
+ cwd: cleanupPaths.projectRoot,
5622
+ cmd: tr(lang, "messages", "worktreeCleanupCommand", {
5623
+ projectGitCwd: cleanupPaths.projectRoot,
5624
+ worktreePath: cleanupPaths.worktreePath
5625
+ })
5626
+ }
5627
+ ];
5628
+ }
5629
+ }
5630
+ return [
5467
5631
  {
5468
5632
  type: "instruction",
5469
5633
  category: "feature_done",
5470
5634
  message: tr(lang, "messages", "featureDone")
5471
5635
  }
5472
5636
  ];
5473
- const cleanupPaths = resolveManagedWorktreeCleanupPaths(
5474
- f.git.projectGitCwd
5475
- );
5476
- if (cleanupPaths) {
5477
- actions.push({
5478
- type: "command",
5479
- category: "worktree_cleanup",
5480
- requiresUserCheck: true,
5481
- scope: "project",
5482
- cwd: cleanupPaths.projectRoot,
5483
- cmd: tr(lang, "messages", "worktreeCleanupCommand", {
5484
- projectGitCwd: cleanupPaths.projectRoot,
5485
- worktreePath: cleanupPaths.worktreePath
5486
- })
5487
- });
5488
- }
5489
- return actions;
5490
5637
  }
5491
5638
  }
5492
5639
  }
@@ -5864,17 +6011,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
5864
6011
  }
5865
6012
  }
5866
6013
  var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
5867
- var WORKTREE_MARKER = `${path13.sep}.worktrees${path13.sep}`;
6014
+ var WORKTREE_MARKER = `${path15.sep}.worktrees${path15.sep}`;
5868
6015
  function resetContextGitCaches() {
5869
6016
  GIT_WORKTREE_CACHE.clear();
5870
6017
  }
5871
6018
  function isManagedWorktreePath(cwd) {
5872
6019
  if (!cwd) return false;
5873
- const normalized = path13.resolve(cwd);
6020
+ const normalized = path15.resolve(cwd);
5874
6021
  return normalized.includes(WORKTREE_MARKER);
5875
6022
  }
5876
6023
  function resolveProjectRootFromGitCwd(cwd) {
5877
- const normalized = path13.resolve(cwd);
6024
+ const normalized = path15.resolve(cwd);
5878
6025
  const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
5879
6026
  if (markerIndex <= 0) return normalized;
5880
6027
  const projectRoot = normalized.slice(0, markerIndex);
@@ -5893,7 +6040,7 @@ function getGitTopLevel(ctx, cwd) {
5893
6040
  }
5894
6041
  function listGitWorktrees(ctx, cwd) {
5895
6042
  const topLevel = getGitTopLevel(ctx, cwd) || cwd;
5896
- const cacheKey = path13.resolve(topLevel);
6043
+ const cacheKey = path15.resolve(topLevel);
5897
6044
  const cached = GIT_WORKTREE_CACHE.get(cacheKey);
5898
6045
  if (cached) return cached;
5899
6046
  try {
@@ -5994,12 +6141,12 @@ function countDocumentLines(content) {
5994
6141
  if (lines[lines.length - 1] === "") return lines.length - 1;
5995
6142
  return lines.length;
5996
6143
  }
5997
- function escapeRegExp2(value) {
6144
+ function escapeRegExp3(value) {
5998
6145
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5999
6146
  }
6000
6147
  function extractSpecValue(content, key) {
6001
6148
  const regex = new RegExp(
6002
- `^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:\\s*(.*)$`,
6149
+ `^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
6003
6150
  "m"
6004
6151
  );
6005
6152
  const match = content.match(regex);
@@ -6007,7 +6154,7 @@ function extractSpecValue(content, key) {
6007
6154
  }
6008
6155
  function hasSpecKey(content, key) {
6009
6156
  const regex = new RegExp(
6010
- `^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:`,
6157
+ `^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:`,
6011
6158
  "m"
6012
6159
  );
6013
6160
  return regex.test(content);
@@ -6199,7 +6346,7 @@ function splitReviewLogSections(content, headerRegex) {
6199
6346
  }
6200
6347
  function collectStructuredReviewEntries(section, keys) {
6201
6348
  const lines = section.split("\n");
6202
- const escaped = keys.map((key) => escapeRegExp2(key));
6349
+ const escaped = keys.map((key) => escapeRegExp3(key));
6203
6350
  const fieldRegex = new RegExp(
6204
6351
  `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*(.*)$`,
6205
6352
  "i"
@@ -6381,17 +6528,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
6381
6528
  if (!evidencePath) return [];
6382
6529
  if (/^https?:\/\//i.test(evidencePath)) return [];
6383
6530
  const candidates = /* @__PURE__ */ new Set();
6384
- if (path13.isAbsolute(evidencePath)) {
6385
- candidates.add(path13.resolve(evidencePath));
6531
+ if (path15.isAbsolute(evidencePath)) {
6532
+ candidates.add(path15.resolve(evidencePath));
6386
6533
  } else {
6387
- candidates.add(path13.resolve(context.featurePath, evidencePath));
6388
- candidates.add(path13.resolve(context.docsDir, evidencePath));
6389
- candidates.add(path13.resolve(path13.dirname(context.docsDir), evidencePath));
6534
+ candidates.add(path15.resolve(context.featurePath, evidencePath));
6535
+ candidates.add(path15.resolve(context.docsDir, evidencePath));
6536
+ candidates.add(path15.resolve(path15.dirname(context.docsDir), evidencePath));
6390
6537
  const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
6391
6538
  if (normalizedEvidencePath.startsWith("docs/")) {
6392
6539
  const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
6393
6540
  if (withoutDocsPrefix) {
6394
- candidates.add(path13.resolve(context.docsDir, withoutDocsPrefix));
6541
+ candidates.add(path15.resolve(context.docsDir, withoutDocsPrefix));
6395
6542
  }
6396
6543
  }
6397
6544
  }
@@ -6453,13 +6600,13 @@ function parsePrLink(value) {
6453
6600
  return trimmed;
6454
6601
  }
6455
6602
  function normalizeGitPath(value) {
6456
- return value.split(path13.sep).join("/");
6603
+ return value.split(path15.sep).join("/");
6457
6604
  }
6458
6605
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
6459
- const relativeDocsDir = path13.relative(projectGitCwd, docsDir);
6606
+ const relativeDocsDir = path15.relative(projectGitCwd, docsDir);
6460
6607
  if (!relativeDocsDir) return [];
6461
- if (path13.isAbsolute(relativeDocsDir)) return [];
6462
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path13.sep}`)) {
6608
+ if (path15.isAbsolute(relativeDocsDir)) return [];
6609
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path15.sep}`)) {
6463
6610
  return [];
6464
6611
  }
6465
6612
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
@@ -6497,7 +6644,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
6497
6644
  const seen = /* @__PURE__ */ new Set();
6498
6645
  const out = [];
6499
6646
  for (const name of names) {
6500
- const candidate = path13.resolve(projectRoot, ".worktrees", name);
6647
+ const candidate = path15.resolve(projectRoot, ".worktrees", name);
6501
6648
  if (seen.has(candidate)) continue;
6502
6649
  seen.add(candidate);
6503
6650
  out.push(candidate);
@@ -6511,7 +6658,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
6511
6658
  slug,
6512
6659
  folderName
6513
6660
  )) {
6514
- if (!fs10.existsSync(candidate)) continue;
6661
+ if (!fs12.existsSync(candidate)) continue;
6515
6662
  return candidate;
6516
6663
  }
6517
6664
  return void 0;
@@ -6541,7 +6688,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
6541
6688
  slug,
6542
6689
  folderName
6543
6690
  )) {
6544
- if (!fs10.existsSync(candidate)) continue;
6691
+ if (!fs12.existsSync(candidate)) continue;
6545
6692
  const branchName = getCurrentBranch(ctx, candidate);
6546
6693
  if (!expectedBranchesSet.has(branchName)) continue;
6547
6694
  return {
@@ -6682,10 +6829,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
6682
6829
  const normalizedCandidates = uniqueNormalizedPaths(
6683
6830
  candidates.map((candidate) => {
6684
6831
  if (!candidate) return "";
6685
- if (!path13.isAbsolute(candidate)) return candidate;
6686
- const relative = path13.relative(projectGitCwd, candidate);
6832
+ if (!path15.isAbsolute(candidate)) return candidate;
6833
+ const relative = path15.relative(projectGitCwd, candidate);
6687
6834
  if (!relative) return "";
6688
- if (relative === ".." || relative.startsWith(`..${path13.sep}`))
6835
+ if (relative === ".." || relative.startsWith(`..${path15.sep}`))
6689
6836
  return "";
6690
6837
  return relative;
6691
6838
  }).filter(Boolean)
@@ -6699,7 +6846,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
6699
6846
  if (cached) return [...cached];
6700
6847
  const existing = [];
6701
6848
  for (const candidate of normalizedCandidates) {
6702
- if (await ctx.fs.pathExists(path13.join(projectGitCwd, candidate))) {
6849
+ if (await ctx.fs.pathExists(path15.join(projectGitCwd, candidate))) {
6703
6850
  existing.push(candidate);
6704
6851
  }
6705
6852
  }
@@ -6787,16 +6934,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
6787
6934
  const lang = options.lang;
6788
6935
  const workflowPolicy = resolveWorkflowPolicy(options.workflow);
6789
6936
  const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
6790
- const folderName = path13.basename(featurePath);
6937
+ const folderName = path15.basename(featurePath);
6791
6938
  const match = folderName.match(/^(F\d+)-(.+)$/);
6792
6939
  const id = match?.[1];
6793
6940
  const slug = match?.[2] || folderName;
6794
- const specPath = path13.join(featurePath, "spec.md");
6795
- const planPath = path13.join(featurePath, "plan.md");
6796
- const tasksPath = path13.join(featurePath, "tasks.md");
6797
- const decisionsPath = path13.join(featurePath, "decisions.md");
6798
- const issueDocPath = path13.join(featurePath, "issue.md");
6799
- const prDocPath = path13.join(featurePath, "pr.md");
6941
+ const specPath = path15.join(featurePath, "spec.md");
6942
+ const planPath = path15.join(featurePath, "plan.md");
6943
+ const tasksPath = path15.join(featurePath, "tasks.md");
6944
+ const decisionsPath = path15.join(featurePath, "decisions.md");
6945
+ const issueDocPath = path15.join(featurePath, "issue.md");
6946
+ const prDocPath = path15.join(featurePath, "pr.md");
6800
6947
  let specStatus;
6801
6948
  let issueNumber;
6802
6949
  const specExists = await ctx.fs.pathExists(specPath);
@@ -7058,7 +7205,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7058
7205
  } else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
7059
7206
  warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
7060
7207
  }
7061
- const relativeFeaturePathFromDocs = path13.relative(
7208
+ const relativeFeaturePathFromDocs = path15.relative(
7062
7209
  context.docsDir,
7063
7210
  featurePath
7064
7211
  );
@@ -7235,7 +7382,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7235
7382
  }
7236
7383
  const tasksDocApproved = !tasksDocStatusFieldExists || tasksDocStatus === "Approved";
7237
7384
  const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist }) && tasksDocApproved;
7238
- const workflowDone = implementationDone && !docsHasCommitRequiredChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({
7385
+ const workflowBaseDone = implementationDone && !docsHasCommitRequiredChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({
7239
7386
  docs: { prFieldExists, prStatusFieldExists }
7240
7387
  }) && !!prLink) && (!workflowPolicy.requireMerge || prStatus === "Approved") && isPrePrReviewSatisfied2(
7241
7388
  {
@@ -7253,6 +7400,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7253
7400
  },
7254
7401
  prePrReviewPolicy
7255
7402
  );
7403
+ const cleanupPending = workflowBaseDone && (projectInManagedWorktree || !!expectedWorktreePath);
7404
+ const workflowDone = workflowBaseDone && !cleanupPending;
7256
7405
  if (implementationDone && !workflowDone) {
7257
7406
  if (specStatus !== "Approved") {
7258
7407
  warnings.push(tr(lang, "warnings", "workflowSpecNotApproved"));
@@ -7314,7 +7463,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7314
7463
  path: featurePath,
7315
7464
  completion: {
7316
7465
  implementationDone,
7317
- workflowDone
7466
+ workflowDone,
7467
+ cleanupPending
7318
7468
  },
7319
7469
  issueNumber,
7320
7470
  specStatus,
@@ -7421,7 +7571,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7421
7571
  async function listFeatureDirs(ctx, rootDir) {
7422
7572
  const dirs = await listSubdirectories(ctx.fs, rootDir);
7423
7573
  return dirs.filter(
7424
- (value) => path13.basename(value).trim().toLowerCase() !== "feature-base"
7574
+ (value) => path15.basename(value).trim().toLowerCase() !== "feature-base"
7425
7575
  );
7426
7576
  }
7427
7577
  function normalizeRelPath(value) {
@@ -7562,7 +7712,7 @@ async function scanFeatures(ctx) {
7562
7712
  if (config.projectType === "single") {
7563
7713
  const featureDirs = await listFeatureDirs(
7564
7714
  ctx,
7565
- path13.join(config.docsDir, "features")
7715
+ path15.join(config.docsDir, "features")
7566
7716
  );
7567
7717
  componentFeatureDirs.set("single", featureDirs);
7568
7718
  allFeatureDirs.push(...featureDirs);
@@ -7574,14 +7724,14 @@ async function scanFeatures(ctx) {
7574
7724
  for (const component of components) {
7575
7725
  const componentDirs = await listFeatureDirs(
7576
7726
  ctx,
7577
- path13.join(config.docsDir, "features", component)
7727
+ path15.join(config.docsDir, "features", component)
7578
7728
  );
7579
7729
  componentFeatureDirs.set(component, componentDirs);
7580
7730
  allFeatureDirs.push(...componentDirs);
7581
7731
  }
7582
7732
  }
7583
7733
  const relativeFeaturePaths = allFeatureDirs.map(
7584
- (dir) => normalizeRelPath(path13.relative(config.docsDir, dir))
7734
+ (dir) => normalizeRelPath(path15.relative(config.docsDir, dir))
7585
7735
  );
7586
7736
  const docsGitMeta = buildDocsFeatureGitMeta(
7587
7737
  ctx,
@@ -7598,7 +7748,7 @@ async function scanFeatures(ctx) {
7598
7748
  const parsed = await Promise.all(
7599
7749
  target.dirs.map(async (dir) => {
7600
7750
  const relativeFeaturePathFromDocs = normalizeRelPath(
7601
- path13.relative(config.docsDir, dir)
7751
+ path15.relative(config.docsDir, dir)
7602
7752
  );
7603
7753
  const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
7604
7754
  return parseFeature(
@@ -7668,13 +7818,13 @@ async function runStatus(options) {
7668
7818
  );
7669
7819
  }
7670
7820
  const { docsDir, projectType, projectName, lang } = ctx.config;
7671
- const featuresDir = path13.join(docsDir, "features");
7821
+ const featuresDir = path15.join(docsDir, "features");
7672
7822
  const scan = await scanFeatures(ctx);
7673
7823
  const features = [];
7674
7824
  const idMap = /* @__PURE__ */ new Map();
7675
7825
  for (const f of scan.features) {
7676
7826
  const id = f.id || "UNKNOWN";
7677
- const relPath = path13.relative(docsDir, f.path);
7827
+ const relPath = path15.relative(docsDir, f.path);
7678
7828
  if (!idMap.has(id)) idMap.set(id, []);
7679
7829
  idMap.get(id).push(relPath);
7680
7830
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -7765,7 +7915,7 @@ async function runStatus(options) {
7765
7915
  }
7766
7916
  console.log();
7767
7917
  if (options.write) {
7768
- const outputPath = path13.join(featuresDir, "status.md");
7918
+ const outputPath = path15.join(featuresDir, "status.md");
7769
7919
  const date = getLocalDateString();
7770
7920
  const content = [
7771
7921
  "# Feature Status",
@@ -7786,18 +7936,18 @@ async function runStatus(options) {
7786
7936
  );
7787
7937
  }
7788
7938
  }
7789
- function escapeRegExp3(value) {
7939
+ function escapeRegExp4(value) {
7790
7940
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7791
7941
  }
7792
7942
  async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
7793
7943
  try {
7794
- const specPath = path13.join(featureDir, "spec.md");
7944
+ const specPath = path15.join(featureDir, "spec.md");
7795
7945
  if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
7796
7946
  const content = await fsAdapter.readFile(specPath, "utf-8");
7797
7947
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
7798
7948
  for (const key of keys) {
7799
7949
  const regex = new RegExp(
7800
- `^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
7950
+ `^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*)$`,
7801
7951
  "m"
7802
7952
  );
7803
7953
  const match = content.match(regex);
@@ -7875,8 +8025,8 @@ async function runUpdate(options) {
7875
8025
  console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
7876
8026
  }
7877
8027
  if (agentsMode === "all") {
7878
- const commonAgentsBase = path13.join(templatesDir, lang, "common", "agents");
7879
- const targetAgentsBase = path13.join(docsDir, "agents");
8028
+ const commonAgentsBase = path15.join(templatesDir, lang, "common", "agents");
8029
+ const targetAgentsBase = path15.join(docsDir, "agents");
7880
8030
  const commonAgents = commonAgentsBase;
7881
8031
  const targetAgents = targetAgentsBase;
7882
8032
  const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
@@ -7974,21 +8124,21 @@ async function collectAgentsMdTargets(cwd, config) {
7974
8124
  const targets = /* @__PURE__ */ new Set();
7975
8125
  const docsRepo = config.docsRepo ?? "embedded";
7976
8126
  if (docsRepo === "embedded") {
7977
- const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path13.resolve(config.docsDir, "..");
7978
- targets.add(path13.join(repoRoot, "AGENTS.md"));
8127
+ const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path15.resolve(config.docsDir, "..");
8128
+ targets.add(path15.join(repoRoot, "AGENTS.md"));
7979
8129
  return [...targets];
7980
8130
  }
7981
- targets.add(path13.join(config.docsDir, "AGENTS.md"));
8131
+ targets.add(path15.join(config.docsDir, "AGENTS.md"));
7982
8132
  const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
7983
8133
  const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
7984
8134
  for (const rawRoot of rawRoots) {
7985
8135
  const value = String(rawRoot || "").trim();
7986
8136
  if (!value) continue;
7987
- const resolved = path13.resolve(baseDir, value);
8137
+ const resolved = path15.resolve(baseDir, value);
7988
8138
  if (!await fs.pathExists(resolved)) continue;
7989
8139
  const stat = await fs.stat(resolved);
7990
8140
  if (!stat.isDirectory()) continue;
7991
- targets.add(path13.join(resolved, "AGENTS.md"));
8141
+ targets.add(path15.join(resolved, "AGENTS.md"));
7992
8142
  }
7993
8143
  return [...targets];
7994
8144
  }
@@ -8028,7 +8178,7 @@ function normalizeDecisionEnumList2(raw) {
8028
8178
  return [...deduped];
8029
8179
  }
8030
8180
  async function backfillMissingConfigDefaults(docsDir) {
8031
- const configPath = path13.join(docsDir, ".lee-spec-kit.json");
8181
+ const configPath = path15.join(docsDir, ".lee-spec-kit.json");
8032
8182
  if (!await fs.pathExists(configPath)) {
8033
8183
  return { changed: false, changedPaths: [] };
8034
8184
  }
@@ -8151,8 +8301,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
8151
8301
  const files = await fs.readdir(sourceDir);
8152
8302
  let updatedCount = 0;
8153
8303
  for (const file of files) {
8154
- const sourcePath = path13.join(sourceDir, file);
8155
- const targetPath = path13.join(targetDir, file);
8304
+ const sourcePath = path15.join(sourceDir, file);
8305
+ const targetPath = path15.join(targetDir, file);
8156
8306
  const stat = await fs.stat(sourcePath);
8157
8307
  if (stat.isFile()) {
8158
8308
  if (protectedFiles.has(file)) {
@@ -8235,7 +8385,7 @@ function extractPorcelainPaths(line) {
8235
8385
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
8236
8386
  const top = getGitTopLevel2(docsDir);
8237
8387
  if (!top) return null;
8238
- const rel = path13.relative(top, docsDir) || ".";
8388
+ const rel = path15.relative(top, docsDir) || ".";
8239
8389
  try {
8240
8390
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
8241
8391
  cwd: top,
@@ -8247,7 +8397,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
8247
8397
  }
8248
8398
  const ignoredRelPaths = new Set(
8249
8399
  ignoredAbsPaths.map(
8250
- (absPath) => normalizeGitPath2(path13.relative(top, absPath) || ".")
8400
+ (absPath) => normalizeGitPath2(path15.relative(top, absPath) || ".")
8251
8401
  )
8252
8402
  );
8253
8403
  const filtered = output.split("\n").filter((line) => {
@@ -8305,7 +8455,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
8305
8455
  }
8306
8456
  async function runConfig(options) {
8307
8457
  const cwd = process.cwd();
8308
- const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
8458
+ const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
8309
8459
  const config = await getConfig(targetCwd);
8310
8460
  if (!config) {
8311
8461
  throw createCliError(
@@ -8313,7 +8463,7 @@ async function runConfig(options) {
8313
8463
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
8314
8464
  );
8315
8465
  }
8316
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
8466
+ const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
8317
8467
  if (!options.projectRoot) {
8318
8468
  console.log();
8319
8469
  console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -8419,47 +8569,47 @@ var BUILTIN_DOC_DEFINITIONS = [
8419
8569
  {
8420
8570
  id: "agents",
8421
8571
  title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
8422
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "agents.md")
8572
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "agents.md")
8423
8573
  },
8424
8574
  {
8425
8575
  id: "git-workflow",
8426
8576
  title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
8427
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "git-workflow.md")
8577
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "git-workflow.md")
8428
8578
  },
8429
8579
  {
8430
8580
  id: "issue-doc",
8431
8581
  title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
8432
- relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "issue.md")
8582
+ relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "issue.md")
8433
8583
  },
8434
8584
  {
8435
8585
  id: "pr-doc",
8436
8586
  title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
8437
- relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "pr.md")
8587
+ relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "pr.md")
8438
8588
  },
8439
8589
  {
8440
8590
  id: "create-feature",
8441
8591
  title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
8442
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-feature.md")
8592
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-feature.md")
8443
8593
  },
8444
8594
  {
8445
8595
  id: "execute-task",
8446
8596
  title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
8447
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "execute-task.md")
8597
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "execute-task.md")
8448
8598
  },
8449
8599
  {
8450
8600
  id: "create-issue",
8451
8601
  title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
8452
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-issue.md")
8602
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-issue.md")
8453
8603
  },
8454
8604
  {
8455
8605
  id: "create-pr",
8456
8606
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
8457
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-pr.md")
8607
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-pr.md")
8458
8608
  },
8459
8609
  {
8460
8610
  id: "split-feature",
8461
8611
  title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
8462
- relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "split-feature.md")
8612
+ relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "split-feature.md")
8463
8613
  }
8464
8614
  ];
8465
8615
  var DOC_FOLLOWUPS = {
@@ -8556,7 +8706,7 @@ function listBuiltinDocs(projectType, lang) {
8556
8706
  id: doc.id,
8557
8707
  title: doc.title[lang],
8558
8708
  relativePath,
8559
- absolutePath: path13.join(templatesDir, relativePath)
8709
+ absolutePath: path15.join(templatesDir, relativePath)
8560
8710
  };
8561
8711
  });
8562
8712
  }
@@ -8966,6 +9116,13 @@ async function resolveContextSelection(ctx, featureName, options) {
8966
9116
 
8967
9117
  // src/services/ContextPresenter.ts
8968
9118
  function getActionExecutionMetadata(action) {
9119
+ if (action.category === "task_execute" && action.taskExecutePhase === "start") {
9120
+ return {
9121
+ handoffOnly: true,
9122
+ advancesWorkflow: false,
9123
+ nextMainState: "task_complete"
9124
+ };
9125
+ }
8969
9126
  if (action.category === "code_review_run") {
8970
9127
  return {
8971
9128
  handoffOnly: true,
@@ -9050,6 +9207,7 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
9050
9207
  pauseAndReportWhen: [
9051
9208
  "approvalRequest.required=true",
9052
9209
  "AUTO_GATE_REACHED",
9210
+ "AUTO_DELEGATED_HANDOFF",
9053
9211
  "AUTO_MANUAL_REQUIRED",
9054
9212
  "command execution error"
9055
9213
  ],
@@ -9415,6 +9573,9 @@ function buildRequiredDocHints(actionOptions) {
9415
9573
  }
9416
9574
  function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
9417
9575
  if (f.completion.implementationDone && !f.completion.workflowDone) {
9576
+ if (f.completion.cleanupPending) {
9577
+ return tr(lang, "cli", "context.list.cleanupPending");
9578
+ }
9418
9579
  if (f.git.docsHasCommitRequiredChanges) {
9419
9580
  return tr(lang, "cli", "context.list.docsCommitNeeded");
9420
9581
  }
@@ -9593,7 +9754,7 @@ function getApprovalSessionId() {
9593
9754
  function getApprovalTicketPaths(config) {
9594
9755
  return {
9595
9756
  runtimePath: getApprovalTicketStorePath(config.docsDir),
9596
- legacyPath: path13.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
9757
+ legacyPath: path15.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
9597
9758
  };
9598
9759
  }
9599
9760
  async function loadApprovalTicketStore(storePath) {
@@ -9607,7 +9768,7 @@ async function loadApprovalTicketStore(storePath) {
9607
9768
  }
9608
9769
  }
9609
9770
  async function saveApprovalTicketStore(storePath, payload) {
9610
- await fs.ensureDir(path13.dirname(storePath));
9771
+ await fs.ensureDir(path15.dirname(storePath));
9611
9772
  await fs.writeJson(storePath, payload, { spaces: 2 });
9612
9773
  }
9613
9774
  function pruneApprovalTickets(tickets, nowMs) {
@@ -9793,6 +9954,14 @@ function getCommandExecutionLockPath(action, config) {
9793
9954
  return getProjectExecutionLockPath(action.cwd);
9794
9955
  }
9795
9956
  function buildApprovedHandoffMetadata(action, featureRef) {
9957
+ if (action.category === "task_execute" && action.taskExecutePhase === "start") {
9958
+ const taskId = action.cmd.match(/\b--task\s+([^\s]+)/)?.[1];
9959
+ return {
9960
+ delegatedWorkRequired: true,
9961
+ doNotReapproveSameLabel: true,
9962
+ reuseKey: taskId ? `task:${featureRef}:${taskId}` : `task:${featureRef}`
9963
+ };
9964
+ }
9796
9965
  if (action.category === "pre_pr_review_run") {
9797
9966
  return {
9798
9967
  delegatedWorkRequired: true,
@@ -10460,7 +10629,7 @@ async function runContext(featureName, options) {
10460
10629
  untilCategories: autoRunPlan.untilCategories,
10461
10630
  unknownCategories: autoRunPlan.unknownCategories,
10462
10631
  manualBoundary: autoRunPlan.manualBoundary,
10463
- guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories.'
10632
+ guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true`, when auto mode reaches configured gate categories, or when a delegated handoff pause must be resumed.'
10464
10633
  },
10465
10634
  approvalRequest: {
10466
10635
  guidance: approvalGuidance.replace(
@@ -10684,7 +10853,7 @@ async function runContext(featureName, options) {
10684
10853
  if (f.issueNumber) {
10685
10854
  console.log(` \u2022 Issue: #${f.issueNumber}`);
10686
10855
  }
10687
- console.log(` \u2022 Path: ${path13.relative(cwd, f.path)}`);
10856
+ console.log(` \u2022 Path: ${path15.relative(cwd, f.path)}`);
10688
10857
  if (f.git.projectBranch) {
10689
10858
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
10690
10859
  }
@@ -10836,7 +11005,7 @@ function extractTitleAfterId(line, id) {
10836
11005
  return cleaned ? cleaned : void 0;
10837
11006
  }
10838
11007
  async function scanPrdRequirements(fsAdapter, docsDir) {
10839
- const prdDir = path13.join(docsDir, "prd");
11008
+ const prdDir = path15.join(docsDir, "prd");
10840
11009
  const files = await walkFiles(fsAdapter, prdDir, {
10841
11010
  extensions: [".md"],
10842
11011
  ignoreDirs: [".git", "node_modules", "dist", "tmp"]
@@ -10844,7 +11013,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
10844
11013
  const definitions = /* @__PURE__ */ new Map();
10845
11014
  const duplicates = [];
10846
11015
  for (const filePath of files) {
10847
- if (path13.basename(filePath).toLowerCase() === "readme.md") {
11016
+ if (path15.basename(filePath).toLowerCase() === "readme.md") {
10848
11017
  continue;
10849
11018
  }
10850
11019
  let content = "";
@@ -10853,7 +11022,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
10853
11022
  } catch {
10854
11023
  continue;
10855
11024
  }
10856
- const relFile = normalizeRelPath2(path13.relative(docsDir, filePath));
11025
+ const relFile = normalizeRelPath2(path15.relative(docsDir, filePath));
10857
11026
  const lines = content.split(/\r?\n/);
10858
11027
  let inCodeBlock = false;
10859
11028
  for (let i = 0; i < lines.length; i += 1) {
@@ -10928,7 +11097,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
10928
11097
  ]);
10929
11098
  function formatPath(cwd, p) {
10930
11099
  if (!p) return "";
10931
- return path13.isAbsolute(p) ? path13.relative(cwd, p) : p;
11100
+ return path15.isAbsolute(p) ? path15.relative(cwd, p) : p;
10932
11101
  }
10933
11102
  function detectPlaceholders(content) {
10934
11103
  const patterns = [
@@ -11087,7 +11256,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
11087
11256
  const placeholderContext = {
11088
11257
  projectName: config.projectName,
11089
11258
  featureName: f.slug,
11090
- featurePath: f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path),
11259
+ featurePath: f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path),
11091
11260
  repoType: f.type,
11092
11261
  featureNumber
11093
11262
  };
@@ -11097,7 +11266,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
11097
11266
  "tasks.md"
11098
11267
  ];
11099
11268
  for (const file of files) {
11100
- const fullPath = path13.join(f.path, file);
11269
+ const fullPath = path15.join(f.path, file);
11101
11270
  if (!await fs.pathExists(fullPath)) continue;
11102
11271
  const original = await fs.readFile(fullPath, "utf-8");
11103
11272
  let next = original;
@@ -11150,7 +11319,7 @@ async function checkDocsStructure(config, cwd) {
11150
11319
  const issues = [];
11151
11320
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
11152
11321
  for (const dir of requiredDirs) {
11153
- const p = path13.join(config.docsDir, dir);
11322
+ const p = path15.join(config.docsDir, dir);
11154
11323
  if (!await fs.pathExists(p)) {
11155
11324
  issues.push({
11156
11325
  level: "error",
@@ -11162,7 +11331,7 @@ async function checkDocsStructure(config, cwd) {
11162
11331
  });
11163
11332
  }
11164
11333
  }
11165
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
11334
+ const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
11166
11335
  if (!await fs.pathExists(configPath)) {
11167
11336
  issues.push({
11168
11337
  level: "warn",
@@ -11190,7 +11359,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11190
11359
  }
11191
11360
  const idMap = /* @__PURE__ */ new Map();
11192
11361
  for (const f of features) {
11193
- const rel = f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path);
11362
+ const rel = f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path);
11194
11363
  const id = f.id || "UNKNOWN";
11195
11364
  if (!idMap.has(id)) idMap.set(id, []);
11196
11365
  idMap.get(id).push(rel);
@@ -11198,7 +11367,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11198
11367
  if (!isInitialTemplateState) {
11199
11368
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
11200
11369
  for (const file of featureDocs) {
11201
- const p = path13.join(f.path, file);
11370
+ const p = path15.join(f.path, file);
11202
11371
  if (!await fs.pathExists(p)) continue;
11203
11372
  const content = await fs.readFile(p, "utf-8");
11204
11373
  const placeholders = detectPlaceholders(content);
@@ -11213,7 +11382,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11213
11382
  });
11214
11383
  }
11215
11384
  if (decisionsPlaceholderMode !== "off") {
11216
- const decisionsPath = path13.join(f.path, "decisions.md");
11385
+ const decisionsPath = path15.join(f.path, "decisions.md");
11217
11386
  if (await fs.pathExists(decisionsPath)) {
11218
11387
  const content = await fs.readFile(decisionsPath, "utf-8");
11219
11388
  const placeholders = detectPlaceholders(content);
@@ -11242,7 +11411,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11242
11411
  level: "warn",
11243
11412
  code: "spec_status_unset",
11244
11413
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
11245
- path: formatPath(cwd, path13.join(f.path, "spec.md"))
11414
+ path: formatPath(cwd, path15.join(f.path, "spec.md"))
11246
11415
  });
11247
11416
  }
11248
11417
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -11250,7 +11419,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11250
11419
  level: "warn",
11251
11420
  code: "plan_status_unset",
11252
11421
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
11253
- path: formatPath(cwd, path13.join(f.path, "plan.md"))
11422
+ path: formatPath(cwd, path15.join(f.path, "plan.md"))
11254
11423
  });
11255
11424
  }
11256
11425
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -11258,11 +11427,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11258
11427
  level: "warn",
11259
11428
  code: "tasks_empty",
11260
11429
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
11261
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
11430
+ path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11262
11431
  });
11263
11432
  }
11264
11433
  if (f.docs.tasksExists) {
11265
- const tasksPath = path13.join(f.path, "tasks.md");
11434
+ const tasksPath = path15.join(f.path, "tasks.md");
11266
11435
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
11267
11436
  const unknownPrdTags = [...new Set(
11268
11437
  parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
@@ -11284,7 +11453,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11284
11453
  level: "warn",
11285
11454
  code: "tasks_doc_status_missing",
11286
11455
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
11287
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
11456
+ path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11288
11457
  });
11289
11458
  }
11290
11459
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -11292,7 +11461,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11292
11461
  level: "warn",
11293
11462
  code: "tasks_doc_status_unset",
11294
11463
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
11295
- path: formatPath(cwd, path13.join(f.path, "tasks.md"))
11464
+ path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11296
11465
  });
11297
11466
  }
11298
11467
  }
@@ -11316,7 +11485,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11316
11485
  level: "warn",
11317
11486
  code: "missing_feature_id",
11318
11487
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
11319
- path: formatPath(cwd, path13.join(config.docsDir, p))
11488
+ path: formatPath(cwd, path15.join(config.docsDir, p))
11320
11489
  });
11321
11490
  }
11322
11491
  return issues;
@@ -11447,7 +11616,7 @@ function doctorCommand(program2) {
11447
11616
  }
11448
11617
  console.log();
11449
11618
  console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
11450
- console.log(chalk9.gray(`- Docs: ${path13.relative(cwd, docsDir)}`));
11619
+ console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, docsDir)}`));
11451
11620
  console.log(chalk9.gray(`- Type: ${projectType}`));
11452
11621
  console.log(chalk9.gray(`- Lang: ${lang}`));
11453
11622
  console.log();
@@ -11628,7 +11797,7 @@ async function runView(featureName, options) {
11628
11797
  }
11629
11798
  console.log();
11630
11799
  console.log(chalk9.bold("\u{1F4CA} Workflow View"));
11631
- console.log(chalk9.gray(`- Docs: ${path13.relative(cwd, config.docsDir)}`));
11800
+ console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, config.docsDir)}`));
11632
11801
  console.log(
11633
11802
  chalk9.gray(
11634
11803
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -11713,13 +11882,13 @@ function normalizeRunId(raw) {
11713
11882
  return value;
11714
11883
  }
11715
11884
  function getFlowRunBaseDir(cwd) {
11716
- return path13.join(getRuntimeStateDir(cwd), "flow-runs");
11885
+ return path15.join(getRuntimeStateDir(cwd), "flow-runs");
11717
11886
  }
11718
11887
  function getFlowRunPath(cwd, runId) {
11719
- return path13.join(getFlowRunBaseDir(cwd), `${runId}.json`);
11888
+ return path15.join(getFlowRunBaseDir(cwd), `${runId}.json`);
11720
11889
  }
11721
11890
  function getFlowRunLockPath(cwd, runId) {
11722
- return path13.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
11891
+ return path15.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
11723
11892
  }
11724
11893
  async function readFlowRunRecordUnsafe(cwd, runId) {
11725
11894
  const normalized = normalizeRunId(runId);
@@ -11745,7 +11914,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
11745
11914
  }
11746
11915
  async function writeFlowRunRecord(cwd, record) {
11747
11916
  const filePath = getFlowRunPath(cwd, record.runId);
11748
- await fs.ensureDir(path13.dirname(filePath));
11917
+ await fs.ensureDir(path15.dirname(filePath));
11749
11918
  await fs.writeJson(filePath, record, { spaces: 2 });
11750
11919
  }
11751
11920
  async function createFlowRunRecord(cwd, input) {
@@ -11895,6 +12064,7 @@ function toCompactAutoRun(autoRun) {
11895
12064
  iterations: autoRun.iterations,
11896
12065
  executionCount: autoRun.executions.length,
11897
12066
  lastExecution,
12067
+ delegated: autoRun.delegated ?? null,
11898
12068
  gate: autoRun.gate ?? null,
11899
12069
  manual: autoRun.manual ?? null,
11900
12070
  resume: autoRun.resume,
@@ -11939,6 +12109,7 @@ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
11939
12109
  pauseAndReportWhen: [
11940
12110
  "approvalRequest.required=true",
11941
12111
  "AUTO_GATE_REACHED",
12112
+ "AUTO_DELEGATED_HANDOFF",
11942
12113
  "AUTO_MANUAL_REQUIRED",
11943
12114
  "command execution error"
11944
12115
  ],
@@ -12164,6 +12335,7 @@ function resolveAutoMode(config, options, requestText) {
12164
12335
  }
12165
12336
  function toAutoReasonCode(status) {
12166
12337
  const map = {
12338
+ delegated_handoff: "AUTO_DELEGATED_HANDOFF",
12167
12339
  gate_reached: "AUTO_GATE_REACHED",
12168
12340
  manual_required: "AUTO_MANUAL_REQUIRED",
12169
12341
  no_action_options: "AUTO_NO_ACTION_OPTIONS",
@@ -12187,6 +12359,7 @@ function isAutoRunFailureStatus(status) {
12187
12359
  }
12188
12360
  function toFlowRunStatus(status) {
12189
12361
  switch (status) {
12362
+ case "delegated_handoff":
12190
12363
  case "gate_reached":
12191
12364
  case "manual_required":
12192
12365
  return "paused";
@@ -12201,6 +12374,17 @@ function toFlowRunStatus(status) {
12201
12374
  return "failed";
12202
12375
  }
12203
12376
  }
12377
+ function isTaskCommitCheckpointOption(option, state) {
12378
+ if (!option || state.status !== "single_matched" || !state.matchedFeature) {
12379
+ return false;
12380
+ }
12381
+ if (state.matchedFeature.currentSubstateId !== "task_commit_pending") {
12382
+ return false;
12383
+ }
12384
+ if (option.action.type !== "command") return false;
12385
+ if (option.action.category === "docs_commit") return true;
12386
+ return option.action.category === "task_execute" && option.action.scope === "project" && /\bgit\s+commit\b/i.test(option.action.cmd);
12387
+ }
12204
12388
  async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
12205
12389
  const contextArgs = [
12206
12390
  "context",
@@ -12378,6 +12562,29 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
12378
12562
  manual: null
12379
12563
  };
12380
12564
  }
12565
+ const taskCommitCheckpoint = actionOptions.find(
12566
+ (option) => isTaskCommitCheckpointOption(option, state)
12567
+ );
12568
+ if (taskCommitCheckpoint) {
12569
+ return {
12570
+ enabled: true,
12571
+ untilCategories,
12572
+ request: requestText,
12573
+ preset: metadata?.preset ?? null,
12574
+ source: metadata?.source ?? null,
12575
+ resume,
12576
+ status: "manual_required",
12577
+ reasonCode: toAutoReasonCode("manual_required"),
12578
+ iterations,
12579
+ executions,
12580
+ gate: null,
12581
+ manual: {
12582
+ label: taskCommitCheckpoint.label,
12583
+ category: taskCommitCheckpoint.action.category,
12584
+ detail: taskCommitCheckpoint.detail
12585
+ }
12586
+ };
12587
+ }
12381
12588
  const executable = actionOptions.find(
12382
12589
  (option) => option.action.type === "command"
12383
12590
  );
@@ -12463,16 +12670,18 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
12463
12670
  preset: metadata?.preset ?? null,
12464
12671
  source: metadata?.source ?? null,
12465
12672
  resume,
12466
- status: "manual_required",
12467
- reasonCode: toAutoReasonCode("manual_required"),
12673
+ status: "delegated_handoff",
12674
+ reasonCode: toAutoReasonCode("delegated_handoff"),
12468
12675
  iterations,
12469
12676
  executions,
12470
- gate: null,
12471
- manual: {
12677
+ delegated: {
12472
12678
  label: executable.label,
12473
12679
  category: executable.action.category,
12474
- detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail
12475
- }
12680
+ detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail,
12681
+ nextMainState: executeResult?.nextMainState
12682
+ },
12683
+ gate: null,
12684
+ manual: null
12476
12685
  };
12477
12686
  }
12478
12687
  if (executeResult?.status !== "approved_executed") {
@@ -12663,6 +12872,20 @@ async function runFlow(featureName, options) {
12663
12872
  "`--request` requires auto mode. Use `--auto-until-category`, `--auto-preset`, or configure `workflow.auto.defaultPreset`."
12664
12873
  );
12665
12874
  }
12875
+ if (autoMode && !resolvedFeatureName && requestText) {
12876
+ const bootstrapped = await bootstrapFeatureFromIdeaRequest(
12877
+ config,
12878
+ requestText,
12879
+ selectedComponent
12880
+ );
12881
+ if (bootstrapped) {
12882
+ resolvedFeatureName = bootstrapped.featureRef;
12883
+ if (!selectedComponent && bootstrapped.component) {
12884
+ selectedComponent = bootstrapped.component;
12885
+ selectionOptions.component = bootstrapped.component;
12886
+ }
12887
+ }
12888
+ }
12666
12889
  if (autoMode && !featureName) {
12667
12890
  if (!resolvedFeatureName) {
12668
12891
  throw createCliError(
@@ -12757,7 +12980,8 @@ async function runFlow(featureName, options) {
12757
12980
  },
12758
12981
  lastAutoStatus: autoRun.status,
12759
12982
  lastReasonCode: autoRun.reasonCode,
12760
- lastError: autoRun.error
12983
+ lastError: autoRun.error,
12984
+ lastDelegatedHandoff: autoRun.delegated ?? null
12761
12985
  })
12762
12986
  );
12763
12987
  autoRun.run = {
@@ -12943,6 +13167,45 @@ async function runFlow(featureName, options) {
12943
13167
  );
12944
13168
  console.log();
12945
13169
  }
13170
+ async function bootstrapFeatureFromIdeaRequest(config, requestText, selectedComponent) {
13171
+ const ideaRef = extractExplicitIdeaRef(requestText);
13172
+ if (!ideaRef) return null;
13173
+ const resolvedIdea = await resolveIdeaReference(
13174
+ config.docsDir,
13175
+ ideaRef,
13176
+ config.lang
13177
+ );
13178
+ const existingFeatureRef = await readIdeaMetadataValue(
13179
+ resolvedIdea.path,
13180
+ "Feature"
13181
+ );
13182
+ if (existingFeatureRef && existingFeatureRef !== "-") {
13183
+ return { featureRef: existingFeatureRef, component: selectedComponent };
13184
+ }
13185
+ let component = selectedComponent;
13186
+ if (config.projectType === "multi" && !component) {
13187
+ const ideaComponent = await readIdeaMetadataValue(
13188
+ resolvedIdea.path,
13189
+ "Component"
13190
+ );
13191
+ if (ideaComponent && ideaComponent !== "-" && ideaComponent !== "all") {
13192
+ component = ideaComponent.toLowerCase();
13193
+ } else {
13194
+ return null;
13195
+ }
13196
+ }
13197
+ const featureName = await deriveFeatureNameFromIdea(resolvedIdea.path);
13198
+ const result = await runFeature(featureName, {
13199
+ component,
13200
+ idea: ideaRef,
13201
+ nonInteractive: true,
13202
+ json: true
13203
+ });
13204
+ return {
13205
+ featureRef: `${result.featureId}-${result.featureName}`,
13206
+ component
13207
+ };
13208
+ }
12946
13209
  function runProcess(bin, args, cwd) {
12947
13210
  const result = spawnSync(bin, args, {
12948
13211
  cwd,
@@ -13056,27 +13319,27 @@ function tg(lang, key, vars = {}) {
13056
13319
  function detectGithubCliLangSync(cwd) {
13057
13320
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
13058
13321
  const startDirs = [
13059
- explicitDocsDir ? path13.resolve(explicitDocsDir) : "",
13060
- path13.resolve(cwd)
13322
+ explicitDocsDir ? path15.resolve(explicitDocsDir) : "",
13323
+ path15.resolve(cwd)
13061
13324
  ].filter(Boolean);
13062
13325
  const scanOrder = [];
13063
13326
  const seen = /* @__PURE__ */ new Set();
13064
13327
  for (const start of startDirs) {
13065
13328
  let current = start;
13066
13329
  while (true) {
13067
- const abs = path13.resolve(current);
13330
+ const abs = path15.resolve(current);
13068
13331
  if (!seen.has(abs)) {
13069
13332
  scanOrder.push(abs);
13070
13333
  seen.add(abs);
13071
13334
  }
13072
- const parent = path13.dirname(abs);
13335
+ const parent = path15.dirname(abs);
13073
13336
  if (parent === abs) break;
13074
13337
  current = parent;
13075
13338
  }
13076
13339
  }
13077
13340
  for (const base of scanOrder) {
13078
- for (const docsDir of [path13.join(base, "docs"), base]) {
13079
- const configPath = path13.join(docsDir, ".lee-spec-kit.json");
13341
+ for (const docsDir of [path15.join(base, "docs"), base]) {
13342
+ const configPath = path15.join(docsDir, ".lee-spec-kit.json");
13080
13343
  if (fs.existsSync(configPath)) {
13081
13344
  try {
13082
13345
  const parsed = fs.readJsonSync(configPath);
@@ -13085,11 +13348,11 @@ function detectGithubCliLangSync(cwd) {
13085
13348
  } catch {
13086
13349
  }
13087
13350
  }
13088
- const agentsPath = path13.join(docsDir, "agents");
13089
- const featuresPath = path13.join(docsDir, "features");
13351
+ const agentsPath = path15.join(docsDir, "agents");
13352
+ const featuresPath = path15.join(docsDir, "features");
13090
13353
  if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
13091
13354
  for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
13092
- const file = path13.join(agentsPath, probe);
13355
+ const file = path15.join(agentsPath, probe);
13093
13356
  if (!fs.existsSync(file)) continue;
13094
13357
  try {
13095
13358
  const content = fs.readFileSync(file, "utf-8");
@@ -13109,13 +13372,13 @@ function parseLabels(raw, lang) {
13109
13372
  }
13110
13373
  return [...new Set(labels)];
13111
13374
  }
13112
- function escapeRegExp4(value) {
13375
+ function escapeRegExp5(value) {
13113
13376
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13114
13377
  }
13115
13378
  function extractDraftMetadataValue(content, keys) {
13116
13379
  for (const key of keys) {
13117
13380
  const re = new RegExp(
13118
- `^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
13381
+ `^\\s*-\\s*\\*\\*${escapeRegExp5(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
13119
13382
  "mi"
13120
13383
  );
13121
13384
  const match = content.match(re);
@@ -13194,7 +13457,7 @@ async function prepareGithubBody(params) {
13194
13457
  };
13195
13458
  }
13196
13459
  }
13197
- await fs.ensureDir(path13.dirname(defaultBodyFile));
13460
+ await fs.ensureDir(path15.dirname(defaultBodyFile));
13198
13461
  await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
13199
13462
  return {
13200
13463
  body: generatedBody,
@@ -13234,7 +13497,7 @@ function ensureSections(body, sections, kind, lang) {
13234
13497
  };
13235
13498
  const hasMetadataField = (field) => {
13236
13499
  const re = new RegExp(
13237
- `^\\s*-\\s*\\*\\*${escapeRegExp4(field)}\\*\\*\\s*:`,
13500
+ `^\\s*-\\s*\\*\\*${escapeRegExp5(field)}\\*\\*\\s*:`,
13238
13501
  "m"
13239
13502
  );
13240
13503
  return re.test(body);
@@ -13264,7 +13527,7 @@ function ensureSections(body, sections, kind, lang) {
13264
13527
  }
13265
13528
  function ensureDocsExist(docsDir, relativePaths, lang) {
13266
13529
  const missing = relativePaths.filter(
13267
- (relativePath) => !fs.existsSync(path13.join(docsDir, relativePath))
13530
+ (relativePath) => !fs.existsSync(path15.join(docsDir, relativePath))
13268
13531
  );
13269
13532
  if (missing.length > 0) {
13270
13533
  throw createCliError(
@@ -13274,18 +13537,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
13274
13537
  }
13275
13538
  }
13276
13539
  function buildDefaultBodyFileName(kind, docsDir, component) {
13277
- const key = `${path13.resolve(docsDir)}::${component.trim().toLowerCase()}`;
13540
+ const key = `${path15.resolve(docsDir)}::${component.trim().toLowerCase()}`;
13278
13541
  const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
13279
13542
  return `lee-spec-kit.${digest}.${kind}.md`;
13280
13543
  }
13281
13544
  function toBodyFilePath(raw, kind, docsDir, component, lang) {
13282
- const selected = raw?.trim() || path13.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
13545
+ const selected = raw?.trim() || path15.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
13283
13546
  assertValid(
13284
13547
  validatePathWithLang(selected, lang),
13285
13548
  `github.${kind}.bodyFile`,
13286
13549
  lang
13287
13550
  );
13288
- return path13.resolve(selected);
13551
+ return path15.resolve(selected);
13289
13552
  }
13290
13553
  function toProjectRootDocsPath(relativePathFromDocs) {
13291
13554
  const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -14122,7 +14385,7 @@ function stripMarkdownCodeContexts(body) {
14122
14385
  function hasIssueClosingKeyword(body, issueNumber) {
14123
14386
  if (!issueNumber) return false;
14124
14387
  const cleaned = stripMarkdownCodeContexts(body);
14125
- const issue = escapeRegExp4(issueNumber);
14388
+ const issue = escapeRegExp5(issueNumber);
14126
14389
  const closeKeywordRegex = new RegExp(
14127
14390
  `\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
14128
14391
  "i"
@@ -14137,6 +14400,85 @@ function ensureIssueClosingLine(body, issueNumber) {
14137
14400
  return `${trimmed}${separator}Closes #${issueNumber}
14138
14401
  `;
14139
14402
  }
14403
+ function extractTasksIssueReference(tasksContent) {
14404
+ return extractDraftMetadataValue(tasksContent, [
14405
+ "Issue",
14406
+ "Issue Number",
14407
+ "\uC774\uC288",
14408
+ "\uC774\uC288 \uBC88\uD638"
14409
+ ]);
14410
+ }
14411
+ function parseStrictIssueReference(raw) {
14412
+ const value = (raw || "").trim();
14413
+ if (!value) return void 0;
14414
+ const match = value.match(/^#?\s*(\d+)\s*$/);
14415
+ return match?.[1];
14416
+ }
14417
+ function resolvePrClosingIssueNumber(tasksContent, featureIssueNumber, lang) {
14418
+ const rawIssueReference = extractTasksIssueReference(tasksContent);
14419
+ const parsedRawIssueNumber = parseStrictIssueReference(rawIssueReference);
14420
+ if (rawIssueReference && !parsedRawIssueNumber) {
14421
+ throw createCliError(
14422
+ "PRECONDITION_FAILED",
14423
+ tg(lang, "invalidIssueReference", {
14424
+ value: rawIssueReference.trim()
14425
+ })
14426
+ );
14427
+ }
14428
+ if (parsedRawIssueNumber && /^0+$/.test(parsedRawIssueNumber)) {
14429
+ throw createCliError(
14430
+ "PRECONDITION_FAILED",
14431
+ tg(lang, "invalidIssueReference", {
14432
+ value: rawIssueReference?.trim() || parsedRawIssueNumber
14433
+ })
14434
+ );
14435
+ }
14436
+ const issueNumber = (featureIssueNumber || parsedRawIssueNumber || "").trim();
14437
+ if (!issueNumber) return void 0;
14438
+ return issueNumber;
14439
+ }
14440
+ function assertRemoteIssueExists(issueNumber, cwd, lang) {
14441
+ if (!issueNumber) return;
14442
+ const result = runProcess(
14443
+ "gh",
14444
+ ["issue", "view", issueNumber, "--json", "number,state"],
14445
+ cwd
14446
+ );
14447
+ if (result.code !== 0) {
14448
+ const detail = (result.stderr || result.stdout || "").trim();
14449
+ if (/not found|could not resolve|404|no issue/i.test(detail)) {
14450
+ throw createCliError(
14451
+ "PRECONDITION_FAILED",
14452
+ tg(lang, "issueNotFound", {
14453
+ issue: `#${issueNumber}`
14454
+ })
14455
+ );
14456
+ }
14457
+ throw createCliError(
14458
+ "EXECUTION_FAILED",
14459
+ `${tg(lang, "issueLookupFailed")}${detail ? `: ${detail}` : ""}`
14460
+ );
14461
+ }
14462
+ let payload;
14463
+ try {
14464
+ payload = JSON.parse((result.stdout || "").trim());
14465
+ } catch {
14466
+ throw createCliError(
14467
+ "EXECUTION_FAILED",
14468
+ tg(lang, "ghInvalidJson", {
14469
+ snippet: (result.stdout || "").trim().slice(0, 160)
14470
+ })
14471
+ );
14472
+ }
14473
+ if (String(payload?.number || "") !== String(issueNumber)) {
14474
+ throw createCliError(
14475
+ "PRECONDITION_FAILED",
14476
+ tg(lang, "issueNotFound", {
14477
+ issue: `#${issueNumber}`
14478
+ })
14479
+ );
14480
+ }
14481
+ }
14140
14482
  function getRequiredIssueSections(lang) {
14141
14483
  return getGithubDraftRequiredSections("issue", lang);
14142
14484
  }
@@ -14340,7 +14682,7 @@ function ensureCleanWorktree(cwd, lang) {
14340
14682
  function commitAndPushPaths(cwd, absPaths, message, lang, options) {
14341
14683
  const uniqueRelativePaths = [
14342
14684
  ...new Set(
14343
- absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path13.relative(cwd, absPath) || absPath)
14685
+ absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path15.relative(cwd, absPath) || absPath)
14344
14686
  )
14345
14687
  ];
14346
14688
  if (uniqueRelativePaths.length === 0) return;
@@ -14536,15 +14878,15 @@ function githubCommand(program2) {
14536
14878
  config.lang
14537
14879
  );
14538
14880
  const specContent = await fs.readFile(
14539
- path13.join(config.docsDir, paths.specPath),
14881
+ path15.join(config.docsDir, paths.specPath),
14540
14882
  "utf-8"
14541
14883
  );
14542
14884
  const planContent = await fs.readFile(
14543
- path13.join(config.docsDir, paths.planPath),
14885
+ path15.join(config.docsDir, paths.planPath),
14544
14886
  "utf-8"
14545
14887
  );
14546
14888
  const tasksContent = await fs.readFile(
14547
- path13.join(config.docsDir, paths.tasksPath),
14889
+ path15.join(config.docsDir, paths.tasksPath),
14548
14890
  "utf-8"
14549
14891
  );
14550
14892
  const overview = resolveOverviewFromSpec(
@@ -14587,7 +14929,7 @@ function githubCommand(program2) {
14587
14929
  create: options.create,
14588
14930
  explicitBodyFile,
14589
14931
  defaultBodyFile,
14590
- workflowDraftPath: path13.join(config.docsDir, paths.issuePath),
14932
+ workflowDraftPath: path15.join(config.docsDir, paths.issuePath),
14591
14933
  generatedBody,
14592
14934
  requiredSections: getRequiredIssueSections(config.lang),
14593
14935
  kindLabel: tg(config.lang, "kindIssue"),
@@ -14603,7 +14945,7 @@ function githubCommand(program2) {
14603
14945
  `${feature.type}-issue-sanitized`,
14604
14946
  config.lang
14605
14947
  );
14606
- await fs.ensureDir(path13.dirname(sanitizedBodyFile));
14948
+ await fs.ensureDir(path15.dirname(sanitizedBodyFile));
14607
14949
  await fs.writeFile(sanitizedBodyFile, body, "utf-8");
14608
14950
  bodyFile = sanitizedBodyFile;
14609
14951
  }
@@ -14652,12 +14994,12 @@ function githubCommand(program2) {
14652
14994
  const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
14653
14995
  if (syncedIssueNumber) {
14654
14996
  const synced = syncTasksIssueMetadata(
14655
- path13.join(config.docsDir, paths.tasksPath),
14997
+ path15.join(config.docsDir, paths.tasksPath),
14656
14998
  syncedIssueNumber,
14657
14999
  config.lang
14658
15000
  );
14659
15001
  const draftSynced = syncIssueDraftMetadata(
14660
- path13.join(config.docsDir, paths.issuePath),
15002
+ path15.join(config.docsDir, paths.issuePath),
14661
15003
  syncedIssueNumber
14662
15004
  );
14663
15005
  syncChanged = synced.changed || draftSynced.changed;
@@ -14768,13 +15110,13 @@ function githubCommand(program2) {
14768
15110
  config.lang
14769
15111
  );
14770
15112
  const specContent = await fs.readFile(
14771
- path13.join(config.docsDir, paths.specPath),
15113
+ path15.join(config.docsDir, paths.specPath),
14772
15114
  "utf-8"
14773
15115
  );
14774
- const planPath = path13.join(config.docsDir, paths.planPath);
15116
+ const planPath = path15.join(config.docsDir, paths.planPath);
14775
15117
  const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
14776
15118
  const tasksContent = await fs.readFile(
14777
- path13.join(config.docsDir, paths.tasksPath),
15119
+ path15.join(config.docsDir, paths.tasksPath),
14778
15120
  "utf-8"
14779
15121
  );
14780
15122
  const overview = resolveOverviewFromSpec(
@@ -14822,7 +15164,7 @@ function githubCommand(program2) {
14822
15164
  create: options.create,
14823
15165
  explicitBodyFile,
14824
15166
  defaultBodyFile,
14825
- workflowDraftPath: path13.join(config.docsDir, paths.prPath),
15167
+ workflowDraftPath: path15.join(config.docsDir, paths.prPath),
14826
15168
  generatedBody,
14827
15169
  requiredSections: getRequiredPrSections(config.lang),
14828
15170
  kindLabel: tg(config.lang, "kindPr"),
@@ -14840,7 +15182,7 @@ function githubCommand(program2) {
14840
15182
  `${feature.type}-pr-sanitized`,
14841
15183
  config.lang
14842
15184
  );
14843
- await fs.ensureDir(path13.dirname(sanitizedBodyFile));
15185
+ await fs.ensureDir(path15.dirname(sanitizedBodyFile));
14844
15186
  await fs.writeFile(sanitizedBodyFile, body, "utf-8");
14845
15187
  bodyFile = sanitizedBodyFile;
14846
15188
  }
@@ -14858,9 +15200,23 @@ function githubCommand(program2) {
14858
15200
  let syncChanged = false;
14859
15201
  const pushDocsSync = shouldPushDocsSync(config);
14860
15202
  if (options.create) {
15203
+ const projectGitCwd = resolveGithubProjectCwd(
15204
+ config,
15205
+ feature
15206
+ );
15207
+ const closingIssueNumber = resolvePrClosingIssueNumber(
15208
+ tasksContent,
15209
+ feature.issueNumber,
15210
+ config.lang
15211
+ );
15212
+ assertRemoteIssueExists(
15213
+ closingIssueNumber,
15214
+ projectGitCwd,
15215
+ config.lang
15216
+ );
14861
15217
  const normalizedBody = ensureIssueClosingLine(
14862
15218
  body,
14863
- feature.issueNumber
15219
+ closingIssueNumber
14864
15220
  );
14865
15221
  if (normalizedBody !== body) {
14866
15222
  body = normalizedBody;
@@ -14868,15 +15224,11 @@ function githubCommand(program2) {
14868
15224
  if (preparedBody.source === "generated") {
14869
15225
  await fs.writeFile(bodyFile, body, "utf-8");
14870
15226
  } else {
14871
- await fs.ensureDir(path13.dirname(fallbackBodyFile));
15227
+ await fs.ensureDir(path15.dirname(fallbackBodyFile));
14872
15228
  await fs.writeFile(fallbackBodyFile, body, "utf-8");
14873
15229
  bodyFile = fallbackBodyFile;
14874
15230
  }
14875
15231
  }
14876
- const projectGitCwd = resolveGithubProjectCwd(
14877
- config,
14878
- feature
14879
- );
14880
15232
  ensureNoTodoPlaceholders(
14881
15233
  body,
14882
15234
  tg(config.lang, "kindPr"),
@@ -14929,13 +15281,13 @@ function githubCommand(program2) {
14929
15281
  }
14930
15282
  if (prUrl && options.syncTasks !== false) {
14931
15283
  const syncedTasks = syncTasksPrMetadata(
14932
- path13.join(config.docsDir, paths.tasksPath),
15284
+ path15.join(config.docsDir, paths.tasksPath),
14933
15285
  prUrl,
14934
15286
  "Review",
14935
15287
  config.lang
14936
15288
  );
14937
15289
  const syncedDraft = syncPrDraftMetadata(
14938
- path13.join(config.docsDir, paths.prPath),
15290
+ path15.join(config.docsDir, paths.prPath),
14939
15291
  prUrl,
14940
15292
  "Review"
14941
15293
  );
@@ -14976,13 +15328,13 @@ function githubCommand(program2) {
14976
15328
  mergeAlreadyMerged = merged.alreadyMerged;
14977
15329
  if (prUrl && options.syncTasks !== false) {
14978
15330
  const mergedTasksSync = syncTasksPrMetadata(
14979
- path13.join(config.docsDir, paths.tasksPath),
15331
+ path15.join(config.docsDir, paths.tasksPath),
14980
15332
  prUrl,
14981
15333
  "Approved",
14982
15334
  config.lang
14983
15335
  );
14984
15336
  const mergedDraftSync = syncPrDraftMetadata(
14985
- path13.join(config.docsDir, paths.prPath),
15337
+ path15.join(config.docsDir, paths.prPath),
14986
15338
  prUrl,
14987
15339
  "Approved"
14988
15340
  );
@@ -15253,7 +15605,7 @@ function docsCommand(program2) {
15253
15605
  );
15254
15606
  return;
15255
15607
  }
15256
- const relativeFromCwd = path13.relative(process.cwd(), loaded.entry.absolutePath);
15608
+ const relativeFromCwd = path15.relative(process.cwd(), loaded.entry.absolutePath);
15257
15609
  console.log();
15258
15610
  console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
15259
15611
  console.log(
@@ -15333,7 +15685,7 @@ function detectCommand(program2) {
15333
15685
  }
15334
15686
  async function runDetect(options) {
15335
15687
  const cwd = process.cwd();
15336
- const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
15688
+ const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
15337
15689
  const config = await getConfig(targetCwd);
15338
15690
  const detected = !!config;
15339
15691
  const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
@@ -15360,7 +15712,7 @@ async function runDetect(options) {
15360
15712
  );
15361
15713
  return;
15362
15714
  }
15363
- const configPath2 = path13.join(config.docsDir, ".lee-spec-kit.json");
15715
+ const configPath2 = path15.join(config.docsDir, ".lee-spec-kit.json");
15364
15716
  const configFilePresent2 = await fs.pathExists(configPath2);
15365
15717
  const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
15366
15718
  console.log(
@@ -15394,7 +15746,7 @@ async function runDetect(options) {
15394
15746
  console.log();
15395
15747
  return;
15396
15748
  }
15397
- const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
15749
+ const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
15398
15750
  const configFilePresent = await fs.pathExists(configPath);
15399
15751
  const detectionSource = configFilePresent ? "config" : "heuristic";
15400
15752
  console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
@@ -15471,19 +15823,19 @@ function hasTemplateMarkers(content) {
15471
15823
  return patterns.some((pattern) => pattern.test(content));
15472
15824
  }
15473
15825
  async function countFeatureDirs(ctx, docsDir, projectType) {
15474
- const featuresRoot = path13.join(docsDir, "features");
15826
+ const featuresRoot = path15.join(docsDir, "features");
15475
15827
  if (projectType === "single") {
15476
15828
  const dirs = await listSubdirectories(ctx.fs, featuresRoot);
15477
- return dirs.filter((value) => path13.basename(value) !== "feature-base").length;
15829
+ return dirs.filter((value) => path15.basename(value) !== "feature-base").length;
15478
15830
  }
15479
15831
  const components = await listSubdirectories(ctx.fs, featuresRoot);
15480
15832
  let total = 0;
15481
15833
  for (const componentDir of components) {
15482
- const componentName = path13.basename(componentDir).trim().toLowerCase();
15834
+ const componentName = path15.basename(componentDir).trim().toLowerCase();
15483
15835
  if (!componentName || componentName === "feature-base") continue;
15484
15836
  const dirs = await listSubdirectories(ctx.fs, componentDir);
15485
15837
  total += dirs.filter(
15486
- (value) => path13.basename(value) !== "feature-base"
15838
+ (value) => path15.basename(value) !== "feature-base"
15487
15839
  ).length;
15488
15840
  }
15489
15841
  return total;
@@ -15495,7 +15847,7 @@ async function hasUserPrdFile(ctx, prdDir) {
15495
15847
  ignoreDirs: ["node_modules"]
15496
15848
  });
15497
15849
  return files.some(
15498
- (absolutePath) => path13.basename(absolutePath).toLowerCase() !== "readme.md"
15850
+ (absolutePath) => path15.basename(absolutePath).toLowerCase() !== "readme.md"
15499
15851
  );
15500
15852
  }
15501
15853
  function finalizeChecks(checks) {
@@ -15664,7 +16016,7 @@ async function runOnboardChecks(ctx) {
15664
16016
  });
15665
16017
  }
15666
16018
  }
15667
- const constitutionPath = path13.join(docsDir, "agents", "constitution.md");
16019
+ const constitutionPath = path15.join(docsDir, "agents", "constitution.md");
15668
16020
  if (!await fs.pathExists(constitutionPath)) {
15669
16021
  checks.push({
15670
16022
  id: "constitution_exists",
@@ -15706,7 +16058,7 @@ async function runOnboardChecks(ctx) {
15706
16058
  });
15707
16059
  }
15708
16060
  }
15709
- const customPath = path13.join(docsDir, "agents", "custom.md");
16061
+ const customPath = path15.join(docsDir, "agents", "custom.md");
15710
16062
  if (await fs.pathExists(customPath)) {
15711
16063
  const content = await fs.readFile(customPath, "utf-8");
15712
16064
  if (hasTemplateMarkers(content)) {
@@ -15735,7 +16087,7 @@ async function runOnboardChecks(ctx) {
15735
16087
  });
15736
16088
  }
15737
16089
  }
15738
- const prdDir = path13.join(docsDir, "prd");
16090
+ const prdDir = path15.join(docsDir, "prd");
15739
16091
  const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
15740
16092
  const prdReady = await hasUserPrdFile(ctx, prdDir);
15741
16093
  if (!prdReady) {
@@ -15753,7 +16105,7 @@ async function runOnboardChecks(ctx) {
15753
16105
  "PRD is empty. If features already exist, fill PRD as soon as possible."
15754
16106
  ),
15755
16107
  path: prdDir,
15756
- suggestedCommand: `touch ${quotePath(path13.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
16108
+ suggestedCommand: `touch ${quotePath(path15.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
15757
16109
  });
15758
16110
  } else {
15759
16111
  checks.push({
@@ -16191,7 +16543,7 @@ var PrePrReviewValidator = class {
16191
16543
  return result.evidence;
16192
16544
  }
16193
16545
  async validateEvidenceWithScope(evidencePath, projectRoot) {
16194
- const fullPath = path13.resolve(evidencePath);
16546
+ const fullPath = path15.resolve(evidencePath);
16195
16547
  if (!await fs.pathExists(fullPath)) {
16196
16548
  throw createCliError(
16197
16549
  "INVALID_ARGUMENT",
@@ -16289,9 +16641,9 @@ var PrePrReviewValidator = class {
16289
16641
  ]);
16290
16642
  const reviewedFiles = new Set(
16291
16643
  normalizedEvidence.files.map(
16292
- (f) => path13.relative(
16644
+ (f) => path15.relative(
16293
16645
  projectRoot,
16294
- path13.resolve(projectRoot, f.path)
16646
+ path15.resolve(projectRoot, f.path)
16295
16647
  )
16296
16648
  ).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
16297
16649
  );
@@ -16487,7 +16839,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
16487
16839
  residualRisks: ["none"],
16488
16840
  commandsExecuted: []
16489
16841
  };
16490
- function escapeRegExp5(value) {
16842
+ function escapeRegExp6(value) {
16491
16843
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16492
16844
  }
16493
16845
  function normalizeDecision(raw) {
@@ -16501,14 +16853,14 @@ function normalizeDecision(raw) {
16501
16853
  return null;
16502
16854
  }
16503
16855
  function findSpecLineIndex(lines, keys) {
16504
- const escaped = keys.map((key) => escapeRegExp5(key));
16856
+ const escaped = keys.map((key) => escapeRegExp6(key));
16505
16857
  const re = new RegExp(
16506
16858
  `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
16507
16859
  );
16508
16860
  return lines.findIndex((line) => re.test(line));
16509
16861
  }
16510
16862
  function replaceSpecLine(line, keys, preferredKey, value) {
16511
- const escaped = keys.map((key) => escapeRegExp5(key));
16863
+ const escaped = keys.map((key) => escapeRegExp6(key));
16512
16864
  const re = new RegExp(
16513
16865
  `^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
16514
16866
  );
@@ -16752,7 +17104,7 @@ async function runPrePrReviewRun(featureName, options) {
16752
17104
  );
16753
17105
  const policy = resolvePrePrReviewPolicy(config.workflow);
16754
17106
  const preferred = getPreferredKeys(config.lang);
16755
- const tasksPath = path13.join(feature.path, "tasks.md");
17107
+ const tasksPath = path15.join(feature.path, "tasks.md");
16756
17108
  let tasksUpdated = false;
16757
17109
  if (await fs.pathExists(tasksPath)) {
16758
17110
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
@@ -16799,8 +17151,6 @@ async function runPrePrReviewRun(featureName, options) {
16799
17151
  handoffOnly: true,
16800
17152
  advancesWorkflow: false,
16801
17153
  reuseKey: `pre-pr:${featureRef}`,
16802
- suggestedParallelism: 1,
16803
- fallbackToMainAgentWhenQuotaExceeded: true,
16804
17154
  nextStepRequirement: "generate_review_trace_then_record",
16805
17155
  delegatedWorkRequired: true,
16806
17156
  nextMainState: "pre_pr_review_in_progress",
@@ -16863,7 +17213,7 @@ async function runPrePrReview(featureName, options) {
16863
17213
  `tasks.md not found for feature: ${feature.folderName}`
16864
17214
  );
16865
17215
  }
16866
- const tasksPath = path13.join(feature.path, "tasks.md");
17216
+ const tasksPath = path15.join(feature.path, "tasks.md");
16867
17217
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
16868
17218
  const policy = resolvePrePrReviewPolicy(config.workflow);
16869
17219
  const preferred = getPreferredKeys(config.lang);
@@ -16961,7 +17311,7 @@ async function runPrePrReview(featureName, options) {
16961
17311
  }
16962
17312
  }
16963
17313
  }
16964
- const decisionsPath = path13.join(feature.path, "decisions.md");
17314
+ const decisionsPath = path15.join(feature.path, "decisions.md");
16965
17315
  const decisionLogEntry = buildReportContent({
16966
17316
  folderName: feature.folderName,
16967
17317
  date,
@@ -16977,9 +17327,9 @@ async function runPrePrReview(featureName, options) {
16977
17327
  await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
16978
17328
  }
16979
17329
  const decisionsPathFromDocs = normalizePathForDoc(
16980
- path13.join(feature.docs.featurePathFromDocs, "decisions.md")
17330
+ path15.join(feature.docs.featurePathFromDocs, "decisions.md")
16981
17331
  );
16982
- const evidencePath = path13.basename(config.docsDir) === "docs" ? normalizePathForDoc(path13.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
17332
+ const evidencePath = path15.basename(config.docsDir) === "docs" ? normalizePathForDoc(path15.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
16983
17333
  let nextTasks = tasksContent;
16984
17334
  nextTasks = upsertSpecLine(
16985
17335
  nextTasks,
@@ -17034,18 +17384,18 @@ async function runPrePrReview(featureName, options) {
17034
17384
  }
17035
17385
  console.log();
17036
17386
  }
17037
- function escapeRegExp6(value) {
17387
+ function escapeRegExp7(value) {
17038
17388
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17039
17389
  }
17040
17390
  function findSpecLineIndex2(lines, keys) {
17041
- const escaped = keys.map((key) => escapeRegExp6(key));
17391
+ const escaped = keys.map((key) => escapeRegExp7(key));
17042
17392
  const re = new RegExp(
17043
17393
  `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
17044
17394
  );
17045
17395
  return lines.findIndex((line) => re.test(line));
17046
17396
  }
17047
17397
  function replaceSpecLine2(line, keys, preferredKey, value) {
17048
- const escaped = keys.map((key) => escapeRegExp6(key));
17398
+ const escaped = keys.map((key) => escapeRegExp7(key));
17049
17399
  const re = new RegExp(
17050
17400
  `^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
17051
17401
  );
@@ -17120,7 +17470,7 @@ async function runCodeReviewRun(featureName, options) {
17120
17470
  );
17121
17471
  }
17122
17472
  const feature = state.matchedFeature;
17123
- const tasksPath = path13.join(feature.path, "tasks.md");
17473
+ const tasksPath = path15.join(feature.path, "tasks.md");
17124
17474
  let tasksUpdated = false;
17125
17475
  if (await fs.pathExists(tasksPath)) {
17126
17476
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
@@ -17150,12 +17500,10 @@ async function runCodeReviewRun(featureName, options) {
17150
17500
  handoffOnly: true,
17151
17501
  advancesWorkflow: false,
17152
17502
  reuseKey: `code-review:${feature.folderName}`,
17153
- suggestedParallelism: 1,
17154
- fallbackToMainAgentWhenQuotaExceeded: true,
17155
17503
  nextMainState: "code_review_running",
17156
17504
  tasksUpdated,
17157
17505
  tasksPath,
17158
- decisionsPath: path13.join(feature.path, "decisions.md"),
17506
+ decisionsPath: path15.join(feature.path, "decisions.md"),
17159
17507
  prompt,
17160
17508
  recordedAt: getLocalDateString()
17161
17509
  };
@@ -17173,12 +17521,6 @@ async function runCodeReviewRun(featureName, options) {
17173
17521
  console.log(chalk9.gray(`- substate: ${payload.substateId}`));
17174
17522
  console.log(chalk9.gray(`- owner: ${payload.owner}`));
17175
17523
  console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
17176
- console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
17177
- console.log(
17178
- chalk9.gray(
17179
- `- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
17180
- )
17181
- );
17182
17524
  console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
17183
17525
  if (tasksUpdated) {
17184
17526
  console.log(chalk9.gray(`- tasks.md updated: ${payload.tasksPath}`));
@@ -17280,7 +17622,7 @@ async function runRequirements(options) {
17280
17622
  }
17281
17623
  for (const feature of scan.features) {
17282
17624
  if (!feature.docs.tasksExists) continue;
17283
- const tasksPath = path13.join(feature.path, "tasks.md");
17625
+ const tasksPath = path15.join(feature.path, "tasks.md");
17284
17626
  let tasksContent = "";
17285
17627
  try {
17286
17628
  tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
@@ -17414,7 +17756,7 @@ async function runRequirements(options) {
17414
17756
  process.stdout.write(`${lines.join("\n")}
17415
17757
  `);
17416
17758
  if (options.write) {
17417
- const outputPath = path13.join(docsDir, "prd", "status.md");
17759
+ const outputPath = path15.join(docsDir, "prd", "status.md");
17418
17760
  await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
17419
17761
  `, "utf-8");
17420
17762
  console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
@@ -17499,7 +17841,7 @@ async function resolveTaskRunContext(featureName, options) {
17499
17841
  }
17500
17842
  async function runTaskRun(featureName, options) {
17501
17843
  const { config, feature } = await resolveTaskRunContext(featureName, options);
17502
- const tasksPath = path13.join(feature.path, "tasks.md");
17844
+ const tasksPath = path15.join(feature.path, "tasks.md");
17503
17845
  if (!await fs.pathExists(tasksPath)) {
17504
17846
  throw createCliError(
17505
17847
  "PRECONDITION_FAILED",
@@ -17556,8 +17898,6 @@ async function runTaskRun(featureName, options) {
17556
17898
  owner: "subagent",
17557
17899
  handoffOnly: true,
17558
17900
  reuseKey: `task:${feature.folderName}:${resolvedTask.taskId}`,
17559
- suggestedParallelism: 1,
17560
- fallbackToMainAgentWhenQuotaExceeded: true,
17561
17901
  nextMainState: "task_complete",
17562
17902
  tasksUpdated,
17563
17903
  tasksPath,
@@ -17573,12 +17913,6 @@ async function runTaskRun(featureName, options) {
17573
17913
  console.log(chalk9.gray(`- substate: ${payload.substateId}`));
17574
17914
  console.log(chalk9.gray(`- owner: ${payload.owner}`));
17575
17915
  console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
17576
- console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
17577
- console.log(
17578
- chalk9.gray(
17579
- `- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
17580
- )
17581
- );
17582
17916
  console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
17583
17917
  if (tasksUpdated) {
17584
17918
  console.log();
@@ -17658,7 +17992,7 @@ async function resolveTaskCompleteContext(featureName, options) {
17658
17992
  }
17659
17993
  async function runTaskComplete(featureName, options) {
17660
17994
  const { feature } = await resolveTaskCompleteContext(featureName, options);
17661
- const tasksPath = path13.join(feature.path, "tasks.md");
17995
+ const tasksPath = path15.join(feature.path, "tasks.md");
17662
17996
  if (!await fs.pathExists(tasksPath)) {
17663
17997
  throw createCliError(
17664
17998
  "PRECONDITION_FAILED",
@@ -17754,6 +18088,42 @@ function taskCompleteCommand(program2) {
17754
18088
  }
17755
18089
  );
17756
18090
  }
18091
+ function setupCommand(program2) {
18092
+ const setup = program2.command("setup").description("Developer environment setup helpers");
18093
+ setup.command("codex-bootstrap").description(
18094
+ "Install a small Codex global bootstrap that reads ./docs/AGENTS.md"
18095
+ ).option(
18096
+ "--remove",
18097
+ "Remove the lee-spec-kit managed Codex bootstrap block"
18098
+ ).action(async (options) => {
18099
+ const lang = DEFAULT_LANG;
18100
+ try {
18101
+ const filePath = getCodexConfigPath();
18102
+ if (options.remove) {
18103
+ const result2 = await removeLeeSpecKitCodexBootstrap(filePath);
18104
+ const message = result2.changed ? tr(lang, "cli", "setup.codexBootstrapRemoved", {
18105
+ path: filePath
18106
+ }) : tr(lang, "cli", "setup.codexBootstrapAlreadyAbsent", {
18107
+ path: filePath
18108
+ });
18109
+ console.log(chalk9.green(message));
18110
+ return;
18111
+ }
18112
+ const result = await upsertLeeSpecKitCodexBootstrap(filePath);
18113
+ const key = result.action === "noop" ? "setup.codexBootstrapAlreadyInstalled" : "setup.codexBootstrapInstalled";
18114
+ console.log(chalk9.green(tr(lang, "cli", key, { path: filePath })));
18115
+ } catch (error) {
18116
+ const cliError = toCliError(error);
18117
+ const suggestions = getCliErrorSuggestions(cliError.code, lang);
18118
+ console.error(
18119
+ chalk9.red(tr(lang, "cli", "common.errorLabel")),
18120
+ chalk9.red(`[${cliError.code}] ${cliError.message}`)
18121
+ );
18122
+ printCliErrorSuggestions(suggestions, lang);
18123
+ process.exitCode = 1;
18124
+ }
18125
+ });
18126
+ }
17757
18127
  function isBannerDisabled() {
17758
18128
  const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
17759
18129
  return v === "1";
@@ -17797,11 +18167,11 @@ ${version}
17797
18167
  }
17798
18168
  return `${ascii}${footer}`;
17799
18169
  }
17800
- var CACHE_FILE = path13.join(os.homedir(), ".lee-spec-kit-version-cache.json");
18170
+ var CACHE_FILE = path15.join(os.homedir(), ".lee-spec-kit-version-cache.json");
17801
18171
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
17802
18172
  function getCurrentVersion() {
17803
18173
  try {
17804
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
18174
+ const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
17805
18175
  if (fs.existsSync(packageJsonPath)) {
17806
18176
  const pkg = fs.readJsonSync(packageJsonPath);
17807
18177
  return pkg.version;
@@ -17905,7 +18275,7 @@ function shouldCheckForUpdates() {
17905
18275
  if (shouldCheckForUpdates()) checkForUpdates();
17906
18276
  function getCliVersion() {
17907
18277
  try {
17908
- const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
18278
+ const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
17909
18279
  if (fs.existsSync(packageJsonPath)) {
17910
18280
  const pkg = fs.readJsonSync(packageJsonPath);
17911
18281
  if (pkg?.version) return String(pkg.version);
@@ -17940,6 +18310,7 @@ codeReviewRunCommand(program);
17940
18310
  taskRunCommand(program);
17941
18311
  taskCompleteCommand(program);
17942
18312
  requirementsCommand(program);
18313
+ setupCommand(program);
17943
18314
  await program.parseAsync();
17944
18315
  //# sourceMappingURL=index.js.map
17945
18316
  //# sourceMappingURL=index.js.map