lee-spec-kit 0.7.3 → 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 +533 -280
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
}
|
|
@@ -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,13 @@ 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"));
|
|
3067
3183
|
}
|
|
3068
3184
|
var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
|
|
3069
3185
|
var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
|
|
@@ -3075,7 +3191,7 @@ function extractExplicitIdeaRef(requestText) {
|
|
|
3075
3191
|
return null;
|
|
3076
3192
|
}
|
|
3077
3193
|
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3078
|
-
const ideasDir =
|
|
3194
|
+
const ideasDir = path15.join(docsDir, "ideas");
|
|
3079
3195
|
const trimmedRef = ref.trim();
|
|
3080
3196
|
if (!trimmedRef) {
|
|
3081
3197
|
throw createCliError(
|
|
@@ -3084,7 +3200,7 @@ async function resolveIdeaReference(docsDir, ref, lang) {
|
|
|
3084
3200
|
);
|
|
3085
3201
|
}
|
|
3086
3202
|
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3087
|
-
const candidate =
|
|
3203
|
+
const candidate = path15.resolve(process.cwd(), trimmedRef);
|
|
3088
3204
|
if (await fs.pathExists(candidate)) {
|
|
3089
3205
|
return { path: candidate };
|
|
3090
3206
|
}
|
|
@@ -3103,11 +3219,11 @@ async function resolveIdeaReference(docsDir, ref, lang) {
|
|
|
3103
3219
|
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3104
3220
|
const exactName = `${trimmedRef}.md`;
|
|
3105
3221
|
if (files.includes(exactName)) {
|
|
3106
|
-
return { path:
|
|
3222
|
+
return { path: path15.join(ideasDir, exactName) };
|
|
3107
3223
|
}
|
|
3108
3224
|
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3109
3225
|
if (byId.length === 1) {
|
|
3110
|
-
return { path:
|
|
3226
|
+
return { path: path15.join(ideasDir, byId[0]) };
|
|
3111
3227
|
}
|
|
3112
3228
|
if (byId.length > 1) {
|
|
3113
3229
|
throw createCliError(
|
|
@@ -3131,7 +3247,7 @@ async function readIdeaMetadataValue(ideaPath, label) {
|
|
|
3131
3247
|
async function deriveFeatureNameFromIdea(ideaPath) {
|
|
3132
3248
|
const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
|
|
3133
3249
|
if (ideaName && ideaName !== "-") return ideaName;
|
|
3134
|
-
const basename =
|
|
3250
|
+
const basename = path15.basename(ideaPath, ".md");
|
|
3135
3251
|
return basename.replace(/^I\d{3,}-/, "");
|
|
3136
3252
|
}
|
|
3137
3253
|
function escapeRegExp(value) {
|
|
@@ -3287,19 +3403,19 @@ async function runFeature(name, options) {
|
|
|
3287
3403
|
}
|
|
3288
3404
|
let featuresDir;
|
|
3289
3405
|
if (projectType === "multi") {
|
|
3290
|
-
featuresDir =
|
|
3406
|
+
featuresDir = path15.join(docsDir, "features", component);
|
|
3291
3407
|
} else {
|
|
3292
|
-
featuresDir =
|
|
3408
|
+
featuresDir = path15.join(docsDir, "features");
|
|
3293
3409
|
}
|
|
3294
3410
|
const featureFolderName = `${featureId}-${name}`;
|
|
3295
|
-
const featureDir =
|
|
3411
|
+
const featureDir = path15.join(featuresDir, featureFolderName);
|
|
3296
3412
|
if (await fs.pathExists(featureDir)) {
|
|
3297
3413
|
throw createCliError(
|
|
3298
3414
|
"INVALID_ARGUMENT",
|
|
3299
3415
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3300
3416
|
);
|
|
3301
3417
|
}
|
|
3302
|
-
const featureBasePath =
|
|
3418
|
+
const featureBasePath = path15.join(
|
|
3303
3419
|
getTemplatesDir(),
|
|
3304
3420
|
lang,
|
|
3305
3421
|
"common",
|
|
@@ -3346,8 +3462,8 @@ async function runFeature(name, options) {
|
|
|
3346
3462
|
await replaceInFiles(fsAdapter, featureDir, replacements);
|
|
3347
3463
|
if (linkedIdea) {
|
|
3348
3464
|
await stampIdeaReferenceInSpec(
|
|
3349
|
-
|
|
3350
|
-
|
|
3465
|
+
path15.join(featureDir, "spec.md"),
|
|
3466
|
+
path15.relative(featureDir, linkedIdea.path)
|
|
3351
3467
|
);
|
|
3352
3468
|
await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
|
|
3353
3469
|
}
|
|
@@ -3375,7 +3491,7 @@ async function runFeature(name, options) {
|
|
|
3375
3491
|
featureName: name,
|
|
3376
3492
|
component: projectType === "multi" ? component : void 0,
|
|
3377
3493
|
featurePath: featureDir,
|
|
3378
|
-
featurePathFromDocs:
|
|
3494
|
+
featurePathFromDocs: path15.relative(docsDir, featureDir)
|
|
3379
3495
|
};
|
|
3380
3496
|
},
|
|
3381
3497
|
{ owner: "feature" }
|
|
@@ -3435,9 +3551,9 @@ function escapeRegExp2(value) {
|
|
|
3435
3551
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3436
3552
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3437
3553
|
const candidates = [
|
|
3438
|
-
...explicitDocsDir ? [
|
|
3439
|
-
|
|
3440
|
-
|
|
3554
|
+
...explicitDocsDir ? [path15.resolve(explicitDocsDir)] : [],
|
|
3555
|
+
path15.resolve(cwd, "docs"),
|
|
3556
|
+
path15.resolve(cwd)
|
|
3441
3557
|
];
|
|
3442
3558
|
const endAt = Date.now() + timeoutMs;
|
|
3443
3559
|
while (Date.now() < endAt) {
|
|
@@ -3464,12 +3580,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3464
3580
|
return getConfig(cwd);
|
|
3465
3581
|
}
|
|
3466
3582
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3467
|
-
const featuresDir =
|
|
3583
|
+
const featuresDir = path15.join(docsDir, "features");
|
|
3468
3584
|
let max = 0;
|
|
3469
3585
|
const scanDirs = [];
|
|
3470
3586
|
if (projectType === "multi") {
|
|
3471
3587
|
scanDirs.push(
|
|
3472
|
-
...components.map((component) =>
|
|
3588
|
+
...components.map((component) => path15.join(featuresDir, component))
|
|
3473
3589
|
);
|
|
3474
3590
|
} else {
|
|
3475
3591
|
scanDirs.push(featuresDir);
|
|
@@ -3570,16 +3686,16 @@ async function runIdea(name, options) {
|
|
|
3570
3686
|
getDocsLockPath(docsDir),
|
|
3571
3687
|
async () => {
|
|
3572
3688
|
const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
|
|
3573
|
-
const ideasDir =
|
|
3689
|
+
const ideasDir = path15.join(docsDir, "ideas");
|
|
3574
3690
|
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3575
|
-
const ideaPath =
|
|
3691
|
+
const ideaPath = path15.join(ideasDir, ideaFileName);
|
|
3576
3692
|
if (await fs.pathExists(ideaPath)) {
|
|
3577
3693
|
throw createCliError(
|
|
3578
3694
|
"INVALID_ARGUMENT",
|
|
3579
3695
|
tr(lang, "cli", "idea.fileExists", { path: ideaPath })
|
|
3580
3696
|
);
|
|
3581
3697
|
}
|
|
3582
|
-
const templatePath =
|
|
3698
|
+
const templatePath = path15.join(
|
|
3583
3699
|
getTemplatesDir(),
|
|
3584
3700
|
lang,
|
|
3585
3701
|
"common",
|
|
@@ -3617,7 +3733,7 @@ async function runIdea(name, options) {
|
|
|
3617
3733
|
ideaName: name,
|
|
3618
3734
|
component: component || void 0,
|
|
3619
3735
|
ideaPath,
|
|
3620
|
-
ideaPathFromDocs:
|
|
3736
|
+
ideaPathFromDocs: path15.relative(docsDir, ideaPath)
|
|
3621
3737
|
};
|
|
3622
3738
|
},
|
|
3623
3739
|
{ owner: "idea" }
|
|
@@ -3635,7 +3751,7 @@ function applyIdeaTemplate(template, values) {
|
|
|
3635
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);
|
|
3636
3752
|
}
|
|
3637
3753
|
async function getNextIdeaId(docsDir) {
|
|
3638
|
-
const ideasDir =
|
|
3754
|
+
const ideasDir = path15.join(docsDir, "ideas");
|
|
3639
3755
|
let max = 0;
|
|
3640
3756
|
if (await fs.pathExists(ideasDir)) {
|
|
3641
3757
|
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
@@ -4074,7 +4190,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
4074
4190
|
}
|
|
4075
4191
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
4076
4192
|
try {
|
|
4077
|
-
const raw =
|
|
4193
|
+
const raw = fs12.readFileSync(evidencePath, "utf-8");
|
|
4078
4194
|
const parsed = JSON.parse(raw);
|
|
4079
4195
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
4080
4196
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -4090,31 +4206,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
4090
4206
|
const candidates = [];
|
|
4091
4207
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
4092
4208
|
if (explicit && explicit !== "-") {
|
|
4093
|
-
if (
|
|
4209
|
+
if (path15.isAbsolute(explicit)) {
|
|
4094
4210
|
candidates.push(explicit);
|
|
4095
4211
|
} else {
|
|
4096
|
-
candidates.push(
|
|
4097
|
-
candidates.push(
|
|
4212
|
+
candidates.push(path15.resolve(feature.path, explicit));
|
|
4213
|
+
candidates.push(path15.resolve(docsRoot, explicit));
|
|
4098
4214
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
4099
4215
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
4100
4216
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
4101
4217
|
if (withoutDocsPrefix) {
|
|
4102
|
-
candidates.push(
|
|
4218
|
+
candidates.push(path15.resolve(docsRoot, withoutDocsPrefix));
|
|
4103
4219
|
}
|
|
4104
4220
|
}
|
|
4105
4221
|
}
|
|
4106
4222
|
}
|
|
4107
|
-
candidates.push(
|
|
4108
|
-
candidates.push(
|
|
4223
|
+
candidates.push(path15.join(feature.path, "review-trace.json"));
|
|
4224
|
+
candidates.push(path15.join(docsRoot, "review-trace.json"));
|
|
4109
4225
|
const seen = /* @__PURE__ */ new Set();
|
|
4110
4226
|
for (const candidate of candidates) {
|
|
4111
|
-
const abs =
|
|
4227
|
+
const abs = path15.resolve(candidate);
|
|
4112
4228
|
if (seen.has(abs)) continue;
|
|
4113
4229
|
seen.add(abs);
|
|
4114
|
-
if (!
|
|
4230
|
+
if (!fs12.existsSync(abs)) continue;
|
|
4115
4231
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
4116
4232
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
4117
|
-
const rel =
|
|
4233
|
+
const rel = path15.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
4118
4234
|
if (rel && !rel.startsWith("../")) {
|
|
4119
4235
|
return rel;
|
|
4120
4236
|
}
|
|
@@ -4155,8 +4271,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
4155
4271
|
}
|
|
4156
4272
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
4157
4273
|
if (!projectGitCwd) return null;
|
|
4158
|
-
const normalized =
|
|
4159
|
-
const marker = `${
|
|
4274
|
+
const normalized = path15.resolve(projectGitCwd);
|
|
4275
|
+
const marker = `${path15.sep}.worktrees${path15.sep}`;
|
|
4160
4276
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
4161
4277
|
if (markerIndex <= 0) return null;
|
|
4162
4278
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -4166,6 +4282,10 @@ function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
|
4166
4282
|
worktreePath: normalized
|
|
4167
4283
|
};
|
|
4168
4284
|
}
|
|
4285
|
+
function resolveFeatureWorktreeCleanupPaths(feature) {
|
|
4286
|
+
const cleanupCandidate = feature.git.projectInManagedWorktree ? feature.git.projectGitCwd : feature.git.expectedWorktreePath;
|
|
4287
|
+
return resolveManagedWorktreeCleanupPaths(cleanupCandidate);
|
|
4288
|
+
}
|
|
4169
4289
|
function shouldBlockTaskCommitGate(policy, check) {
|
|
4170
4290
|
if (policy !== "strict") return false;
|
|
4171
4291
|
return !check.pass;
|
|
@@ -4199,7 +4319,7 @@ function toTaskKey(rawTitle) {
|
|
|
4199
4319
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
4200
4320
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
4201
4321
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
4202
|
-
|
|
4322
|
+
path15.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
4203
4323
|
);
|
|
4204
4324
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
4205
4325
|
"diff",
|
|
@@ -4274,7 +4394,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
4274
4394
|
return { pass: true };
|
|
4275
4395
|
}
|
|
4276
4396
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
4277
|
-
const relativeDocsDir =
|
|
4397
|
+
const relativeDocsDir = path15.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
4278
4398
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
4279
4399
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
4280
4400
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -5484,35 +5604,36 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
5484
5604
|
step: 15,
|
|
5485
5605
|
name: tr(lang, "steps", "featureDone"),
|
|
5486
5606
|
checklist: {
|
|
5487
|
-
done: (f) =>
|
|
5607
|
+
done: (f) => f.completion.workflowDone
|
|
5488
5608
|
},
|
|
5489
5609
|
current: {
|
|
5490
5610
|
when: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy),
|
|
5491
5611
|
actions: (f) => {
|
|
5492
|
-
|
|
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 [
|
|
5493
5631
|
{
|
|
5494
5632
|
type: "instruction",
|
|
5495
5633
|
category: "feature_done",
|
|
5496
5634
|
message: tr(lang, "messages", "featureDone")
|
|
5497
5635
|
}
|
|
5498
5636
|
];
|
|
5499
|
-
const cleanupPaths = resolveManagedWorktreeCleanupPaths(
|
|
5500
|
-
f.git.projectGitCwd
|
|
5501
|
-
);
|
|
5502
|
-
if (cleanupPaths) {
|
|
5503
|
-
actions.push({
|
|
5504
|
-
type: "command",
|
|
5505
|
-
category: "worktree_cleanup",
|
|
5506
|
-
requiresUserCheck: true,
|
|
5507
|
-
scope: "project",
|
|
5508
|
-
cwd: cleanupPaths.projectRoot,
|
|
5509
|
-
cmd: tr(lang, "messages", "worktreeCleanupCommand", {
|
|
5510
|
-
projectGitCwd: cleanupPaths.projectRoot,
|
|
5511
|
-
worktreePath: cleanupPaths.worktreePath
|
|
5512
|
-
})
|
|
5513
|
-
});
|
|
5514
|
-
}
|
|
5515
|
-
return actions;
|
|
5516
5637
|
}
|
|
5517
5638
|
}
|
|
5518
5639
|
}
|
|
@@ -5890,17 +6011,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
5890
6011
|
}
|
|
5891
6012
|
}
|
|
5892
6013
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
5893
|
-
var WORKTREE_MARKER = `${
|
|
6014
|
+
var WORKTREE_MARKER = `${path15.sep}.worktrees${path15.sep}`;
|
|
5894
6015
|
function resetContextGitCaches() {
|
|
5895
6016
|
GIT_WORKTREE_CACHE.clear();
|
|
5896
6017
|
}
|
|
5897
6018
|
function isManagedWorktreePath(cwd) {
|
|
5898
6019
|
if (!cwd) return false;
|
|
5899
|
-
const normalized =
|
|
6020
|
+
const normalized = path15.resolve(cwd);
|
|
5900
6021
|
return normalized.includes(WORKTREE_MARKER);
|
|
5901
6022
|
}
|
|
5902
6023
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
5903
|
-
const normalized =
|
|
6024
|
+
const normalized = path15.resolve(cwd);
|
|
5904
6025
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
5905
6026
|
if (markerIndex <= 0) return normalized;
|
|
5906
6027
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -5919,7 +6040,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
5919
6040
|
}
|
|
5920
6041
|
function listGitWorktrees(ctx, cwd) {
|
|
5921
6042
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
5922
|
-
const cacheKey =
|
|
6043
|
+
const cacheKey = path15.resolve(topLevel);
|
|
5923
6044
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
5924
6045
|
if (cached) return cached;
|
|
5925
6046
|
try {
|
|
@@ -6407,17 +6528,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6407
6528
|
if (!evidencePath) return [];
|
|
6408
6529
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6409
6530
|
const candidates = /* @__PURE__ */ new Set();
|
|
6410
|
-
if (
|
|
6411
|
-
candidates.add(
|
|
6531
|
+
if (path15.isAbsolute(evidencePath)) {
|
|
6532
|
+
candidates.add(path15.resolve(evidencePath));
|
|
6412
6533
|
} else {
|
|
6413
|
-
candidates.add(
|
|
6414
|
-
candidates.add(
|
|
6415
|
-
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));
|
|
6416
6537
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6417
6538
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6418
6539
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6419
6540
|
if (withoutDocsPrefix) {
|
|
6420
|
-
candidates.add(
|
|
6541
|
+
candidates.add(path15.resolve(context.docsDir, withoutDocsPrefix));
|
|
6421
6542
|
}
|
|
6422
6543
|
}
|
|
6423
6544
|
}
|
|
@@ -6479,13 +6600,13 @@ function parsePrLink(value) {
|
|
|
6479
6600
|
return trimmed;
|
|
6480
6601
|
}
|
|
6481
6602
|
function normalizeGitPath(value) {
|
|
6482
|
-
return value.split(
|
|
6603
|
+
return value.split(path15.sep).join("/");
|
|
6483
6604
|
}
|
|
6484
6605
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6485
|
-
const relativeDocsDir =
|
|
6606
|
+
const relativeDocsDir = path15.relative(projectGitCwd, docsDir);
|
|
6486
6607
|
if (!relativeDocsDir) return [];
|
|
6487
|
-
if (
|
|
6488
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6608
|
+
if (path15.isAbsolute(relativeDocsDir)) return [];
|
|
6609
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path15.sep}`)) {
|
|
6489
6610
|
return [];
|
|
6490
6611
|
}
|
|
6491
6612
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6523,7 +6644,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6523
6644
|
const seen = /* @__PURE__ */ new Set();
|
|
6524
6645
|
const out = [];
|
|
6525
6646
|
for (const name of names) {
|
|
6526
|
-
const candidate =
|
|
6647
|
+
const candidate = path15.resolve(projectRoot, ".worktrees", name);
|
|
6527
6648
|
if (seen.has(candidate)) continue;
|
|
6528
6649
|
seen.add(candidate);
|
|
6529
6650
|
out.push(candidate);
|
|
@@ -6537,7 +6658,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6537
6658
|
slug,
|
|
6538
6659
|
folderName
|
|
6539
6660
|
)) {
|
|
6540
|
-
if (!
|
|
6661
|
+
if (!fs12.existsSync(candidate)) continue;
|
|
6541
6662
|
return candidate;
|
|
6542
6663
|
}
|
|
6543
6664
|
return void 0;
|
|
@@ -6567,7 +6688,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6567
6688
|
slug,
|
|
6568
6689
|
folderName
|
|
6569
6690
|
)) {
|
|
6570
|
-
if (!
|
|
6691
|
+
if (!fs12.existsSync(candidate)) continue;
|
|
6571
6692
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6572
6693
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6573
6694
|
return {
|
|
@@ -6708,10 +6829,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6708
6829
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6709
6830
|
candidates.map((candidate) => {
|
|
6710
6831
|
if (!candidate) return "";
|
|
6711
|
-
if (!
|
|
6712
|
-
const relative =
|
|
6832
|
+
if (!path15.isAbsolute(candidate)) return candidate;
|
|
6833
|
+
const relative = path15.relative(projectGitCwd, candidate);
|
|
6713
6834
|
if (!relative) return "";
|
|
6714
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
6835
|
+
if (relative === ".." || relative.startsWith(`..${path15.sep}`))
|
|
6715
6836
|
return "";
|
|
6716
6837
|
return relative;
|
|
6717
6838
|
}).filter(Boolean)
|
|
@@ -6725,7 +6846,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6725
6846
|
if (cached) return [...cached];
|
|
6726
6847
|
const existing = [];
|
|
6727
6848
|
for (const candidate of normalizedCandidates) {
|
|
6728
|
-
if (await ctx.fs.pathExists(
|
|
6849
|
+
if (await ctx.fs.pathExists(path15.join(projectGitCwd, candidate))) {
|
|
6729
6850
|
existing.push(candidate);
|
|
6730
6851
|
}
|
|
6731
6852
|
}
|
|
@@ -6813,16 +6934,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6813
6934
|
const lang = options.lang;
|
|
6814
6935
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
6815
6936
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
6816
|
-
const folderName =
|
|
6937
|
+
const folderName = path15.basename(featurePath);
|
|
6817
6938
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6818
6939
|
const id = match?.[1];
|
|
6819
6940
|
const slug = match?.[2] || folderName;
|
|
6820
|
-
const specPath =
|
|
6821
|
-
const planPath =
|
|
6822
|
-
const tasksPath =
|
|
6823
|
-
const decisionsPath =
|
|
6824
|
-
const issueDocPath =
|
|
6825
|
-
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");
|
|
6826
6947
|
let specStatus;
|
|
6827
6948
|
let issueNumber;
|
|
6828
6949
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -7084,7 +7205,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7084
7205
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
7085
7206
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
7086
7207
|
}
|
|
7087
|
-
const relativeFeaturePathFromDocs =
|
|
7208
|
+
const relativeFeaturePathFromDocs = path15.relative(
|
|
7088
7209
|
context.docsDir,
|
|
7089
7210
|
featurePath
|
|
7090
7211
|
);
|
|
@@ -7261,7 +7382,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7261
7382
|
}
|
|
7262
7383
|
const tasksDocApproved = !tasksDocStatusFieldExists || tasksDocStatus === "Approved";
|
|
7263
7384
|
const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist }) && tasksDocApproved;
|
|
7264
|
-
const
|
|
7385
|
+
const workflowBaseDone = implementationDone && !docsHasCommitRequiredChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({
|
|
7265
7386
|
docs: { prFieldExists, prStatusFieldExists }
|
|
7266
7387
|
}) && !!prLink) && (!workflowPolicy.requireMerge || prStatus === "Approved") && isPrePrReviewSatisfied2(
|
|
7267
7388
|
{
|
|
@@ -7279,6 +7400,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7279
7400
|
},
|
|
7280
7401
|
prePrReviewPolicy
|
|
7281
7402
|
);
|
|
7403
|
+
const cleanupPending = workflowBaseDone && (projectInManagedWorktree || !!expectedWorktreePath);
|
|
7404
|
+
const workflowDone = workflowBaseDone && !cleanupPending;
|
|
7282
7405
|
if (implementationDone && !workflowDone) {
|
|
7283
7406
|
if (specStatus !== "Approved") {
|
|
7284
7407
|
warnings.push(tr(lang, "warnings", "workflowSpecNotApproved"));
|
|
@@ -7340,7 +7463,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7340
7463
|
path: featurePath,
|
|
7341
7464
|
completion: {
|
|
7342
7465
|
implementationDone,
|
|
7343
|
-
workflowDone
|
|
7466
|
+
workflowDone,
|
|
7467
|
+
cleanupPending
|
|
7344
7468
|
},
|
|
7345
7469
|
issueNumber,
|
|
7346
7470
|
specStatus,
|
|
@@ -7447,7 +7571,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7447
7571
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7448
7572
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7449
7573
|
return dirs.filter(
|
|
7450
|
-
(value) =>
|
|
7574
|
+
(value) => path15.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7451
7575
|
);
|
|
7452
7576
|
}
|
|
7453
7577
|
function normalizeRelPath(value) {
|
|
@@ -7588,7 +7712,7 @@ async function scanFeatures(ctx) {
|
|
|
7588
7712
|
if (config.projectType === "single") {
|
|
7589
7713
|
const featureDirs = await listFeatureDirs(
|
|
7590
7714
|
ctx,
|
|
7591
|
-
|
|
7715
|
+
path15.join(config.docsDir, "features")
|
|
7592
7716
|
);
|
|
7593
7717
|
componentFeatureDirs.set("single", featureDirs);
|
|
7594
7718
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7600,14 +7724,14 @@ async function scanFeatures(ctx) {
|
|
|
7600
7724
|
for (const component of components) {
|
|
7601
7725
|
const componentDirs = await listFeatureDirs(
|
|
7602
7726
|
ctx,
|
|
7603
|
-
|
|
7727
|
+
path15.join(config.docsDir, "features", component)
|
|
7604
7728
|
);
|
|
7605
7729
|
componentFeatureDirs.set(component, componentDirs);
|
|
7606
7730
|
allFeatureDirs.push(...componentDirs);
|
|
7607
7731
|
}
|
|
7608
7732
|
}
|
|
7609
7733
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7610
|
-
(dir) => normalizeRelPath(
|
|
7734
|
+
(dir) => normalizeRelPath(path15.relative(config.docsDir, dir))
|
|
7611
7735
|
);
|
|
7612
7736
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7613
7737
|
ctx,
|
|
@@ -7624,7 +7748,7 @@ async function scanFeatures(ctx) {
|
|
|
7624
7748
|
const parsed = await Promise.all(
|
|
7625
7749
|
target.dirs.map(async (dir) => {
|
|
7626
7750
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7627
|
-
|
|
7751
|
+
path15.relative(config.docsDir, dir)
|
|
7628
7752
|
);
|
|
7629
7753
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7630
7754
|
return parseFeature(
|
|
@@ -7694,13 +7818,13 @@ async function runStatus(options) {
|
|
|
7694
7818
|
);
|
|
7695
7819
|
}
|
|
7696
7820
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7697
|
-
const featuresDir =
|
|
7821
|
+
const featuresDir = path15.join(docsDir, "features");
|
|
7698
7822
|
const scan = await scanFeatures(ctx);
|
|
7699
7823
|
const features = [];
|
|
7700
7824
|
const idMap = /* @__PURE__ */ new Map();
|
|
7701
7825
|
for (const f of scan.features) {
|
|
7702
7826
|
const id = f.id || "UNKNOWN";
|
|
7703
|
-
const relPath =
|
|
7827
|
+
const relPath = path15.relative(docsDir, f.path);
|
|
7704
7828
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7705
7829
|
idMap.get(id).push(relPath);
|
|
7706
7830
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7791,7 +7915,7 @@ async function runStatus(options) {
|
|
|
7791
7915
|
}
|
|
7792
7916
|
console.log();
|
|
7793
7917
|
if (options.write) {
|
|
7794
|
-
const outputPath =
|
|
7918
|
+
const outputPath = path15.join(featuresDir, "status.md");
|
|
7795
7919
|
const date = getLocalDateString();
|
|
7796
7920
|
const content = [
|
|
7797
7921
|
"# Feature Status",
|
|
@@ -7817,7 +7941,7 @@ function escapeRegExp4(value) {
|
|
|
7817
7941
|
}
|
|
7818
7942
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7819
7943
|
try {
|
|
7820
|
-
const specPath =
|
|
7944
|
+
const specPath = path15.join(featureDir, "spec.md");
|
|
7821
7945
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7822
7946
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7823
7947
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
@@ -7901,8 +8025,8 @@ async function runUpdate(options) {
|
|
|
7901
8025
|
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
7902
8026
|
}
|
|
7903
8027
|
if (agentsMode === "all") {
|
|
7904
|
-
const commonAgentsBase =
|
|
7905
|
-
const targetAgentsBase =
|
|
8028
|
+
const commonAgentsBase = path15.join(templatesDir, lang, "common", "agents");
|
|
8029
|
+
const targetAgentsBase = path15.join(docsDir, "agents");
|
|
7906
8030
|
const commonAgents = commonAgentsBase;
|
|
7907
8031
|
const targetAgents = targetAgentsBase;
|
|
7908
8032
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -8000,21 +8124,21 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
8000
8124
|
const targets = /* @__PURE__ */ new Set();
|
|
8001
8125
|
const docsRepo = config.docsRepo ?? "embedded";
|
|
8002
8126
|
if (docsRepo === "embedded") {
|
|
8003
|
-
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) ||
|
|
8004
|
-
targets.add(
|
|
8127
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path15.resolve(config.docsDir, "..");
|
|
8128
|
+
targets.add(path15.join(repoRoot, "AGENTS.md"));
|
|
8005
8129
|
return [...targets];
|
|
8006
8130
|
}
|
|
8007
|
-
targets.add(
|
|
8131
|
+
targets.add(path15.join(config.docsDir, "AGENTS.md"));
|
|
8008
8132
|
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
8009
8133
|
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
8010
8134
|
for (const rawRoot of rawRoots) {
|
|
8011
8135
|
const value = String(rawRoot || "").trim();
|
|
8012
8136
|
if (!value) continue;
|
|
8013
|
-
const resolved =
|
|
8137
|
+
const resolved = path15.resolve(baseDir, value);
|
|
8014
8138
|
if (!await fs.pathExists(resolved)) continue;
|
|
8015
8139
|
const stat = await fs.stat(resolved);
|
|
8016
8140
|
if (!stat.isDirectory()) continue;
|
|
8017
|
-
targets.add(
|
|
8141
|
+
targets.add(path15.join(resolved, "AGENTS.md"));
|
|
8018
8142
|
}
|
|
8019
8143
|
return [...targets];
|
|
8020
8144
|
}
|
|
@@ -8054,7 +8178,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
8054
8178
|
return [...deduped];
|
|
8055
8179
|
}
|
|
8056
8180
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
8057
|
-
const configPath =
|
|
8181
|
+
const configPath = path15.join(docsDir, ".lee-spec-kit.json");
|
|
8058
8182
|
if (!await fs.pathExists(configPath)) {
|
|
8059
8183
|
return { changed: false, changedPaths: [] };
|
|
8060
8184
|
}
|
|
@@ -8177,8 +8301,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
8177
8301
|
const files = await fs.readdir(sourceDir);
|
|
8178
8302
|
let updatedCount = 0;
|
|
8179
8303
|
for (const file of files) {
|
|
8180
|
-
const sourcePath =
|
|
8181
|
-
const targetPath =
|
|
8304
|
+
const sourcePath = path15.join(sourceDir, file);
|
|
8305
|
+
const targetPath = path15.join(targetDir, file);
|
|
8182
8306
|
const stat = await fs.stat(sourcePath);
|
|
8183
8307
|
if (stat.isFile()) {
|
|
8184
8308
|
if (protectedFiles.has(file)) {
|
|
@@ -8261,7 +8385,7 @@ function extractPorcelainPaths(line) {
|
|
|
8261
8385
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
8262
8386
|
const top = getGitTopLevel2(docsDir);
|
|
8263
8387
|
if (!top) return null;
|
|
8264
|
-
const rel =
|
|
8388
|
+
const rel = path15.relative(top, docsDir) || ".";
|
|
8265
8389
|
try {
|
|
8266
8390
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
8267
8391
|
cwd: top,
|
|
@@ -8273,7 +8397,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
8273
8397
|
}
|
|
8274
8398
|
const ignoredRelPaths = new Set(
|
|
8275
8399
|
ignoredAbsPaths.map(
|
|
8276
|
-
(absPath) => normalizeGitPath2(
|
|
8400
|
+
(absPath) => normalizeGitPath2(path15.relative(top, absPath) || ".")
|
|
8277
8401
|
)
|
|
8278
8402
|
);
|
|
8279
8403
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -8331,7 +8455,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
8331
8455
|
}
|
|
8332
8456
|
async function runConfig(options) {
|
|
8333
8457
|
const cwd = process.cwd();
|
|
8334
|
-
const targetCwd = options.dir ?
|
|
8458
|
+
const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
|
|
8335
8459
|
const config = await getConfig(targetCwd);
|
|
8336
8460
|
if (!config) {
|
|
8337
8461
|
throw createCliError(
|
|
@@ -8339,7 +8463,7 @@ async function runConfig(options) {
|
|
|
8339
8463
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
8340
8464
|
);
|
|
8341
8465
|
}
|
|
8342
|
-
const configPath =
|
|
8466
|
+
const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
8343
8467
|
if (!options.projectRoot) {
|
|
8344
8468
|
console.log();
|
|
8345
8469
|
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -8445,47 +8569,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8445
8569
|
{
|
|
8446
8570
|
id: "agents",
|
|
8447
8571
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8448
|
-
relativePath: (_, lang) =>
|
|
8572
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "agents.md")
|
|
8449
8573
|
},
|
|
8450
8574
|
{
|
|
8451
8575
|
id: "git-workflow",
|
|
8452
8576
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8453
|
-
relativePath: (_, lang) =>
|
|
8577
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "git-workflow.md")
|
|
8454
8578
|
},
|
|
8455
8579
|
{
|
|
8456
8580
|
id: "issue-doc",
|
|
8457
8581
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8458
|
-
relativePath: (_, lang) =>
|
|
8582
|
+
relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8459
8583
|
},
|
|
8460
8584
|
{
|
|
8461
8585
|
id: "pr-doc",
|
|
8462
8586
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8463
|
-
relativePath: (_, lang) =>
|
|
8587
|
+
relativePath: (_, lang) => path15.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8464
8588
|
},
|
|
8465
8589
|
{
|
|
8466
8590
|
id: "create-feature",
|
|
8467
8591
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8468
|
-
relativePath: (_, lang) =>
|
|
8592
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8469
8593
|
},
|
|
8470
8594
|
{
|
|
8471
8595
|
id: "execute-task",
|
|
8472
8596
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8473
|
-
relativePath: (_, lang) =>
|
|
8597
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8474
8598
|
},
|
|
8475
8599
|
{
|
|
8476
8600
|
id: "create-issue",
|
|
8477
8601
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8478
|
-
relativePath: (_, lang) =>
|
|
8602
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8479
8603
|
},
|
|
8480
8604
|
{
|
|
8481
8605
|
id: "create-pr",
|
|
8482
8606
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8483
|
-
relativePath: (_, lang) =>
|
|
8607
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8484
8608
|
},
|
|
8485
8609
|
{
|
|
8486
8610
|
id: "split-feature",
|
|
8487
8611
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8488
|
-
relativePath: (_, lang) =>
|
|
8612
|
+
relativePath: (_, lang) => path15.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8489
8613
|
}
|
|
8490
8614
|
];
|
|
8491
8615
|
var DOC_FOLLOWUPS = {
|
|
@@ -8582,7 +8706,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8582
8706
|
id: doc.id,
|
|
8583
8707
|
title: doc.title[lang],
|
|
8584
8708
|
relativePath,
|
|
8585
|
-
absolutePath:
|
|
8709
|
+
absolutePath: path15.join(templatesDir, relativePath)
|
|
8586
8710
|
};
|
|
8587
8711
|
});
|
|
8588
8712
|
}
|
|
@@ -9449,6 +9573,9 @@ function buildRequiredDocHints(actionOptions) {
|
|
|
9449
9573
|
}
|
|
9450
9574
|
function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
|
|
9451
9575
|
if (f.completion.implementationDone && !f.completion.workflowDone) {
|
|
9576
|
+
if (f.completion.cleanupPending) {
|
|
9577
|
+
return tr(lang, "cli", "context.list.cleanupPending");
|
|
9578
|
+
}
|
|
9452
9579
|
if (f.git.docsHasCommitRequiredChanges) {
|
|
9453
9580
|
return tr(lang, "cli", "context.list.docsCommitNeeded");
|
|
9454
9581
|
}
|
|
@@ -9627,7 +9754,7 @@ function getApprovalSessionId() {
|
|
|
9627
9754
|
function getApprovalTicketPaths(config) {
|
|
9628
9755
|
return {
|
|
9629
9756
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
9630
|
-
legacyPath:
|
|
9757
|
+
legacyPath: path15.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
9631
9758
|
};
|
|
9632
9759
|
}
|
|
9633
9760
|
async function loadApprovalTicketStore(storePath) {
|
|
@@ -9641,7 +9768,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9641
9768
|
}
|
|
9642
9769
|
}
|
|
9643
9770
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9644
|
-
await fs.ensureDir(
|
|
9771
|
+
await fs.ensureDir(path15.dirname(storePath));
|
|
9645
9772
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9646
9773
|
}
|
|
9647
9774
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -10726,7 +10853,7 @@ async function runContext(featureName, options) {
|
|
|
10726
10853
|
if (f.issueNumber) {
|
|
10727
10854
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10728
10855
|
}
|
|
10729
|
-
console.log(` \u2022 Path: ${
|
|
10856
|
+
console.log(` \u2022 Path: ${path15.relative(cwd, f.path)}`);
|
|
10730
10857
|
if (f.git.projectBranch) {
|
|
10731
10858
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10732
10859
|
}
|
|
@@ -10878,7 +11005,7 @@ function extractTitleAfterId(line, id) {
|
|
|
10878
11005
|
return cleaned ? cleaned : void 0;
|
|
10879
11006
|
}
|
|
10880
11007
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
10881
|
-
const prdDir =
|
|
11008
|
+
const prdDir = path15.join(docsDir, "prd");
|
|
10882
11009
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
10883
11010
|
extensions: [".md"],
|
|
10884
11011
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -10886,7 +11013,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10886
11013
|
const definitions = /* @__PURE__ */ new Map();
|
|
10887
11014
|
const duplicates = [];
|
|
10888
11015
|
for (const filePath of files) {
|
|
10889
|
-
if (
|
|
11016
|
+
if (path15.basename(filePath).toLowerCase() === "readme.md") {
|
|
10890
11017
|
continue;
|
|
10891
11018
|
}
|
|
10892
11019
|
let content = "";
|
|
@@ -10895,7 +11022,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10895
11022
|
} catch {
|
|
10896
11023
|
continue;
|
|
10897
11024
|
}
|
|
10898
|
-
const relFile = normalizeRelPath2(
|
|
11025
|
+
const relFile = normalizeRelPath2(path15.relative(docsDir, filePath));
|
|
10899
11026
|
const lines = content.split(/\r?\n/);
|
|
10900
11027
|
let inCodeBlock = false;
|
|
10901
11028
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -10970,7 +11097,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
10970
11097
|
]);
|
|
10971
11098
|
function formatPath(cwd, p) {
|
|
10972
11099
|
if (!p) return "";
|
|
10973
|
-
return
|
|
11100
|
+
return path15.isAbsolute(p) ? path15.relative(cwd, p) : p;
|
|
10974
11101
|
}
|
|
10975
11102
|
function detectPlaceholders(content) {
|
|
10976
11103
|
const patterns = [
|
|
@@ -11129,7 +11256,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11129
11256
|
const placeholderContext = {
|
|
11130
11257
|
projectName: config.projectName,
|
|
11131
11258
|
featureName: f.slug,
|
|
11132
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11259
|
+
featurePath: f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path),
|
|
11133
11260
|
repoType: f.type,
|
|
11134
11261
|
featureNumber
|
|
11135
11262
|
};
|
|
@@ -11139,7 +11266,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11139
11266
|
"tasks.md"
|
|
11140
11267
|
];
|
|
11141
11268
|
for (const file of files) {
|
|
11142
|
-
const fullPath =
|
|
11269
|
+
const fullPath = path15.join(f.path, file);
|
|
11143
11270
|
if (!await fs.pathExists(fullPath)) continue;
|
|
11144
11271
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
11145
11272
|
let next = original;
|
|
@@ -11192,7 +11319,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11192
11319
|
const issues = [];
|
|
11193
11320
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
11194
11321
|
for (const dir of requiredDirs) {
|
|
11195
|
-
const p =
|
|
11322
|
+
const p = path15.join(config.docsDir, dir);
|
|
11196
11323
|
if (!await fs.pathExists(p)) {
|
|
11197
11324
|
issues.push({
|
|
11198
11325
|
level: "error",
|
|
@@ -11204,7 +11331,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11204
11331
|
});
|
|
11205
11332
|
}
|
|
11206
11333
|
}
|
|
11207
|
-
const configPath =
|
|
11334
|
+
const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
11208
11335
|
if (!await fs.pathExists(configPath)) {
|
|
11209
11336
|
issues.push({
|
|
11210
11337
|
level: "warn",
|
|
@@ -11232,7 +11359,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11232
11359
|
}
|
|
11233
11360
|
const idMap = /* @__PURE__ */ new Map();
|
|
11234
11361
|
for (const f of features) {
|
|
11235
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11362
|
+
const rel = f.docs.featurePathFromDocs || path15.relative(config.docsDir, f.path);
|
|
11236
11363
|
const id = f.id || "UNKNOWN";
|
|
11237
11364
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
11238
11365
|
idMap.get(id).push(rel);
|
|
@@ -11240,7 +11367,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11240
11367
|
if (!isInitialTemplateState) {
|
|
11241
11368
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
11242
11369
|
for (const file of featureDocs) {
|
|
11243
|
-
const p =
|
|
11370
|
+
const p = path15.join(f.path, file);
|
|
11244
11371
|
if (!await fs.pathExists(p)) continue;
|
|
11245
11372
|
const content = await fs.readFile(p, "utf-8");
|
|
11246
11373
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11255,7 +11382,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11255
11382
|
});
|
|
11256
11383
|
}
|
|
11257
11384
|
if (decisionsPlaceholderMode !== "off") {
|
|
11258
|
-
const decisionsPath =
|
|
11385
|
+
const decisionsPath = path15.join(f.path, "decisions.md");
|
|
11259
11386
|
if (await fs.pathExists(decisionsPath)) {
|
|
11260
11387
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
11261
11388
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11284,7 +11411,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11284
11411
|
level: "warn",
|
|
11285
11412
|
code: "spec_status_unset",
|
|
11286
11413
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
11287
|
-
path: formatPath(cwd,
|
|
11414
|
+
path: formatPath(cwd, path15.join(f.path, "spec.md"))
|
|
11288
11415
|
});
|
|
11289
11416
|
}
|
|
11290
11417
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -11292,7 +11419,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11292
11419
|
level: "warn",
|
|
11293
11420
|
code: "plan_status_unset",
|
|
11294
11421
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
11295
|
-
path: formatPath(cwd,
|
|
11422
|
+
path: formatPath(cwd, path15.join(f.path, "plan.md"))
|
|
11296
11423
|
});
|
|
11297
11424
|
}
|
|
11298
11425
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -11300,11 +11427,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11300
11427
|
level: "warn",
|
|
11301
11428
|
code: "tasks_empty",
|
|
11302
11429
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
11303
|
-
path: formatPath(cwd,
|
|
11430
|
+
path: formatPath(cwd, path15.join(f.path, "tasks.md"))
|
|
11304
11431
|
});
|
|
11305
11432
|
}
|
|
11306
11433
|
if (f.docs.tasksExists) {
|
|
11307
|
-
const tasksPath =
|
|
11434
|
+
const tasksPath = path15.join(f.path, "tasks.md");
|
|
11308
11435
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
11309
11436
|
const unknownPrdTags = [...new Set(
|
|
11310
11437
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -11326,7 +11453,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11326
11453
|
level: "warn",
|
|
11327
11454
|
code: "tasks_doc_status_missing",
|
|
11328
11455
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
11329
|
-
path: formatPath(cwd,
|
|
11456
|
+
path: formatPath(cwd, path15.join(f.path, "tasks.md"))
|
|
11330
11457
|
});
|
|
11331
11458
|
}
|
|
11332
11459
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -11334,7 +11461,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11334
11461
|
level: "warn",
|
|
11335
11462
|
code: "tasks_doc_status_unset",
|
|
11336
11463
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
11337
|
-
path: formatPath(cwd,
|
|
11464
|
+
path: formatPath(cwd, path15.join(f.path, "tasks.md"))
|
|
11338
11465
|
});
|
|
11339
11466
|
}
|
|
11340
11467
|
}
|
|
@@ -11358,7 +11485,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11358
11485
|
level: "warn",
|
|
11359
11486
|
code: "missing_feature_id",
|
|
11360
11487
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
11361
|
-
path: formatPath(cwd,
|
|
11488
|
+
path: formatPath(cwd, path15.join(config.docsDir, p))
|
|
11362
11489
|
});
|
|
11363
11490
|
}
|
|
11364
11491
|
return issues;
|
|
@@ -11489,7 +11616,7 @@ function doctorCommand(program2) {
|
|
|
11489
11616
|
}
|
|
11490
11617
|
console.log();
|
|
11491
11618
|
console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
|
|
11492
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
11619
|
+
console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, docsDir)}`));
|
|
11493
11620
|
console.log(chalk9.gray(`- Type: ${projectType}`));
|
|
11494
11621
|
console.log(chalk9.gray(`- Lang: ${lang}`));
|
|
11495
11622
|
console.log();
|
|
@@ -11670,7 +11797,7 @@ async function runView(featureName, options) {
|
|
|
11670
11797
|
}
|
|
11671
11798
|
console.log();
|
|
11672
11799
|
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11673
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
11800
|
+
console.log(chalk9.gray(`- Docs: ${path15.relative(cwd, config.docsDir)}`));
|
|
11674
11801
|
console.log(
|
|
11675
11802
|
chalk9.gray(
|
|
11676
11803
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -11755,13 +11882,13 @@ function normalizeRunId(raw) {
|
|
|
11755
11882
|
return value;
|
|
11756
11883
|
}
|
|
11757
11884
|
function getFlowRunBaseDir(cwd) {
|
|
11758
|
-
return
|
|
11885
|
+
return path15.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11759
11886
|
}
|
|
11760
11887
|
function getFlowRunPath(cwd, runId) {
|
|
11761
|
-
return
|
|
11888
|
+
return path15.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11762
11889
|
}
|
|
11763
11890
|
function getFlowRunLockPath(cwd, runId) {
|
|
11764
|
-
return
|
|
11891
|
+
return path15.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11765
11892
|
}
|
|
11766
11893
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11767
11894
|
const normalized = normalizeRunId(runId);
|
|
@@ -11787,7 +11914,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11787
11914
|
}
|
|
11788
11915
|
async function writeFlowRunRecord(cwd, record) {
|
|
11789
11916
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
11790
|
-
await fs.ensureDir(
|
|
11917
|
+
await fs.ensureDir(path15.dirname(filePath));
|
|
11791
11918
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
11792
11919
|
}
|
|
11793
11920
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -13192,27 +13319,27 @@ function tg(lang, key, vars = {}) {
|
|
|
13192
13319
|
function detectGithubCliLangSync(cwd) {
|
|
13193
13320
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
13194
13321
|
const startDirs = [
|
|
13195
|
-
explicitDocsDir ?
|
|
13196
|
-
|
|
13322
|
+
explicitDocsDir ? path15.resolve(explicitDocsDir) : "",
|
|
13323
|
+
path15.resolve(cwd)
|
|
13197
13324
|
].filter(Boolean);
|
|
13198
13325
|
const scanOrder = [];
|
|
13199
13326
|
const seen = /* @__PURE__ */ new Set();
|
|
13200
13327
|
for (const start of startDirs) {
|
|
13201
13328
|
let current = start;
|
|
13202
13329
|
while (true) {
|
|
13203
|
-
const abs =
|
|
13330
|
+
const abs = path15.resolve(current);
|
|
13204
13331
|
if (!seen.has(abs)) {
|
|
13205
13332
|
scanOrder.push(abs);
|
|
13206
13333
|
seen.add(abs);
|
|
13207
13334
|
}
|
|
13208
|
-
const parent =
|
|
13335
|
+
const parent = path15.dirname(abs);
|
|
13209
13336
|
if (parent === abs) break;
|
|
13210
13337
|
current = parent;
|
|
13211
13338
|
}
|
|
13212
13339
|
}
|
|
13213
13340
|
for (const base of scanOrder) {
|
|
13214
|
-
for (const docsDir of [
|
|
13215
|
-
const configPath =
|
|
13341
|
+
for (const docsDir of [path15.join(base, "docs"), base]) {
|
|
13342
|
+
const configPath = path15.join(docsDir, ".lee-spec-kit.json");
|
|
13216
13343
|
if (fs.existsSync(configPath)) {
|
|
13217
13344
|
try {
|
|
13218
13345
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -13221,11 +13348,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
13221
13348
|
} catch {
|
|
13222
13349
|
}
|
|
13223
13350
|
}
|
|
13224
|
-
const agentsPath =
|
|
13225
|
-
const featuresPath =
|
|
13351
|
+
const agentsPath = path15.join(docsDir, "agents");
|
|
13352
|
+
const featuresPath = path15.join(docsDir, "features");
|
|
13226
13353
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
13227
13354
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
13228
|
-
const file =
|
|
13355
|
+
const file = path15.join(agentsPath, probe);
|
|
13229
13356
|
if (!fs.existsSync(file)) continue;
|
|
13230
13357
|
try {
|
|
13231
13358
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -13330,7 +13457,7 @@ async function prepareGithubBody(params) {
|
|
|
13330
13457
|
};
|
|
13331
13458
|
}
|
|
13332
13459
|
}
|
|
13333
|
-
await fs.ensureDir(
|
|
13460
|
+
await fs.ensureDir(path15.dirname(defaultBodyFile));
|
|
13334
13461
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
13335
13462
|
return {
|
|
13336
13463
|
body: generatedBody,
|
|
@@ -13400,7 +13527,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
13400
13527
|
}
|
|
13401
13528
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
13402
13529
|
const missing = relativePaths.filter(
|
|
13403
|
-
(relativePath) => !fs.existsSync(
|
|
13530
|
+
(relativePath) => !fs.existsSync(path15.join(docsDir, relativePath))
|
|
13404
13531
|
);
|
|
13405
13532
|
if (missing.length > 0) {
|
|
13406
13533
|
throw createCliError(
|
|
@@ -13410,18 +13537,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
13410
13537
|
}
|
|
13411
13538
|
}
|
|
13412
13539
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
13413
|
-
const key = `${
|
|
13540
|
+
const key = `${path15.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
13414
13541
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
13415
13542
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
13416
13543
|
}
|
|
13417
13544
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
13418
|
-
const selected = raw?.trim() ||
|
|
13545
|
+
const selected = raw?.trim() || path15.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
13419
13546
|
assertValid(
|
|
13420
13547
|
validatePathWithLang(selected, lang),
|
|
13421
13548
|
`github.${kind}.bodyFile`,
|
|
13422
13549
|
lang
|
|
13423
13550
|
);
|
|
13424
|
-
return
|
|
13551
|
+
return path15.resolve(selected);
|
|
13425
13552
|
}
|
|
13426
13553
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
13427
13554
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -14273,6 +14400,85 @@ function ensureIssueClosingLine(body, issueNumber) {
|
|
|
14273
14400
|
return `${trimmed}${separator}Closes #${issueNumber}
|
|
14274
14401
|
`;
|
|
14275
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
|
+
}
|
|
14276
14482
|
function getRequiredIssueSections(lang) {
|
|
14277
14483
|
return getGithubDraftRequiredSections("issue", lang);
|
|
14278
14484
|
}
|
|
@@ -14476,7 +14682,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
14476
14682
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
14477
14683
|
const uniqueRelativePaths = [
|
|
14478
14684
|
...new Set(
|
|
14479
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14685
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path15.relative(cwd, absPath) || absPath)
|
|
14480
14686
|
)
|
|
14481
14687
|
];
|
|
14482
14688
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14672,15 +14878,15 @@ function githubCommand(program2) {
|
|
|
14672
14878
|
config.lang
|
|
14673
14879
|
);
|
|
14674
14880
|
const specContent = await fs.readFile(
|
|
14675
|
-
|
|
14881
|
+
path15.join(config.docsDir, paths.specPath),
|
|
14676
14882
|
"utf-8"
|
|
14677
14883
|
);
|
|
14678
14884
|
const planContent = await fs.readFile(
|
|
14679
|
-
|
|
14885
|
+
path15.join(config.docsDir, paths.planPath),
|
|
14680
14886
|
"utf-8"
|
|
14681
14887
|
);
|
|
14682
14888
|
const tasksContent = await fs.readFile(
|
|
14683
|
-
|
|
14889
|
+
path15.join(config.docsDir, paths.tasksPath),
|
|
14684
14890
|
"utf-8"
|
|
14685
14891
|
);
|
|
14686
14892
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14723,7 +14929,7 @@ function githubCommand(program2) {
|
|
|
14723
14929
|
create: options.create,
|
|
14724
14930
|
explicitBodyFile,
|
|
14725
14931
|
defaultBodyFile,
|
|
14726
|
-
workflowDraftPath:
|
|
14932
|
+
workflowDraftPath: path15.join(config.docsDir, paths.issuePath),
|
|
14727
14933
|
generatedBody,
|
|
14728
14934
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
14729
14935
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -14739,7 +14945,7 @@ function githubCommand(program2) {
|
|
|
14739
14945
|
`${feature.type}-issue-sanitized`,
|
|
14740
14946
|
config.lang
|
|
14741
14947
|
);
|
|
14742
|
-
await fs.ensureDir(
|
|
14948
|
+
await fs.ensureDir(path15.dirname(sanitizedBodyFile));
|
|
14743
14949
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14744
14950
|
bodyFile = sanitizedBodyFile;
|
|
14745
14951
|
}
|
|
@@ -14788,12 +14994,12 @@ function githubCommand(program2) {
|
|
|
14788
14994
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
14789
14995
|
if (syncedIssueNumber) {
|
|
14790
14996
|
const synced = syncTasksIssueMetadata(
|
|
14791
|
-
|
|
14997
|
+
path15.join(config.docsDir, paths.tasksPath),
|
|
14792
14998
|
syncedIssueNumber,
|
|
14793
14999
|
config.lang
|
|
14794
15000
|
);
|
|
14795
15001
|
const draftSynced = syncIssueDraftMetadata(
|
|
14796
|
-
|
|
15002
|
+
path15.join(config.docsDir, paths.issuePath),
|
|
14797
15003
|
syncedIssueNumber
|
|
14798
15004
|
);
|
|
14799
15005
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -14904,13 +15110,13 @@ function githubCommand(program2) {
|
|
|
14904
15110
|
config.lang
|
|
14905
15111
|
);
|
|
14906
15112
|
const specContent = await fs.readFile(
|
|
14907
|
-
|
|
15113
|
+
path15.join(config.docsDir, paths.specPath),
|
|
14908
15114
|
"utf-8"
|
|
14909
15115
|
);
|
|
14910
|
-
const planPath =
|
|
15116
|
+
const planPath = path15.join(config.docsDir, paths.planPath);
|
|
14911
15117
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
14912
15118
|
const tasksContent = await fs.readFile(
|
|
14913
|
-
|
|
15119
|
+
path15.join(config.docsDir, paths.tasksPath),
|
|
14914
15120
|
"utf-8"
|
|
14915
15121
|
);
|
|
14916
15122
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14958,7 +15164,7 @@ function githubCommand(program2) {
|
|
|
14958
15164
|
create: options.create,
|
|
14959
15165
|
explicitBodyFile,
|
|
14960
15166
|
defaultBodyFile,
|
|
14961
|
-
workflowDraftPath:
|
|
15167
|
+
workflowDraftPath: path15.join(config.docsDir, paths.prPath),
|
|
14962
15168
|
generatedBody,
|
|
14963
15169
|
requiredSections: getRequiredPrSections(config.lang),
|
|
14964
15170
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -14976,7 +15182,7 @@ function githubCommand(program2) {
|
|
|
14976
15182
|
`${feature.type}-pr-sanitized`,
|
|
14977
15183
|
config.lang
|
|
14978
15184
|
);
|
|
14979
|
-
await fs.ensureDir(
|
|
15185
|
+
await fs.ensureDir(path15.dirname(sanitizedBodyFile));
|
|
14980
15186
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14981
15187
|
bodyFile = sanitizedBodyFile;
|
|
14982
15188
|
}
|
|
@@ -14994,9 +15200,23 @@ function githubCommand(program2) {
|
|
|
14994
15200
|
let syncChanged = false;
|
|
14995
15201
|
const pushDocsSync = shouldPushDocsSync(config);
|
|
14996
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
|
+
);
|
|
14997
15217
|
const normalizedBody = ensureIssueClosingLine(
|
|
14998
15218
|
body,
|
|
14999
|
-
|
|
15219
|
+
closingIssueNumber
|
|
15000
15220
|
);
|
|
15001
15221
|
if (normalizedBody !== body) {
|
|
15002
15222
|
body = normalizedBody;
|
|
@@ -15004,15 +15224,11 @@ function githubCommand(program2) {
|
|
|
15004
15224
|
if (preparedBody.source === "generated") {
|
|
15005
15225
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
15006
15226
|
} else {
|
|
15007
|
-
await fs.ensureDir(
|
|
15227
|
+
await fs.ensureDir(path15.dirname(fallbackBodyFile));
|
|
15008
15228
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
15009
15229
|
bodyFile = fallbackBodyFile;
|
|
15010
15230
|
}
|
|
15011
15231
|
}
|
|
15012
|
-
const projectGitCwd = resolveGithubProjectCwd(
|
|
15013
|
-
config,
|
|
15014
|
-
feature
|
|
15015
|
-
);
|
|
15016
15232
|
ensureNoTodoPlaceholders(
|
|
15017
15233
|
body,
|
|
15018
15234
|
tg(config.lang, "kindPr"),
|
|
@@ -15065,13 +15281,13 @@ function githubCommand(program2) {
|
|
|
15065
15281
|
}
|
|
15066
15282
|
if (prUrl && options.syncTasks !== false) {
|
|
15067
15283
|
const syncedTasks = syncTasksPrMetadata(
|
|
15068
|
-
|
|
15284
|
+
path15.join(config.docsDir, paths.tasksPath),
|
|
15069
15285
|
prUrl,
|
|
15070
15286
|
"Review",
|
|
15071
15287
|
config.lang
|
|
15072
15288
|
);
|
|
15073
15289
|
const syncedDraft = syncPrDraftMetadata(
|
|
15074
|
-
|
|
15290
|
+
path15.join(config.docsDir, paths.prPath),
|
|
15075
15291
|
prUrl,
|
|
15076
15292
|
"Review"
|
|
15077
15293
|
);
|
|
@@ -15112,13 +15328,13 @@ function githubCommand(program2) {
|
|
|
15112
15328
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
15113
15329
|
if (prUrl && options.syncTasks !== false) {
|
|
15114
15330
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
15115
|
-
|
|
15331
|
+
path15.join(config.docsDir, paths.tasksPath),
|
|
15116
15332
|
prUrl,
|
|
15117
15333
|
"Approved",
|
|
15118
15334
|
config.lang
|
|
15119
15335
|
);
|
|
15120
15336
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
15121
|
-
|
|
15337
|
+
path15.join(config.docsDir, paths.prPath),
|
|
15122
15338
|
prUrl,
|
|
15123
15339
|
"Approved"
|
|
15124
15340
|
);
|
|
@@ -15389,7 +15605,7 @@ function docsCommand(program2) {
|
|
|
15389
15605
|
);
|
|
15390
15606
|
return;
|
|
15391
15607
|
}
|
|
15392
|
-
const relativeFromCwd =
|
|
15608
|
+
const relativeFromCwd = path15.relative(process.cwd(), loaded.entry.absolutePath);
|
|
15393
15609
|
console.log();
|
|
15394
15610
|
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
15395
15611
|
console.log(
|
|
@@ -15469,7 +15685,7 @@ function detectCommand(program2) {
|
|
|
15469
15685
|
}
|
|
15470
15686
|
async function runDetect(options) {
|
|
15471
15687
|
const cwd = process.cwd();
|
|
15472
|
-
const targetCwd = options.dir ?
|
|
15688
|
+
const targetCwd = options.dir ? path15.resolve(cwd, options.dir) : cwd;
|
|
15473
15689
|
const config = await getConfig(targetCwd);
|
|
15474
15690
|
const detected = !!config;
|
|
15475
15691
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -15496,7 +15712,7 @@ async function runDetect(options) {
|
|
|
15496
15712
|
);
|
|
15497
15713
|
return;
|
|
15498
15714
|
}
|
|
15499
|
-
const configPath2 =
|
|
15715
|
+
const configPath2 = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
15500
15716
|
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
15501
15717
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
15502
15718
|
console.log(
|
|
@@ -15530,7 +15746,7 @@ async function runDetect(options) {
|
|
|
15530
15746
|
console.log();
|
|
15531
15747
|
return;
|
|
15532
15748
|
}
|
|
15533
|
-
const configPath =
|
|
15749
|
+
const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
15534
15750
|
const configFilePresent = await fs.pathExists(configPath);
|
|
15535
15751
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
15536
15752
|
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
@@ -15607,19 +15823,19 @@ function hasTemplateMarkers(content) {
|
|
|
15607
15823
|
return patterns.some((pattern) => pattern.test(content));
|
|
15608
15824
|
}
|
|
15609
15825
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15610
|
-
const featuresRoot =
|
|
15826
|
+
const featuresRoot = path15.join(docsDir, "features");
|
|
15611
15827
|
if (projectType === "single") {
|
|
15612
15828
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15613
|
-
return dirs.filter((value) =>
|
|
15829
|
+
return dirs.filter((value) => path15.basename(value) !== "feature-base").length;
|
|
15614
15830
|
}
|
|
15615
15831
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15616
15832
|
let total = 0;
|
|
15617
15833
|
for (const componentDir of components) {
|
|
15618
|
-
const componentName =
|
|
15834
|
+
const componentName = path15.basename(componentDir).trim().toLowerCase();
|
|
15619
15835
|
if (!componentName || componentName === "feature-base") continue;
|
|
15620
15836
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15621
15837
|
total += dirs.filter(
|
|
15622
|
-
(value) =>
|
|
15838
|
+
(value) => path15.basename(value) !== "feature-base"
|
|
15623
15839
|
).length;
|
|
15624
15840
|
}
|
|
15625
15841
|
return total;
|
|
@@ -15631,7 +15847,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15631
15847
|
ignoreDirs: ["node_modules"]
|
|
15632
15848
|
});
|
|
15633
15849
|
return files.some(
|
|
15634
|
-
(absolutePath) =>
|
|
15850
|
+
(absolutePath) => path15.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15635
15851
|
);
|
|
15636
15852
|
}
|
|
15637
15853
|
function finalizeChecks(checks) {
|
|
@@ -15800,7 +16016,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15800
16016
|
});
|
|
15801
16017
|
}
|
|
15802
16018
|
}
|
|
15803
|
-
const constitutionPath =
|
|
16019
|
+
const constitutionPath = path15.join(docsDir, "agents", "constitution.md");
|
|
15804
16020
|
if (!await fs.pathExists(constitutionPath)) {
|
|
15805
16021
|
checks.push({
|
|
15806
16022
|
id: "constitution_exists",
|
|
@@ -15842,7 +16058,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15842
16058
|
});
|
|
15843
16059
|
}
|
|
15844
16060
|
}
|
|
15845
|
-
const customPath =
|
|
16061
|
+
const customPath = path15.join(docsDir, "agents", "custom.md");
|
|
15846
16062
|
if (await fs.pathExists(customPath)) {
|
|
15847
16063
|
const content = await fs.readFile(customPath, "utf-8");
|
|
15848
16064
|
if (hasTemplateMarkers(content)) {
|
|
@@ -15871,7 +16087,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15871
16087
|
});
|
|
15872
16088
|
}
|
|
15873
16089
|
}
|
|
15874
|
-
const prdDir =
|
|
16090
|
+
const prdDir = path15.join(docsDir, "prd");
|
|
15875
16091
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
15876
16092
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
15877
16093
|
if (!prdReady) {
|
|
@@ -15889,7 +16105,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15889
16105
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
15890
16106
|
),
|
|
15891
16107
|
path: prdDir,
|
|
15892
|
-
suggestedCommand: `touch ${quotePath(
|
|
16108
|
+
suggestedCommand: `touch ${quotePath(path15.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
15893
16109
|
});
|
|
15894
16110
|
} else {
|
|
15895
16111
|
checks.push({
|
|
@@ -16327,7 +16543,7 @@ var PrePrReviewValidator = class {
|
|
|
16327
16543
|
return result.evidence;
|
|
16328
16544
|
}
|
|
16329
16545
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
16330
|
-
const fullPath =
|
|
16546
|
+
const fullPath = path15.resolve(evidencePath);
|
|
16331
16547
|
if (!await fs.pathExists(fullPath)) {
|
|
16332
16548
|
throw createCliError(
|
|
16333
16549
|
"INVALID_ARGUMENT",
|
|
@@ -16425,9 +16641,9 @@ var PrePrReviewValidator = class {
|
|
|
16425
16641
|
]);
|
|
16426
16642
|
const reviewedFiles = new Set(
|
|
16427
16643
|
normalizedEvidence.files.map(
|
|
16428
|
-
(f) =>
|
|
16644
|
+
(f) => path15.relative(
|
|
16429
16645
|
projectRoot,
|
|
16430
|
-
|
|
16646
|
+
path15.resolve(projectRoot, f.path)
|
|
16431
16647
|
)
|
|
16432
16648
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
16433
16649
|
);
|
|
@@ -16888,7 +17104,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16888
17104
|
);
|
|
16889
17105
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16890
17106
|
const preferred = getPreferredKeys(config.lang);
|
|
16891
|
-
const tasksPath =
|
|
17107
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
16892
17108
|
let tasksUpdated = false;
|
|
16893
17109
|
if (await fs.pathExists(tasksPath)) {
|
|
16894
17110
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16997,7 +17213,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16997
17213
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
16998
17214
|
);
|
|
16999
17215
|
}
|
|
17000
|
-
const tasksPath =
|
|
17216
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
17001
17217
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
17002
17218
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
17003
17219
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -17095,7 +17311,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
17095
17311
|
}
|
|
17096
17312
|
}
|
|
17097
17313
|
}
|
|
17098
|
-
const decisionsPath =
|
|
17314
|
+
const decisionsPath = path15.join(feature.path, "decisions.md");
|
|
17099
17315
|
const decisionLogEntry = buildReportContent({
|
|
17100
17316
|
folderName: feature.folderName,
|
|
17101
17317
|
date,
|
|
@@ -17111,9 +17327,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
17111
17327
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
17112
17328
|
}
|
|
17113
17329
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
17114
|
-
|
|
17330
|
+
path15.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
17115
17331
|
);
|
|
17116
|
-
const evidencePath =
|
|
17332
|
+
const evidencePath = path15.basename(config.docsDir) === "docs" ? normalizePathForDoc(path15.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
17117
17333
|
let nextTasks = tasksContent;
|
|
17118
17334
|
nextTasks = upsertSpecLine(
|
|
17119
17335
|
nextTasks,
|
|
@@ -17254,7 +17470,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17254
17470
|
);
|
|
17255
17471
|
}
|
|
17256
17472
|
const feature = state.matchedFeature;
|
|
17257
|
-
const tasksPath =
|
|
17473
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
17258
17474
|
let tasksUpdated = false;
|
|
17259
17475
|
if (await fs.pathExists(tasksPath)) {
|
|
17260
17476
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -17287,7 +17503,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17287
17503
|
nextMainState: "code_review_running",
|
|
17288
17504
|
tasksUpdated,
|
|
17289
17505
|
tasksPath,
|
|
17290
|
-
decisionsPath:
|
|
17506
|
+
decisionsPath: path15.join(feature.path, "decisions.md"),
|
|
17291
17507
|
prompt,
|
|
17292
17508
|
recordedAt: getLocalDateString()
|
|
17293
17509
|
};
|
|
@@ -17406,7 +17622,7 @@ async function runRequirements(options) {
|
|
|
17406
17622
|
}
|
|
17407
17623
|
for (const feature of scan.features) {
|
|
17408
17624
|
if (!feature.docs.tasksExists) continue;
|
|
17409
|
-
const tasksPath =
|
|
17625
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
17410
17626
|
let tasksContent = "";
|
|
17411
17627
|
try {
|
|
17412
17628
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -17540,7 +17756,7 @@ async function runRequirements(options) {
|
|
|
17540
17756
|
process.stdout.write(`${lines.join("\n")}
|
|
17541
17757
|
`);
|
|
17542
17758
|
if (options.write) {
|
|
17543
|
-
const outputPath =
|
|
17759
|
+
const outputPath = path15.join(docsDir, "prd", "status.md");
|
|
17544
17760
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
17545
17761
|
`, "utf-8");
|
|
17546
17762
|
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
@@ -17625,7 +17841,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
17625
17841
|
}
|
|
17626
17842
|
async function runTaskRun(featureName, options) {
|
|
17627
17843
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
17628
|
-
const tasksPath =
|
|
17844
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
17629
17845
|
if (!await fs.pathExists(tasksPath)) {
|
|
17630
17846
|
throw createCliError(
|
|
17631
17847
|
"PRECONDITION_FAILED",
|
|
@@ -17776,7 +17992,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
17776
17992
|
}
|
|
17777
17993
|
async function runTaskComplete(featureName, options) {
|
|
17778
17994
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
17779
|
-
const tasksPath =
|
|
17995
|
+
const tasksPath = path15.join(feature.path, "tasks.md");
|
|
17780
17996
|
if (!await fs.pathExists(tasksPath)) {
|
|
17781
17997
|
throw createCliError(
|
|
17782
17998
|
"PRECONDITION_FAILED",
|
|
@@ -17872,6 +18088,42 @@ function taskCompleteCommand(program2) {
|
|
|
17872
18088
|
}
|
|
17873
18089
|
);
|
|
17874
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
|
+
}
|
|
17875
18127
|
function isBannerDisabled() {
|
|
17876
18128
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
17877
18129
|
return v === "1";
|
|
@@ -17915,11 +18167,11 @@ ${version}
|
|
|
17915
18167
|
}
|
|
17916
18168
|
return `${ascii}${footer}`;
|
|
17917
18169
|
}
|
|
17918
|
-
var CACHE_FILE =
|
|
18170
|
+
var CACHE_FILE = path15.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
17919
18171
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
17920
18172
|
function getCurrentVersion() {
|
|
17921
18173
|
try {
|
|
17922
|
-
const packageJsonPath =
|
|
18174
|
+
const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
|
|
17923
18175
|
if (fs.existsSync(packageJsonPath)) {
|
|
17924
18176
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17925
18177
|
return pkg.version;
|
|
@@ -18023,7 +18275,7 @@ function shouldCheckForUpdates() {
|
|
|
18023
18275
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
18024
18276
|
function getCliVersion() {
|
|
18025
18277
|
try {
|
|
18026
|
-
const packageJsonPath =
|
|
18278
|
+
const packageJsonPath = path15.join(__dirname$1, "..", "package.json");
|
|
18027
18279
|
if (fs.existsSync(packageJsonPath)) {
|
|
18028
18280
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
18029
18281
|
if (pkg?.version) return String(pkg.version);
|
|
@@ -18058,6 +18310,7 @@ codeReviewRunCommand(program);
|
|
|
18058
18310
|
taskRunCommand(program);
|
|
18059
18311
|
taskCompleteCommand(program);
|
|
18060
18312
|
requirementsCommand(program);
|
|
18313
|
+
setupCommand(program);
|
|
18061
18314
|
await program.parseAsync();
|
|
18062
18315
|
//# sourceMappingURL=index.js.map
|
|
18063
18316
|
//# sourceMappingURL=index.js.map
|