lee-spec-kit 0.7.9 → 0.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import path15 from 'path';
2
+ import path16 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
5
  import fs from 'fs-extra';
@@ -8,11 +8,12 @@ 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 fs12 from 'fs';
11
+ import fs13 from 'fs';
12
+ import fs20 from 'fs/promises';
12
13
  import { Buffer as Buffer$1 } from 'buffer';
13
14
 
14
15
  var getFilename = () => fileURLToPath(import.meta.url);
15
- var getDirname = () => path15.dirname(getFilename());
16
+ var getDirname = () => path16.dirname(getFilename());
16
17
  var __dirname$1 = /* @__PURE__ */ getDirname();
17
18
  async function walkFiles(fsAdapter, rootDir, options = {}) {
18
19
  const out = [];
@@ -25,7 +26,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
25
26
  async function visit(current) {
26
27
  const entries = await fsAdapter.readdir(current);
27
28
  for (const entryName of entries) {
28
- const absolute = path15.join(current, entryName);
29
+ const absolute = path16.join(current, entryName);
29
30
  const stat = await fsAdapter.stat(absolute);
30
31
  if (stat.isDirectory()) {
31
32
  if (ignored.has(entryName.trim().toLowerCase())) continue;
@@ -34,7 +35,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
34
35
  }
35
36
  if (!stat.isFile()) continue;
36
37
  if (normalizedExtensions.size > 0) {
37
- const ext = path15.extname(entryName).toLowerCase();
38
+ const ext = path16.extname(entryName).toLowerCase();
38
39
  if (!normalizedExtensions.has(ext)) continue;
39
40
  }
40
41
  out.push(absolute);
@@ -50,7 +51,7 @@ async function listSubdirectories(fsAdapter, rootDir) {
50
51
  const entries = await fsAdapter.readdir(rootDir);
51
52
  const dirs = [];
52
53
  for (const entryName of entries) {
53
- const absolute = path15.join(rootDir, entryName);
54
+ const absolute = path16.join(rootDir, entryName);
54
55
  const stat = await fsAdapter.stat(absolute);
55
56
  if (stat.isDirectory()) {
56
57
  dirs.push(absolute);
@@ -151,10 +152,10 @@ var DefaultFileSystemAdapter = class {
151
152
  }
152
153
  };
153
154
  var __filename2 = fileURLToPath(import.meta.url);
154
- var __dirname2 = path15.dirname(__filename2);
155
+ var __dirname2 = path16.dirname(__filename2);
155
156
  function getTemplatesDir() {
156
- const rootDir = path15.resolve(__dirname2, "..");
157
- return path15.join(rootDir, "templates");
157
+ const rootDir = path16.resolve(__dirname2, "..");
158
+ return path16.join(rootDir, "templates");
158
159
  }
159
160
 
160
161
  // src/utils/locales/ko/cli.ts
@@ -212,7 +213,7 @@ var koCli = {
212
213
  "doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
213
214
  "doctor.issue.tasksDocStatusUnset": "tasks.md\uC758 \uBB38\uC11C \uC0C1\uD0DC(Doc Status)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
214
215
  "doctor.issue.tasksDocStatusMissing": "tasks.md\uC5D0 \uBB38\uC11C \uC0C1\uD0DC(Doc Status) \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **\uBB38\uC11C \uC0C1\uD0DC**: -`\uC640 `\uAC12: Draft | Review | Approved`\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
215
- "doctor.issue.tasksPrdTagUnknown": "tasks.md\uC5D0 \uC815\uC758\uB418\uC9C0 \uC54A\uC740 PRD \uD0DC\uADF8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: {ids}{extra}. tasks.md\uC5D0\uC11C PRD-FR-001 \uAC19\uC740 ID\uB97C \uC784\uC758\uB85C \uB9CC\uB4E4\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 docs/prd \uB610\uB294 \uC0C1\uC704 \uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C\uC5D0 ID\uB97C backfill\uD55C \uB4A4 spec.md `PRD Refs`\uC640 tasks \uD0DC\uADF8\uB97C \uD568\uAED8 \uB9DE\uCD94\uC138\uC694.",
216
+ "doctor.issue.tasksPrdTagUnknown": "tasks.md\uC5D0 \uC815\uC758\uB418\uC9C0 \uC54A\uC740 PRD \uD0DC\uADF8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: {ids}{extra}. tasks.md\uC5D0\uC11C `PRD-*` ID\uB97C \uC784\uC758\uB85C \uB9CC\uB4E4\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 docs/prd \uB610\uB294 \uC0C1\uC704 \uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C\uC5D0 ID\uB97C backfill\uD55C \uB4A4 spec.md `PRD Refs`\uC640 tasks \uD0DC\uADF8\uB97C \uD568\uAED8 \uB9DE\uCD94\uC138\uC694.",
216
217
  "doctor.issue.unmanagedDocsEntry": "lee-spec-kit \uBB38\uC11C \uD45C\uBA74 \uBC16\uC758 \uBB38\uC11C \uC5D4\uD2B8\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4: {path}. \uCD5C\uC885 SSOT\uB85C \uC4F0\uC9C0 \uB9D0\uACE0 feature-local docs\uB85C \uC815\uADDC\uD654\uD558\uAC70\uB098, \uC758\uB3C4\uB41C \uACBD\uB85C\uB77C\uBA74 `.lee-spec-kit.json`\uC758 `allowedDocsEntries`\uC5D0 \uCD94\uAC00\uD558\uC138\uC694.",
217
218
  "doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
218
219
  "doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
@@ -255,7 +256,7 @@ var koCli = {
255
256
  "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
256
257
  "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
257
258
  "init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
258
- "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",
259
+ "init.log.nextSteps4": " 4. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 AGENTS.md \uC5C6\uC774 Codex\uB97C \uC4F4\uB2E4\uBA74 bootstrap helper \uB97C \uC120\uD0DD\uC801\uC73C\uB85C \uC124\uCE58: npx lee-spec-kit integrations codex",
259
260
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
260
261
  "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
261
262
  "init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
@@ -274,10 +275,10 @@ var koCli = {
274
275
  "idea.nextSteps1": " 1. \uBC94\uC704, PRD Refs, \uC2B9\uACA9 \uBA54\uBAA8\uB97C \uC791\uC131",
275
276
  "idea.nextSteps2": " 2. Feature\uB85C \uC2B9\uACA9: npx lee-spec-kit feature <name> --idea {ideaId}",
276
277
  "idea.nextSteps3": " 3. Feature\uB85C \uB9CC\uB4E4\uC9C0 \uC54A\uC744 \uACBD\uC6B0 Dropped\uB85C \uD45C\uC2DC",
277
- "setup.codexBootstrapInstalled": "\u2705 Codex bootstrap \uC124\uCE58 \uC644\uB8CC: {path}",
278
- "setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: {path}",
279
- "setup.codexBootstrapRemoved": "\u2705 Codex bootstrap \uC81C\uAC70 \uC644\uB8CC: {path}",
280
- "setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
278
+ "setup.codexBootstrapInstalled": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC124\uCE58 \uC644\uB8CC: {path}",
279
+ "setup.codexBootstrapAlreadyInstalled": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: {path}",
280
+ "setup.codexBootstrapRemoved": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC81C\uAC70 \uC644\uB8CC: {path}",
281
+ "setup.codexBootstrapAlreadyAbsent": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC774 \uC774\uBBF8 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
281
282
  "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)",
282
283
  "github.cmdIssueDescription": "feature \uBB38\uC11C \uAE30\uBC18 GitHub issue \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131",
283
284
  "github.cmdPrDescription": "GitHub PR \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131 + tasks \uB3D9\uAE30\uD654 + merge \uC7AC\uC2DC\uB3C4",
@@ -478,7 +479,7 @@ var koContext = {
478
479
  "context.checkRequired": "[\uD655\uC778 \uD544\uC694] ",
479
480
  "context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uC815\uCC45\uC740 \uC138\uC158 \uC2DC\uC791(\uB610\uB294 context \uC555\uCD95/\uB9AC\uC14B \uC9C1\uD6C4)\uC5D0 1\uD68C \uD655\uC778\uD558\uACE0, \uC774\uD6C4\uC5D0\uB294 \uC815\uCC45/\uC124\uC815 \uBCC0\uACBD \uB610\uB294 \uC0AC\uC6A9\uC790 \uC0C8\uB85C\uACE0\uCE68 \uC694\uCCAD \uC2DC\uC5D0\uB9CC \uC7AC\uD655\uC778\uD558\uC138\uC694. (git push/merge/merge commit \uD3EC\uD568) [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uB77C\uBCA8 \uD1A0\uD070 \uADDC\uCE59(`A`, `A OK`, `A \uC9C4\uD589\uD574`)\uC5D0 \uB9DE\uB294 \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589\uD558\uC138\uC694. (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
480
481
  "context.actionOptionHint": "\uB77C\uBCA8 \uC751\uB2F5 \uADDC\uCE59: `A`, `A OK`, `A \uC9C4\uD589\uD574` \uC911 \uD558\uB098\uC758 \uD615\uC2DD\uC73C\uB85C \uC751\uB2F5",
481
- "context.actionExplainHint": "CLI\uAC00 \uC900 \uC2B9\uC778 \uBB38\uAD6C\uB97C \uBA3C\uC800 \uADF8\uB300\uB85C \uBCF4\uC5EC\uC8FC\uC138\uC694. \uCD94\uAC00 \uC124\uBA85\uC740 \uC0AC\uC6A9\uC790\uAC00 \uBB3C\uC744 \uB54C\uB9CC \uB367\uBD99\uC774\uACE0, \uC2B9\uC778 \uBB38\uAD6C \uC790\uCCB4\uB294 \uBC14\uAFB8\uC9C0 \uB9C8\uC138\uC694.",
482
+ "context.actionExplainHint": "\uC2B9\uC778 \uB300\uAE30 \uC0C1\uD0DC\uB77C\uBA74 `matchedFeature.currentSubstate*` \uAE30\uBC18 \uD604\uC7AC \uB2E8\uACC4 \uD55C \uC904 \uC694\uC57D\uC744 \uBA3C\uC800 \uBD99\uC77C \uC218 \uC788\uACE0, \uADF8 \uB2E4\uC74C CLI\uAC00 \uC900 \uC2B9\uC778 \uBB38\uAD6C\uB97C \uADF8\uB300\uB85C \uBCF4\uC5EC\uC8FC\uC138\uC694. \uCD94\uAC00 \uC124\uBA85\uC740 \uC0AC\uC6A9\uC790\uAC00 \uBB3C\uC744 \uB54C\uB9CC \uB367\uBD99\uC774\uACE0, \uC2B9\uC778 \uBB38\uAD6C \uC790\uCCB4\uB294 \uBC14\uAFB8\uC9C0 \uB9C8\uC138\uC694.",
482
483
  "context.finalLabelPrompt": "\uD604\uC7AC \uC120\uD0DD \uAC00\uB2A5\uD55C \uB77C\uBCA8: {labels}. \uB77C\uBCA8 \uC751\uB2F5 \uADDC\uCE59(`A`, `A OK`, `A \uC9C4\uD589\uD574`)\uC73C\uB85C \uC751\uB2F5\uD558\uC138\uC694. (\uC608: `{example}`)",
483
484
  "context.finalLabelPromptWithRequest": "\uD604\uC7AC \uC120\uD0DD \uAC00\uB2A5\uD55C \uB77C\uBCA8: {labels}. \uB77C\uBCA8 \uC751\uB2F5 \uADDC\uCE59(`A`, `A OK`, `A \uC9C4\uD589\uD574`)\uC73C\uB85C \uC751\uB2F5\uD558\uC138\uC694. (\uC608: `{example}`) \uC694\uCCAD \uD14D\uC2A4\uD2B8\uAC00 \uD544\uC694\uD55C \uB77C\uBCA8\uC740 \uB2E4\uC74C \uD615\uC2DD\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694: {requestExamples}",
484
485
  "context.suggestionHeader": "\uCD94\uCC9C \uB2E4\uC74C \uC120\uD0DD\uC9C0",
@@ -508,6 +509,7 @@ var koContext = {
508
509
  "context.actionDetail.tasksWriteCreate": "tasks.md\uB97C \uC0DD\uC131\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815\uD558\uC138\uC694",
509
510
  "context.actionDetail.tasksWriteNeedAtLeastOne": "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uCD94\uAC00\uD558\uC138\uC694",
510
511
  "context.actionDetail.tasksWriteImprove": "tasks.md\uB97C \uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C \uC815\uB82C\uD558\uC138\uC694",
512
+ "context.actionDetail.tasksWriteChangeSync": "\uCD94\uAC00 \uAD6C\uD604 \uC804\uC5D0 \uC0C8 \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC744 tasks.md\uC5D0 \uBA3C\uC800 \uBC18\uC601\uD558\uC138\uC694",
511
513
  "context.actionDetail.tasksApprove": "tasks.md\uB97C \uC2B9\uC778\uD569\uB2C8\uB2E4",
512
514
  "context.actionDetail.issueCreate": "\uC774\uC288\uB97C \uC0DD\uC131\uD558\uACE0 tasks.md\uC758 \uC774\uC288 \uC815\uBCF4\uB97C \uB9DE\uCD94\uC138\uC694",
513
515
  "context.actionDetail.issueCreateAndWrite": "\uC774\uC288 \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uB77C\uBCA8 \uC2B9\uC778(`A` \uB610\uB294 `A OK`) \uD6C4 \uC774\uC288\uB97C \uC0DD\uC131\uD574 \uBC88\uD638\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
@@ -680,6 +682,7 @@ var koMessages = {
680
682
  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.",
681
683
  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.",
682
684
  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.",
685
+ changeRequestSync: 'tasks.md\uC5D0 \uC0C8 \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC774 \uAE30\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: "{request}". \uCD94\uAC00 \uAD6C\uD604 \uC804\uC5D0 \uBA3C\uC800 tasks.md\uB97C \uAC31\uC2E0\uD558\uC138\uC694. \uC601\uD5A5\uBC1B\uB294 \uD0DC\uC2A4\uD06C\uB97C \uCD94\uAC00\uD558\uAC70\uB098 \uC7AC\uD0DC\uAE45\uD558\uACE0, \uC774 \uC694\uCCAD\uC774 \uC2E4\uC81C \uC0AC\uC6A9\uC790 \uB3D9\uC791\uC774\uB098 \uBC94\uC704\uB97C \uBC14\uAFB8\uBA74 decisions.md\uC640 spec/PRD \uCC38\uC870\uB3C4 \uD568\uAED8 \uB9DE\uCD94\uC138\uC694. \uB3D9\uAE30\uD654\uAC00 \uB05D\uB098\uBA74 `\uB300\uAE30 \uC911 \uBCC0\uACBD \uC694\uCCAD` \uAC12\uC744 \uBE44\uC6B0\uACE0 context/flow\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.',
683
686
  docsUnmanagedNormalize: "lee-spec-kit \uBB38\uC11C \uD45C\uBA74 \uBC16\uC758 \uBB38\uC11C \uC5D4\uD2B8\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4: {paths}. Feature `{featureRef}`\uB97C \uACC4\uC18D \uC9C4\uD589\uD558\uAE30 \uC804\uC5D0 \uC774 \uBB38\uC11C\uB4E4\uC740 reference \uC785\uB825\uC73C\uB85C\uB9CC \uCDE8\uAE09\uD558\uC138\uC694. \uC0AC\uC6A9\uC790 \uBC94\uC704/acceptance\uB294 `spec.md`, \uC544\uD0A4\uD14D\uCC98/\uD30C\uC77C/\uD14C\uC2A4\uD2B8 \uB0B4\uC6A9\uC740 `plan.md`, \uC2E4\uD589 \uC791\uC5C5\uC740 `tasks.md`, \uADFC\uAC70/\uD2B8\uB808\uC774\uB4DC\uC624\uD504\uB294 `decisions.md`\uB85C \uC815\uADDC\uD654\uD55C \uB4A4 `npx lee-spec-kit context {featureRef}`\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
684
687
  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.",
685
688
  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."
@@ -789,7 +792,7 @@ var enCli = {
789
792
  "doctor.issue.tasksEmpty": "tasks.md has no tasks.",
790
793
  "doctor.issue.tasksDocStatusUnset": "tasks.md Doc Status is not set. (Set it to Draft, Review, or Approved.)",
791
794
  "doctor.issue.tasksDocStatusMissing": "tasks.md is missing the Doc Status field. Add `- **Doc Status**: -` and `Values: Draft | Review | Approved`.",
792
- "doctor.issue.tasksPrdTagUnknown": "tasks.md uses PRD tags with no matching source definition: {ids}{extra}. Do not invent IDs like PRD-FR-001 in tasks.md. Backfill IDs in docs/prd or the upstream requirements doc first, then align spec.md `PRD Refs` and task tags.",
795
+ "doctor.issue.tasksPrdTagUnknown": "tasks.md uses PRD tags with no matching source definition: {ids}{extra}. Do not invent `PRD-*` IDs in tasks.md. Backfill IDs in docs/prd or the upstream requirements doc first, then align spec.md `PRD Refs` and task tags.",
793
796
  "doctor.issue.unmanagedDocsEntry": "Unmanaged docs entry detected outside the lee-spec-kit docs surface: {path}. Treat it as reference input only, normalize it into feature-local docs, or allowlist it in `.lee-spec-kit.json` `allowedDocsEntries`.",
794
797
  "doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
795
798
  "doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
@@ -832,7 +835,7 @@ var enCli = {
832
835
  "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
833
836
  "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
834
837
  "init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
835
- "init.log.nextSteps4": " 4. If you use Codex without repo-root AGENTS.md, install bootstrap: npx lee-spec-kit setup codex-bootstrap",
838
+ "init.log.nextSteps4": " 4. If you use Codex without repo-root AGENTS.md, you can optionally install the bootstrap helper: npx lee-spec-kit integrations codex",
836
839
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
837
840
  "init.log.gitInit": "\u{1F4E6} Initializing Git...",
838
841
  "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.)',
@@ -851,10 +854,10 @@ var enCli = {
851
854
  "idea.nextSteps1": " 1. Fill scope, PRD refs, and promotion notes",
852
855
  "idea.nextSteps2": " 2. Promote it with: npx lee-spec-kit feature <name> --idea {ideaId}",
853
856
  "idea.nextSteps3": " 3. Mark it dropped if it should not become a feature",
854
- "setup.codexBootstrapInstalled": "\u2705 Codex bootstrap installed: {path}",
855
- "setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap already installed: {path}",
856
- "setup.codexBootstrapRemoved": "\u2705 Codex bootstrap removed: {path}",
857
- "setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap is already absent: {path}",
857
+ "setup.codexBootstrapInstalled": "\u2705 Optional Codex bootstrap installed: {path}",
858
+ "setup.codexBootstrapAlreadyInstalled": "\u2705 Optional Codex bootstrap already installed: {path}",
859
+ "setup.codexBootstrapRemoved": "\u2705 Optional Codex bootstrap removed: {path}",
860
+ "setup.codexBootstrapAlreadyAbsent": "\u2705 Optional Codex bootstrap is already absent: {path}",
858
861
  "github.cmdGithubDescription": "GitHub workflow helpers (issue/pr templates, validation, merge retry)",
859
862
  "github.cmdIssueDescription": "Generate/create GitHub issue body from feature docs with validation",
860
863
  "github.cmdPrDescription": "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry",
@@ -1055,7 +1058,7 @@ var enContext = {
1055
1058
  "context.checkRequired": "[CHECK required] ",
1056
1059
  "context.checkPolicyHint": "\u2139\uFE0F Check user-approval policy once at session start (or right after context compression/reset); re-check only when policy/config changes or the user explicitly requests refresh. (includes git push/merge and merge commits). If you see [CHECK required], wait for a reply that follows label-token rules (`A`, `A OK`, `A proceed`) before proceeding (config: approval can override)",
1057
1060
  "context.actionOptionHint": "Label reply rules: answer as `A`, `A OK`, or `A proceed`",
1058
- "context.actionExplainHint": "Use the exact approval lines from the CLI first. Add extra explanation only if the user asks, and do not paraphrase the approval prompts.",
1061
+ "context.actionExplainHint": "If approval is pending, you may add one brief current-stage recap from `matchedFeature.currentSubstate*`, then use the exact approval lines from the CLI. Add further explanation only if the user asks, and do not paraphrase the approval prompts.",
1059
1062
  "context.finalLabelPrompt": "Available labels now: {labels}. Reply using label-token rules (`A`, `A OK`, `A proceed`). (e.g. `{example}`)",
1060
1063
  "context.finalLabelPromptWithRequest": "Available labels now: {labels}. Reply using label-token rules (`A`, `A OK`, `A proceed`). (e.g. `{example}`) Labels that require a request must be replied as: {requestExamples}",
1061
1064
  "context.suggestionHeader": "Suggested Next Options",
@@ -1085,6 +1088,7 @@ var enContext = {
1085
1088
  "context.actionDetail.tasksWriteCreate": "Create tasks.md and set Doc Status to Review",
1086
1089
  "context.actionDetail.tasksWriteNeedAtLeastOne": "Add at least one task to tasks.md",
1087
1090
  "context.actionDetail.tasksWriteImprove": "Refine tasks.md and align Doc Status",
1091
+ "context.actionDetail.tasksWriteChangeSync": "Sync the new user request into tasks.md before more implementation",
1088
1092
  "context.actionDetail.tasksApprove": "Approve tasks.md",
1089
1093
  "context.actionDetail.issueCreate": "Create the issue and sync issue fields in tasks.md",
1090
1094
  "context.actionDetail.issueCreateAndWrite": "Draft issue content, get explicit OK, then create and sync Issue",
@@ -1257,6 +1261,7 @@ var enMessages = {
1257
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.",
1258
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.",
1259
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.",
1264
+ changeRequestSync: 'A pending user request is recorded in tasks.md: "{request}". Before more implementation, update tasks.md first. Add or retag the affected task, and if this changes shipped behavior or scope, sync decisions.md plus spec/PRD references as needed. Clear `Pending Change Request` after syncing, then rerun context/flow.',
1260
1265
  docsUnmanagedNormalize: "Unmanaged docs entries were found outside the lee-spec-kit docs surface: {paths}. Before continuing feature `{featureRef}`, treat them as reference inputs only. Normalize user-facing scope and acceptance into `spec.md`, architecture/file/test details into `plan.md`, executable work into `tasks.md`, and rationale/trade-offs into `decisions.md`, then rerun `npx lee-spec-kit context {featureRef}`.",
1261
1266
  featureDone: "All workflow requirements and local cleanup are complete. This feature is fully finished.",
1262
1267
  fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
@@ -1757,10 +1762,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
1757
1762
  var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
1758
1763
  var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
1759
1764
  function toScopeKey(value) {
1760
- return createHash("sha1").update(path15.resolve(value)).digest("hex").slice(0, 16);
1765
+ return createHash("sha1").update(path16.resolve(value)).digest("hex").slice(0, 16);
1761
1766
  }
1762
1767
  function getTempRuntimeDir(scopePath) {
1763
- return path15.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1768
+ return path16.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1764
1769
  }
1765
1770
  function resolveGitRuntimeDir(cwd) {
1766
1771
  try {
@@ -1774,38 +1779,38 @@ function resolveGitRuntimeDir(cwd) {
1774
1779
  }
1775
1780
  ).trim();
1776
1781
  if (!out) return null;
1777
- return path15.isAbsolute(out) ? out : path15.resolve(cwd, out);
1782
+ return path16.isAbsolute(out) ? out : path16.resolve(cwd, out);
1778
1783
  } catch {
1779
1784
  return null;
1780
1785
  }
1781
1786
  }
1782
1787
  function getRuntimeStateDir(cwd) {
1783
- const resolved = path15.resolve(cwd);
1788
+ const resolved = path16.resolve(cwd);
1784
1789
  return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
1785
1790
  }
1786
1791
  function getDocsLockPath(docsDir) {
1787
- return path15.join(
1792
+ return path16.join(
1788
1793
  getRuntimeStateDir(docsDir),
1789
1794
  "locks",
1790
1795
  `docs-${toScopeKey(docsDir)}.lock`
1791
1796
  );
1792
1797
  }
1793
1798
  function getInitLockPath(targetDir) {
1794
- return path15.join(
1795
- getRuntimeStateDir(path15.dirname(path15.resolve(targetDir))),
1799
+ return path16.join(
1800
+ getRuntimeStateDir(path16.dirname(path16.resolve(targetDir))),
1796
1801
  "locks",
1797
1802
  `init-${toScopeKey(targetDir)}.lock`
1798
1803
  );
1799
1804
  }
1800
1805
  function getApprovalTicketStorePath(docsDir) {
1801
- return path15.join(
1806
+ return path16.join(
1802
1807
  getRuntimeStateDir(docsDir),
1803
1808
  "tickets",
1804
1809
  `approval-${toScopeKey(docsDir)}.json`
1805
1810
  );
1806
1811
  }
1807
1812
  function getProjectExecutionLockPath(cwd) {
1808
- return path15.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1813
+ return path16.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1809
1814
  }
1810
1815
  async function isStaleLock(lockPath, staleMs) {
1811
1816
  try {
@@ -1846,7 +1851,7 @@ function isProcessAlive(pid) {
1846
1851
  }
1847
1852
  }
1848
1853
  async function tryAcquire(lockPath, owner) {
1849
- await fs.ensureDir(path15.dirname(lockPath));
1854
+ await fs.ensureDir(path16.dirname(lockPath));
1850
1855
  try {
1851
1856
  const fd = await fs.open(lockPath, "wx");
1852
1857
  const payload = JSON.stringify(
@@ -1923,30 +1928,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
1923
1928
  "pr-template.md"
1924
1929
  ];
1925
1930
  var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1926
- var ENGINE_MANAGED_FEATURE_PATH = path15.join(
1931
+ var ENGINE_MANAGED_FEATURE_PATH = path16.join(
1927
1932
  "features",
1928
1933
  "feature-base"
1929
1934
  );
1930
1935
  async function pruneEngineManagedDocs(docsDir) {
1931
1936
  const removed = [];
1932
1937
  for (const file of ENGINE_MANAGED_AGENT_FILES) {
1933
- const target = path15.join(docsDir, "agents", file);
1938
+ const target = path16.join(docsDir, "agents", file);
1934
1939
  if (await fs.pathExists(target)) {
1935
1940
  await fs.remove(target);
1936
- removed.push(path15.relative(docsDir, target));
1941
+ removed.push(path16.relative(docsDir, target));
1937
1942
  }
1938
1943
  }
1939
1944
  for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1940
- const target = path15.join(docsDir, "agents", dir);
1945
+ const target = path16.join(docsDir, "agents", dir);
1941
1946
  if (await fs.pathExists(target)) {
1942
1947
  await fs.remove(target);
1943
- removed.push(path15.relative(docsDir, target));
1948
+ removed.push(path16.relative(docsDir, target));
1944
1949
  }
1945
1950
  }
1946
- const featureBasePath = path15.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1951
+ const featureBasePath = path16.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1947
1952
  if (await fs.pathExists(featureBasePath)) {
1948
1953
  await fs.remove(featureBasePath);
1949
- removed.push(path15.relative(docsDir, featureBasePath));
1954
+ removed.push(path16.relative(docsDir, featureBasePath));
1950
1955
  }
1951
1956
  return removed;
1952
1957
  }
@@ -2027,25 +2032,24 @@ Auto-run continuity (main/sub-agent orchestration):
2027
2032
  3. Else run \`npx lee-spec-kit context --json-compact\` (fallback: \`--json\`) and continue from current \`actionOptions\`/\`autoRun\`.
2028
2033
  - Pause and report to user only when:
2029
2034
  - \`approvalRequest.required === true\`, or
2030
- - \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\`, \`AUTO_DELEGATED_HANDOFF\`, or \`AUTO_MANUAL_REQUIRED\`, or
2035
+ - \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\`, \`AUTO_DELEGATED_HANDOFF\`, \`AUTO_MANUAL_REQUIRED\`, or \`AUTO_SELECTION_REQUIRED\`, or
2031
2036
  - command execution fails (non-zero/error), or
2032
2037
  - user explicitly asks to pause.
2038
+ - Special case: if \`matchedFeature.currentSubstateId === "change_request_sync"\` or \`matchedFeature.pendingChangeRequest\` is present, treat that \`AUTO_MANUAL_REQUIRED\` state as an internal docs-sync step first, not an immediate user-facing stop. Update \`tasks.md\` first, and if shipped behavior or scope changed, sync \`decisions.md\` plus \`spec.md\` / PRD refs as needed. Clear the pending change field after syncing, then continue with fresh \`context\`/\`flow\`. Only pause/report there if approval becomes required, execution fails, or the user explicitly asked to pause.
2033
2039
 
2034
2040
  User-facing output rule (state-aware):
2035
2041
 
2036
2042
  - Treat approval as a separate state.
2037
- - Approval-waiting state means:
2038
- - \`context --json-compact\` (or \`context --json\`) includes one or more \`actionOptions\`, and
2039
- - you are explicitly waiting for user approval before execution.
2043
+ - Approval-waiting state means the latest \`context --json-compact\` / \`flow --json-compact\` (fallback: \`context --json\` / \`flow --json\`) shows \`approvalRequest.required === true\`.
2044
+ - Do not infer approval-waiting from conversation tone, action type, or whether \`actionOptions[]\` merely exist.
2040
2045
 
2041
2046
  In approval-waiting state:
2042
2047
 
2043
- 1. Show \`actionOptions[*].approvalPrompt\` lines (at minimum, the primary label line like \`A: ...\`) exactly as provided.
2044
- 2. End with \`approvalRequest.finalPrompt\` exactly as provided.
2045
- 3. Do not paraphrase or omit these lines.
2046
- 4. Prefer \`approvalRequest.userFacingLines\` as the source for user-facing approval text.
2047
- 5. Prefer \`matchedFeature.currentSubstateOwner\` plus \`agentOrchestration.subAgentHandoff\` as the delegation SSOT.
2048
- 6. When \`matchedFeature.currentSubstateOwner="subagent"\` and \`agentOrchestration.subAgentHandoff.required=true\` with \`mode="command"\`, call \`spawn_agent\` first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, continue the delegated work immediately and do not re-open the same approval label.
2048
+ 1. If \`matchedFeature.currentSubstateId\` is present, prepend one brief current-stage recap derived from \`matchedFeature.currentSubstateId/currentSubstateOwner/currentSubstatePhase\`.
2049
+ 2. Then show \`approvalRequest.userFacingLines\` exactly as provided. If those lines are unavailable, fall back to \`actionOptions[*].approvalPrompt\` plus \`approvalRequest.finalPrompt\`.
2050
+ 3. Do not paraphrase, reorder, or omit the CLI-provided approval lines.
2051
+ 4. Prefer \`matchedFeature.currentSubstateOwner\` plus \`agentOrchestration.subAgentHandoff\` as the delegation SSOT.
2052
+ 5. When \`matchedFeature.currentSubstateOwner="subagent"\` and \`agentOrchestration.subAgentHandoff.required=true\` with \`mode="command"\`, call \`spawn_agent\` first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, continue the delegated work immediately and do not re-open the same approval label.
2049
2053
 
2050
2054
  In non-approval state (progress updates, analysis, tool execution logs, unrelated Q&A):
2051
2055
 
@@ -2056,10 +2060,11 @@ In non-approval state (progress updates, analysis, tool execution logs, unrelate
2056
2060
  If approval is still pending after answering an unrelated question:
2057
2061
 
2058
2062
  - First answer the question.
2059
- - Then re-open approval using both:
2060
- - \`actionOptions[*].approvalPrompt\` (label meaning included), and
2061
- - \`approvalRequest.finalPrompt\` (format line).
2062
- - Never output \`finalPrompt\` alone without the matching \`A: ...\` prompt.`;
2063
+ - Re-check the latest \`approvalRequest.required\`.
2064
+ - If it is still \`true\`, re-open approval with:
2065
+ - one brief current-stage recap from \`matchedFeature.currentSubstateId/currentSubstateOwner/currentSubstatePhase\` when available, then
2066
+ - the exact CLI-provided approval lines from \`approvalRequest.userFacingLines\` (fallback: \`actionOptions[*].approvalPrompt\` + \`approvalRequest.finalPrompt\`).
2067
+ - Never output \`finalPrompt\` alone without the matching \`A: ...\` prompt, and do not add custom label-summary wrappers such as \`Available labels:\`.`;
2063
2068
  function renderManagedSegment(lang, docsRepo) {
2064
2069
  return `${LEE_SPEC_KIT_AGENTS_BEGIN}
2065
2070
  ${CANONICAL_LEE_SPEC_KIT_AGENTS_TEXT}
@@ -2127,10 +2132,10 @@ function renderManagedBlock2() {
2127
2132
  function getCodexHome() {
2128
2133
  const explicit = String(process.env.CODEX_HOME || "").trim();
2129
2134
  if (explicit) return explicit;
2130
- return path15.join(os.homedir(), ".codex");
2135
+ return path16.join(os.homedir(), ".codex");
2131
2136
  }
2132
2137
  function getCodexConfigPath() {
2133
- return path15.join(getCodexHome(), "config.toml");
2138
+ return path16.join(getCodexHome(), "config.toml");
2134
2139
  }
2135
2140
  function contentIncludesRequiredBootstrap(content) {
2136
2141
  const matchesCurrent = REQUIRED_FALLBACKS.every((value) => content.includes(value)) && REQUIRED_COMPACT_LINES.every((line) => content.includes(line));
@@ -2149,7 +2154,7 @@ async function hasLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
2149
2154
  async function upsertLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
2150
2155
  const block = renderManagedBlock2();
2151
2156
  const segment = renderManagedSegment2();
2152
- await fs.ensureDir(path15.dirname(filePath));
2157
+ await fs.ensureDir(path16.dirname(filePath));
2153
2158
  const exists = await fs.pathExists(filePath);
2154
2159
  if (!exists) {
2155
2160
  await fs.writeFile(filePath, block, "utf-8");
@@ -2283,37 +2288,24 @@ function validatePromptPathValue(value, lang) {
2283
2288
  function validatePromptUrlValue(value, lang) {
2284
2289
  return value.trim() ? true : tr(lang, "cli", "init.validation.enterUrl");
2285
2290
  }
2286
- var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
2287
- "spec_approve",
2288
- "implementation_approve"
2289
- ];
2290
- function createDefaultApprovalConfig() {
2291
- return {
2292
- mode: "category",
2293
- default: "skip",
2294
- requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
2295
- };
2296
- }
2297
2291
  function getAncestorDirs(startDir) {
2298
2292
  const dirs = [];
2299
- let current = path15.resolve(startDir);
2293
+ let current = path16.resolve(startDir);
2300
2294
  while (true) {
2301
2295
  dirs.push(current);
2302
- const parent = path15.dirname(current);
2296
+ const parent = path16.dirname(current);
2303
2297
  if (parent === current) break;
2304
2298
  current = parent;
2305
2299
  }
2306
2300
  return dirs;
2307
2301
  }
2308
2302
  function hasWorkspaceBoundary(dir) {
2309
- return fs.existsSync(path15.join(dir, "package.json")) || fs.existsSync(path15.join(dir, ".git"));
2303
+ return fs.existsSync(path16.join(dir, "package.json")) || fs.existsSync(path16.join(dir, ".git"));
2310
2304
  }
2311
2305
  function getSearchBaseDirs(cwd) {
2312
2306
  const ancestors = getAncestorDirs(cwd);
2313
2307
  const boundaryIndex = ancestors.findIndex(hasWorkspaceBoundary);
2314
- if (boundaryIndex === -1) {
2315
- return [ancestors[0]];
2316
- }
2308
+ if (boundaryIndex === -1) return [ancestors[0]];
2317
2309
  return ancestors.slice(0, boundaryIndex + 1);
2318
2310
  }
2319
2311
  var FEATURE_FOLDER_PATTERN = /^F\d{3,}-/i;
@@ -2322,7 +2314,7 @@ function normalizeComponentKeys(value) {
2322
2314
  return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
2323
2315
  }
2324
2316
  async function inferComponentsFromFeaturesDir(docsDir) {
2325
- const featuresPath = path15.join(docsDir, "features");
2317
+ const featuresPath = path16.join(docsDir, "features");
2326
2318
  if (!await fs.pathExists(featuresPath)) return [];
2327
2319
  const entries = await fs.readdir(featuresPath, { withFileTypes: true });
2328
2320
  const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
@@ -2330,24 +2322,42 @@ async function inferComponentsFromFeaturesDir(docsDir) {
2330
2322
  );
2331
2323
  return [...new Set(inferred)];
2332
2324
  }
2333
- async function getConfig(cwd) {
2325
+ function toProjectConfig(docsDir, configFile, projectType, components) {
2326
+ return {
2327
+ schemaId: "lee-spec",
2328
+ docsDir,
2329
+ projectName: configFile.projectName,
2330
+ projectType,
2331
+ components: projectType === "multi" ? components : void 0,
2332
+ lang: configFile.lang,
2333
+ docsRepo: configFile.docsRepo,
2334
+ pushDocs: configFile.pushDocs,
2335
+ docsRemote: configFile.docsRemote,
2336
+ projectRoot: configFile.projectRoot,
2337
+ allowedDocsEntries: configFile.allowedDocsEntries,
2338
+ pr: configFile.pr,
2339
+ workflow: configFile.workflow,
2340
+ approval: configFile.approval
2341
+ };
2342
+ }
2343
+ async function detectLeeSpecProject(cwd) {
2334
2344
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2335
2345
  const baseDirs = [
2336
- ...explicitDocsDir ? [path15.resolve(explicitDocsDir)] : [],
2346
+ ...explicitDocsDir ? [path16.resolve(explicitDocsDir)] : [],
2337
2347
  ...getSearchBaseDirs(cwd)
2338
2348
  ];
2339
2349
  const visitedBaseDirs = /* @__PURE__ */ new Set();
2340
2350
  const visitedDocsDirs = /* @__PURE__ */ new Set();
2341
2351
  for (const baseDir of baseDirs) {
2342
- const resolvedBaseDir = path15.resolve(baseDir);
2352
+ const resolvedBaseDir = path16.resolve(baseDir);
2343
2353
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
2344
2354
  visitedBaseDirs.add(resolvedBaseDir);
2345
- const possibleDocsDirs = [path15.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2355
+ const possibleDocsDirs = [path16.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2346
2356
  for (const docsDir of possibleDocsDirs) {
2347
- const resolvedDocsDir = path15.resolve(docsDir);
2357
+ const resolvedDocsDir = path16.resolve(docsDir);
2348
2358
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
2349
2359
  visitedDocsDirs.add(resolvedDocsDir);
2350
- const configPath = path15.join(resolvedDocsDir, ".lee-spec-kit.json");
2360
+ const configPath = path16.join(resolvedDocsDir, ".lee-spec-kit.json");
2351
2361
  if (await fs.pathExists(configPath)) {
2352
2362
  try {
2353
2363
  const configFile = await fs.readJson(configPath);
@@ -2361,33 +2371,32 @@ async function getConfig(cwd) {
2361
2371
  Array.isArray(configFile.components) && configFile.components.length > 0 ? configFile.components : inferredComponents
2362
2372
  );
2363
2373
  return {
2374
+ detected: true,
2375
+ schemaId: "lee-spec",
2376
+ detectionSource: "config",
2364
2377
  docsDir: resolvedDocsDir,
2365
- projectName: configFile.projectName,
2366
- projectType,
2367
- components: projectType === "multi" ? components : void 0,
2368
- lang: configFile.lang,
2369
- docsRepo: configFile.docsRepo,
2370
- pushDocs: configFile.pushDocs,
2371
- docsRemote: configFile.docsRemote,
2372
- projectRoot: configFile.projectRoot,
2373
- allowedDocsEntries: configFile.allowedDocsEntries,
2374
- pr: configFile.pr,
2375
- workflow: configFile.workflow,
2376
- approval: configFile.approval
2378
+ configPath,
2379
+ configFilePresent: true,
2380
+ config: toProjectConfig(
2381
+ resolvedDocsDir,
2382
+ configFile,
2383
+ projectType,
2384
+ components
2385
+ )
2377
2386
  };
2378
2387
  } catch {
2379
2388
  }
2380
2389
  }
2381
- const agentsPath = path15.join(resolvedDocsDir, "agents");
2382
- const featuresPath = path15.join(resolvedDocsDir, "features");
2390
+ const agentsPath = path16.join(resolvedDocsDir, "agents");
2391
+ const featuresPath = path16.join(resolvedDocsDir, "features");
2383
2392
  if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
2384
2393
  const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
2385
2394
  const projectType = inferredComponents.length > 0 ? "multi" : "single";
2386
2395
  const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
2387
2396
  const langProbeCandidates = [
2388
- path15.join(agentsPath, "custom.md"),
2389
- path15.join(agentsPath, "constitution.md"),
2390
- path15.join(agentsPath, "agents.md")
2397
+ path16.join(agentsPath, "custom.md"),
2398
+ path16.join(agentsPath, "constitution.md"),
2399
+ path16.join(agentsPath, "agents.md")
2391
2400
  ];
2392
2401
  let lang = "en";
2393
2402
  for (const candidate of langProbeCandidates) {
@@ -2398,11 +2407,198 @@ async function getConfig(cwd) {
2398
2407
  break;
2399
2408
  }
2400
2409
  }
2401
- return { docsDir: resolvedDocsDir, projectType, components, lang };
2410
+ return {
2411
+ detected: true,
2412
+ schemaId: "lee-spec",
2413
+ detectionSource: "heuristic",
2414
+ docsDir: resolvedDocsDir,
2415
+ configPath: null,
2416
+ configFilePresent: false,
2417
+ config: {
2418
+ schemaId: "lee-spec",
2419
+ docsDir: resolvedDocsDir,
2420
+ projectType,
2421
+ components,
2422
+ lang
2423
+ }
2424
+ };
2402
2425
  }
2403
2426
  }
2404
2427
  }
2405
- return null;
2428
+ return {
2429
+ detected: false,
2430
+ schemaId: "lee-spec",
2431
+ detectionSource: null,
2432
+ docsDir: null,
2433
+ configPath: null,
2434
+ configFilePresent: false,
2435
+ config: null
2436
+ };
2437
+ }
2438
+ function resolveLeeSpecFeaturePaths(input) {
2439
+ const featureFolderName = `${input.featureId}-${input.featureName}`;
2440
+ const featuresDir = input.projectType === "multi" ? path16.join(input.docsDir, "features", input.component || "") : path16.join(input.docsDir, "features");
2441
+ const featureDir = path16.join(featuresDir, featureFolderName);
2442
+ return {
2443
+ featureFolderName,
2444
+ featuresDir,
2445
+ featureDir,
2446
+ featurePathFromDocs: path16.relative(input.docsDir, featureDir)
2447
+ };
2448
+ }
2449
+ async function getNextLeeSpecFeatureId(docsDir, projectType, components) {
2450
+ const featuresDir = path16.join(docsDir, "features");
2451
+ let max = 0;
2452
+ const scanDirs = [];
2453
+ if (projectType === "multi") {
2454
+ scanDirs.push(
2455
+ ...components.map((component) => path16.join(featuresDir, component))
2456
+ );
2457
+ } else {
2458
+ scanDirs.push(featuresDir);
2459
+ }
2460
+ for (const dir of scanDirs) {
2461
+ if (!await fs.pathExists(dir)) continue;
2462
+ const entries = await fs.readdir(dir, { withFileTypes: true });
2463
+ for (const entry of entries) {
2464
+ if (!entry.isDirectory()) continue;
2465
+ const match = entry.name.match(/^F(\d+)-/);
2466
+ if (!match) continue;
2467
+ const num = parseInt(match[1], 10);
2468
+ if (num > max) max = num;
2469
+ }
2470
+ }
2471
+ const next = max + 1;
2472
+ const width = Math.max(3, String(next).length);
2473
+ return `F${String(next).padStart(width, "0")}`;
2474
+ }
2475
+ function parseFeatureFolderName(folderName, component) {
2476
+ const match = folderName.match(/^(F\d+)-(.+)$/);
2477
+ if (!match) return null;
2478
+ const featureRef = {
2479
+ id: match[1],
2480
+ slug: match[2],
2481
+ folderName
2482
+ };
2483
+ if (component) {
2484
+ featureRef.component = component;
2485
+ }
2486
+ return featureRef;
2487
+ }
2488
+ async function listLeeSpecFeatures(cwd) {
2489
+ const detected = await detectLeeSpecProject(cwd);
2490
+ const docsDir = detected.docsDir;
2491
+ if (!docsDir) return [];
2492
+ const featuresRoot = path16.join(docsDir, "features");
2493
+ if (!await fs.pathExists(featuresRoot)) return [];
2494
+ const refs = [];
2495
+ const topLevelEntries = await fs.readdir(featuresRoot, { withFileTypes: true });
2496
+ for (const entry of topLevelEntries) {
2497
+ if (!entry.isDirectory()) continue;
2498
+ const singleProjectFeature = parseFeatureFolderName(entry.name);
2499
+ if (singleProjectFeature) {
2500
+ refs.push(singleProjectFeature);
2501
+ continue;
2502
+ }
2503
+ const component = entry.name.trim().toLowerCase();
2504
+ if (!component) continue;
2505
+ const componentDir = path16.join(featuresRoot, entry.name);
2506
+ const componentEntries = await fs.readdir(componentDir, { withFileTypes: true });
2507
+ for (const child of componentEntries) {
2508
+ if (!child.isDirectory()) continue;
2509
+ const featureRef = parseFeatureFolderName(child.name, component);
2510
+ if (featureRef) refs.push(featureRef);
2511
+ }
2512
+ }
2513
+ refs.sort((left, right) => {
2514
+ const leftKey = `${left.id || ""}:${left.component || ""}:${left.folderName}`;
2515
+ const rightKey = `${right.id || ""}:${right.component || ""}:${right.folderName}`;
2516
+ return leftKey.localeCompare(rightKey);
2517
+ });
2518
+ return refs;
2519
+ }
2520
+
2521
+ // src/adapters/schema/lee-spec-kit/index.ts
2522
+ var leeSpecSchemaAdapter = {
2523
+ schemaId: "lee-spec",
2524
+ async detect(cwd) {
2525
+ const detection = await detectLeeSpecProject(cwd);
2526
+ return {
2527
+ detected: detection.detected,
2528
+ docsDir: detection.docsDir,
2529
+ schemaId: detection.schemaId,
2530
+ detectionSource: detection.detectionSource,
2531
+ config: detection.config,
2532
+ configPath: detection.configPath,
2533
+ configFilePresent: detection.configFilePresent
2534
+ };
2535
+ },
2536
+ async listFeatures(cwd) {
2537
+ return listLeeSpecFeatures(cwd);
2538
+ },
2539
+ async getNextFeatureId(input) {
2540
+ return getNextLeeSpecFeatureId(
2541
+ input.docsDir,
2542
+ input.projectType,
2543
+ input.components
2544
+ );
2545
+ },
2546
+ resolveFeaturePaths(input) {
2547
+ return resolveLeeSpecFeaturePaths(input);
2548
+ }
2549
+ };
2550
+
2551
+ // src/adapters/schema/index.ts
2552
+ var SCHEMA_ADAPTERS = [leeSpecSchemaAdapter];
2553
+ function createEmptyDetection() {
2554
+ return {
2555
+ detected: false,
2556
+ docsDir: null,
2557
+ schemaId: null,
2558
+ detectionSource: null,
2559
+ config: null,
2560
+ configPath: null,
2561
+ configFilePresent: false,
2562
+ adapter: null
2563
+ };
2564
+ }
2565
+ function getSchemaAdapterById(schemaId) {
2566
+ if (!schemaId) return null;
2567
+ return SCHEMA_ADAPTERS.find((adapter) => adapter.schemaId === schemaId) ?? null;
2568
+ }
2569
+ function getSchemaAdapterForConfig(config) {
2570
+ return getSchemaAdapterById(config?.schemaId ?? null);
2571
+ }
2572
+ async function detectSchemaProject(cwd) {
2573
+ for (const adapter of SCHEMA_ADAPTERS) {
2574
+ const detection = await adapter.detect(cwd);
2575
+ if (detection.detected) {
2576
+ return {
2577
+ ...detection,
2578
+ adapter
2579
+ };
2580
+ }
2581
+ }
2582
+ return createEmptyDetection();
2583
+ }
2584
+
2585
+ // src/config/load.ts
2586
+ async function getConfig(cwd) {
2587
+ const detected = await detectSchemaProject(cwd);
2588
+ return detected.config;
2589
+ }
2590
+
2591
+ // src/config/types.ts
2592
+ var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
2593
+ "spec_approve",
2594
+ "implementation_approve"
2595
+ ];
2596
+ function createDefaultApprovalConfig() {
2597
+ return {
2598
+ mode: "category",
2599
+ default: "skip",
2600
+ requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
2601
+ };
2406
2602
  }
2407
2603
 
2408
2604
  // src/commands/init.ts
@@ -2468,7 +2664,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
2468
2664
  }
2469
2665
  async function runInit(options) {
2470
2666
  const cwd = process.cwd();
2471
- const defaultName = path15.basename(cwd);
2667
+ const defaultName = path16.basename(cwd);
2472
2668
  let projectName = options.name || defaultName;
2473
2669
  let projectType = options.type;
2474
2670
  let components = parseComponentsOption(options.components);
@@ -2479,7 +2675,7 @@ async function runInit(options) {
2479
2675
  let docsRemote = options.docsRemote;
2480
2676
  let projectRoot;
2481
2677
  const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
2482
- const targetDir = path15.resolve(cwd, options.dir || "./docs");
2678
+ const targetDir = path16.resolve(cwd, options.dir || "./docs");
2483
2679
  const skipPrompts = !!options.yes || !!options.nonInteractive;
2484
2680
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
2485
2681
  throw createCliError(
@@ -2876,7 +3072,7 @@ async function runInit(options) {
2876
3072
  );
2877
3073
  console.log();
2878
3074
  const templatesDir = getTemplatesDir();
2879
- const commonPath = path15.join(templatesDir, lang, "common");
3075
+ const commonPath = path16.join(templatesDir, lang, "common");
2880
3076
  if (!await fs.pathExists(commonPath)) {
2881
3077
  throw new Error(
2882
3078
  tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
@@ -2885,11 +3081,11 @@ async function runInit(options) {
2885
3081
  const fsAdapter = new DefaultFileSystemAdapter();
2886
3082
  await copyTemplates(fsAdapter, commonPath, targetDir);
2887
3083
  if (projectType === "multi") {
2888
- const featuresRoot = path15.join(targetDir, "features");
3084
+ const featuresRoot = path16.join(targetDir, "features");
2889
3085
  for (const component of components) {
2890
- const componentDir = path15.join(featuresRoot, component);
3086
+ const componentDir = path16.join(featuresRoot, component);
2891
3087
  await fs.ensureDir(componentDir);
2892
- const readmePath = path15.join(componentDir, "README.md");
3088
+ const readmePath = path16.join(componentDir, "README.md");
2893
3089
  if (!await fs.pathExists(readmePath)) {
2894
3090
  await fs.writeFile(
2895
3091
  readmePath,
@@ -2916,6 +3112,7 @@ async function runInit(options) {
2916
3112
  createdAt: getLocalDateString(),
2917
3113
  docsRepo,
2918
3114
  workflow: {
3115
+ preset: workflowMode,
2919
3116
  mode: workflowMode,
2920
3117
  requireWorktree: false,
2921
3118
  codeDirtyScope: "auto",
@@ -2948,20 +3145,20 @@ async function runInit(options) {
2948
3145
  config.projectRoot = projectRoot;
2949
3146
  }
2950
3147
  }
2951
- const configPath = path15.join(targetDir, ".lee-spec-kit.json");
3148
+ const configPath = path16.join(targetDir, ".lee-spec-kit.json");
2952
3149
  await fs.writeJson(configPath, config, { spaces: 2 });
2953
3150
  const extraCommitPathsAbs = [];
2954
3151
  try {
2955
3152
  if (docsRepo === "embedded") {
2956
3153
  const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
2957
- const agentsMdPath = path15.join(repoRoot, "AGENTS.md");
3154
+ const agentsMdPath = path16.join(repoRoot, "AGENTS.md");
2958
3155
  const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
2959
3156
  lang,
2960
3157
  docsRepo
2961
3158
  });
2962
3159
  if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
2963
3160
  } else {
2964
- await upsertLeeSpecKitAgentsMd(path15.join(targetDir, "AGENTS.md"), {
3161
+ await upsertLeeSpecKitAgentsMd(path16.join(targetDir, "AGENTS.md"), {
2965
3162
  lang,
2966
3163
  docsRepo
2967
3164
  });
@@ -2971,16 +3168,16 @@ async function runInit(options) {
2971
3168
  } else if (projectRoot && typeof projectRoot === "object") {
2972
3169
  roots.push(...Object.values(projectRoot));
2973
3170
  }
2974
- const resolvedCwd = path15.resolve(cwd);
3171
+ const resolvedCwd = path16.resolve(cwd);
2975
3172
  for (const raw of roots) {
2976
3173
  const value = String(raw || "").trim();
2977
3174
  if (!value) continue;
2978
- const abs = path15.resolve(cwd, value);
2979
- if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path15.sep}`)) {
3175
+ const abs = path16.resolve(cwd, value);
3176
+ if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path16.sep}`)) {
2980
3177
  if (await fs.pathExists(abs)) {
2981
3178
  const stat = await fs.stat(abs);
2982
3179
  if (stat.isDirectory()) {
2983
- await upsertLeeSpecKitAgentsMd(path15.join(abs, "AGENTS.md"), {
3180
+ await upsertLeeSpecKitAgentsMd(path16.join(abs, "AGENTS.md"), {
2984
3181
  lang,
2985
3182
  docsRepo
2986
3183
  });
@@ -3073,7 +3270,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
3073
3270
  console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
3074
3271
  runGitOrThrow(["init"], gitWorkdir);
3075
3272
  }
3076
- const relativePath = docsRepo === "standalone" ? "." : path15.relative(gitWorkdir, targetDir);
3273
+ const relativePath = docsRepo === "standalone" ? "." : path16.relative(gitWorkdir, targetDir);
3077
3274
  const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
3078
3275
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
3079
3276
  console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
@@ -3100,7 +3297,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
3100
3297
  console.log();
3101
3298
  return;
3102
3299
  }
3103
- const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path15.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
3300
+ const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path16.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
3104
3301
  const pathsToStage = [relativePath, ...extraRelativePaths];
3105
3302
  for (const p of pathsToStage) {
3106
3303
  runGitOrThrow(["add", p], gitWorkdir);
@@ -3188,13 +3385,13 @@ async function patchMarkdownIfExists(filePath, transform) {
3188
3385
  await fs.writeFile(filePath, transform(content), "utf-8");
3189
3386
  }
3190
3387
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
3191
- await patchMarkdownIfExists(path15.join(featureDir, "spec.md"), sanitizeSpecForLocal);
3388
+ await patchMarkdownIfExists(path16.join(featureDir, "spec.md"), sanitizeSpecForLocal);
3192
3389
  await patchMarkdownIfExists(
3193
- path15.join(featureDir, "tasks.md"),
3390
+ path16.join(featureDir, "tasks.md"),
3194
3391
  (content) => sanitizeTasksForLocal(content, lang)
3195
3392
  );
3196
- await fs.remove(path15.join(featureDir, "issue.md"));
3197
- await fs.remove(path15.join(featureDir, "pr.md"));
3393
+ await fs.remove(path16.join(featureDir, "issue.md"));
3394
+ await fs.remove(path16.join(featureDir, "pr.md"));
3198
3395
  }
3199
3396
  var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
3200
3397
  var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
@@ -3206,7 +3403,7 @@ function extractExplicitIdeaRef(requestText) {
3206
3403
  return null;
3207
3404
  }
3208
3405
  async function resolveIdeaReference(docsDir, ref, lang) {
3209
- const ideasDir = path15.join(docsDir, "ideas");
3406
+ const ideasDir = path16.join(docsDir, "ideas");
3210
3407
  const trimmedRef = ref.trim();
3211
3408
  if (!trimmedRef) {
3212
3409
  throw createCliError(
@@ -3215,7 +3412,7 @@ async function resolveIdeaReference(docsDir, ref, lang) {
3215
3412
  );
3216
3413
  }
3217
3414
  if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
3218
- const candidate = path15.resolve(process.cwd(), trimmedRef);
3415
+ const candidate = path16.resolve(process.cwd(), trimmedRef);
3219
3416
  if (await fs.pathExists(candidate)) {
3220
3417
  return { path: candidate };
3221
3418
  }
@@ -3234,11 +3431,11 @@ async function resolveIdeaReference(docsDir, ref, lang) {
3234
3431
  const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
3235
3432
  const exactName = `${trimmedRef}.md`;
3236
3433
  if (files.includes(exactName)) {
3237
- return { path: path15.join(ideasDir, exactName) };
3434
+ return { path: path16.join(ideasDir, exactName) };
3238
3435
  }
3239
3436
  const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
3240
3437
  if (byId.length === 1) {
3241
- return { path: path15.join(ideasDir, byId[0]) };
3438
+ return { path: path16.join(ideasDir, byId[0]) };
3242
3439
  }
3243
3440
  if (byId.length > 1) {
3244
3441
  throw createCliError(
@@ -3262,7 +3459,7 @@ async function readIdeaMetadataValue(ideaPath, label) {
3262
3459
  async function deriveFeatureNameFromIdea(ideaPath) {
3263
3460
  const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
3264
3461
  if (ideaName && ideaName !== "-") return ideaName;
3265
- const basename = path15.basename(ideaPath, ".md");
3462
+ const basename = path16.basename(ideaPath, ".md");
3266
3463
  return basename.replace(/^I\d{3,}-/, "");
3267
3464
  }
3268
3465
  function escapeRegExp(value) {
@@ -3348,6 +3545,7 @@ async function runFeature(name, options) {
3348
3545
  }
3349
3546
  const { docsDir, projectType, lang } = config;
3350
3547
  const projectName = config.projectName;
3548
+ const schemaAdapter = getSchemaAdapterForConfig(config);
3351
3549
  const configuredComponents = resolveProjectComponents(
3352
3550
  projectType,
3353
3551
  config.components
@@ -3410,27 +3608,38 @@ async function runFeature(name, options) {
3410
3608
  );
3411
3609
  featureId = options.id;
3412
3610
  } else {
3413
- featureId = await getNextFeatureId(
3611
+ if (!schemaAdapter?.getNextFeatureId) {
3612
+ throw createCliError(
3613
+ "PRECONDITION_FAILED",
3614
+ `Schema "${config.schemaId || "unknown"}" does not support feature ID allocation.`
3615
+ );
3616
+ }
3617
+ featureId = await schemaAdapter.getNextFeatureId({
3414
3618
  docsDir,
3415
3619
  projectType,
3416
- configuredComponents
3417
- );
3620
+ components: configuredComponents
3621
+ });
3418
3622
  }
3419
- let featuresDir;
3420
- if (projectType === "multi") {
3421
- featuresDir = path15.join(docsDir, "features", component);
3422
- } else {
3423
- featuresDir = path15.join(docsDir, "features");
3623
+ if (!schemaAdapter?.resolveFeaturePaths) {
3624
+ throw createCliError(
3625
+ "PRECONDITION_FAILED",
3626
+ `Schema "${config.schemaId || "unknown"}" does not support feature path resolution.`
3627
+ );
3424
3628
  }
3425
- const featureFolderName = `${featureId}-${name}`;
3426
- const featureDir = path15.join(featuresDir, featureFolderName);
3629
+ const { featureFolderName, featureDir, featurePathFromDocs } = schemaAdapter.resolveFeaturePaths({
3630
+ docsDir,
3631
+ projectType,
3632
+ component: projectType === "multi" ? component : void 0,
3633
+ featureId,
3634
+ featureName: name
3635
+ });
3427
3636
  if (await fs.pathExists(featureDir)) {
3428
3637
  throw createCliError(
3429
3638
  "INVALID_ARGUMENT",
3430
3639
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
3431
3640
  );
3432
3641
  }
3433
- const featureBasePath = path15.join(
3642
+ const featureBasePath = path16.join(
3434
3643
  getTemplatesDir(),
3435
3644
  lang,
3436
3645
  "common",
@@ -3477,8 +3686,8 @@ async function runFeature(name, options) {
3477
3686
  await replaceInFiles(fsAdapter, featureDir, replacements);
3478
3687
  if (linkedIdea) {
3479
3688
  await stampIdeaReferenceInSpec(
3480
- path15.join(featureDir, "spec.md"),
3481
- path15.relative(featureDir, linkedIdea.path)
3689
+ path16.join(featureDir, "spec.md"),
3690
+ path16.relative(featureDir, linkedIdea.path)
3482
3691
  );
3483
3692
  await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
3484
3693
  }
@@ -3506,7 +3715,7 @@ async function runFeature(name, options) {
3506
3715
  featureName: name,
3507
3716
  component: projectType === "multi" ? component : void 0,
3508
3717
  featurePath: featureDir,
3509
- featurePathFromDocs: path15.relative(docsDir, featureDir)
3718
+ featurePathFromDocs
3510
3719
  };
3511
3720
  },
3512
3721
  { owner: "feature" }
@@ -3566,9 +3775,9 @@ function escapeRegExp2(value) {
3566
3775
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
3567
3776
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
3568
3777
  const candidates = [
3569
- ...explicitDocsDir ? [path15.resolve(explicitDocsDir)] : [],
3570
- path15.resolve(cwd, "docs"),
3571
- path15.resolve(cwd)
3778
+ ...explicitDocsDir ? [path16.resolve(explicitDocsDir)] : [],
3779
+ path16.resolve(cwd, "docs"),
3780
+ path16.resolve(cwd)
3572
3781
  ];
3573
3782
  const endAt = Date.now() + timeoutMs;
3574
3783
  while (Date.now() < endAt) {
@@ -3594,33 +3803,6 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
3594
3803
  }
3595
3804
  return getConfig(cwd);
3596
3805
  }
3597
- async function getNextFeatureId(docsDir, projectType, components) {
3598
- const featuresDir = path15.join(docsDir, "features");
3599
- let max = 0;
3600
- const scanDirs = [];
3601
- if (projectType === "multi") {
3602
- scanDirs.push(
3603
- ...components.map((component) => path15.join(featuresDir, component))
3604
- );
3605
- } else {
3606
- scanDirs.push(featuresDir);
3607
- }
3608
- for (const dir of scanDirs) {
3609
- if (!await fs.pathExists(dir)) continue;
3610
- const entries = await fs.readdir(dir, { withFileTypes: true });
3611
- for (const entry of entries) {
3612
- if (!entry.isDirectory()) continue;
3613
- const match = entry.name.match(/^F(\d+)-/);
3614
- if (match) {
3615
- const num = parseInt(match[1], 10);
3616
- if (num > max) max = num;
3617
- }
3618
- }
3619
- }
3620
- const next = max + 1;
3621
- const width = Math.max(3, String(next).length);
3622
- return `F${String(next).padStart(width, "0")}`;
3623
- }
3624
3806
  function ideaCommand(program2) {
3625
3807
  program2.command("idea <name>").description("Create a new indexed idea document").option("--component <component>", "Component name (optional)").option("--id <id>", "Idea ID (default: auto)").option("-d, --desc <description>", "Idea description for the document").option("--non-interactive", "Reserved for parity with other generators").option("--json", "Output in JSON format for agents").action(async (name, options) => {
3626
3808
  try {
@@ -3701,16 +3883,16 @@ async function runIdea(name, options) {
3701
3883
  getDocsLockPath(docsDir),
3702
3884
  async () => {
3703
3885
  const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
3704
- const ideasDir = path15.join(docsDir, "ideas");
3886
+ const ideasDir = path16.join(docsDir, "ideas");
3705
3887
  const ideaFileName = `${ideaId}-${name}.md`;
3706
- const ideaPath = path15.join(ideasDir, ideaFileName);
3888
+ const ideaPath = path16.join(ideasDir, ideaFileName);
3707
3889
  if (await fs.pathExists(ideaPath)) {
3708
3890
  throw createCliError(
3709
3891
  "INVALID_ARGUMENT",
3710
3892
  tr(lang, "cli", "idea.fileExists", { path: ideaPath })
3711
3893
  );
3712
3894
  }
3713
- const templatePath = path15.join(
3895
+ const templatePath = path16.join(
3714
3896
  getTemplatesDir(),
3715
3897
  lang,
3716
3898
  "common",
@@ -3748,7 +3930,7 @@ async function runIdea(name, options) {
3748
3930
  ideaName: name,
3749
3931
  component: component || void 0,
3750
3932
  ideaPath,
3751
- ideaPathFromDocs: path15.relative(docsDir, ideaPath)
3933
+ ideaPathFromDocs: path16.relative(docsDir, ideaPath)
3752
3934
  };
3753
3935
  },
3754
3936
  { owner: "idea" }
@@ -3766,7 +3948,7 @@ function applyIdeaTemplate(template, values) {
3766
3948
  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);
3767
3949
  }
3768
3950
  async function getNextIdeaId(docsDir) {
3769
- const ideasDir = path15.join(docsDir, "ideas");
3951
+ const ideasDir = path16.join(docsDir, "ideas");
3770
3952
  let max = 0;
3771
3953
  if (await fs.pathExists(ideasDir)) {
3772
3954
  const entries = await fs.readdir(ideasDir, { withFileTypes: true });
@@ -3895,25 +4077,33 @@ var SUBAGENT_HANDOFF_CATEGORIES = [
3895
4077
  "pre_pr_review_run"
3896
4078
  ];
3897
4079
 
3898
- // src/utils/workflow.ts
3899
- var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
3900
- var DEFAULT_PRE_PR_DECISION_ENUM = [
3901
- "approve",
3902
- "changes_requested",
3903
- "blocked"
3904
- ];
3905
- function resolveWorkflowPolicy(workflow) {
3906
- const mode = workflow?.mode === "local" ? "local" : "github";
3907
- const policy = mode === "local" ? {
3908
- mode,
3909
- requireIssue: false,
3910
- requireBranch: false,
3911
- requireWorktree: false,
3912
- requirePr: false,
3913
- requireReview: false,
3914
- requireMerge: false
3915
- } : {
3916
- mode,
4080
+ // src/core/workflow/presets.ts
4081
+ function resolveWorkflowPreset(preset, mode) {
4082
+ const normalizedPreset = preset === "local" || preset === "strict" || preset === "github" ? preset : void 0;
4083
+ if (normalizedPreset === "local" || mode === "local") {
4084
+ return {
4085
+ mode: "local",
4086
+ requireIssue: false,
4087
+ requireBranch: false,
4088
+ requireWorktree: false,
4089
+ requirePr: false,
4090
+ requireReview: false,
4091
+ requireMerge: false
4092
+ };
4093
+ }
4094
+ if (normalizedPreset === "strict") {
4095
+ return {
4096
+ mode: "github",
4097
+ requireIssue: true,
4098
+ requireBranch: true,
4099
+ requireWorktree: true,
4100
+ requirePr: true,
4101
+ requireReview: true,
4102
+ requireMerge: true
4103
+ };
4104
+ }
4105
+ return {
4106
+ mode: "github",
3917
4107
  requireIssue: true,
3918
4108
  requireBranch: true,
3919
4109
  requireWorktree: false,
@@ -3921,6 +4111,20 @@ function resolveWorkflowPolicy(workflow) {
3921
4111
  requireReview: true,
3922
4112
  requireMerge: true
3923
4113
  };
4114
+ }
4115
+
4116
+ // src/core/workflow/policies.ts
4117
+ var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
4118
+ var DEFAULT_PRE_PR_DECISION_ENUM = [
4119
+ "approve",
4120
+ "changes_requested",
4121
+ "blocked"
4122
+ ];
4123
+ function resolveWorkflowPolicy(workflow) {
4124
+ const policy = resolveWorkflowPreset(
4125
+ workflow?.preset,
4126
+ workflow?.mode
4127
+ );
3924
4128
  if (typeof workflow?.requireIssue === "boolean") {
3925
4129
  policy.requireIssue = workflow.requireIssue;
3926
4130
  }
@@ -3986,18 +4190,9 @@ function normalizeDecisionEnumList(input) {
3986
4190
  for (const raw of input) {
3987
4191
  const value = String(raw || "").trim().toLowerCase();
3988
4192
  if (!value) continue;
3989
- if (value === "approve") {
3990
- deduped.add("approve");
3991
- continue;
3992
- }
3993
- if (value === "changes_requested") {
3994
- deduped.add("changes_requested");
3995
- continue;
3996
- }
3997
- if (value === "blocked") {
3998
- deduped.add("blocked");
3999
- continue;
4000
- }
4193
+ if (value === "approve") deduped.add("approve");
4194
+ if (value === "changes_requested") deduped.add("changes_requested");
4195
+ if (value === "blocked") deduped.add("blocked");
4001
4196
  }
4002
4197
  return [...deduped];
4003
4198
  }
@@ -4034,6 +4229,15 @@ function resolvePrePrReviewPolicy(workflow) {
4034
4229
  };
4035
4230
  }
4036
4231
 
4232
+ // src/core/workflow/engine.ts
4233
+ function resolveWorkflowRuntime(workflow) {
4234
+ return {
4235
+ workflowPolicy: resolveWorkflowPolicy(workflow),
4236
+ prePrReviewPolicy: resolvePrePrReviewPolicy(workflow),
4237
+ taskCommitGatePolicy: resolveTaskCommitGatePolicy(workflow)
4238
+ };
4239
+ }
4240
+
4037
4241
  // src/utils/agent-orchestration.ts
4038
4242
  function getPrePrReviewPrompt(lang, skills, fallbackText) {
4039
4243
  if (lang === "ko") {
@@ -4083,6 +4287,9 @@ function isCompletionChecklistDone(feature) {
4083
4287
  function isTasksDocApproved(feature) {
4084
4288
  return !feature.docs.tasksDocStatusFieldExists || feature.tasksDocStatus === "Approved";
4085
4289
  }
4290
+ function hasPendingChangeRequest(feature) {
4291
+ return typeof feature.pendingChangeRequest === "string" && feature.pendingChangeRequest.trim().length > 0;
4292
+ }
4086
4293
  function isImplementationDone(feature) {
4087
4294
  return feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature);
4088
4295
  }
@@ -4207,7 +4414,7 @@ function isNonNegativeIntegerValue(value) {
4207
4414
  }
4208
4415
  function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
4209
4416
  try {
4210
- const raw = fs12.readFileSync(evidencePath, "utf-8");
4417
+ const raw = fs13.readFileSync(evidencePath, "utf-8");
4211
4418
  const parsed = JSON.parse(raw);
4212
4419
  const evidenceFeature = (parsed.feature || "").toString().trim();
4213
4420
  if (evidenceFeature && evidenceFeature !== feature.folderName) {
@@ -4223,31 +4430,31 @@ function resolvePrePrReviewEvidencePath(feature) {
4223
4430
  const candidates = [];
4224
4431
  const explicit = (feature.prePrReview.evidence || "").trim();
4225
4432
  if (explicit && explicit !== "-") {
4226
- if (path15.isAbsolute(explicit)) {
4433
+ if (path16.isAbsolute(explicit)) {
4227
4434
  candidates.push(explicit);
4228
4435
  } else {
4229
- candidates.push(path15.resolve(feature.path, explicit));
4230
- candidates.push(path15.resolve(docsRoot, explicit));
4436
+ candidates.push(path16.resolve(feature.path, explicit));
4437
+ candidates.push(path16.resolve(docsRoot, explicit));
4231
4438
  const normalizedExplicit = explicit.replace(/\\/g, "/");
4232
4439
  if (normalizedExplicit.startsWith("docs/")) {
4233
4440
  const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
4234
4441
  if (withoutDocsPrefix) {
4235
- candidates.push(path15.resolve(docsRoot, withoutDocsPrefix));
4442
+ candidates.push(path16.resolve(docsRoot, withoutDocsPrefix));
4236
4443
  }
4237
4444
  }
4238
4445
  }
4239
4446
  }
4240
- candidates.push(path15.join(feature.path, "review-trace.json"));
4241
- candidates.push(path15.join(docsRoot, "review-trace.json"));
4447
+ candidates.push(path16.join(feature.path, "review-trace.json"));
4448
+ candidates.push(path16.join(docsRoot, "review-trace.json"));
4242
4449
  const seen = /* @__PURE__ */ new Set();
4243
4450
  for (const candidate of candidates) {
4244
- const abs = path15.resolve(candidate);
4451
+ const abs = path16.resolve(candidate);
4245
4452
  if (seen.has(abs)) continue;
4246
4453
  seen.add(abs);
4247
- if (!fs12.existsSync(abs)) continue;
4454
+ if (!fs13.existsSync(abs)) continue;
4248
4455
  if (!abs.toLowerCase().endsWith(".json")) continue;
4249
4456
  if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
4250
- const rel = path15.relative(docsRoot, abs).replace(/\\/g, "/");
4457
+ const rel = path16.relative(docsRoot, abs).replace(/\\/g, "/");
4251
4458
  if (rel && !rel.startsWith("../")) {
4252
4459
  return rel;
4253
4460
  }
@@ -4288,8 +4495,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
4288
4495
  }
4289
4496
  function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
4290
4497
  if (!projectGitCwd) return null;
4291
- const normalized = path15.resolve(projectGitCwd);
4292
- const marker = `${path15.sep}.worktrees${path15.sep}`;
4498
+ const normalized = path16.resolve(projectGitCwd);
4499
+ const marker = `${path16.sep}.worktrees${path16.sep}`;
4293
4500
  const markerIndex = normalized.lastIndexOf(marker);
4294
4501
  if (markerIndex <= 0) return null;
4295
4502
  const projectRoot = normalized.slice(0, markerIndex);
@@ -4336,7 +4543,7 @@ function toTaskKey(rawTitle) {
4336
4543
  function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
4337
4544
  const docsGitCwd = feature.git.docsGitCwd;
4338
4545
  const tasksRelativePath = normalizeGitRelativePath(
4339
- path15.join(feature.docs.featurePathFromDocs, "tasks.md")
4546
+ path16.join(feature.docs.featurePathFromDocs, "tasks.md")
4340
4547
  );
4341
4548
  const diff = readGitText(ctx, docsGitCwd, [
4342
4549
  "diff",
@@ -4411,7 +4618,7 @@ function checkTaskCommitGate(ctx, feature) {
4411
4618
  return { pass: true };
4412
4619
  }
4413
4620
  const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
4414
- const relativeDocsDir = path15.relative(projectGitCwd, feature.git.docsGitCwd);
4621
+ const relativeDocsDir = path16.relative(projectGitCwd, feature.git.docsGitCwd);
4415
4622
  const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
4416
4623
  if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
4417
4624
  args.push(`:(exclude)${normalizedDocsDir}/**`);
@@ -4448,11 +4655,23 @@ function getTaskCommitGateReasonText(lang, check) {
4448
4655
  }
4449
4656
  function getStepDefinitions(ctx) {
4450
4657
  const lang = ctx.config.lang;
4451
- const workflow = ctx.config.workflow;
4452
- const workflowPolicy = resolveWorkflowPolicy(workflow);
4453
- const prePrReviewPolicy = resolvePrePrReviewPolicy(workflow);
4454
- const taskCommitGatePolicy = resolveTaskCommitGatePolicy(workflow);
4455
- const isTaskExecuteCurrent = (f) => f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && isTasksDocApproved(f) && (!workflowPolicy.requireBranch || f.git.onExpectedBranch || f.tasks.done === f.tasks.total);
4658
+ const {
4659
+ workflowPolicy,
4660
+ prePrReviewPolicy,
4661
+ taskCommitGatePolicy
4662
+ } = resolveWorkflowRuntime(ctx.config.workflow);
4663
+ const isTaskExecuteCurrent = (f) => f.docs.tasksExists && f.tasks.total > 0 && (hasPendingChangeRequest(f) || f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && isTasksDocApproved(f) && (!workflowPolicy.requireBranch || f.git.onExpectedBranch || f.tasks.done === f.tasks.total);
4664
+ const getChangeRequestSyncActions = (f) => [
4665
+ {
4666
+ type: "instruction",
4667
+ category: "tasks_write",
4668
+ requiresUserCheck: false,
4669
+ uiDetailKey: "context.actionDetail.tasksWriteChangeSync",
4670
+ message: tr(lang, "messages", "changeRequestSync", {
4671
+ request: f.pendingChangeRequest || "-"
4672
+ })
4673
+ }
4674
+ ];
4456
4675
  const isTaskExecuteWorktreeBlocked = (f) => isTaskExecuteCurrent(f) && workflowPolicy.requireWorktree && f.tasks.done < f.tasks.total && !!f.issueNumber && !f.git.projectInManagedWorktree;
4457
4676
  const isTaskExecuteFinalize = (f) => isTaskExecuteCurrent(f) && f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f);
4458
4677
  const isTaskExecuteCommitPending = (f) => isTaskExecuteCurrent(f) && (isTaskExecuteFinalize(f) && (f.git.docsHasCommitRequiredChanges || f.git.projectHasUncommittedChanges) || !!f.nextTodoTask && (f.git.docsHasCommitRequiredChanges || f.git.projectHasUncommittedChanges));
@@ -5253,6 +5472,14 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
5253
5472
  detail: (f) => f.tasks.total > 0 ? `(${f.tasks.done}/${f.tasks.total})` : ""
5254
5473
  },
5255
5474
  substates: [
5475
+ {
5476
+ id: "change_request_sync",
5477
+ phase: "ready",
5478
+ owner: "main",
5479
+ category: "tasks_write",
5480
+ when: (f) => isTaskExecuteCurrent(f) && hasPendingChangeRequest(f),
5481
+ actions: (f) => getChangeRequestSyncActions(f)
5482
+ },
5256
5483
  {
5257
5484
  id: "task_blocked",
5258
5485
  phase: "blocked",
@@ -6006,17 +6233,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
6006
6233
  }
6007
6234
  }
6008
6235
  var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
6009
- var WORKTREE_MARKER = `${path15.sep}.worktrees${path15.sep}`;
6236
+ var WORKTREE_MARKER = `${path16.sep}.worktrees${path16.sep}`;
6010
6237
  function resetContextGitCaches() {
6011
6238
  GIT_WORKTREE_CACHE.clear();
6012
6239
  }
6013
6240
  function isManagedWorktreePath(cwd) {
6014
6241
  if (!cwd) return false;
6015
- const normalized = path15.resolve(cwd);
6242
+ const normalized = path16.resolve(cwd);
6016
6243
  return normalized.includes(WORKTREE_MARKER);
6017
6244
  }
6018
6245
  function resolveProjectRootFromGitCwd(cwd) {
6019
- const normalized = path15.resolve(cwd);
6246
+ const normalized = path16.resolve(cwd);
6020
6247
  const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
6021
6248
  if (markerIndex <= 0) return normalized;
6022
6249
  const projectRoot = normalized.slice(0, markerIndex);
@@ -6035,7 +6262,7 @@ function getGitTopLevel(ctx, cwd) {
6035
6262
  }
6036
6263
  function listGitWorktrees(ctx, cwd) {
6037
6264
  const topLevel = getGitTopLevel(ctx, cwd) || cwd;
6038
- const cacheKey = path15.resolve(topLevel);
6265
+ const cacheKey = path16.resolve(topLevel);
6039
6266
  const cached = GIT_WORKTREE_CACHE.get(cacheKey);
6040
6267
  if (cached) return cached;
6041
6268
  try {
@@ -6164,6 +6391,15 @@ function extractFirstSpecValue(content, keys) {
6164
6391
  }
6165
6392
  return void 0;
6166
6393
  }
6394
+ function parsePendingChangeRequest(value) {
6395
+ if (!value) return void 0;
6396
+ const normalized = value.trim().replace(/\s+/g, " ");
6397
+ if (!normalized) return void 0;
6398
+ if (/^(?:-|#|none|n\/a|na|없음|미정)$/i.test(normalized)) {
6399
+ return void 0;
6400
+ }
6401
+ return normalized;
6402
+ }
6167
6403
  function parseDocStatus(value) {
6168
6404
  if (!value) return void 0;
6169
6405
  const trimmed = value.trim();
@@ -6523,17 +6759,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
6523
6759
  if (!evidencePath) return [];
6524
6760
  if (/^https?:\/\//i.test(evidencePath)) return [];
6525
6761
  const candidates = /* @__PURE__ */ new Set();
6526
- if (path15.isAbsolute(evidencePath)) {
6527
- candidates.add(path15.resolve(evidencePath));
6762
+ if (path16.isAbsolute(evidencePath)) {
6763
+ candidates.add(path16.resolve(evidencePath));
6528
6764
  } else {
6529
- candidates.add(path15.resolve(context.featurePath, evidencePath));
6530
- candidates.add(path15.resolve(context.docsDir, evidencePath));
6531
- candidates.add(path15.resolve(path15.dirname(context.docsDir), evidencePath));
6765
+ candidates.add(path16.resolve(context.featurePath, evidencePath));
6766
+ candidates.add(path16.resolve(context.docsDir, evidencePath));
6767
+ candidates.add(path16.resolve(path16.dirname(context.docsDir), evidencePath));
6532
6768
  const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
6533
6769
  if (normalizedEvidencePath.startsWith("docs/")) {
6534
6770
  const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
6535
6771
  if (withoutDocsPrefix) {
6536
- candidates.add(path15.resolve(context.docsDir, withoutDocsPrefix));
6772
+ candidates.add(path16.resolve(context.docsDir, withoutDocsPrefix));
6537
6773
  }
6538
6774
  }
6539
6775
  }
@@ -6595,13 +6831,13 @@ function parsePrLink(value) {
6595
6831
  return trimmed;
6596
6832
  }
6597
6833
  function normalizeGitPath(value) {
6598
- return value.split(path15.sep).join("/");
6834
+ return value.split(path16.sep).join("/");
6599
6835
  }
6600
6836
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
6601
- const relativeDocsDir = path15.relative(projectGitCwd, docsDir);
6837
+ const relativeDocsDir = path16.relative(projectGitCwd, docsDir);
6602
6838
  if (!relativeDocsDir) return [];
6603
- if (path15.isAbsolute(relativeDocsDir)) return [];
6604
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path15.sep}`)) {
6839
+ if (path16.isAbsolute(relativeDocsDir)) return [];
6840
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path16.sep}`)) {
6605
6841
  return [];
6606
6842
  }
6607
6843
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
@@ -6639,7 +6875,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
6639
6875
  const seen = /* @__PURE__ */ new Set();
6640
6876
  const out = [];
6641
6877
  for (const name of names) {
6642
- const candidate = path15.resolve(projectRoot, ".worktrees", name);
6878
+ const candidate = path16.resolve(projectRoot, ".worktrees", name);
6643
6879
  if (seen.has(candidate)) continue;
6644
6880
  seen.add(candidate);
6645
6881
  out.push(candidate);
@@ -6653,7 +6889,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
6653
6889
  slug,
6654
6890
  folderName
6655
6891
  )) {
6656
- if (!fs12.existsSync(candidate)) continue;
6892
+ if (!fs13.existsSync(candidate)) continue;
6657
6893
  return candidate;
6658
6894
  }
6659
6895
  return void 0;
@@ -6683,7 +6919,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
6683
6919
  slug,
6684
6920
  folderName
6685
6921
  )) {
6686
- if (!fs12.existsSync(candidate)) continue;
6922
+ if (!fs13.existsSync(candidate)) continue;
6687
6923
  const branchName = getCurrentBranch(ctx, candidate);
6688
6924
  if (!expectedBranchesSet.has(branchName)) continue;
6689
6925
  return {
@@ -6824,10 +7060,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
6824
7060
  const normalizedCandidates = uniqueNormalizedPaths(
6825
7061
  candidates.map((candidate) => {
6826
7062
  if (!candidate) return "";
6827
- if (!path15.isAbsolute(candidate)) return candidate;
6828
- const relative = path15.relative(projectGitCwd, candidate);
7063
+ if (!path16.isAbsolute(candidate)) return candidate;
7064
+ const relative = path16.relative(projectGitCwd, candidate);
6829
7065
  if (!relative) return "";
6830
- if (relative === ".." || relative.startsWith(`..${path15.sep}`))
7066
+ if (relative === ".." || relative.startsWith(`..${path16.sep}`))
6831
7067
  return "";
6832
7068
  return relative;
6833
7069
  }).filter(Boolean)
@@ -6841,7 +7077,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
6841
7077
  if (cached) return [...cached];
6842
7078
  const existing = [];
6843
7079
  for (const candidate of normalizedCandidates) {
6844
- if (await ctx.fs.pathExists(path15.join(projectGitCwd, candidate))) {
7080
+ if (await ctx.fs.pathExists(path16.join(projectGitCwd, candidate))) {
6845
7081
  existing.push(candidate);
6846
7082
  }
6847
7083
  }
@@ -6927,18 +7163,19 @@ function isPrePrReviewSatisfied2(feature, policy) {
6927
7163
  }
6928
7164
  async function parseFeature(ctx, featurePath, type, context, options) {
6929
7165
  const lang = options.lang;
6930
- const workflowPolicy = resolveWorkflowPolicy(options.workflow);
6931
- const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
6932
- const folderName = path15.basename(featurePath);
7166
+ const { workflowPolicy, prePrReviewPolicy } = resolveWorkflowRuntime(
7167
+ options.workflow
7168
+ );
7169
+ const folderName = path16.basename(featurePath);
6933
7170
  const match = folderName.match(/^(F\d+)-(.+)$/);
6934
7171
  const id = match?.[1];
6935
7172
  const slug = match?.[2] || folderName;
6936
- const specPath = path15.join(featurePath, "spec.md");
6937
- const planPath = path15.join(featurePath, "plan.md");
6938
- const tasksPath = path15.join(featurePath, "tasks.md");
6939
- const decisionsPath = path15.join(featurePath, "decisions.md");
6940
- const issueDocPath = path15.join(featurePath, "issue.md");
6941
- const prDocPath = path15.join(featurePath, "pr.md");
7173
+ const specPath = path16.join(featurePath, "spec.md");
7174
+ const planPath = path16.join(featurePath, "plan.md");
7175
+ const tasksPath = path16.join(featurePath, "tasks.md");
7176
+ const decisionsPath = path16.join(featurePath, "decisions.md");
7177
+ const issueDocPath = path16.join(featurePath, "issue.md");
7178
+ const prDocPath = path16.join(featurePath, "pr.md");
6942
7179
  let specStatus;
6943
7180
  let issueNumber;
6944
7181
  const specExists = await ctx.fs.pathExists(specPath);
@@ -6989,6 +7226,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
6989
7226
  let lastDoneTask;
6990
7227
  let nextTodoTask;
6991
7228
  let tasksDocStatus;
7229
+ let pendingChangeRequest;
6992
7230
  let tasksDocStatusFieldExists = false;
6993
7231
  let completionChecklist;
6994
7232
  let prePrReviewStatus;
@@ -7050,6 +7288,11 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7050
7288
  "Doc Status"
7051
7289
  ]);
7052
7290
  tasksDocStatus = parseDocStatus(tasksDocStatusValue);
7291
+ const pendingChangeRequestValue = extractFirstSpecValue(content, [
7292
+ "Pending Change Request",
7293
+ "\uB300\uAE30 \uC911 \uBCC0\uACBD \uC694\uCCAD"
7294
+ ]);
7295
+ pendingChangeRequest = parsePendingChangeRequest(pendingChangeRequestValue);
7053
7296
  const prValue = extractFirstSpecValue(content, ["PR", "Pull Request"]);
7054
7297
  prFieldExists = hasAnySpecKey(content, ["PR", "Pull Request"]);
7055
7298
  prLink = parsePrLink(prValue);
@@ -7200,7 +7443,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7200
7443
  } else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
7201
7444
  warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
7202
7445
  }
7203
- const relativeFeaturePathFromDocs = path15.relative(
7446
+ const relativeFeaturePathFromDocs = path16.relative(
7204
7447
  context.docsDir,
7205
7448
  featurePath
7206
7449
  );
@@ -7456,6 +7699,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7456
7699
  folderName,
7457
7700
  type,
7458
7701
  path: featurePath,
7702
+ pendingChangeRequest,
7459
7703
  completion: {
7460
7704
  implementationDone,
7461
7705
  workflowDone,
@@ -7566,7 +7810,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
7566
7810
  async function listFeatureDirs(ctx, rootDir) {
7567
7811
  const dirs = await listSubdirectories(ctx.fs, rootDir);
7568
7812
  return dirs.filter(
7569
- (value) => path15.basename(value).trim().toLowerCase() !== "feature-base"
7813
+ (value) => path16.basename(value).trim().toLowerCase() !== "feature-base"
7570
7814
  );
7571
7815
  }
7572
7816
  function normalizeRelPath(value) {
@@ -7707,7 +7951,7 @@ async function scanFeatures(ctx) {
7707
7951
  if (config.projectType === "single") {
7708
7952
  const featureDirs = await listFeatureDirs(
7709
7953
  ctx,
7710
- path15.join(config.docsDir, "features")
7954
+ path16.join(config.docsDir, "features")
7711
7955
  );
7712
7956
  componentFeatureDirs.set("single", featureDirs);
7713
7957
  allFeatureDirs.push(...featureDirs);
@@ -7719,14 +7963,14 @@ async function scanFeatures(ctx) {
7719
7963
  for (const component of components) {
7720
7964
  const componentDirs = await listFeatureDirs(
7721
7965
  ctx,
7722
- path15.join(config.docsDir, "features", component)
7966
+ path16.join(config.docsDir, "features", component)
7723
7967
  );
7724
7968
  componentFeatureDirs.set(component, componentDirs);
7725
7969
  allFeatureDirs.push(...componentDirs);
7726
7970
  }
7727
7971
  }
7728
7972
  const relativeFeaturePaths = allFeatureDirs.map(
7729
- (dir) => normalizeRelPath(path15.relative(config.docsDir, dir))
7973
+ (dir) => normalizeRelPath(path16.relative(config.docsDir, dir))
7730
7974
  );
7731
7975
  const docsGitMeta = buildDocsFeatureGitMeta(
7732
7976
  ctx,
@@ -7743,7 +7987,7 @@ async function scanFeatures(ctx) {
7743
7987
  const parsed = await Promise.all(
7744
7988
  target.dirs.map(async (dir) => {
7745
7989
  const relativeFeaturePathFromDocs = normalizeRelPath(
7746
- path15.relative(config.docsDir, dir)
7990
+ path16.relative(config.docsDir, dir)
7747
7991
  );
7748
7992
  const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
7749
7993
  return parseFeature(
@@ -7813,13 +8057,13 @@ async function runStatus(options) {
7813
8057
  );
7814
8058
  }
7815
8059
  const { docsDir, projectType, projectName, lang } = ctx.config;
7816
- const featuresDir = path15.join(docsDir, "features");
8060
+ const featuresDir = path16.join(docsDir, "features");
7817
8061
  const scan = await scanFeatures(ctx);
7818
8062
  const features = [];
7819
8063
  const idMap = /* @__PURE__ */ new Map();
7820
8064
  for (const f of scan.features) {
7821
8065
  const id = f.id || "UNKNOWN";
7822
- const relPath = path15.relative(docsDir, f.path);
8066
+ const relPath = path16.relative(docsDir, f.path);
7823
8067
  if (!idMap.has(id)) idMap.set(id, []);
7824
8068
  idMap.get(id).push(relPath);
7825
8069
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -7910,7 +8154,7 @@ async function runStatus(options) {
7910
8154
  }
7911
8155
  console.log();
7912
8156
  if (options.write) {
7913
- const outputPath = path15.join(featuresDir, "status.md");
8157
+ const outputPath = path16.join(featuresDir, "status.md");
7914
8158
  const date = getLocalDateString();
7915
8159
  const content = [
7916
8160
  "# Feature Status",
@@ -7936,7 +8180,7 @@ function escapeRegExp4(value) {
7936
8180
  }
7937
8181
  async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
7938
8182
  try {
7939
- const specPath = path15.join(featureDir, "spec.md");
8183
+ const specPath = path16.join(featureDir, "spec.md");
7940
8184
  if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
7941
8185
  const content = await fsAdapter.readFile(specPath, "utf-8");
7942
8186
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
@@ -8024,8 +8268,8 @@ async function runUpdate(options) {
8024
8268
  let updatedCount = 0;
8025
8269
  if (updateAgents) {
8026
8270
  console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
8027
- const commonAgentsBase = path15.join(templatesDir, lang, "common", "agents");
8028
- const targetAgentsBase = path15.join(docsDir, "agents");
8271
+ const commonAgentsBase = path16.join(templatesDir, lang, "common", "agents");
8272
+ const targetAgentsBase = path16.join(docsDir, "agents");
8029
8273
  const commonAgents = commonAgentsBase;
8030
8274
  const targetAgents = targetAgentsBase;
8031
8275
  const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
@@ -8118,21 +8362,21 @@ async function collectAgentsMdTargets(cwd, config) {
8118
8362
  const targets = /* @__PURE__ */ new Set();
8119
8363
  const docsRepo = config.docsRepo ?? "embedded";
8120
8364
  if (docsRepo === "embedded") {
8121
- const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path15.resolve(config.docsDir, "..");
8122
- targets.add(path15.join(repoRoot, "AGENTS.md"));
8365
+ const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path16.resolve(config.docsDir, "..");
8366
+ targets.add(path16.join(repoRoot, "AGENTS.md"));
8123
8367
  return [...targets];
8124
8368
  }
8125
- targets.add(path15.join(config.docsDir, "AGENTS.md"));
8369
+ targets.add(path16.join(config.docsDir, "AGENTS.md"));
8126
8370
  const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
8127
8371
  const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
8128
8372
  for (const rawRoot of rawRoots) {
8129
8373
  const value = String(rawRoot || "").trim();
8130
8374
  if (!value) continue;
8131
- const resolved = path15.resolve(baseDir, value);
8375
+ const resolved = path16.resolve(baseDir, value);
8132
8376
  if (!await fs.pathExists(resolved)) continue;
8133
8377
  const stat = await fs.stat(resolved);
8134
8378
  if (!stat.isDirectory()) continue;
8135
- targets.add(path15.join(resolved, "AGENTS.md"));
8379
+ targets.add(path16.join(resolved, "AGENTS.md"));
8136
8380
  }
8137
8381
  return [...targets];
8138
8382
  }
@@ -8172,7 +8416,7 @@ function normalizeDecisionEnumList2(raw) {
8172
8416
  return [...deduped];
8173
8417
  }
8174
8418
  async function backfillMissingConfigDefaults(docsDir) {
8175
- const configPath = path15.join(docsDir, ".lee-spec-kit.json");
8419
+ const configPath = path16.join(docsDir, ".lee-spec-kit.json");
8176
8420
  if (!await fs.pathExists(configPath)) {
8177
8421
  return { changed: false, changedPaths: [] };
8178
8422
  }
@@ -8191,6 +8435,8 @@ async function backfillMissingConfigDefaults(docsDir) {
8191
8435
  changedPaths.push("workflow");
8192
8436
  }
8193
8437
  const workflow = raw.workflow;
8438
+ const inferredPreset = workflow.mode === "local" ? "local" : "github";
8439
+ setIfMissing(workflow, "preset", inferredPreset, "workflow.preset");
8194
8440
  setIfMissing(workflow, "mode", "github", "workflow.mode");
8195
8441
  setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
8196
8442
  setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
@@ -8299,8 +8545,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
8299
8545
  const files = await fs.readdir(sourceDir);
8300
8546
  let updatedCount = 0;
8301
8547
  for (const file of files) {
8302
- const sourcePath = path15.join(sourceDir, file);
8303
- const targetPath = path15.join(targetDir, file);
8548
+ const sourcePath = path16.join(sourceDir, file);
8549
+ const targetPath = path16.join(targetDir, file);
8304
8550
  const stat = await fs.stat(sourcePath);
8305
8551
  if (stat.isFile()) {
8306
8552
  if (protectedFiles.has(file)) {
@@ -8383,7 +8629,7 @@ function extractPorcelainPaths(line) {
8383
8629
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
8384
8630
  const top = getGitTopLevel2(docsDir);
8385
8631
  if (!top) return null;
8386
- const rel = path15.relative(top, docsDir) || ".";
8632
+ const rel = path16.relative(top, docsDir) || ".";
8387
8633
  try {
8388
8634
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
8389
8635
  cwd: top,
@@ -8395,7 +8641,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
8395
8641
  }
8396
8642
  const ignoredRelPaths = new Set(
8397
8643
  ignoredAbsPaths.map(
8398
- (absPath) => normalizeGitPath2(path15.relative(top, absPath) || ".")
8644
+ (absPath) => normalizeGitPath2(path16.relative(top, absPath) || ".")
8399
8645
  )
8400
8646
  );
8401
8647
  const filtered = output.split("\n").filter((line) => {
@@ -8453,7 +8699,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
8453
8699
  }
8454
8700
  async function runConfig(options) {
8455
8701
  const cwd = process.cwd();
8456
- const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
8702
+ const targetCwd = options.dir ? path16.resolve(cwd, options.dir) : cwd;
8457
8703
  const config = await getConfig(targetCwd);
8458
8704
  if (!config) {
8459
8705
  throw createCliError(
@@ -8461,7 +8707,7 @@ async function runConfig(options) {
8461
8707
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
8462
8708
  );
8463
8709
  }
8464
- const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
8710
+ const configPath = path16.join(config.docsDir, ".lee-spec-kit.json");
8465
8711
  if (!options.projectRoot) {
8466
8712
  console.log();
8467
8713
  console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -8567,47 +8813,47 @@ var BUILTIN_DOC_DEFINITIONS = [
8567
8813
  {
8568
8814
  id: "agents",
8569
8815
  title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
8570
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "agents.md")
8816
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "agents.md")
8571
8817
  },
8572
8818
  {
8573
8819
  id: "git-workflow",
8574
8820
  title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
8575
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "git-workflow.md")
8821
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "git-workflow.md")
8576
8822
  },
8577
8823
  {
8578
8824
  id: "issue-doc",
8579
8825
  title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
8580
- relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "issue.md")
8826
+ relativePath: (_, lang) => path16.join(lang, "common", "features", "feature-base", "issue.md")
8581
8827
  },
8582
8828
  {
8583
8829
  id: "pr-doc",
8584
8830
  title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
8585
- relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "pr.md")
8831
+ relativePath: (_, lang) => path16.join(lang, "common", "features", "feature-base", "pr.md")
8586
8832
  },
8587
8833
  {
8588
8834
  id: "create-feature",
8589
8835
  title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
8590
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-feature.md")
8836
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-feature.md")
8591
8837
  },
8592
8838
  {
8593
8839
  id: "execute-task",
8594
8840
  title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
8595
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "execute-task.md")
8841
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "execute-task.md")
8596
8842
  },
8597
8843
  {
8598
8844
  id: "create-issue",
8599
8845
  title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
8600
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-issue.md")
8846
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-issue.md")
8601
8847
  },
8602
8848
  {
8603
8849
  id: "create-pr",
8604
8850
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
8605
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-pr.md")
8851
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-pr.md")
8606
8852
  },
8607
8853
  {
8608
8854
  id: "split-feature",
8609
8855
  title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
8610
- relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "split-feature.md")
8856
+ relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "split-feature.md")
8611
8857
  }
8612
8858
  ];
8613
8859
  var DOC_FOLLOWUPS = {
@@ -8705,7 +8951,7 @@ function listBuiltinDocs(projectType, lang) {
8705
8951
  id: doc.id,
8706
8952
  title: doc.title[lang],
8707
8953
  relativePath,
8708
- absolutePath: path15.join(templatesDir, relativePath)
8954
+ absolutePath: path16.join(templatesDir, relativePath)
8709
8955
  };
8710
8956
  });
8711
8957
  }
@@ -8759,7 +9005,7 @@ function toAllowedSet(values, extras) {
8759
9005
  );
8760
9006
  }
8761
9007
  function isDocLikeFile(name) {
8762
- return DOC_LIKE_FILE_EXTENSIONS.has(path15.extname(name).toLowerCase());
9008
+ return DOC_LIKE_FILE_EXTENSIONS.has(path16.extname(name).toLowerCase());
8763
9009
  }
8764
9010
  async function collectUnmanagedDocsEntries(docsDir, allowed) {
8765
9011
  const allowedDirs = toAllowedSet(DEFAULT_MANAGED_DOC_DIRS, allowed?.dirs);
@@ -8775,7 +9021,7 @@ async function collectUnmanagedDocsEntries(docsDir, allowed) {
8775
9021
  unmanaged.push({
8776
9022
  name,
8777
9023
  kind: "dir",
8778
- absPath: path15.join(docsDir, name),
9024
+ absPath: path16.join(docsDir, name),
8779
9025
  relPath: `docs/${name}`
8780
9026
  });
8781
9027
  continue;
@@ -8786,7 +9032,7 @@ async function collectUnmanagedDocsEntries(docsDir, allowed) {
8786
9032
  unmanaged.push({
8787
9033
  name,
8788
9034
  kind: "file",
8789
- absPath: path15.join(docsDir, name),
9035
+ absPath: path16.join(docsDir, name),
8790
9036
  relPath: `docs/${name}`
8791
9037
  });
8792
9038
  }
@@ -9843,7 +10089,7 @@ async function loadApprovalTicketStore(storePath) {
9843
10089
  }
9844
10090
  }
9845
10091
  async function saveApprovalTicketStore(storePath, payload) {
9846
- await fs.ensureDir(path15.dirname(storePath));
10092
+ await fs.ensureDir(path16.dirname(storePath));
9847
10093
  await fs.writeJson(storePath, payload, { spaces: 2 });
9848
10094
  }
9849
10095
  function pruneApprovalTickets(tickets, nowMs) {
@@ -9983,6 +10229,81 @@ async function consumeApprovalTicket(config, token, expected) {
9983
10229
  }
9984
10230
 
9985
10231
  // src/services/ActionExecutor.ts
10232
+ var PENDING_CHANGE_REQUEST_KEYS = [
10233
+ "Pending Change Request",
10234
+ "\uB300\uAE30 \uC911 \uBCC0\uACBD \uC694\uCCAD"
10235
+ ];
10236
+ function escapeRegExp5(value) {
10237
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10238
+ }
10239
+ function findSpecLineIndex(lines, keys) {
10240
+ const escaped = keys.map((key) => escapeRegExp5(key));
10241
+ const re = new RegExp(
10242
+ `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:`,
10243
+ "i"
10244
+ );
10245
+ return lines.findIndex((line) => re.test(line));
10246
+ }
10247
+ function replaceSpecLine(line, keys, preferredKey, value) {
10248
+ const escaped = keys.map((key) => escapeRegExp5(key));
10249
+ const re = new RegExp(
10250
+ `^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`,
10251
+ "i"
10252
+ );
10253
+ if (!re.test(line)) return line;
10254
+ return line.replace(re, `$1${preferredKey}$2${value}`);
10255
+ }
10256
+ function computeSpecInsertIndex(lines, anchorKeys) {
10257
+ const anchorIndex = findSpecLineIndex(lines, anchorKeys);
10258
+ if (anchorIndex !== -1) {
10259
+ let cursor = anchorIndex + 1;
10260
+ while (cursor < lines.length && /^\s{2,}-\s+/.test(lines[cursor])) {
10261
+ cursor += 1;
10262
+ }
10263
+ return cursor;
10264
+ }
10265
+ const sectionIndex = lines.findIndex(
10266
+ (line) => /^\s*##\s+(Task List|태스크 목록)\s*$/.test(line)
10267
+ );
10268
+ if (sectionIndex !== -1) return sectionIndex;
10269
+ return lines.length;
10270
+ }
10271
+ function upsertPendingChangeRequestLine(content, requestText, lang) {
10272
+ const normalizedRequest = requestText.trim().replace(/\s+/g, " ");
10273
+ const preferredKey = lang === "ko" ? "\uB300\uAE30 \uC911 \uBCC0\uACBD \uC694\uCCAD" : "Pending Change Request";
10274
+ const lines = content.split("\n");
10275
+ const index = findSpecLineIndex(lines, PENDING_CHANGE_REQUEST_KEYS);
10276
+ if (index !== -1) {
10277
+ lines[index] = replaceSpecLine(
10278
+ lines[index],
10279
+ PENDING_CHANGE_REQUEST_KEYS,
10280
+ preferredKey,
10281
+ normalizedRequest
10282
+ );
10283
+ return lines.join("\n");
10284
+ }
10285
+ const insertAt = computeSpecInsertIndex(lines, [
10286
+ "Branch",
10287
+ "\uBE0C\uB79C\uCE58",
10288
+ "Issue",
10289
+ "Issue Number",
10290
+ "\uC774\uC288 \uBC88\uD638"
10291
+ ]);
10292
+ lines.splice(insertAt, 0, `- **${preferredKey}**: ${normalizedRequest}`);
10293
+ return lines.join("\n");
10294
+ }
10295
+ async function persistPendingChangeRequest(featurePath, requestText, lang) {
10296
+ const tasksPath = path16.join(featurePath, "tasks.md");
10297
+ let content;
10298
+ try {
10299
+ content = await fs20.readFile(tasksPath, "utf-8");
10300
+ } catch {
10301
+ return;
10302
+ }
10303
+ const nextContent = upsertPendingChangeRequestLine(content, requestText, lang);
10304
+ if (nextContent === content) return;
10305
+ await fs20.writeFile(tasksPath, nextContent, "utf-8");
10306
+ }
9986
10307
  function executeCommandAction(cmd, jsonMode, cwd) {
9987
10308
  const shellPath = process.env.SHELL || (process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh");
9988
10309
  if (jsonMode) {
@@ -10123,6 +10444,17 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
10123
10444
  action: freshSelected.action
10124
10445
  });
10125
10446
  const featureRef = freshState.matchedFeature.folderName;
10447
+ if (selectedAction.category === "user_request_replan" && userRequest && freshState.matchedFeature.docs.tasksExists) {
10448
+ await withFileLock(
10449
+ getDocsLockPath(config.docsDir),
10450
+ async () => persistPendingChangeRequest(
10451
+ freshState.matchedFeature.path,
10452
+ userRequest,
10453
+ lang
10454
+ ),
10455
+ { owner: "context-approve:pending-change-request" }
10456
+ );
10457
+ }
10126
10458
  if (!options.execute) {
10127
10459
  const ticket = executeRequiresTicket ? await issueApprovalTicket(config, {
10128
10460
  contextVersion: freshState.contextVersion,
@@ -10387,9 +10719,11 @@ async function runContext(featureName, options) {
10387
10719
  const cwd = process.cwd();
10388
10720
  const config = await getConfig(cwd);
10389
10721
  const lang = config?.lang ?? "en";
10390
- const workflowPolicy = resolveWorkflowPolicy(config?.workflow);
10391
- const prePrReviewPolicy = resolvePrePrReviewPolicy(config?.workflow);
10392
- const taskCommitGatePolicy = resolveTaskCommitGatePolicy(config?.workflow);
10722
+ const {
10723
+ workflowPolicy,
10724
+ prePrReviewPolicy,
10725
+ taskCommitGatePolicy
10726
+ } = resolveWorkflowRuntime(config?.workflow);
10393
10727
  if (!config) {
10394
10728
  throw createCliError(
10395
10729
  "CONFIG_NOT_FOUND",
@@ -10688,7 +11022,7 @@ async function runContext(featureName, options) {
10688
11022
  approvalRequest: {
10689
11023
  guidance: approvalGuidance.replace(
10690
11024
  "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user.",
10691
- "User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`."
11025
+ "If approval is pending, you may prepend one brief current-stage recap derived from `matchedFeature.currentSubstate*`, then user-facing output must reuse the exact approval prompts (`A: ...`) and `finalPrompt`."
10692
11026
  ),
10693
11027
  required: approvalRequired,
10694
11028
  finalPrompt: finalApprovalPrompt,
@@ -10907,7 +11241,7 @@ async function runContext(featureName, options) {
10907
11241
  if (f.issueNumber) {
10908
11242
  console.log(` \u2022 Issue: #${f.issueNumber}`);
10909
11243
  }
10910
- console.log(` \u2022 Path: ${path15.relative(cwd, f.path)}`);
11244
+ console.log(` \u2022 Path: ${path16.relative(cwd, f.path)}`);
10911
11245
  if (f.git.projectBranch) {
10912
11246
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
10913
11247
  }
@@ -11040,9 +11374,10 @@ function printChecklist(f, stepDefinitions) {
11040
11374
  console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
11041
11375
  });
11042
11376
  }
11043
- var PRD_REQUIREMENT_ID_RE = /\bPRD-(?:FR|US|NFR)-\d+\b/gi;
11377
+ var PRD_REQUIREMENT_ID_EXACT_RE = /^PRD-[A-Z0-9]+(?:-[A-Z0-9]+)*$/i;
11378
+ var PRD_REQUIREMENT_ID_RE = /\bPRD-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/gi;
11044
11379
  function isPrdRequirementId(value) {
11045
- return /^PRD-(?:FR|US|NFR)-\d+$/i.test(value.trim());
11380
+ return PRD_REQUIREMENT_ID_EXACT_RE.test(value.trim());
11046
11381
  }
11047
11382
  function isNonPrdTag(value) {
11048
11383
  const trimmed = value.trim();
@@ -11059,7 +11394,7 @@ function extractTitleAfterId(line, id) {
11059
11394
  return cleaned ? cleaned : void 0;
11060
11395
  }
11061
11396
  async function scanPrdRequirements(fsAdapter, docsDir) {
11062
- const prdDir = path15.join(docsDir, "prd");
11397
+ const prdDir = path16.join(docsDir, "prd");
11063
11398
  const files = await walkFiles(fsAdapter, prdDir, {
11064
11399
  extensions: [".md"],
11065
11400
  ignoreDirs: [".git", "node_modules", "dist", "tmp"]
@@ -11067,7 +11402,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
11067
11402
  const definitions = /* @__PURE__ */ new Map();
11068
11403
  const duplicates = [];
11069
11404
  for (const filePath of files) {
11070
- if (path15.basename(filePath).toLowerCase() === "readme.md") {
11405
+ if (path16.basename(filePath).toLowerCase() === "readme.md") {
11071
11406
  continue;
11072
11407
  }
11073
11408
  let content = "";
@@ -11076,7 +11411,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
11076
11411
  } catch {
11077
11412
  continue;
11078
11413
  }
11079
- const relFile = normalizeRelPath2(path15.relative(docsDir, filePath));
11414
+ const relFile = normalizeRelPath2(path16.relative(docsDir, filePath));
11080
11415
  const lines = content.split(/\r?\n/);
11081
11416
  let inCodeBlock = false;
11082
11417
  for (let i = 0; i < lines.length; i += 1) {
@@ -11151,7 +11486,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
11151
11486
  ]);
11152
11487
  function formatPath(cwd, p) {
11153
11488
  if (!p) return "";
11154
- return path15.isAbsolute(p) ? path15.relative(cwd, p) : p;
11489
+ return path16.isAbsolute(p) ? path16.relative(cwd, p) : p;
11155
11490
  }
11156
11491
  function detectPlaceholders(content) {
11157
11492
  const patterns = [
@@ -11310,7 +11645,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
11310
11645
  const placeholderContext = {
11311
11646
  projectName: config.projectName,
11312
11647
  featureName: f.slug,
11313
- featurePath: f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path),
11648
+ featurePath: f.docs.featurePathFromDocs || path16.relative(config.docsDir, f.path),
11314
11649
  repoType: f.type,
11315
11650
  featureNumber
11316
11651
  };
@@ -11320,7 +11655,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
11320
11655
  "tasks.md"
11321
11656
  ];
11322
11657
  for (const file of files) {
11323
- const fullPath = path15.join(f.path, file);
11658
+ const fullPath = path16.join(f.path, file);
11324
11659
  if (!await fs.pathExists(fullPath)) continue;
11325
11660
  const original = await fs.readFile(fullPath, "utf-8");
11326
11661
  let next = original;
@@ -11373,7 +11708,7 @@ async function checkDocsStructure(config, cwd) {
11373
11708
  const issues = [];
11374
11709
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
11375
11710
  for (const dir of requiredDirs) {
11376
- const p = path15.join(config.docsDir, dir);
11711
+ const p = path16.join(config.docsDir, dir);
11377
11712
  if (!await fs.pathExists(p)) {
11378
11713
  issues.push({
11379
11714
  level: "error",
@@ -11385,7 +11720,7 @@ async function checkDocsStructure(config, cwd) {
11385
11720
  });
11386
11721
  }
11387
11722
  }
11388
- const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
11723
+ const configPath = path16.join(config.docsDir, ".lee-spec-kit.json");
11389
11724
  if (!await fs.pathExists(configPath)) {
11390
11725
  issues.push({
11391
11726
  level: "warn",
@@ -11427,7 +11762,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11427
11762
  }
11428
11763
  const idMap = /* @__PURE__ */ new Map();
11429
11764
  for (const f of features) {
11430
- const rel = f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path);
11765
+ const rel = f.docs.featurePathFromDocs || path16.relative(config.docsDir, f.path);
11431
11766
  const id = f.id || "UNKNOWN";
11432
11767
  if (!idMap.has(id)) idMap.set(id, []);
11433
11768
  idMap.get(id).push(rel);
@@ -11435,7 +11770,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11435
11770
  if (!isInitialTemplateState) {
11436
11771
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
11437
11772
  for (const file of featureDocs) {
11438
- const p = path15.join(f.path, file);
11773
+ const p = path16.join(f.path, file);
11439
11774
  if (!await fs.pathExists(p)) continue;
11440
11775
  const content = await fs.readFile(p, "utf-8");
11441
11776
  const placeholders = detectPlaceholders(content);
@@ -11450,7 +11785,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11450
11785
  });
11451
11786
  }
11452
11787
  if (decisionsPlaceholderMode !== "off") {
11453
- const decisionsPath = path15.join(f.path, "decisions.md");
11788
+ const decisionsPath = path16.join(f.path, "decisions.md");
11454
11789
  if (await fs.pathExists(decisionsPath)) {
11455
11790
  const content = await fs.readFile(decisionsPath, "utf-8");
11456
11791
  const placeholders = detectPlaceholders(content);
@@ -11479,7 +11814,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11479
11814
  level: "warn",
11480
11815
  code: "spec_status_unset",
11481
11816
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
11482
- path: formatPath(cwd, path15.join(f.path, "spec.md"))
11817
+ path: formatPath(cwd, path16.join(f.path, "spec.md"))
11483
11818
  });
11484
11819
  }
11485
11820
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -11487,7 +11822,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11487
11822
  level: "warn",
11488
11823
  code: "plan_status_unset",
11489
11824
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
11490
- path: formatPath(cwd, path15.join(f.path, "plan.md"))
11825
+ path: formatPath(cwd, path16.join(f.path, "plan.md"))
11491
11826
  });
11492
11827
  }
11493
11828
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -11495,11 +11830,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11495
11830
  level: "warn",
11496
11831
  code: "tasks_empty",
11497
11832
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
11498
- path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11833
+ path: formatPath(cwd, path16.join(f.path, "tasks.md"))
11499
11834
  });
11500
11835
  }
11501
11836
  if (f.docs.tasksExists) {
11502
- const tasksPath = path15.join(f.path, "tasks.md");
11837
+ const tasksPath = path16.join(f.path, "tasks.md");
11503
11838
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
11504
11839
  const unknownPrdTags = [...new Set(
11505
11840
  parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
@@ -11521,7 +11856,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11521
11856
  level: "warn",
11522
11857
  code: "tasks_doc_status_missing",
11523
11858
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
11524
- path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11859
+ path: formatPath(cwd, path16.join(f.path, "tasks.md"))
11525
11860
  });
11526
11861
  }
11527
11862
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -11529,7 +11864,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11529
11864
  level: "warn",
11530
11865
  code: "tasks_doc_status_unset",
11531
11866
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
11532
- path: formatPath(cwd, path15.join(f.path, "tasks.md"))
11867
+ path: formatPath(cwd, path16.join(f.path, "tasks.md"))
11533
11868
  });
11534
11869
  }
11535
11870
  }
@@ -11553,7 +11888,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
11553
11888
  level: "warn",
11554
11889
  code: "missing_feature_id",
11555
11890
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
11556
- path: formatPath(cwd, path15.join(config.docsDir, p))
11891
+ path: formatPath(cwd, path16.join(config.docsDir, p))
11557
11892
  });
11558
11893
  }
11559
11894
  return issues;
@@ -11700,7 +12035,7 @@ function doctorCommand(program2) {
11700
12035
  }
11701
12036
  console.log();
11702
12037
  console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
11703
- console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, docsDir)}`));
12038
+ console.log(chalk9.gray(`- Docs: ${path16.relative(cwd, docsDir)}`));
11704
12039
  console.log(chalk9.gray(`- Type: ${projectType}`));
11705
12040
  console.log(chalk9.gray(`- Lang: ${lang}`));
11706
12041
  console.log();
@@ -11881,7 +12216,7 @@ async function runView(featureName, options) {
11881
12216
  }
11882
12217
  console.log();
11883
12218
  console.log(chalk9.bold("\u{1F4CA} Workflow View"));
11884
- console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, config.docsDir)}`));
12219
+ console.log(chalk9.gray(`- Docs: ${path16.relative(cwd, config.docsDir)}`));
11885
12220
  console.log(
11886
12221
  chalk9.gray(
11887
12222
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -11966,13 +12301,13 @@ function normalizeRunId(raw) {
11966
12301
  return value;
11967
12302
  }
11968
12303
  function getFlowRunBaseDir(cwd) {
11969
- return path15.join(getRuntimeStateDir(cwd), "flow-runs");
12304
+ return path16.join(getRuntimeStateDir(cwd), "flow-runs");
11970
12305
  }
11971
12306
  function getFlowRunPath(cwd, runId) {
11972
- return path15.join(getFlowRunBaseDir(cwd), `${runId}.json`);
12307
+ return path16.join(getFlowRunBaseDir(cwd), `${runId}.json`);
11973
12308
  }
11974
12309
  function getFlowRunLockPath(cwd, runId) {
11975
- return path15.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
12310
+ return path16.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
11976
12311
  }
11977
12312
  async function readFlowRunRecordUnsafe(cwd, runId) {
11978
12313
  const normalized = normalizeRunId(runId);
@@ -11998,7 +12333,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
11998
12333
  }
11999
12334
  async function writeFlowRunRecord(cwd, record) {
12000
12335
  const filePath = getFlowRunPath(cwd, record.runId);
12001
- await fs.ensureDir(path15.dirname(filePath));
12336
+ await fs.ensureDir(path16.dirname(filePath));
12002
12337
  await fs.writeJson(filePath, record, { spaces: 2 });
12003
12338
  }
12004
12339
  async function createFlowRunRecord(cwd, input) {
@@ -12167,8 +12502,13 @@ function toCompactStatusReport(report) {
12167
12502
  };
12168
12503
  }
12169
12504
  function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
12505
+ const resumableStatuses = /* @__PURE__ */ new Set([
12506
+ "delegated_handoff",
12507
+ "gate_reached",
12508
+ "manual_required"
12509
+ ]);
12170
12510
  const resumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
12171
- const handoffRequired = !!autoRun && !!resumeCommand;
12511
+ const handoffRequired = !!autoRun && !!resumeCommand && resumableStatuses.has(autoRun.status);
12172
12512
  const verifyCacheKey = handoffRequired ? `${(featureRef || "unknown").toLowerCase()}|${Buffer$1.from(
12173
12513
  resumeCommand
12174
12514
  ).toString("base64").slice(0, 12)}` : "";
@@ -12424,7 +12764,10 @@ function resolveAutoMode(config, options, requestText) {
12424
12764
  if (requestText) {
12425
12765
  return resolveConfigDefaultAutoMode(config);
12426
12766
  }
12427
- return null;
12767
+ if (options.approve || options.execute || options.resume) {
12768
+ return null;
12769
+ }
12770
+ return resolveConfigDefaultAutoMode(config);
12428
12771
  }
12429
12772
  function toAutoReasonCode(status) {
12430
12773
  const map = {
@@ -12442,8 +12785,6 @@ function toAutoReasonCode(status) {
12442
12785
  }
12443
12786
  function isAutoRunFailureStatus(status) {
12444
12787
  return [
12445
- "manual_required",
12446
- "selection_required",
12447
12788
  "no_progress",
12448
12789
  "request_label_missing",
12449
12790
  "request_failed",
@@ -12455,10 +12796,10 @@ function toFlowRunStatus(status) {
12455
12796
  case "delegated_handoff":
12456
12797
  case "gate_reached":
12457
12798
  case "manual_required":
12799
+ case "selection_required":
12458
12800
  return "paused";
12459
12801
  case "no_action_options":
12460
12802
  return "completed";
12461
- case "selection_required":
12462
12803
  case "no_progress":
12463
12804
  case "request_label_missing":
12464
12805
  case "request_failed":
@@ -12552,7 +12893,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
12552
12893
  executions,
12553
12894
  gate: null,
12554
12895
  manual: null,
12555
- error: "Auto-run requires a single matched feature. Specify the feature explicitly."
12896
+ error: "Auto-run paused because a single matched feature is required before execution can continue."
12556
12897
  };
12557
12898
  }
12558
12899
  if (actionOptions.length === 0) {
@@ -12799,7 +13140,9 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
12799
13140
 
12800
13141
  // src/commands/flow.ts
12801
13142
  function flowCommand(program2) {
12802
- program2.command("flow [feature-name]").description("Run combined workflow checks (context + status + doctor)").option("--json", "Output in JSON format for agents").option(
13143
+ program2.command("flow [feature-name]").description(
13144
+ "Run the default workflow auto-loop and pause at selection/approval/manual/resume boundaries"
13145
+ ).option("--json", "Output in JSON format for agents").option(
12803
13146
  "--json-compact",
12804
13147
  "Output compact JSON for agents (implies --json, reduced duplication)"
12805
13148
  ).option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
@@ -12979,14 +13322,6 @@ async function runFlow(featureName, options) {
12979
13322
  }
12980
13323
  }
12981
13324
  }
12982
- if (autoMode && !featureName) {
12983
- if (!resolvedFeatureName) {
12984
- throw createCliError(
12985
- "CONTEXT_SELECTION_REQUIRED",
12986
- "Auto mode requires explicit <feature-name> (e.g. F004)."
12987
- );
12988
- }
12989
- }
12990
13325
  if (options.startAuto && !autoMode) {
12991
13326
  throw createCliError(
12992
13327
  "INVALID_ARGUMENT",
@@ -13414,27 +13749,27 @@ function tg(lang, key, vars = {}) {
13414
13749
  function detectGithubCliLangSync(cwd) {
13415
13750
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
13416
13751
  const startDirs = [
13417
- explicitDocsDir ? path15.resolve(explicitDocsDir) : "",
13418
- path15.resolve(cwd)
13752
+ explicitDocsDir ? path16.resolve(explicitDocsDir) : "",
13753
+ path16.resolve(cwd)
13419
13754
  ].filter(Boolean);
13420
13755
  const scanOrder = [];
13421
13756
  const seen = /* @__PURE__ */ new Set();
13422
13757
  for (const start of startDirs) {
13423
13758
  let current = start;
13424
13759
  while (true) {
13425
- const abs = path15.resolve(current);
13760
+ const abs = path16.resolve(current);
13426
13761
  if (!seen.has(abs)) {
13427
13762
  scanOrder.push(abs);
13428
13763
  seen.add(abs);
13429
13764
  }
13430
- const parent = path15.dirname(abs);
13765
+ const parent = path16.dirname(abs);
13431
13766
  if (parent === abs) break;
13432
13767
  current = parent;
13433
13768
  }
13434
13769
  }
13435
13770
  for (const base of scanOrder) {
13436
- for (const docsDir of [path15.join(base, "docs"), base]) {
13437
- const configPath = path15.join(docsDir, ".lee-spec-kit.json");
13771
+ for (const docsDir of [path16.join(base, "docs"), base]) {
13772
+ const configPath = path16.join(docsDir, ".lee-spec-kit.json");
13438
13773
  if (fs.existsSync(configPath)) {
13439
13774
  try {
13440
13775
  const parsed = fs.readJsonSync(configPath);
@@ -13443,11 +13778,11 @@ function detectGithubCliLangSync(cwd) {
13443
13778
  } catch {
13444
13779
  }
13445
13780
  }
13446
- const agentsPath = path15.join(docsDir, "agents");
13447
- const featuresPath = path15.join(docsDir, "features");
13781
+ const agentsPath = path16.join(docsDir, "agents");
13782
+ const featuresPath = path16.join(docsDir, "features");
13448
13783
  if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
13449
13784
  for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
13450
- const file = path15.join(agentsPath, probe);
13785
+ const file = path16.join(agentsPath, probe);
13451
13786
  if (!fs.existsSync(file)) continue;
13452
13787
  try {
13453
13788
  const content = fs.readFileSync(file, "utf-8");
@@ -13467,13 +13802,13 @@ function parseLabels(raw, lang) {
13467
13802
  }
13468
13803
  return [...new Set(labels)];
13469
13804
  }
13470
- function escapeRegExp5(value) {
13805
+ function escapeRegExp6(value) {
13471
13806
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13472
13807
  }
13473
13808
  function extractDraftMetadataValue(content, keys) {
13474
13809
  for (const key of keys) {
13475
13810
  const re = new RegExp(
13476
- `^\\s*-\\s*\\*\\*${escapeRegExp5(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
13811
+ `^\\s*-\\s*\\*\\*${escapeRegExp6(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
13477
13812
  "mi"
13478
13813
  );
13479
13814
  const match = content.match(re);
@@ -13552,7 +13887,7 @@ async function prepareGithubBody(params) {
13552
13887
  };
13553
13888
  }
13554
13889
  }
13555
- await fs.ensureDir(path15.dirname(defaultBodyFile));
13890
+ await fs.ensureDir(path16.dirname(defaultBodyFile));
13556
13891
  await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
13557
13892
  return {
13558
13893
  body: generatedBody,
@@ -13592,7 +13927,7 @@ function ensureSections(body, sections, kind, lang) {
13592
13927
  };
13593
13928
  const hasMetadataField = (field) => {
13594
13929
  const re = new RegExp(
13595
- `^\\s*-\\s*\\*\\*${escapeRegExp5(field)}\\*\\*\\s*:`,
13930
+ `^\\s*-\\s*\\*\\*${escapeRegExp6(field)}\\*\\*\\s*:`,
13596
13931
  "m"
13597
13932
  );
13598
13933
  return re.test(body);
@@ -13622,7 +13957,7 @@ function ensureSections(body, sections, kind, lang) {
13622
13957
  }
13623
13958
  function ensureDocsExist(docsDir, relativePaths, lang) {
13624
13959
  const missing = relativePaths.filter(
13625
- (relativePath) => !fs.existsSync(path15.join(docsDir, relativePath))
13960
+ (relativePath) => !fs.existsSync(path16.join(docsDir, relativePath))
13626
13961
  );
13627
13962
  if (missing.length > 0) {
13628
13963
  throw createCliError(
@@ -13632,18 +13967,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
13632
13967
  }
13633
13968
  }
13634
13969
  function buildDefaultBodyFileName(kind, docsDir, component) {
13635
- const key = `${path15.resolve(docsDir)}::${component.trim().toLowerCase()}`;
13970
+ const key = `${path16.resolve(docsDir)}::${component.trim().toLowerCase()}`;
13636
13971
  const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
13637
13972
  return `lee-spec-kit.${digest}.${kind}.md`;
13638
13973
  }
13639
13974
  function toBodyFilePath(raw, kind, docsDir, component, lang) {
13640
- const selected = raw?.trim() || path15.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
13975
+ const selected = raw?.trim() || path16.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
13641
13976
  assertValid(
13642
13977
  validatePathWithLang(selected, lang),
13643
13978
  `github.${kind}.bodyFile`,
13644
13979
  lang
13645
13980
  );
13646
- return path15.resolve(selected);
13981
+ return path16.resolve(selected);
13647
13982
  }
13648
13983
  function toProjectRootDocsPath(relativePathFromDocs) {
13649
13984
  const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -14480,7 +14815,7 @@ function stripMarkdownCodeContexts(body) {
14480
14815
  function hasIssueClosingKeyword(body, issueNumber) {
14481
14816
  if (!issueNumber) return false;
14482
14817
  const cleaned = stripMarkdownCodeContexts(body);
14483
- const issue = escapeRegExp5(issueNumber);
14818
+ const issue = escapeRegExp6(issueNumber);
14484
14819
  const closeKeywordRegex = new RegExp(
14485
14820
  `\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
14486
14821
  "i"
@@ -14777,7 +15112,7 @@ function ensureCleanWorktree(cwd, lang) {
14777
15112
  function commitAndPushPaths(cwd, absPaths, message, lang, options) {
14778
15113
  const uniqueRelativePaths = [
14779
15114
  ...new Set(
14780
- absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path15.relative(cwd, absPath) || absPath)
15115
+ absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path16.relative(cwd, absPath) || absPath)
14781
15116
  )
14782
15117
  ];
14783
15118
  if (uniqueRelativePaths.length === 0) return;
@@ -14973,15 +15308,15 @@ function githubCommand(program2) {
14973
15308
  config.lang
14974
15309
  );
14975
15310
  const specContent = await fs.readFile(
14976
- path15.join(config.docsDir, paths.specPath),
15311
+ path16.join(config.docsDir, paths.specPath),
14977
15312
  "utf-8"
14978
15313
  );
14979
15314
  const planContent = await fs.readFile(
14980
- path15.join(config.docsDir, paths.planPath),
15315
+ path16.join(config.docsDir, paths.planPath),
14981
15316
  "utf-8"
14982
15317
  );
14983
15318
  const tasksContent = await fs.readFile(
14984
- path15.join(config.docsDir, paths.tasksPath),
15319
+ path16.join(config.docsDir, paths.tasksPath),
14985
15320
  "utf-8"
14986
15321
  );
14987
15322
  const overview = resolveOverviewFromSpec(
@@ -15024,7 +15359,7 @@ function githubCommand(program2) {
15024
15359
  create: options.create,
15025
15360
  explicitBodyFile,
15026
15361
  defaultBodyFile,
15027
- workflowDraftPath: path15.join(config.docsDir, paths.issuePath),
15362
+ workflowDraftPath: path16.join(config.docsDir, paths.issuePath),
15028
15363
  generatedBody,
15029
15364
  requiredSections: getRequiredIssueSections(config.lang),
15030
15365
  kindLabel: tg(config.lang, "kindIssue"),
@@ -15040,7 +15375,7 @@ function githubCommand(program2) {
15040
15375
  `${feature.type}-issue-sanitized`,
15041
15376
  config.lang
15042
15377
  );
15043
- await fs.ensureDir(path15.dirname(sanitizedBodyFile));
15378
+ await fs.ensureDir(path16.dirname(sanitizedBodyFile));
15044
15379
  await fs.writeFile(sanitizedBodyFile, body, "utf-8");
15045
15380
  bodyFile = sanitizedBodyFile;
15046
15381
  }
@@ -15089,12 +15424,12 @@ function githubCommand(program2) {
15089
15424
  const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
15090
15425
  if (syncedIssueNumber) {
15091
15426
  const synced = syncTasksIssueMetadata(
15092
- path15.join(config.docsDir, paths.tasksPath),
15427
+ path16.join(config.docsDir, paths.tasksPath),
15093
15428
  syncedIssueNumber,
15094
15429
  config.lang
15095
15430
  );
15096
15431
  const draftSynced = syncIssueDraftMetadata(
15097
- path15.join(config.docsDir, paths.issuePath),
15432
+ path16.join(config.docsDir, paths.issuePath),
15098
15433
  syncedIssueNumber
15099
15434
  );
15100
15435
  syncChanged = synced.changed || draftSynced.changed;
@@ -15205,13 +15540,13 @@ function githubCommand(program2) {
15205
15540
  config.lang
15206
15541
  );
15207
15542
  const specContent = await fs.readFile(
15208
- path15.join(config.docsDir, paths.specPath),
15543
+ path16.join(config.docsDir, paths.specPath),
15209
15544
  "utf-8"
15210
15545
  );
15211
- const planPath = path15.join(config.docsDir, paths.planPath);
15546
+ const planPath = path16.join(config.docsDir, paths.planPath);
15212
15547
  const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
15213
15548
  const tasksContent = await fs.readFile(
15214
- path15.join(config.docsDir, paths.tasksPath),
15549
+ path16.join(config.docsDir, paths.tasksPath),
15215
15550
  "utf-8"
15216
15551
  );
15217
15552
  const overview = resolveOverviewFromSpec(
@@ -15259,7 +15594,7 @@ function githubCommand(program2) {
15259
15594
  create: options.create,
15260
15595
  explicitBodyFile,
15261
15596
  defaultBodyFile,
15262
- workflowDraftPath: path15.join(config.docsDir, paths.prPath),
15597
+ workflowDraftPath: path16.join(config.docsDir, paths.prPath),
15263
15598
  generatedBody,
15264
15599
  requiredSections: getRequiredPrSections(config.lang),
15265
15600
  kindLabel: tg(config.lang, "kindPr"),
@@ -15277,7 +15612,7 @@ function githubCommand(program2) {
15277
15612
  `${feature.type}-pr-sanitized`,
15278
15613
  config.lang
15279
15614
  );
15280
- await fs.ensureDir(path15.dirname(sanitizedBodyFile));
15615
+ await fs.ensureDir(path16.dirname(sanitizedBodyFile));
15281
15616
  await fs.writeFile(sanitizedBodyFile, body, "utf-8");
15282
15617
  bodyFile = sanitizedBodyFile;
15283
15618
  }
@@ -15319,7 +15654,7 @@ function githubCommand(program2) {
15319
15654
  if (preparedBody.source === "generated") {
15320
15655
  await fs.writeFile(bodyFile, body, "utf-8");
15321
15656
  } else {
15322
- await fs.ensureDir(path15.dirname(fallbackBodyFile));
15657
+ await fs.ensureDir(path16.dirname(fallbackBodyFile));
15323
15658
  await fs.writeFile(fallbackBodyFile, body, "utf-8");
15324
15659
  bodyFile = fallbackBodyFile;
15325
15660
  }
@@ -15376,13 +15711,13 @@ function githubCommand(program2) {
15376
15711
  }
15377
15712
  if (prUrl && options.syncTasks !== false) {
15378
15713
  const syncedTasks = syncTasksPrMetadata(
15379
- path15.join(config.docsDir, paths.tasksPath),
15714
+ path16.join(config.docsDir, paths.tasksPath),
15380
15715
  prUrl,
15381
15716
  "Review",
15382
15717
  config.lang
15383
15718
  );
15384
15719
  const syncedDraft = syncPrDraftMetadata(
15385
- path15.join(config.docsDir, paths.prPath),
15720
+ path16.join(config.docsDir, paths.prPath),
15386
15721
  prUrl,
15387
15722
  "Review"
15388
15723
  );
@@ -15423,13 +15758,13 @@ function githubCommand(program2) {
15423
15758
  mergeAlreadyMerged = merged.alreadyMerged;
15424
15759
  if (prUrl && options.syncTasks !== false) {
15425
15760
  const mergedTasksSync = syncTasksPrMetadata(
15426
- path15.join(config.docsDir, paths.tasksPath),
15761
+ path16.join(config.docsDir, paths.tasksPath),
15427
15762
  prUrl,
15428
15763
  "Approved",
15429
15764
  config.lang
15430
15765
  );
15431
15766
  const mergedDraftSync = syncPrDraftMetadata(
15432
- path15.join(config.docsDir, paths.prPath),
15767
+ path16.join(config.docsDir, paths.prPath),
15433
15768
  prUrl,
15434
15769
  "Approved"
15435
15770
  );
@@ -15700,7 +16035,7 @@ function docsCommand(program2) {
15700
16035
  );
15701
16036
  return;
15702
16037
  }
15703
- const relativeFromCwd = path15.relative(process.cwd(), loaded.entry.absolutePath);
16038
+ const relativeFromCwd = path16.relative(process.cwd(), loaded.entry.absolutePath);
15704
16039
  console.log();
15705
16040
  console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
15706
16041
  console.log(
@@ -15780,8 +16115,9 @@ function detectCommand(program2) {
15780
16115
  }
15781
16116
  async function runDetect(options) {
15782
16117
  const cwd = process.cwd();
15783
- const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
15784
- const config = await getConfig(targetCwd);
16118
+ const targetCwd = options.dir ? path16.resolve(cwd, options.dir) : cwd;
16119
+ const detection = await detectSchemaProject(targetCwd);
16120
+ const config = detection.config;
15785
16121
  const detected = !!config;
15786
16122
  const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
15787
16123
  if (options.json) {
@@ -15807,9 +16143,6 @@ async function runDetect(options) {
15807
16143
  );
15808
16144
  return;
15809
16145
  }
15810
- const configPath2 = path15.join(config.docsDir, ".lee-spec-kit.json");
15811
- const configFilePresent2 = await fs.pathExists(configPath2);
15812
- const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
15813
16146
  console.log(
15814
16147
  JSON.stringify(
15815
16148
  {
@@ -15817,10 +16150,10 @@ async function runDetect(options) {
15817
16150
  reasonCode,
15818
16151
  isLeeSpecKitProject: true,
15819
16152
  targetCwd,
15820
- docsDir: config.docsDir,
15821
- configPath: configFilePresent2 ? configPath2 : null,
15822
- configFilePresent: configFilePresent2,
15823
- detectionSource: detectionSource2,
16153
+ docsDir: detection.docsDir,
16154
+ configPath: detection.configPath,
16155
+ configFilePresent: detection.configFilePresent,
16156
+ detectionSource: detection.detectionSource,
15824
16157
  projectType: config.projectType,
15825
16158
  lang: config.lang,
15826
16159
  projectName: config.projectName ?? null
@@ -15841,14 +16174,11 @@ async function runDetect(options) {
15841
16174
  console.log();
15842
16175
  return;
15843
16176
  }
15844
- const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
15845
- const configFilePresent = await fs.pathExists(configPath);
15846
- const detectionSource = configFilePresent ? "config" : "heuristic";
15847
16177
  console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
15848
- console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${config.docsDir}`));
16178
+ console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${detection.docsDir}`));
15849
16179
  console.log(
15850
16180
  chalk9.gray(
15851
- `- ${tr(lang, "cli", "detect.labelConfigPath")}: ${configFilePresent ? configPath : "-"}`
16181
+ `- ${tr(lang, "cli", "detect.labelConfigPath")}: ${detection.configPath || "-"}`
15852
16182
  )
15853
16183
  );
15854
16184
  console.log(
@@ -15856,7 +16186,7 @@ async function runDetect(options) {
15856
16186
  `- ${tr(lang, "cli", "detect.labelSource")}: ${tr(
15857
16187
  lang,
15858
16188
  "cli",
15859
- detectionSource === "config" ? "detect.sourceConfig" : "detect.sourceHeuristic"
16189
+ detection.detectionSource === "config" ? "detect.sourceConfig" : "detect.sourceHeuristic"
15860
16190
  )}`
15861
16191
  )
15862
16192
  );
@@ -15918,19 +16248,19 @@ function hasTemplateMarkers(content) {
15918
16248
  return patterns.some((pattern) => pattern.test(content));
15919
16249
  }
15920
16250
  async function countFeatureDirs(ctx, docsDir, projectType) {
15921
- const featuresRoot = path15.join(docsDir, "features");
16251
+ const featuresRoot = path16.join(docsDir, "features");
15922
16252
  if (projectType === "single") {
15923
16253
  const dirs = await listSubdirectories(ctx.fs, featuresRoot);
15924
- return dirs.filter((value) => path15.basename(value) !== "feature-base").length;
16254
+ return dirs.filter((value) => path16.basename(value) !== "feature-base").length;
15925
16255
  }
15926
16256
  const components = await listSubdirectories(ctx.fs, featuresRoot);
15927
16257
  let total = 0;
15928
16258
  for (const componentDir of components) {
15929
- const componentName = path15.basename(componentDir).trim().toLowerCase();
16259
+ const componentName = path16.basename(componentDir).trim().toLowerCase();
15930
16260
  if (!componentName || componentName === "feature-base") continue;
15931
16261
  const dirs = await listSubdirectories(ctx.fs, componentDir);
15932
16262
  total += dirs.filter(
15933
- (value) => path15.basename(value) !== "feature-base"
16263
+ (value) => path16.basename(value) !== "feature-base"
15934
16264
  ).length;
15935
16265
  }
15936
16266
  return total;
@@ -15942,7 +16272,7 @@ async function hasUserPrdFile(ctx, prdDir) {
15942
16272
  ignoreDirs: ["node_modules"]
15943
16273
  });
15944
16274
  return files.some(
15945
- (absolutePath) => path15.basename(absolutePath).toLowerCase() !== "readme.md"
16275
+ (absolutePath) => path16.basename(absolutePath).toLowerCase() !== "readme.md"
15946
16276
  );
15947
16277
  }
15948
16278
  function finalizeChecks(checks) {
@@ -16111,7 +16441,7 @@ async function runOnboardChecks(ctx) {
16111
16441
  });
16112
16442
  }
16113
16443
  }
16114
- const constitutionPath = path15.join(docsDir, "agents", "constitution.md");
16444
+ const constitutionPath = path16.join(docsDir, "agents", "constitution.md");
16115
16445
  if (!await fs.pathExists(constitutionPath)) {
16116
16446
  checks.push({
16117
16447
  id: "constitution_exists",
@@ -16153,7 +16483,7 @@ async function runOnboardChecks(ctx) {
16153
16483
  });
16154
16484
  }
16155
16485
  }
16156
- const customPath = path15.join(docsDir, "agents", "custom.md");
16486
+ const customPath = path16.join(docsDir, "agents", "custom.md");
16157
16487
  if (await fs.pathExists(customPath)) {
16158
16488
  const content = await fs.readFile(customPath, "utf-8");
16159
16489
  if (hasTemplateMarkers(content)) {
@@ -16182,7 +16512,7 @@ async function runOnboardChecks(ctx) {
16182
16512
  });
16183
16513
  }
16184
16514
  }
16185
- const prdDir = path15.join(docsDir, "prd");
16515
+ const prdDir = path16.join(docsDir, "prd");
16186
16516
  const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
16187
16517
  const prdReady = await hasUserPrdFile(ctx, prdDir);
16188
16518
  if (!prdReady) {
@@ -16200,7 +16530,7 @@ async function runOnboardChecks(ctx) {
16200
16530
  "PRD is empty. If features already exist, fill PRD as soon as possible."
16201
16531
  ),
16202
16532
  path: prdDir,
16203
- suggestedCommand: `touch ${quotePath(path15.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
16533
+ suggestedCommand: `touch ${quotePath(path16.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
16204
16534
  });
16205
16535
  } else {
16206
16536
  checks.push({
@@ -16638,7 +16968,7 @@ var PrePrReviewValidator = class {
16638
16968
  return result.evidence;
16639
16969
  }
16640
16970
  async validateEvidenceWithScope(evidencePath, projectRoot) {
16641
- const fullPath = path15.resolve(evidencePath);
16971
+ const fullPath = path16.resolve(evidencePath);
16642
16972
  if (!await fs.pathExists(fullPath)) {
16643
16973
  throw createCliError(
16644
16974
  "INVALID_ARGUMENT",
@@ -16736,9 +17066,9 @@ var PrePrReviewValidator = class {
16736
17066
  ]);
16737
17067
  const reviewedFiles = new Set(
16738
17068
  normalizedEvidence.files.map(
16739
- (f) => path15.relative(
17069
+ (f) => path16.relative(
16740
17070
  projectRoot,
16741
- path15.resolve(projectRoot, f.path)
17071
+ path16.resolve(projectRoot, f.path)
16742
17072
  )
16743
17073
  ).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
16744
17074
  );
@@ -16934,7 +17264,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
16934
17264
  residualRisks: ["none"],
16935
17265
  commandsExecuted: []
16936
17266
  };
16937
- function escapeRegExp6(value) {
17267
+ function escapeRegExp7(value) {
16938
17268
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16939
17269
  }
16940
17270
  function normalizeDecision(raw) {
@@ -16947,15 +17277,15 @@ function normalizeDecision(raw) {
16947
17277
  if (value === "blocked" || value === "block") return "blocked";
16948
17278
  return null;
16949
17279
  }
16950
- function findSpecLineIndex(lines, keys) {
16951
- const escaped = keys.map((key) => escapeRegExp6(key));
17280
+ function findSpecLineIndex2(lines, keys) {
17281
+ const escaped = keys.map((key) => escapeRegExp7(key));
16952
17282
  const re = new RegExp(
16953
17283
  `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
16954
17284
  );
16955
17285
  return lines.findIndex((line) => re.test(line));
16956
17286
  }
16957
- function replaceSpecLine(line, keys, preferredKey, value) {
16958
- const escaped = keys.map((key) => escapeRegExp6(key));
17287
+ function replaceSpecLine2(line, keys, preferredKey, value) {
17288
+ const escaped = keys.map((key) => escapeRegExp7(key));
16959
17289
  const re = new RegExp(
16960
17290
  `^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
16961
17291
  );
@@ -16963,7 +17293,7 @@ function replaceSpecLine(line, keys, preferredKey, value) {
16963
17293
  return line.replace(re, `$1${preferredKey}$2${value}`);
16964
17294
  }
16965
17295
  function computeInsertIndex(lines, anchorKeys) {
16966
- const anchorIndex = findSpecLineIndex(lines, anchorKeys);
17296
+ const anchorIndex = findSpecLineIndex2(lines, anchorKeys);
16967
17297
  if (anchorIndex !== -1) {
16968
17298
  let cursor = anchorIndex + 1;
16969
17299
  while (cursor < lines.length && /^\s{2,}-\s+/.test(lines[cursor])) {
@@ -16979,9 +17309,9 @@ function computeInsertIndex(lines, anchorKeys) {
16979
17309
  }
16980
17310
  function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
16981
17311
  const lines = content.split("\n");
16982
- const index = findSpecLineIndex(lines, keys);
17312
+ const index = findSpecLineIndex2(lines, keys);
16983
17313
  if (index !== -1) {
16984
- lines[index] = replaceSpecLine(lines[index], keys, preferredKey, value);
17314
+ lines[index] = replaceSpecLine2(lines[index], keys, preferredKey, value);
16985
17315
  return lines.join("\n");
16986
17316
  }
16987
17317
  const insertAt = computeInsertIndex(lines, anchorKeys);
@@ -17199,7 +17529,7 @@ async function runPrePrReviewRun(featureName, options) {
17199
17529
  );
17200
17530
  const policy = resolvePrePrReviewPolicy(config.workflow);
17201
17531
  const preferred = getPreferredKeys(config.lang);
17202
- const tasksPath = path15.join(feature.path, "tasks.md");
17532
+ const tasksPath = path16.join(feature.path, "tasks.md");
17203
17533
  let tasksUpdated = false;
17204
17534
  if (await fs.pathExists(tasksPath)) {
17205
17535
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
@@ -17308,7 +17638,7 @@ async function runPrePrReview(featureName, options) {
17308
17638
  `tasks.md not found for feature: ${feature.folderName}`
17309
17639
  );
17310
17640
  }
17311
- const tasksPath = path15.join(feature.path, "tasks.md");
17641
+ const tasksPath = path16.join(feature.path, "tasks.md");
17312
17642
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
17313
17643
  const policy = resolvePrePrReviewPolicy(config.workflow);
17314
17644
  const preferred = getPreferredKeys(config.lang);
@@ -17406,7 +17736,7 @@ async function runPrePrReview(featureName, options) {
17406
17736
  }
17407
17737
  }
17408
17738
  }
17409
- const decisionsPath = path15.join(feature.path, "decisions.md");
17739
+ const decisionsPath = path16.join(feature.path, "decisions.md");
17410
17740
  const decisionLogEntry = buildReportContent({
17411
17741
  folderName: feature.folderName,
17412
17742
  date,
@@ -17422,9 +17752,9 @@ async function runPrePrReview(featureName, options) {
17422
17752
  await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
17423
17753
  }
17424
17754
  const decisionsPathFromDocs = normalizePathForDoc(
17425
- path15.join(feature.docs.featurePathFromDocs, "decisions.md")
17755
+ path16.join(feature.docs.featurePathFromDocs, "decisions.md")
17426
17756
  );
17427
- const evidencePath = path15.basename(config.docsDir) === "docs" ? normalizePathForDoc(path15.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
17757
+ const evidencePath = path16.basename(config.docsDir) === "docs" ? normalizePathForDoc(path16.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
17428
17758
  let nextTasks = tasksContent;
17429
17759
  nextTasks = upsertSpecLine(
17430
17760
  nextTasks,
@@ -17479,18 +17809,18 @@ async function runPrePrReview(featureName, options) {
17479
17809
  }
17480
17810
  console.log();
17481
17811
  }
17482
- function escapeRegExp7(value) {
17812
+ function escapeRegExp8(value) {
17483
17813
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17484
17814
  }
17485
- function findSpecLineIndex2(lines, keys) {
17486
- const escaped = keys.map((key) => escapeRegExp7(key));
17815
+ function findSpecLineIndex3(lines, keys) {
17816
+ const escaped = keys.map((key) => escapeRegExp8(key));
17487
17817
  const re = new RegExp(
17488
17818
  `^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
17489
17819
  );
17490
17820
  return lines.findIndex((line) => re.test(line));
17491
17821
  }
17492
- function replaceSpecLine2(line, keys, preferredKey, value) {
17493
- const escaped = keys.map((key) => escapeRegExp7(key));
17822
+ function replaceSpecLine3(line, keys, preferredKey, value) {
17823
+ const escaped = keys.map((key) => escapeRegExp8(key));
17494
17824
  const re = new RegExp(
17495
17825
  `^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
17496
17826
  );
@@ -17498,7 +17828,7 @@ function replaceSpecLine2(line, keys, preferredKey, value) {
17498
17828
  return line.replace(re, `$1${preferredKey}$2${value}`);
17499
17829
  }
17500
17830
  function computeInsertIndex2(lines, anchorKeys) {
17501
- const anchorIndex = findSpecLineIndex2(lines, anchorKeys);
17831
+ const anchorIndex = findSpecLineIndex3(lines, anchorKeys);
17502
17832
  if (anchorIndex !== -1) {
17503
17833
  let cursor = anchorIndex + 1;
17504
17834
  while (cursor < lines.length && /^\s{2,}-\s+/.test(lines[cursor])) {
@@ -17514,9 +17844,9 @@ function computeInsertIndex2(lines, anchorKeys) {
17514
17844
  }
17515
17845
  function upsertSpecLine2(content, keys, preferredKey, value, anchorKeys) {
17516
17846
  const lines = content.split("\n");
17517
- const index = findSpecLineIndex2(lines, keys);
17847
+ const index = findSpecLineIndex3(lines, keys);
17518
17848
  if (index !== -1) {
17519
- lines[index] = replaceSpecLine2(lines[index], keys, preferredKey, value);
17849
+ lines[index] = replaceSpecLine3(lines[index], keys, preferredKey, value);
17520
17850
  return lines.join("\n");
17521
17851
  }
17522
17852
  const insertAt = computeInsertIndex2(lines, anchorKeys);
@@ -17565,7 +17895,7 @@ async function runCodeReviewRun(featureName, options) {
17565
17895
  );
17566
17896
  }
17567
17897
  const feature = state.matchedFeature;
17568
- const tasksPath = path15.join(feature.path, "tasks.md");
17898
+ const tasksPath = path16.join(feature.path, "tasks.md");
17569
17899
  let tasksUpdated = false;
17570
17900
  if (await fs.pathExists(tasksPath)) {
17571
17901
  const tasksContent = await fs.readFile(tasksPath, "utf-8");
@@ -17598,7 +17928,7 @@ async function runCodeReviewRun(featureName, options) {
17598
17928
  nextMainState: "code_review_running",
17599
17929
  tasksUpdated,
17600
17930
  tasksPath,
17601
- decisionsPath: path15.join(feature.path, "decisions.md"),
17931
+ decisionsPath: path16.join(feature.path, "decisions.md"),
17602
17932
  prompt,
17603
17933
  recordedAt: getLocalDateString()
17604
17934
  };
@@ -17717,7 +18047,7 @@ async function runRequirements(options) {
17717
18047
  }
17718
18048
  for (const feature of scan.features) {
17719
18049
  if (!feature.docs.tasksExists) continue;
17720
- const tasksPath = path15.join(feature.path, "tasks.md");
18050
+ const tasksPath = path16.join(feature.path, "tasks.md");
17721
18051
  let tasksContent = "";
17722
18052
  try {
17723
18053
  tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
@@ -17851,7 +18181,7 @@ async function runRequirements(options) {
17851
18181
  process.stdout.write(`${lines.join("\n")}
17852
18182
  `);
17853
18183
  if (options.write) {
17854
- const outputPath = path15.join(docsDir, "prd", "status.md");
18184
+ const outputPath = path16.join(docsDir, "prd", "status.md");
17855
18185
  await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
17856
18186
  `, "utf-8");
17857
18187
  console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
@@ -17965,8 +18295,8 @@ function parseTaskChecklist(lines, taskLineIndex) {
17965
18295
  function ensureTaskDetailsReady(lines, task) {
17966
18296
  const acceptance = parseTaskAcceptance(lines, task.index);
17967
18297
  const checklist = parseTaskChecklist(lines, task.index);
17968
- const acceptanceHasPlaceholder = !acceptance || acceptance.items.length === 0 || acceptance.placeholderCount > 0;
17969
- const checklistHasPlaceholder = !checklist || checklist.total === 0 || checklist.placeholderCount > 0;
18298
+ const acceptanceHasPlaceholder = !!acceptance && (acceptance.items.length === 0 || acceptance.placeholderCount > 0);
18299
+ const checklistHasPlaceholder = !!checklist && (checklist.total === 0 || checklist.placeholderCount > 0);
17970
18300
  if (acceptanceHasPlaceholder || checklistHasPlaceholder) {
17971
18301
  throw createCliError(
17972
18302
  "PRECONDITION_FAILED",
@@ -18038,7 +18368,7 @@ async function resolveTaskRunContext(featureName, options) {
18038
18368
  }
18039
18369
  async function runTaskRun(featureName, options) {
18040
18370
  const { config, feature } = await resolveTaskRunContext(featureName, options);
18041
- const tasksPath = path15.join(feature.path, "tasks.md");
18371
+ const tasksPath = path16.join(feature.path, "tasks.md");
18042
18372
  if (!await fs.pathExists(tasksPath)) {
18043
18373
  throw createCliError(
18044
18374
  "PRECONDITION_FAILED",
@@ -18177,7 +18507,7 @@ async function resolveTaskCompleteContext(featureName, options) {
18177
18507
  }
18178
18508
  async function runTaskComplete(featureName, options) {
18179
18509
  const { feature } = await resolveTaskCompleteContext(featureName, options);
18180
- const tasksPath = path15.join(feature.path, "tasks.md");
18510
+ const tasksPath = path16.join(feature.path, "tasks.md");
18181
18511
  if (!await fs.pathExists(tasksPath)) {
18182
18512
  throw createCliError(
18183
18513
  "PRECONDITION_FAILED",
@@ -18295,7 +18625,7 @@ function normalizeTaskDetailItems(values, flagName) {
18295
18625
  }
18296
18626
  return normalized;
18297
18627
  }
18298
- function escapeRegExp8(value) {
18628
+ function escapeRegExp9(value) {
18299
18629
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18300
18630
  }
18301
18631
  function findTaskListHeadingIndex(lines) {
@@ -18330,7 +18660,7 @@ function normalizeTaskRef(value) {
18330
18660
  if (!trimmed) {
18331
18661
  throw createCliError(
18332
18662
  "INVALID_ARGUMENT",
18333
- "`--ref` is required. Use `NON-PRD` or an existing `PRD-...` requirement ID."
18663
+ "`--ref` is required. Use `NON-PRD` or an existing `PRD-*` requirement ID."
18334
18664
  );
18335
18665
  }
18336
18666
  if (isNonPrdTag(trimmed)) return "NON-PRD";
@@ -18338,14 +18668,14 @@ function normalizeTaskRef(value) {
18338
18668
  if (!isPrdRequirementId(normalized)) {
18339
18669
  throw createCliError(
18340
18670
  "INVALID_ARGUMENT",
18341
- "`--ref` must be `NON-PRD` or an existing `PRD-FR-001`-style requirement ID."
18671
+ "`--ref` must be `NON-PRD` or an existing `PRD-*` requirement ID (for example `PRD-FR-001` or `PRD-SCOPE-V1-DESKTOP-EDITOR`)."
18342
18672
  );
18343
18673
  }
18344
18674
  return normalized;
18345
18675
  }
18346
18676
  function getNextTaskSequence(content, featureFolderName) {
18347
18677
  const taskIdPattern = new RegExp(
18348
- `\\bT-${escapeRegExp8(featureFolderName)}-(\\d+)\\b`,
18678
+ `\\bT-${escapeRegExp9(featureFolderName)}-(\\d+)\\b`,
18349
18679
  "g"
18350
18680
  );
18351
18681
  let max = 0;
@@ -18408,7 +18738,7 @@ async function runTaskAdd(featureName, options) {
18408
18738
  );
18409
18739
  }
18410
18740
  }
18411
- const tasksPath = path15.join(feature.path, "tasks.md");
18741
+ const tasksPath = path16.join(feature.path, "tasks.md");
18412
18742
  if (!await fs.pathExists(tasksPath)) {
18413
18743
  throw createCliError(
18414
18744
  "PRECONDITION_FAILED",
@@ -18475,7 +18805,7 @@ async function runTaskAdd(featureName, options) {
18475
18805
  }
18476
18806
  function taskCommand(program2) {
18477
18807
  const task = program2.command("task").description("Manage tasks");
18478
- task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option(
18808
+ task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or existing PRD-* key").option(
18479
18809
  "--acceptance <text>",
18480
18810
  "Acceptance item. Repeat to add more than one.",
18481
18811
  collectRepeatableOption,
@@ -18511,10 +18841,9 @@ function taskCommand(program2) {
18511
18841
  }
18512
18842
  });
18513
18843
  }
18514
- function setupCommand(program2) {
18515
- const setup = program2.command("setup").description("Developer environment setup helpers");
18516
- setup.command("codex-bootstrap").description(
18517
- "Install a small Codex global bootstrap that reads ./AGENTS.md or ./docs/AGENTS.md"
18844
+ function registerCodexIntegration(parent) {
18845
+ parent.command("codex").alias("codex-bootstrap").description(
18846
+ "Install or remove the optional Codex bootstrap that re-reads ./AGENTS.md or ./docs/AGENTS.md"
18518
18847
  ).option(
18519
18848
  "--remove",
18520
18849
  "Remove the lee-spec-kit managed Codex bootstrap block"
@@ -18547,6 +18876,10 @@ function setupCommand(program2) {
18547
18876
  }
18548
18877
  });
18549
18878
  }
18879
+ function integrationsCommand(program2) {
18880
+ const integrations = program2.command("integrations").alias("setup").description("Optional developer integration helpers");
18881
+ registerCodexIntegration(integrations);
18882
+ }
18550
18883
  function isBannerDisabled() {
18551
18884
  const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
18552
18885
  return v === "1";
@@ -18590,11 +18923,11 @@ ${version}
18590
18923
  }
18591
18924
  return `${ascii}${footer}`;
18592
18925
  }
18593
- var CACHE_FILE = path15.join(os.homedir(), ".lee-spec-kit-version-cache.json");
18926
+ var CACHE_FILE = path16.join(os.homedir(), ".lee-spec-kit-version-cache.json");
18594
18927
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
18595
18928
  function getCurrentVersion() {
18596
18929
  try {
18597
- const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
18930
+ const packageJsonPath = path16.join(__dirname$1, "..", "package.json");
18598
18931
  if (fs.existsSync(packageJsonPath)) {
18599
18932
  const pkg = fs.readJsonSync(packageJsonPath);
18600
18933
  return pkg.version;
@@ -18698,7 +19031,7 @@ function shouldCheckForUpdates() {
18698
19031
  if (shouldCheckForUpdates()) checkForUpdates();
18699
19032
  function getCliVersion() {
18700
19033
  try {
18701
- const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
19034
+ const packageJsonPath = path16.join(__dirname$1, "..", "package.json");
18702
19035
  if (fs.existsSync(packageJsonPath)) {
18703
19036
  const pkg = fs.readJsonSync(packageJsonPath);
18704
19037
  if (pkg?.version) return String(pkg.version);
@@ -18718,9 +19051,7 @@ function configureRootCommandSurface() {
18718
19051
  }
18719
19052
  }
18720
19053
  var cliVersion = getCliVersion();
18721
- program.name("lee-spec-kit").description(
18722
- "Agent-guided development harness for spec-driven projects"
18723
- ).version(cliVersion).option("--no-banner", "Hide banner in help output");
19054
+ program.name("lee-spec-kit").description("Orchestration harness CLI for AI agent-driven development").version(cliVersion).option("--no-banner", "Hide banner in help output");
18724
19055
  if (shouldShowBanner()) {
18725
19056
  program.addHelpText("beforeAll", getBanner({ version: cliVersion }));
18726
19057
  }
@@ -18744,7 +19075,7 @@ taskRunCommand(program);
18744
19075
  taskCompleteCommand(program);
18745
19076
  taskCommand(program);
18746
19077
  requirementsCommand(program);
18747
- setupCommand(program);
19078
+ integrationsCommand(program);
18748
19079
  configureRootCommandSurface();
18749
19080
  await program.parseAsync();
18750
19081
  //# sourceMappingURL=index.js.map