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