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/README.en.md +24 -22
- package/README.md +22 -23
- package/dist/index.js +778 -447
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +6 -4
- package/templates/en/common/agents/agents.md +12 -5
- package/templates/en/common/agents/skills/execute-task.md +5 -1
- package/templates/en/common/features/README.md +3 -2
- package/templates/en/common/features/feature-base/spec.md +1 -1
- package/templates/en/common/features/feature-base/tasks.md +6 -3
- package/templates/en/common/ideas/README.md +2 -2
- package/templates/en/common/prd/README.md +2 -2
- package/templates/ko/common/README.md +6 -4
- package/templates/ko/common/agents/agents.md +12 -5
- package/templates/ko/common/agents/skills/execute-task.md +5 -1
- package/templates/ko/common/features/README.md +3 -2
- package/templates/ko/common/features/feature-base/spec.md +1 -1
- package/templates/ko/common/features/feature-base/tasks.md +6 -3
- package/templates/ko/common/ideas/README.md +2 -2
- package/templates/ko/common/prd/README.md +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
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
|
|
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 = () =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
155
|
+
var __dirname2 = path16.dirname(__filename2);
|
|
155
156
|
function getTemplatesDir() {
|
|
156
|
-
const rootDir =
|
|
157
|
-
return
|
|
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
|
|
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
|
|
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 \
|
|
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
|
|
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
|
|
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": "
|
|
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(
|
|
1765
|
+
return createHash("sha1").update(path16.resolve(value)).digest("hex").slice(0, 16);
|
|
1761
1766
|
}
|
|
1762
1767
|
function getTempRuntimeDir(scopePath) {
|
|
1763
|
-
return
|
|
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
|
|
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 =
|
|
1788
|
+
const resolved = path16.resolve(cwd);
|
|
1784
1789
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1785
1790
|
}
|
|
1786
1791
|
function getDocsLockPath(docsDir) {
|
|
1787
|
-
return
|
|
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
|
|
1795
|
-
getRuntimeStateDir(
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
1938
|
+
const target = path16.join(docsDir, "agents", file);
|
|
1934
1939
|
if (await fs.pathExists(target)) {
|
|
1935
1940
|
await fs.remove(target);
|
|
1936
|
-
removed.push(
|
|
1941
|
+
removed.push(path16.relative(docsDir, target));
|
|
1937
1942
|
}
|
|
1938
1943
|
}
|
|
1939
1944
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1940
|
-
const target =
|
|
1945
|
+
const target = path16.join(docsDir, "agents", dir);
|
|
1941
1946
|
if (await fs.pathExists(target)) {
|
|
1942
1947
|
await fs.remove(target);
|
|
1943
|
-
removed.push(
|
|
1948
|
+
removed.push(path16.relative(docsDir, target));
|
|
1944
1949
|
}
|
|
1945
1950
|
}
|
|
1946
|
-
const featureBasePath =
|
|
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(
|
|
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 \`
|
|
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
|
-
|
|
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.
|
|
2044
|
-
2.
|
|
2045
|
-
3. Do not paraphrase or omit
|
|
2046
|
-
4. Prefer \`
|
|
2047
|
-
5.
|
|
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
|
-
-
|
|
2060
|
-
|
|
2061
|
-
- \`
|
|
2062
|
-
-
|
|
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
|
|
2135
|
+
return path16.join(os.homedir(), ".codex");
|
|
2131
2136
|
}
|
|
2132
2137
|
function getCodexConfigPath() {
|
|
2133
|
-
return
|
|
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(
|
|
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 =
|
|
2293
|
+
let current = path16.resolve(startDir);
|
|
2300
2294
|
while (true) {
|
|
2301
2295
|
dirs.push(current);
|
|
2302
|
-
const parent =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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 ? [
|
|
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 =
|
|
2352
|
+
const resolvedBaseDir = path16.resolve(baseDir);
|
|
2343
2353
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2344
2354
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2345
|
-
const possibleDocsDirs = [
|
|
2355
|
+
const possibleDocsDirs = [path16.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2346
2356
|
for (const docsDir of possibleDocsDirs) {
|
|
2347
|
-
const resolvedDocsDir =
|
|
2357
|
+
const resolvedDocsDir = path16.resolve(docsDir);
|
|
2348
2358
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2349
2359
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2350
|
-
const configPath =
|
|
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
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
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 =
|
|
2382
|
-
const featuresPath =
|
|
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
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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 {
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3084
|
+
const featuresRoot = path16.join(targetDir, "features");
|
|
2889
3085
|
for (const component of components) {
|
|
2890
|
-
const componentDir =
|
|
3086
|
+
const componentDir = path16.join(featuresRoot, component);
|
|
2891
3087
|
await fs.ensureDir(componentDir);
|
|
2892
|
-
const readmePath =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
2979
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
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(
|
|
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" ? "." :
|
|
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) =>
|
|
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(
|
|
3388
|
+
await patchMarkdownIfExists(path16.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3192
3389
|
await patchMarkdownIfExists(
|
|
3193
|
-
|
|
3390
|
+
path16.join(featureDir, "tasks.md"),
|
|
3194
3391
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3195
3392
|
);
|
|
3196
|
-
await fs.remove(
|
|
3197
|
-
await fs.remove(
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
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 =
|
|
3426
|
-
|
|
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 =
|
|
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
|
-
|
|
3481
|
-
|
|
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
|
|
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 ? [
|
|
3570
|
-
|
|
3571
|
-
|
|
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 =
|
|
3886
|
+
const ideasDir = path16.join(docsDir, "ideas");
|
|
3705
3887
|
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3706
|
-
const ideaPath =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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/
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
"
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
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
|
-
|
|
3991
|
-
|
|
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 =
|
|
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 (
|
|
4433
|
+
if (path16.isAbsolute(explicit)) {
|
|
4227
4434
|
candidates.push(explicit);
|
|
4228
4435
|
} else {
|
|
4229
|
-
candidates.push(
|
|
4230
|
-
candidates.push(
|
|
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(
|
|
4442
|
+
candidates.push(path16.resolve(docsRoot, withoutDocsPrefix));
|
|
4236
4443
|
}
|
|
4237
4444
|
}
|
|
4238
4445
|
}
|
|
4239
4446
|
}
|
|
4240
|
-
candidates.push(
|
|
4241
|
-
candidates.push(
|
|
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 =
|
|
4451
|
+
const abs = path16.resolve(candidate);
|
|
4245
4452
|
if (seen.has(abs)) continue;
|
|
4246
4453
|
seen.add(abs);
|
|
4247
|
-
if (!
|
|
4454
|
+
if (!fs13.existsSync(abs)) continue;
|
|
4248
4455
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
4249
4456
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
4250
|
-
const rel =
|
|
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 =
|
|
4292
|
-
const marker = `${
|
|
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
|
-
|
|
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 =
|
|
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
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
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 = `${
|
|
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 =
|
|
6242
|
+
const normalized = path16.resolve(cwd);
|
|
6016
6243
|
return normalized.includes(WORKTREE_MARKER);
|
|
6017
6244
|
}
|
|
6018
6245
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
6019
|
-
const normalized =
|
|
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 =
|
|
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 (
|
|
6527
|
-
candidates.add(
|
|
6762
|
+
if (path16.isAbsolute(evidencePath)) {
|
|
6763
|
+
candidates.add(path16.resolve(evidencePath));
|
|
6528
6764
|
} else {
|
|
6529
|
-
candidates.add(
|
|
6530
|
-
candidates.add(
|
|
6531
|
-
candidates.add(
|
|
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(
|
|
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(
|
|
6834
|
+
return value.split(path16.sep).join("/");
|
|
6599
6835
|
}
|
|
6600
6836
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6601
|
-
const relativeDocsDir =
|
|
6837
|
+
const relativeDocsDir = path16.relative(projectGitCwd, docsDir);
|
|
6602
6838
|
if (!relativeDocsDir) return [];
|
|
6603
|
-
if (
|
|
6604
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
6828
|
-
const relative =
|
|
7063
|
+
if (!path16.isAbsolute(candidate)) return candidate;
|
|
7064
|
+
const relative = path16.relative(projectGitCwd, candidate);
|
|
6829
7065
|
if (!relative) return "";
|
|
6830
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
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(
|
|
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 =
|
|
6931
|
-
|
|
6932
|
-
|
|
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 =
|
|
6937
|
-
const planPath =
|
|
6938
|
-
const tasksPath =
|
|
6939
|
-
const decisionsPath =
|
|
6940
|
-
const issueDocPath =
|
|
6941
|
-
const prDocPath =
|
|
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 =
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8028
|
-
const targetAgentsBase =
|
|
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) ||
|
|
8122
|
-
targets.add(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
8303
|
-
const targetPath =
|
|
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 =
|
|
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(
|
|
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 ?
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
10391
|
-
|
|
10392
|
-
|
|
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
|
-
"
|
|
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: ${
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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(
|
|
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
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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,
|
|
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,
|
|
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,
|
|
11833
|
+
path: formatPath(cwd, path16.join(f.path, "tasks.md"))
|
|
11499
11834
|
});
|
|
11500
11835
|
}
|
|
11501
11836
|
if (f.docs.tasksExists) {
|
|
11502
|
-
const tasksPath =
|
|
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,
|
|
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,
|
|
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,
|
|
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: ${
|
|
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: ${
|
|
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
|
|
12304
|
+
return path16.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11970
12305
|
}
|
|
11971
12306
|
function getFlowRunPath(cwd, runId) {
|
|
11972
|
-
return
|
|
12307
|
+
return path16.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11973
12308
|
}
|
|
11974
12309
|
function getFlowRunLockPath(cwd, runId) {
|
|
11975
|
-
return
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 ?
|
|
13418
|
-
|
|
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 =
|
|
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 =
|
|
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 [
|
|
13437
|
-
const configPath =
|
|
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 =
|
|
13447
|
-
const featuresPath =
|
|
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 =
|
|
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
|
|
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*\\*\\*${
|
|
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(
|
|
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*\\*\\*${
|
|
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(
|
|
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 = `${
|
|
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() ||
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
-
|
|
15311
|
+
path16.join(config.docsDir, paths.specPath),
|
|
14977
15312
|
"utf-8"
|
|
14978
15313
|
);
|
|
14979
15314
|
const planContent = await fs.readFile(
|
|
14980
|
-
|
|
15315
|
+
path16.join(config.docsDir, paths.planPath),
|
|
14981
15316
|
"utf-8"
|
|
14982
15317
|
);
|
|
14983
15318
|
const tasksContent = await fs.readFile(
|
|
14984
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
15427
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
15093
15428
|
syncedIssueNumber,
|
|
15094
15429
|
config.lang
|
|
15095
15430
|
);
|
|
15096
15431
|
const draftSynced = syncIssueDraftMetadata(
|
|
15097
|
-
|
|
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
|
-
|
|
15543
|
+
path16.join(config.docsDir, paths.specPath),
|
|
15209
15544
|
"utf-8"
|
|
15210
15545
|
);
|
|
15211
|
-
const 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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 ?
|
|
15784
|
-
const
|
|
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:
|
|
15821
|
-
configPath:
|
|
15822
|
-
configFilePresent:
|
|
15823
|
-
detectionSource:
|
|
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")}: ${
|
|
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")}: ${
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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) =>
|
|
17069
|
+
(f) => path16.relative(
|
|
16740
17070
|
projectRoot,
|
|
16741
|
-
|
|
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
|
|
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
|
|
16951
|
-
const escaped = keys.map((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
|
|
16958
|
-
const escaped = keys.map((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 =
|
|
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 =
|
|
17312
|
+
const index = findSpecLineIndex2(lines, keys);
|
|
16983
17313
|
if (index !== -1) {
|
|
16984
|
-
lines[index] =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
17755
|
+
path16.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
17426
17756
|
);
|
|
17427
|
-
const evidencePath =
|
|
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
|
|
17812
|
+
function escapeRegExp8(value) {
|
|
17483
17813
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17484
17814
|
}
|
|
17485
|
-
function
|
|
17486
|
-
const escaped = keys.map((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
|
|
17493
|
-
const escaped = keys.map((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 =
|
|
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 =
|
|
17847
|
+
const index = findSpecLineIndex3(lines, keys);
|
|
17518
17848
|
if (index !== -1) {
|
|
17519
|
-
lines[index] =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
17969
|
-
const checklistHasPlaceholder =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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-${
|
|
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 =
|
|
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
|
|
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
|
|
18515
|
-
|
|
18516
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
19078
|
+
integrationsCommand(program);
|
|
18748
19079
|
configureRootCommandSurface();
|
|
18749
19080
|
await program.parseAsync();
|
|
18750
19081
|
//# sourceMappingURL=index.js.map
|