lee-spec-kit 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1031 -572
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +6 -0
- package/templates/en/common/features/README.md +6 -0
- package/templates/en/common/features/feature-base/spec.md +1 -0
- package/templates/en/common/features/feature-base/tasks.md +4 -0
- package/templates/en/common/ideas/README.md +14 -13
- package/templates/en/common/ideas/idea.md +36 -0
- package/templates/ko/common/README.md +6 -0
- package/templates/ko/common/features/README.md +6 -0
- package/templates/ko/common/features/feature-base/spec.md +1 -0
- package/templates/ko/common/features/feature-base/tasks.md +4 -0
- package/templates/ko/common/ideas/README.md +14 -13
- package/templates/ko/common/ideas/idea.md +36 -0
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path13 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
|
-
import
|
|
7
|
+
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 fs10 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 = () => path13.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 = path13.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 = path13.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 = path13.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 = path13.dirname(__filename2);
|
|
155
155
|
function getTemplatesDir() {
|
|
156
|
-
const rootDir =
|
|
157
|
-
return
|
|
156
|
+
const rootDir = path13.resolve(__dirname2, "..");
|
|
157
|
+
return path13.join(rootDir, "templates");
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/utils/locales/ko/cli.ts
|
|
@@ -175,6 +175,8 @@ var koCli = {
|
|
|
175
175
|
"feature.nextSteps1": " 1. {path}/spec.md \uC791\uC131",
|
|
176
176
|
"feature.nextSteps2": " 2. \uC0AC\uC6A9\uC790 \uB9AC\uBDF0 \uC694\uCCAD",
|
|
177
177
|
"feature.nextSteps3": " 3. \uC2B9\uC778 \uD6C4 plan.md \uC791\uC131",
|
|
178
|
+
"feature.ideaNotFound": "Idea \uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {ref}",
|
|
179
|
+
"feature.ideaAmbiguous": "{ref}\uC640 \uB9E4\uCE6D\uB418\uB294 Idea \uBB38\uC11C\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. \uC815\uD655\uD55C \uACBD\uB85C\uB098 \uC804\uCCB4 indexed \uC774\uB984\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
178
180
|
"config.currentTitle": "\u{1F4CB} \uD604\uC7AC \uC124\uC815:",
|
|
179
181
|
"config.pathLabel": "\uACBD\uB85C",
|
|
180
182
|
"config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
|
|
@@ -268,6 +270,13 @@ var koCli = {
|
|
|
268
270
|
"init.log.gitInitialCommitDone": "\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!",
|
|
269
271
|
"init.warn.skipGitInit": "\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)",
|
|
270
272
|
"init.error.templateNotFound": "\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
|
|
273
|
+
"idea.fileExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 Idea \uBB38\uC11C\uC785\uB2C8\uB2E4: {path}",
|
|
274
|
+
"idea.templateNotFound": "CLI \uB0B4\uC7A5 idea \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
275
|
+
"idea.created": "\u2705 Idea \uBB38\uC11C \uC0DD\uC131 \uC644\uB8CC: {path}",
|
|
276
|
+
"idea.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
277
|
+
"idea.nextSteps1": " 1. \uBC94\uC704, PRD Refs, \uC2B9\uACA9 \uBA54\uBAA8\uB97C \uC791\uC131",
|
|
278
|
+
"idea.nextSteps2": " 2. Feature\uB85C \uC2B9\uACA9: npx lee-spec-kit feature <name> --idea {ideaId}",
|
|
279
|
+
"idea.nextSteps3": " 3. Feature\uB85C \uB9CC\uB4E4\uC9C0 \uC54A\uC744 \uACBD\uC6B0 Dropped\uB85C \uD45C\uC2DC",
|
|
271
280
|
"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)",
|
|
272
281
|
"github.cmdIssueDescription": "feature \uBB38\uC11C \uAE30\uBC18 GitHub issue \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131",
|
|
273
282
|
"github.cmdPrDescription": "GitHub PR \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131 + tasks \uB3D9\uAE30\uD654 + merge \uC7AC\uC2DC\uB3C4",
|
|
@@ -434,11 +443,15 @@ var koCli = {
|
|
|
434
443
|
"validation.workflowModeInvalid": "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBAA8\uB4DC\uB294 {values} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
|
|
435
444
|
"validation.featureIdEmpty": "Feature ID\uB294 \uBE44\uC5B4\uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
436
445
|
"validation.featureIdFormat": "Feature ID\uB294 'F' + \uC22B\uC790 \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4 (\uC608: F001).",
|
|
446
|
+
"validation.ideaIdEmpty": "Idea ID\uB294 \uBE44\uC5B4\uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
447
|
+
"validation.ideaIdFormat": "Idea ID\uB294 'I' + \uC22B\uC790 \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4 (\uC608: I001).",
|
|
437
448
|
"validation.pathEmpty": "\uACBD\uB85C\uB294 \uBE44\uC5B4\uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
438
449
|
"validation.pathNullByte": "\uACBD\uB85C\uC5D0 null \uBB38\uC790\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
439
450
|
"validation.genericFailed": "\uAC80\uC99D \uC2E4\uD328",
|
|
440
451
|
"validation.context.featureName": "\uAE30\uB2A5 \uC774\uB984",
|
|
441
452
|
"validation.context.featureId": "Feature ID",
|
|
453
|
+
"validation.context.ideaName": "Idea \uC774\uB984",
|
|
454
|
+
"validation.context.ideaId": "Idea ID",
|
|
442
455
|
"validation.context.projectName": "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984",
|
|
443
456
|
"validation.context.projectType": "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785",
|
|
444
457
|
"validation.context.language": "\uC5B8\uC5B4",
|
|
@@ -626,9 +639,9 @@ var koMessages = {
|
|
|
626
639
|
taskCommitGateReasonMismatchLastDone: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC774 \uC9C1\uC804 \uC644\uB8CC \uD0DC\uC2A4\uD06C\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
627
640
|
prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (\uD655\uC778 \uD544\uC694)",
|
|
628
641
|
prePrReviewFieldMissing: "tasks.md\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uC804 \uB9AC\uBDF0**: Pending | Running | Done` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
629
|
-
prePrReviewRun: "
|
|
630
|
-
prePrReviewRunning: "Pre-PR \uB9AC\uBDF0
|
|
631
|
-
prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \
|
|
642
|
+
prePrReviewRun: "Pre-PR \uB9AC\uBDF0 handoff\uB97C \uC900\uBE44\uD55C \uB4A4 \uC2E4\uC81C \uB9AC\uBDF0\uB97C \uC774\uC5B4\uC11C \uC218\uD589\uD558\uC138\uC694. `review-trace.json`\uC5D0\uB294 `baseSha`, `headSha`, `changedFiles`, `reviewedFiles`, `riskSummaries`, `Approval Rationale`, \uD30C\uC77C\uBCC4 findings, `Residual Risks`\uB97C \uAD6C\uC870\uD654\uD574 \uB123\uACE0, \uC774\uD6C4 `pre-pr-review`\uB85C \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. `pre-pr-review-run` \uC790\uCCB4\uB294 \uB9AC\uBDF0 \uC644\uB8CC\uB098 \uC0C1\uD0DC \uC804\uC774\uB97C \uC758\uBBF8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uD655\uC778 \uD544\uC694)",
|
|
643
|
+
prePrReviewRunning: "Pre-PR \uB9AC\uBDF0\uAC00 \uC774\uBBF8 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4. \uAE30\uC874 delegated review\uB97C \uC7AC\uC0AC\uC6A9\uD558\uAC70\uB098 \uC774\uC5B4\uC11C \uC218\uD589\uD574 \uAD6C\uC870\uD654\uB41C `review-trace.json`\uC744 \uB9CC\uB4E0 \uB4A4 `pre-pr-review`\uB85C \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. \uAC19\uC740 \uB77C\uBCA8\uC744 \uB2E4\uC2DC \uC2B9\uC778 \uB8E8\uD504\uB85C \uC5F4\uC9C0 \uB9C8\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
644
|
+
prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. repo \uB8E8\uD2B8 \uAE30\uC900 \uC0C1\uB300\uACBD\uB85C\uB97C \uC4F0\uB294 \uC2E4\uC81C JSON \uD30C\uC77C\uC744 \uAC00\uB9AC\uD0A4\uACE0, `baseSha`, `headSha`, `changedFiles`, `reviewedFiles`, `riskSummaries`, `Approval Rationale`, \uD30C\uC77C\uBCC4 findings, `Residual Risks`\uB97C \uD3EC\uD568\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
632
645
|
prePrReviewDecisionMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uACB0\uC815 \uD615\uC2DD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `\uACB0\uC815: ...`(\uB610\uB294 `decision: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
633
646
|
prePrReviewFixRequired: "\uD604\uC7AC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{decision}`\uC785\uB2C8\uB2E4. PR \uC0DD\uC131 \uB2E8\uACC4\uB85C \uC774\uB3D9\uD558\uAE30 \uC804\uC5D0 pre-PR \uC9C0\uC801\uC0AC\uD56D\uC744 \uCF54\uB4DC\uC5D0 \uBC18\uC601\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
634
647
|
prePrReviewDecisionReconfirm: "\uD604\uC7AC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{decision}`\uC785\uB2C8\uB2E4. \uC9C0\uC801\uC0AC\uD56D\uC744 \uBC18\uC601\uD55C \uB4A4 \uC774\uC804 \uC0C1\uD0DC \uC7AC\uC0AC\uC6A9\uC744 \uB9C9\uAE30 \uC704\uD574 \uBA85\uC2DC\uC801\uC73C\uB85C Decision\uC744 \uC9C0\uC815\uD574 \uC7AC\uC2E4\uD589\uD558\uC138\uC694: `{command}` (\uD655\uC778 \uD544\uC694)",
|
|
@@ -731,6 +744,8 @@ var enCli = {
|
|
|
731
744
|
"feature.nextSteps1": " 1. Write {path}/spec.md",
|
|
732
745
|
"feature.nextSteps2": " 2. Ask for review",
|
|
733
746
|
"feature.nextSteps3": " 3. After approval, write plan.md",
|
|
747
|
+
"feature.ideaNotFound": "Idea document not found: {ref}",
|
|
748
|
+
"feature.ideaAmbiguous": "Multiple idea documents matched {ref}. Use an exact path or full indexed name.",
|
|
734
749
|
"config.currentTitle": "\u{1F4CB} Current config:",
|
|
735
750
|
"config.pathLabel": "Path",
|
|
736
751
|
"config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot can only be set in standalone mode.",
|
|
@@ -824,6 +839,13 @@ var enCli = {
|
|
|
824
839
|
"init.log.gitInitialCommitDone": "\u2705 Initial Git commit created!",
|
|
825
840
|
"init.warn.skipGitInit": "\u26A0\uFE0F Skipping Git initialization (please commit manually)",
|
|
826
841
|
"init.error.templateNotFound": "Template not found: {path}",
|
|
842
|
+
"idea.fileExists": "Idea document already exists: {path}",
|
|
843
|
+
"idea.templateNotFound": "Built-in idea template not found.",
|
|
844
|
+
"idea.created": "\u2705 Idea document created: {path}",
|
|
845
|
+
"idea.nextStepsTitle": "Next steps:",
|
|
846
|
+
"idea.nextSteps1": " 1. Fill scope, PRD refs, and promotion notes",
|
|
847
|
+
"idea.nextSteps2": " 2. Promote it with: npx lee-spec-kit feature <name> --idea {ideaId}",
|
|
848
|
+
"idea.nextSteps3": " 3. Mark it dropped if it should not become a feature",
|
|
827
849
|
"github.cmdGithubDescription": "GitHub workflow helpers (issue/pr templates, validation, merge retry)",
|
|
828
850
|
"github.cmdIssueDescription": "Generate/create GitHub issue body from feature docs with validation",
|
|
829
851
|
"github.cmdPrDescription": "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry",
|
|
@@ -990,11 +1012,15 @@ var enCli = {
|
|
|
990
1012
|
"validation.workflowModeInvalid": "Workflow mode must be one of: {values}.",
|
|
991
1013
|
"validation.featureIdEmpty": "Feature ID cannot be empty.",
|
|
992
1014
|
"validation.featureIdFormat": "Feature ID must be 'F' + digits (e.g., F001).",
|
|
1015
|
+
"validation.ideaIdEmpty": "Idea ID cannot be empty.",
|
|
1016
|
+
"validation.ideaIdFormat": "Idea ID must be 'I' + digits (e.g., I001).",
|
|
993
1017
|
"validation.pathEmpty": "Path cannot be empty.",
|
|
994
1018
|
"validation.pathNullByte": "Path cannot contain null bytes.",
|
|
995
1019
|
"validation.genericFailed": "Validation failed",
|
|
996
1020
|
"validation.context.featureName": "Feature name",
|
|
997
1021
|
"validation.context.featureId": "Feature ID",
|
|
1022
|
+
"validation.context.ideaName": "Idea name",
|
|
1023
|
+
"validation.context.ideaId": "Idea ID",
|
|
998
1024
|
"validation.context.projectName": "Project name",
|
|
999
1025
|
"validation.context.projectType": "Project type",
|
|
1000
1026
|
"validation.context.language": "Language",
|
|
@@ -1182,9 +1208,9 @@ var enMessages = {
|
|
|
1182
1208
|
taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
|
|
1183
1209
|
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
|
|
1184
1210
|
prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Running | Done` and run context again. (CHECK required)",
|
|
1185
|
-
prePrReviewRun: "
|
|
1186
|
-
prePrReviewRunning: "
|
|
1187
|
-
prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file
|
|
1211
|
+
prePrReviewRun: "Prepare the pre-PR review handoff, then continue the real review before recording anything. Generate structured `review-trace.json` evidence with `baseSha`, `headSha`, `changedFiles`, `reviewedFiles`, `riskSummaries`, `Approval Rationale`, per-file findings, and `Residual Risks`, then record the outcome with `pre-pr-review`. `pre-pr-review-run` itself does not complete the review or advance state. (CHECK required)",
|
|
1212
|
+
prePrReviewRunning: "A pre-PR review is already in progress. Reuse or resume the delegated review, generate structured `review-trace.json` evidence, then record the result with `pre-pr-review`. Do not re-approve the same label. (CHECK required)",
|
|
1213
|
+
prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point it to a real JSON file that uses repo-relative paths and includes `baseSha`, `headSha`, `changedFiles`, `reviewedFiles`, `riskSummaries`, `Approval Rationale`, per-file findings, and `Residual Risks`. (CHECK required)",
|
|
1188
1214
|
prePrReviewDecisionMissing: "tasks.md `Pre-PR Decision` is empty/placeholder or missing decision format. Record it as `decision: ...` (or `\uACB0\uC815: ...`). (CHECK required)",
|
|
1189
1215
|
prePrReviewFixRequired: "Current `Pre-PR Decision` is `{decision}`. Apply the requested fixes from pre-PR findings before moving to PR creation. (CHECK required)",
|
|
1190
1216
|
prePrReviewDecisionReconfirm: "Current `Pre-PR Decision` is `{decision}`. After fixing findings, rerun pre-PR review with an explicit decision to avoid replaying the prior state: `{command}` (CHECK required)",
|
|
@@ -1674,6 +1700,19 @@ function validateFeatureIdWithLang(id, lang) {
|
|
|
1674
1700
|
}
|
|
1675
1701
|
return { valid: true };
|
|
1676
1702
|
}
|
|
1703
|
+
function validateIdeaIdWithLang(id, lang) {
|
|
1704
|
+
if (!id || id.trim().length === 0) {
|
|
1705
|
+
return { valid: false, error: tr(lang, "cli", "validation.ideaIdEmpty") };
|
|
1706
|
+
}
|
|
1707
|
+
const ideaIdPattern = /^I\d{3,}$/;
|
|
1708
|
+
if (!ideaIdPattern.test(id)) {
|
|
1709
|
+
return {
|
|
1710
|
+
valid: false,
|
|
1711
|
+
error: tr(lang, "cli", "validation.ideaIdFormat")
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return { valid: true };
|
|
1715
|
+
}
|
|
1677
1716
|
function validatePathWithLang(inputPath, lang) {
|
|
1678
1717
|
if (!inputPath || inputPath.trim().length === 0) {
|
|
1679
1718
|
return { valid: false, error: tr(lang, "cli", "validation.pathEmpty") };
|
|
@@ -1702,10 +1741,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
|
|
|
1702
1741
|
var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
|
|
1703
1742
|
var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
|
|
1704
1743
|
function toScopeKey(value) {
|
|
1705
|
-
return createHash("sha1").update(
|
|
1744
|
+
return createHash("sha1").update(path13.resolve(value)).digest("hex").slice(0, 16);
|
|
1706
1745
|
}
|
|
1707
1746
|
function getTempRuntimeDir(scopePath) {
|
|
1708
|
-
return
|
|
1747
|
+
return path13.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
|
|
1709
1748
|
}
|
|
1710
1749
|
function resolveGitRuntimeDir(cwd) {
|
|
1711
1750
|
try {
|
|
@@ -1719,38 +1758,38 @@ function resolveGitRuntimeDir(cwd) {
|
|
|
1719
1758
|
}
|
|
1720
1759
|
).trim();
|
|
1721
1760
|
if (!out) return null;
|
|
1722
|
-
return
|
|
1761
|
+
return path13.isAbsolute(out) ? out : path13.resolve(cwd, out);
|
|
1723
1762
|
} catch {
|
|
1724
1763
|
return null;
|
|
1725
1764
|
}
|
|
1726
1765
|
}
|
|
1727
1766
|
function getRuntimeStateDir(cwd) {
|
|
1728
|
-
const resolved =
|
|
1767
|
+
const resolved = path13.resolve(cwd);
|
|
1729
1768
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1730
1769
|
}
|
|
1731
1770
|
function getDocsLockPath(docsDir) {
|
|
1732
|
-
return
|
|
1771
|
+
return path13.join(
|
|
1733
1772
|
getRuntimeStateDir(docsDir),
|
|
1734
1773
|
"locks",
|
|
1735
1774
|
`docs-${toScopeKey(docsDir)}.lock`
|
|
1736
1775
|
);
|
|
1737
1776
|
}
|
|
1738
1777
|
function getInitLockPath(targetDir) {
|
|
1739
|
-
return
|
|
1740
|
-
getRuntimeStateDir(
|
|
1778
|
+
return path13.join(
|
|
1779
|
+
getRuntimeStateDir(path13.dirname(path13.resolve(targetDir))),
|
|
1741
1780
|
"locks",
|
|
1742
1781
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1743
1782
|
);
|
|
1744
1783
|
}
|
|
1745
1784
|
function getApprovalTicketStorePath(docsDir) {
|
|
1746
|
-
return
|
|
1785
|
+
return path13.join(
|
|
1747
1786
|
getRuntimeStateDir(docsDir),
|
|
1748
1787
|
"tickets",
|
|
1749
1788
|
`approval-${toScopeKey(docsDir)}.json`
|
|
1750
1789
|
);
|
|
1751
1790
|
}
|
|
1752
1791
|
function getProjectExecutionLockPath(cwd) {
|
|
1753
|
-
return
|
|
1792
|
+
return path13.join(getRuntimeStateDir(cwd), "locks", "project.lock");
|
|
1754
1793
|
}
|
|
1755
1794
|
async function isStaleLock(lockPath, staleMs) {
|
|
1756
1795
|
try {
|
|
@@ -1791,7 +1830,7 @@ function isProcessAlive(pid) {
|
|
|
1791
1830
|
}
|
|
1792
1831
|
}
|
|
1793
1832
|
async function tryAcquire(lockPath, owner) {
|
|
1794
|
-
await fs.ensureDir(
|
|
1833
|
+
await fs.ensureDir(path13.dirname(lockPath));
|
|
1795
1834
|
try {
|
|
1796
1835
|
const fd = await fs.open(lockPath, "wx");
|
|
1797
1836
|
const payload = JSON.stringify(
|
|
@@ -1868,30 +1907,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
|
|
|
1868
1907
|
"pr-template.md"
|
|
1869
1908
|
];
|
|
1870
1909
|
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1871
|
-
var ENGINE_MANAGED_FEATURE_PATH =
|
|
1910
|
+
var ENGINE_MANAGED_FEATURE_PATH = path13.join(
|
|
1872
1911
|
"features",
|
|
1873
1912
|
"feature-base"
|
|
1874
1913
|
);
|
|
1875
1914
|
async function pruneEngineManagedDocs(docsDir) {
|
|
1876
1915
|
const removed = [];
|
|
1877
1916
|
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1878
|
-
const target =
|
|
1917
|
+
const target = path13.join(docsDir, "agents", file);
|
|
1879
1918
|
if (await fs.pathExists(target)) {
|
|
1880
1919
|
await fs.remove(target);
|
|
1881
|
-
removed.push(
|
|
1920
|
+
removed.push(path13.relative(docsDir, target));
|
|
1882
1921
|
}
|
|
1883
1922
|
}
|
|
1884
1923
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1885
|
-
const target =
|
|
1924
|
+
const target = path13.join(docsDir, "agents", dir);
|
|
1886
1925
|
if (await fs.pathExists(target)) {
|
|
1887
1926
|
await fs.remove(target);
|
|
1888
|
-
removed.push(
|
|
1927
|
+
removed.push(path13.relative(docsDir, target));
|
|
1889
1928
|
}
|
|
1890
1929
|
}
|
|
1891
|
-
const featureBasePath =
|
|
1930
|
+
const featureBasePath = path13.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1892
1931
|
if (await fs.pathExists(featureBasePath)) {
|
|
1893
1932
|
await fs.remove(featureBasePath);
|
|
1894
|
-
removed.push(
|
|
1933
|
+
removed.push(path13.relative(docsDir, featureBasePath));
|
|
1895
1934
|
}
|
|
1896
1935
|
return removed;
|
|
1897
1936
|
}
|
|
@@ -2171,7 +2210,7 @@ function initCommand(program2) {
|
|
|
2171
2210
|
} catch (error) {
|
|
2172
2211
|
if (error instanceof Error && error.message === "canceled") {
|
|
2173
2212
|
const lang2 = options.lang ?? DEFAULT_LANG;
|
|
2174
|
-
console.log(
|
|
2213
|
+
console.log(chalk9.yellow(`
|
|
2175
2214
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
2176
2215
|
return;
|
|
2177
2216
|
}
|
|
@@ -2179,8 +2218,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2179
2218
|
const cliError = toCliError(error);
|
|
2180
2219
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
2181
2220
|
console.error(
|
|
2182
|
-
|
|
2183
|
-
|
|
2221
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
2222
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
2184
2223
|
);
|
|
2185
2224
|
printCliErrorSuggestions(suggestions, lang);
|
|
2186
2225
|
process.exitCode = 1;
|
|
@@ -2190,7 +2229,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2190
2229
|
}
|
|
2191
2230
|
async function runInit(options) {
|
|
2192
2231
|
const cwd = process.cwd();
|
|
2193
|
-
const defaultName =
|
|
2232
|
+
const defaultName = path13.basename(cwd);
|
|
2194
2233
|
let projectName = options.name || defaultName;
|
|
2195
2234
|
let projectType = options.type;
|
|
2196
2235
|
let components = parseComponentsOption(options.components);
|
|
@@ -2201,7 +2240,7 @@ async function runInit(options) {
|
|
|
2201
2240
|
let docsRemote = options.docsRemote;
|
|
2202
2241
|
let projectRoot;
|
|
2203
2242
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
2204
|
-
const targetDir =
|
|
2243
|
+
const targetDir = path13.resolve(cwd, options.dir || "./docs");
|
|
2205
2244
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
2206
2245
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
2207
2246
|
throw createCliError(
|
|
@@ -2241,18 +2280,18 @@ async function runInit(options) {
|
|
|
2241
2280
|
}
|
|
2242
2281
|
console.log();
|
|
2243
2282
|
console.log(
|
|
2244
|
-
|
|
2283
|
+
chalk9.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
|
|
2245
2284
|
);
|
|
2246
2285
|
if (isInsideGitRepo) {
|
|
2247
|
-
console.log(
|
|
2286
|
+
console.log(chalk9.green(tr(lang, "cli", "init.gitDetected")));
|
|
2248
2287
|
console.log();
|
|
2249
|
-
console.log(
|
|
2250
|
-
console.log(
|
|
2251
|
-
console.log(
|
|
2252
|
-
console.log(
|
|
2288
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.insideProjectRoot")));
|
|
2289
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeEmbeddedDesc")));
|
|
2290
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeStandaloneDesc")));
|
|
2291
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeStandaloneMove")));
|
|
2253
2292
|
} else {
|
|
2254
|
-
console.log(
|
|
2255
|
-
console.log(
|
|
2293
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.gitNotDetected")));
|
|
2294
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
|
|
2256
2295
|
}
|
|
2257
2296
|
console.log();
|
|
2258
2297
|
const response = await prompts(
|
|
@@ -2574,31 +2613,31 @@ async function runInit(options) {
|
|
|
2574
2613
|
initial: false
|
|
2575
2614
|
});
|
|
2576
2615
|
if (!overwrite) {
|
|
2577
|
-
console.log(
|
|
2616
|
+
console.log(chalk9.yellow(tr(lang, "cli", "common.canceled")));
|
|
2578
2617
|
return;
|
|
2579
2618
|
}
|
|
2580
2619
|
}
|
|
2581
2620
|
}
|
|
2582
2621
|
}
|
|
2583
2622
|
console.log();
|
|
2584
|
-
console.log(
|
|
2623
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
2585
2624
|
console.log(
|
|
2586
|
-
|
|
2625
|
+
chalk9.gray(
|
|
2587
2626
|
` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
|
|
2588
2627
|
)
|
|
2589
2628
|
);
|
|
2590
2629
|
console.log(
|
|
2591
|
-
|
|
2630
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
|
|
2592
2631
|
);
|
|
2593
2632
|
console.log(
|
|
2594
|
-
|
|
2633
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`)
|
|
2595
2634
|
);
|
|
2596
2635
|
console.log(
|
|
2597
|
-
|
|
2636
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
2598
2637
|
);
|
|
2599
2638
|
console.log();
|
|
2600
2639
|
const templatesDir = getTemplatesDir();
|
|
2601
|
-
const commonPath =
|
|
2640
|
+
const commonPath = path13.join(templatesDir, lang, "common");
|
|
2602
2641
|
if (!await fs.pathExists(commonPath)) {
|
|
2603
2642
|
throw new Error(
|
|
2604
2643
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2607,11 +2646,11 @@ async function runInit(options) {
|
|
|
2607
2646
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
2608
2647
|
await copyTemplates(fsAdapter, commonPath, targetDir);
|
|
2609
2648
|
if (projectType === "multi") {
|
|
2610
|
-
const featuresRoot =
|
|
2649
|
+
const featuresRoot = path13.join(targetDir, "features");
|
|
2611
2650
|
for (const component of components) {
|
|
2612
|
-
const componentDir =
|
|
2651
|
+
const componentDir = path13.join(featuresRoot, component);
|
|
2613
2652
|
await fs.ensureDir(componentDir);
|
|
2614
|
-
const readmePath =
|
|
2653
|
+
const readmePath = path13.join(componentDir, "README.md");
|
|
2615
2654
|
if (!await fs.pathExists(readmePath)) {
|
|
2616
2655
|
await fs.writeFile(
|
|
2617
2656
|
readmePath,
|
|
@@ -2672,20 +2711,20 @@ async function runInit(options) {
|
|
|
2672
2711
|
config.projectRoot = projectRoot;
|
|
2673
2712
|
}
|
|
2674
2713
|
}
|
|
2675
|
-
const configPath =
|
|
2714
|
+
const configPath = path13.join(targetDir, ".lee-spec-kit.json");
|
|
2676
2715
|
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
2677
2716
|
const extraCommitPathsAbs = [];
|
|
2678
2717
|
try {
|
|
2679
2718
|
if (docsRepo === "embedded") {
|
|
2680
2719
|
const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
|
|
2681
|
-
const agentsMdPath =
|
|
2720
|
+
const agentsMdPath = path13.join(repoRoot, "AGENTS.md");
|
|
2682
2721
|
const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
|
|
2683
2722
|
lang,
|
|
2684
2723
|
docsRepo
|
|
2685
2724
|
});
|
|
2686
2725
|
if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
|
|
2687
2726
|
} else {
|
|
2688
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2727
|
+
await upsertLeeSpecKitAgentsMd(path13.join(targetDir, "AGENTS.md"), {
|
|
2689
2728
|
lang,
|
|
2690
2729
|
docsRepo
|
|
2691
2730
|
});
|
|
@@ -2695,16 +2734,16 @@ async function runInit(options) {
|
|
|
2695
2734
|
} else if (projectRoot && typeof projectRoot === "object") {
|
|
2696
2735
|
roots.push(...Object.values(projectRoot));
|
|
2697
2736
|
}
|
|
2698
|
-
const resolvedCwd =
|
|
2737
|
+
const resolvedCwd = path13.resolve(cwd);
|
|
2699
2738
|
for (const raw of roots) {
|
|
2700
2739
|
const value = String(raw || "").trim();
|
|
2701
2740
|
if (!value) continue;
|
|
2702
|
-
const abs =
|
|
2703
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
2741
|
+
const abs = path13.resolve(cwd, value);
|
|
2742
|
+
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path13.sep}`)) {
|
|
2704
2743
|
if (await fs.pathExists(abs)) {
|
|
2705
2744
|
const stat = await fs.stat(abs);
|
|
2706
2745
|
if (stat.isDirectory()) {
|
|
2707
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2746
|
+
await upsertLeeSpecKitAgentsMd(path13.join(abs, "AGENTS.md"), {
|
|
2708
2747
|
lang,
|
|
2709
2748
|
docsRepo
|
|
2710
2749
|
});
|
|
@@ -2715,7 +2754,7 @@ async function runInit(options) {
|
|
|
2715
2754
|
}
|
|
2716
2755
|
} catch {
|
|
2717
2756
|
}
|
|
2718
|
-
console.log(
|
|
2757
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
2719
2758
|
console.log();
|
|
2720
2759
|
await initGit(
|
|
2721
2760
|
cwd,
|
|
@@ -2726,14 +2765,14 @@ async function runInit(options) {
|
|
|
2726
2765
|
docsRemote,
|
|
2727
2766
|
extraCommitPathsAbs
|
|
2728
2767
|
);
|
|
2729
|
-
console.log(
|
|
2768
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
|
|
2730
2769
|
console.log(
|
|
2731
|
-
|
|
2770
|
+
chalk9.gray(
|
|
2732
2771
|
tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir })
|
|
2733
2772
|
)
|
|
2734
2773
|
);
|
|
2735
|
-
console.log(
|
|
2736
|
-
console.log(
|
|
2774
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
2775
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps3")));
|
|
2737
2776
|
console.log();
|
|
2738
2777
|
},
|
|
2739
2778
|
{ owner: "init" }
|
|
@@ -2788,31 +2827,31 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2788
2827
|
try {
|
|
2789
2828
|
runGitOrThrow(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
|
|
2790
2829
|
console.log(
|
|
2791
|
-
|
|
2830
|
+
chalk9.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit"))
|
|
2792
2831
|
);
|
|
2793
2832
|
} catch {
|
|
2794
|
-
console.log(
|
|
2833
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2795
2834
|
runGitOrThrow(["init"], gitWorkdir);
|
|
2796
2835
|
}
|
|
2797
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2836
|
+
const relativePath = docsRepo === "standalone" ? "." : path13.relative(gitWorkdir, targetDir);
|
|
2798
2837
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2799
2838
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2800
|
-
console.log(
|
|
2801
|
-
console.log(
|
|
2839
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
|
|
2840
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.warn.commitManually")));
|
|
2802
2841
|
console.log();
|
|
2803
2842
|
return;
|
|
2804
2843
|
}
|
|
2805
2844
|
if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
|
|
2806
2845
|
const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
|
|
2807
2846
|
console.log(
|
|
2808
|
-
|
|
2847
|
+
chalk9.yellow(
|
|
2809
2848
|
tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
|
|
2810
2849
|
path: repoRelativePath
|
|
2811
2850
|
})
|
|
2812
2851
|
)
|
|
2813
2852
|
);
|
|
2814
2853
|
console.log(
|
|
2815
|
-
|
|
2854
|
+
chalk9.gray(
|
|
2816
2855
|
tr(lang, "cli", "init.warn.docsPathIgnoredHint", {
|
|
2817
2856
|
path: repoRelativePath
|
|
2818
2857
|
})
|
|
@@ -2821,7 +2860,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2821
2860
|
console.log();
|
|
2822
2861
|
return;
|
|
2823
2862
|
}
|
|
2824
|
-
const extraRelativePaths = extraCommitPathsAbs.map((absPath) =>
|
|
2863
|
+
const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path13.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
|
|
2825
2864
|
const pathsToStage = [relativePath, ...extraRelativePaths];
|
|
2826
2865
|
for (const p of pathsToStage) {
|
|
2827
2866
|
runGitOrThrow(["add", p], gitWorkdir);
|
|
@@ -2840,34 +2879,34 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2840
2879
|
try {
|
|
2841
2880
|
runGitOrThrow(["remote", "add", "origin", docsRemote], gitWorkdir);
|
|
2842
2881
|
console.log(
|
|
2843
|
-
|
|
2882
|
+
chalk9.green(
|
|
2844
2883
|
tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote })
|
|
2845
2884
|
)
|
|
2846
2885
|
);
|
|
2847
2886
|
} catch {
|
|
2848
|
-
console.log(
|
|
2887
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
|
|
2849
2888
|
}
|
|
2850
2889
|
}
|
|
2851
|
-
console.log(
|
|
2890
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
|
|
2852
2891
|
console.log();
|
|
2853
2892
|
} catch {
|
|
2854
|
-
console.log(
|
|
2893
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.skipGitInit")));
|
|
2855
2894
|
console.log();
|
|
2856
2895
|
}
|
|
2857
2896
|
}
|
|
2858
2897
|
function getAncestorDirs(startDir) {
|
|
2859
2898
|
const dirs = [];
|
|
2860
|
-
let current =
|
|
2899
|
+
let current = path13.resolve(startDir);
|
|
2861
2900
|
while (true) {
|
|
2862
2901
|
dirs.push(current);
|
|
2863
|
-
const parent =
|
|
2902
|
+
const parent = path13.dirname(current);
|
|
2864
2903
|
if (parent === current) break;
|
|
2865
2904
|
current = parent;
|
|
2866
2905
|
}
|
|
2867
2906
|
return dirs;
|
|
2868
2907
|
}
|
|
2869
2908
|
function hasWorkspaceBoundary(dir) {
|
|
2870
|
-
return fs.existsSync(
|
|
2909
|
+
return fs.existsSync(path13.join(dir, "package.json")) || fs.existsSync(path13.join(dir, ".git"));
|
|
2871
2910
|
}
|
|
2872
2911
|
function getSearchBaseDirs(cwd) {
|
|
2873
2912
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2883,7 +2922,7 @@ function normalizeComponentKeys(value) {
|
|
|
2883
2922
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2884
2923
|
}
|
|
2885
2924
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2886
|
-
const featuresPath =
|
|
2925
|
+
const featuresPath = path13.join(docsDir, "features");
|
|
2887
2926
|
if (!await fs.pathExists(featuresPath)) return [];
|
|
2888
2927
|
const entries = await fs.readdir(featuresPath, { withFileTypes: true });
|
|
2889
2928
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2894,21 +2933,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2894
2933
|
async function getConfig(cwd) {
|
|
2895
2934
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2896
2935
|
const baseDirs = [
|
|
2897
|
-
...explicitDocsDir ? [
|
|
2936
|
+
...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
|
|
2898
2937
|
...getSearchBaseDirs(cwd)
|
|
2899
2938
|
];
|
|
2900
2939
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2901
2940
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2902
2941
|
for (const baseDir of baseDirs) {
|
|
2903
|
-
const resolvedBaseDir =
|
|
2942
|
+
const resolvedBaseDir = path13.resolve(baseDir);
|
|
2904
2943
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2905
2944
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2906
|
-
const possibleDocsDirs = [
|
|
2945
|
+
const possibleDocsDirs = [path13.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2907
2946
|
for (const docsDir of possibleDocsDirs) {
|
|
2908
|
-
const resolvedDocsDir =
|
|
2947
|
+
const resolvedDocsDir = path13.resolve(docsDir);
|
|
2909
2948
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2910
2949
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2911
|
-
const configPath =
|
|
2950
|
+
const configPath = path13.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2912
2951
|
if (await fs.pathExists(configPath)) {
|
|
2913
2952
|
try {
|
|
2914
2953
|
const configFile = await fs.readJson(configPath);
|
|
@@ -2938,16 +2977,16 @@ async function getConfig(cwd) {
|
|
|
2938
2977
|
} catch {
|
|
2939
2978
|
}
|
|
2940
2979
|
}
|
|
2941
|
-
const agentsPath =
|
|
2942
|
-
const featuresPath =
|
|
2980
|
+
const agentsPath = path13.join(resolvedDocsDir, "agents");
|
|
2981
|
+
const featuresPath = path13.join(resolvedDocsDir, "features");
|
|
2943
2982
|
if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
|
|
2944
2983
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2945
2984
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2946
2985
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2947
2986
|
const langProbeCandidates = [
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2987
|
+
path13.join(agentsPath, "custom.md"),
|
|
2988
|
+
path13.join(agentsPath, "constitution.md"),
|
|
2989
|
+
path13.join(agentsPath, "agents.md")
|
|
2951
2990
|
];
|
|
2952
2991
|
let lang = "en";
|
|
2953
2992
|
for (const candidate of langProbeCandidates) {
|
|
@@ -3018,18 +3057,18 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
3018
3057
|
await fs.writeFile(filePath, transform(content), "utf-8");
|
|
3019
3058
|
}
|
|
3020
3059
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
3021
|
-
await patchMarkdownIfExists(
|
|
3060
|
+
await patchMarkdownIfExists(path13.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3022
3061
|
await patchMarkdownIfExists(
|
|
3023
|
-
|
|
3062
|
+
path13.join(featureDir, "tasks.md"),
|
|
3024
3063
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3025
3064
|
);
|
|
3026
|
-
await fs.remove(
|
|
3027
|
-
await fs.remove(
|
|
3065
|
+
await fs.remove(path13.join(featureDir, "issue.md"));
|
|
3066
|
+
await fs.remove(path13.join(featureDir, "pr.md"));
|
|
3028
3067
|
}
|
|
3029
3068
|
|
|
3030
3069
|
// src/commands/feature.ts
|
|
3031
3070
|
function featureCommand(program2) {
|
|
3032
|
-
program2.command("feature <name>").description("Create a new feature folder").option("--component <component>", "Component name (multi only)").option("--id <id>", "Feature ID (default: auto)").option("-d, --desc <description>", "Feature description for spec.md").option("--non-interactive", "Fail instead of prompting for input").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3071
|
+
program2.command("feature <name>").description("Create a new feature folder").option("--component <component>", "Component name (multi only)").option("--id <id>", "Feature ID (default: auto)").option("-d, --desc <description>", "Feature description for spec.md").option("--idea <ref>", "Idea reference to promote (I001 | I001-slug | docs/ideas/...)").option("--non-interactive", "Fail instead of prompting for input").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3033
3072
|
try {
|
|
3034
3073
|
const result = await runFeature(name, options);
|
|
3035
3074
|
if (options.json) {
|
|
@@ -3062,7 +3101,7 @@ function featureCommand(program2) {
|
|
|
3062
3101
|
);
|
|
3063
3102
|
return;
|
|
3064
3103
|
}
|
|
3065
|
-
console.log(
|
|
3104
|
+
console.log(chalk9.yellow(`
|
|
3066
3105
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
3067
3106
|
return;
|
|
3068
3107
|
}
|
|
@@ -3083,8 +3122,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
3083
3122
|
return;
|
|
3084
3123
|
}
|
|
3085
3124
|
console.error(
|
|
3086
|
-
|
|
3087
|
-
|
|
3125
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
3126
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
3088
3127
|
);
|
|
3089
3128
|
printCliErrorSuggestions(suggestions, lang);
|
|
3090
3129
|
process.exitCode = 1;
|
|
@@ -3110,6 +3149,7 @@ async function runFeature(name, options) {
|
|
|
3110
3149
|
projectType,
|
|
3111
3150
|
config.components
|
|
3112
3151
|
);
|
|
3152
|
+
const linkedIdea = options.idea ? await resolveIdeaReference(docsDir, options.idea, lang) : null;
|
|
3113
3153
|
assertValid(
|
|
3114
3154
|
validateSafeNameWithLang(name, lang),
|
|
3115
3155
|
tr(lang, "cli", "validation.context.featureName"),
|
|
@@ -3175,19 +3215,19 @@ async function runFeature(name, options) {
|
|
|
3175
3215
|
}
|
|
3176
3216
|
let featuresDir;
|
|
3177
3217
|
if (projectType === "multi") {
|
|
3178
|
-
featuresDir =
|
|
3218
|
+
featuresDir = path13.join(docsDir, "features", component);
|
|
3179
3219
|
} else {
|
|
3180
|
-
featuresDir =
|
|
3220
|
+
featuresDir = path13.join(docsDir, "features");
|
|
3181
3221
|
}
|
|
3182
3222
|
const featureFolderName = `${featureId}-${name}`;
|
|
3183
|
-
const featureDir =
|
|
3223
|
+
const featureDir = path13.join(featuresDir, featureFolderName);
|
|
3184
3224
|
if (await fs.pathExists(featureDir)) {
|
|
3185
3225
|
throw createCliError(
|
|
3186
3226
|
"INVALID_ARGUMENT",
|
|
3187
3227
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3188
3228
|
);
|
|
3189
3229
|
}
|
|
3190
|
-
const featureBasePath =
|
|
3230
|
+
const featureBasePath = path13.join(
|
|
3191
3231
|
getTemplatesDir(),
|
|
3192
3232
|
lang,
|
|
3193
3233
|
"common",
|
|
@@ -3232,23 +3272,30 @@ async function runFeature(name, options) {
|
|
|
3232
3272
|
}
|
|
3233
3273
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
3234
3274
|
await replaceInFiles(fsAdapter, featureDir, replacements);
|
|
3275
|
+
if (linkedIdea) {
|
|
3276
|
+
await stampIdeaReferenceInSpec(
|
|
3277
|
+
path13.join(featureDir, "spec.md"),
|
|
3278
|
+
path13.relative(featureDir, linkedIdea.path)
|
|
3279
|
+
);
|
|
3280
|
+
await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
|
|
3281
|
+
}
|
|
3235
3282
|
if (config.workflow?.mode === "local") {
|
|
3236
3283
|
await applyLocalWorkflowTemplateToFeatureDir(featureDir, lang);
|
|
3237
3284
|
}
|
|
3238
3285
|
if (!options.json) {
|
|
3239
3286
|
console.log();
|
|
3240
3287
|
console.log(
|
|
3241
|
-
|
|
3288
|
+
chalk9.green(tr(lang, "cli", "feature.created", { path: featureDir }))
|
|
3242
3289
|
);
|
|
3243
3290
|
console.log();
|
|
3244
|
-
console.log(
|
|
3291
|
+
console.log(chalk9.blue(tr(lang, "cli", "feature.nextStepsTitle")));
|
|
3245
3292
|
console.log(
|
|
3246
|
-
|
|
3293
|
+
chalk9.gray(
|
|
3247
3294
|
tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
|
|
3248
3295
|
)
|
|
3249
3296
|
);
|
|
3250
|
-
console.log(
|
|
3251
|
-
console.log(
|
|
3297
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps2")));
|
|
3298
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps3")));
|
|
3252
3299
|
console.log();
|
|
3253
3300
|
}
|
|
3254
3301
|
return {
|
|
@@ -3256,18 +3303,115 @@ async function runFeature(name, options) {
|
|
|
3256
3303
|
featureName: name,
|
|
3257
3304
|
component: projectType === "multi" ? component : void 0,
|
|
3258
3305
|
featurePath: featureDir,
|
|
3259
|
-
featurePathFromDocs:
|
|
3306
|
+
featurePathFromDocs: path13.relative(docsDir, featureDir)
|
|
3260
3307
|
};
|
|
3261
3308
|
},
|
|
3262
3309
|
{ owner: "feature" }
|
|
3263
3310
|
);
|
|
3264
3311
|
}
|
|
3312
|
+
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3313
|
+
const ideasDir = path13.join(docsDir, "ideas");
|
|
3314
|
+
const trimmedRef = ref.trim();
|
|
3315
|
+
if (!trimmedRef) {
|
|
3316
|
+
throw createCliError(
|
|
3317
|
+
"INVALID_ARGUMENT",
|
|
3318
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref })
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3322
|
+
const candidate = path13.resolve(process.cwd(), trimmedRef);
|
|
3323
|
+
if (await fs.pathExists(candidate)) {
|
|
3324
|
+
return { path: candidate };
|
|
3325
|
+
}
|
|
3326
|
+
throw createCliError(
|
|
3327
|
+
"INVALID_ARGUMENT",
|
|
3328
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
if (!await fs.pathExists(ideasDir)) {
|
|
3332
|
+
throw createCliError(
|
|
3333
|
+
"INVALID_ARGUMENT",
|
|
3334
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3335
|
+
);
|
|
3336
|
+
}
|
|
3337
|
+
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3338
|
+
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3339
|
+
const exactName = `${trimmedRef}.md`;
|
|
3340
|
+
if (files.includes(exactName)) {
|
|
3341
|
+
return { path: path13.join(ideasDir, exactName) };
|
|
3342
|
+
}
|
|
3343
|
+
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3344
|
+
if (byId.length === 1) {
|
|
3345
|
+
return { path: path13.join(ideasDir, byId[0]) };
|
|
3346
|
+
}
|
|
3347
|
+
if (byId.length > 1) {
|
|
3348
|
+
throw createCliError(
|
|
3349
|
+
"INVALID_ARGUMENT",
|
|
3350
|
+
tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
throw createCliError(
|
|
3354
|
+
"INVALID_ARGUMENT",
|
|
3355
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
async function stampIdeaReferenceInSpec(specPath, relativeIdeaPath) {
|
|
3359
|
+
const normalizedPath = relativeIdeaPath.replace(/\\/g, "/");
|
|
3360
|
+
const ideaLine = `- Idea: \`${normalizedPath}\``;
|
|
3361
|
+
let content = await fs.readFile(specPath, "utf-8");
|
|
3362
|
+
if (content.includes(ideaLine)) {
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
if (content.includes("## Related Documents")) {
|
|
3366
|
+
content = content.replace(
|
|
3367
|
+
"## Related Documents\n\n",
|
|
3368
|
+
`## Related Documents
|
|
3369
|
+
|
|
3370
|
+
${ideaLine}
|
|
3371
|
+
`
|
|
3372
|
+
);
|
|
3373
|
+
} else {
|
|
3374
|
+
content = `${content.trimEnd()}
|
|
3375
|
+
|
|
3376
|
+
${ideaLine}
|
|
3377
|
+
`;
|
|
3378
|
+
}
|
|
3379
|
+
await fs.writeFile(specPath, content, "utf-8");
|
|
3380
|
+
}
|
|
3381
|
+
async function markIdeaAsFeatureized(ideaPath, featureFolderName) {
|
|
3382
|
+
let content = await fs.readFile(ideaPath, "utf-8");
|
|
3383
|
+
content = replaceOrAppendIdeaMetadata(content, "Status", "Featureized");
|
|
3384
|
+
content = replaceOrAppendIdeaMetadata(content, "Feature", featureFolderName);
|
|
3385
|
+
await fs.writeFile(ideaPath, content, "utf-8");
|
|
3386
|
+
}
|
|
3387
|
+
function replaceOrAppendIdeaMetadata(content, label, value) {
|
|
3388
|
+
const pattern = new RegExp(`^- \\*\\*${escapeRegExp(label)}\\*\\*:.*$`, "m");
|
|
3389
|
+
const line = `- **${label}**: ${value}`;
|
|
3390
|
+
if (pattern.test(content)) {
|
|
3391
|
+
return content.replace(pattern, line);
|
|
3392
|
+
}
|
|
3393
|
+
const heading = "## Promotion Tracking";
|
|
3394
|
+
if (content.includes(heading)) {
|
|
3395
|
+
return content.replace(heading, `${heading}
|
|
3396
|
+
|
|
3397
|
+
${line}`);
|
|
3398
|
+
}
|
|
3399
|
+
return `${content.trimEnd()}
|
|
3400
|
+
|
|
3401
|
+
${heading}
|
|
3402
|
+
|
|
3403
|
+
${line}
|
|
3404
|
+
`;
|
|
3405
|
+
}
|
|
3406
|
+
function escapeRegExp(value) {
|
|
3407
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3408
|
+
}
|
|
3265
3409
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3266
3410
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3267
3411
|
const candidates = [
|
|
3268
|
-
...explicitDocsDir ? [
|
|
3269
|
-
|
|
3270
|
-
|
|
3412
|
+
...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
|
|
3413
|
+
path13.resolve(cwd, "docs"),
|
|
3414
|
+
path13.resolve(cwd)
|
|
3271
3415
|
];
|
|
3272
3416
|
const endAt = Date.now() + timeoutMs;
|
|
3273
3417
|
while (Date.now() < endAt) {
|
|
@@ -3294,12 +3438,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3294
3438
|
return getConfig(cwd);
|
|
3295
3439
|
}
|
|
3296
3440
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3297
|
-
const featuresDir =
|
|
3441
|
+
const featuresDir = path13.join(docsDir, "features");
|
|
3298
3442
|
let max = 0;
|
|
3299
3443
|
const scanDirs = [];
|
|
3300
3444
|
if (projectType === "multi") {
|
|
3301
3445
|
scanDirs.push(
|
|
3302
|
-
...components.map((component) =>
|
|
3446
|
+
...components.map((component) => path13.join(featuresDir, component))
|
|
3303
3447
|
);
|
|
3304
3448
|
} else {
|
|
3305
3449
|
scanDirs.push(featuresDir);
|
|
@@ -3320,6 +3464,167 @@ async function getNextFeatureId(docsDir, projectType, components) {
|
|
|
3320
3464
|
const width = Math.max(3, String(next).length);
|
|
3321
3465
|
return `F${String(next).padStart(width, "0")}`;
|
|
3322
3466
|
}
|
|
3467
|
+
function ideaCommand(program2) {
|
|
3468
|
+
program2.command("idea <name>").description("Create a new indexed idea document").option("--component <component>", "Component name (optional)").option("--id <id>", "Idea ID (default: auto)").option("-d, --desc <description>", "Idea description for the document").option("--non-interactive", "Reserved for parity with other generators").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3469
|
+
try {
|
|
3470
|
+
const result = await runIdea(name, options);
|
|
3471
|
+
if (options.json) {
|
|
3472
|
+
console.log(
|
|
3473
|
+
JSON.stringify(
|
|
3474
|
+
{
|
|
3475
|
+
status: "ok",
|
|
3476
|
+
reasonCode: "IDEA_CREATED",
|
|
3477
|
+
ideaId: result.ideaId,
|
|
3478
|
+
ideaName: result.ideaName,
|
|
3479
|
+
component: result.component,
|
|
3480
|
+
ideaPath: result.ideaPath,
|
|
3481
|
+
ideaPathFromDocs: result.ideaPathFromDocs
|
|
3482
|
+
},
|
|
3483
|
+
null,
|
|
3484
|
+
2
|
|
3485
|
+
)
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
const config = await getConfig(process.cwd());
|
|
3490
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
3491
|
+
const cliError = toCliError(error);
|
|
3492
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
3493
|
+
if (options.json) {
|
|
3494
|
+
console.log(
|
|
3495
|
+
JSON.stringify({
|
|
3496
|
+
status: "error",
|
|
3497
|
+
reasonCode: cliError.code,
|
|
3498
|
+
error: cliError.message,
|
|
3499
|
+
suggestions
|
|
3500
|
+
})
|
|
3501
|
+
);
|
|
3502
|
+
process.exitCode = 1;
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
console.error(
|
|
3506
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
3507
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
3508
|
+
);
|
|
3509
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
3510
|
+
process.exitCode = 1;
|
|
3511
|
+
}
|
|
3512
|
+
});
|
|
3513
|
+
}
|
|
3514
|
+
async function runIdea(name, options) {
|
|
3515
|
+
const cwd = process.cwd();
|
|
3516
|
+
const config = await getConfig(cwd);
|
|
3517
|
+
if (!config) {
|
|
3518
|
+
throw createCliError(
|
|
3519
|
+
"DOCS_NOT_FOUND",
|
|
3520
|
+
tr(DEFAULT_LANG, "cli", "common.docsNotFound")
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
const { docsDir, projectType, lang } = config;
|
|
3524
|
+
const configuredComponents = resolveProjectComponents(
|
|
3525
|
+
projectType,
|
|
3526
|
+
config.components
|
|
3527
|
+
);
|
|
3528
|
+
assertValid(
|
|
3529
|
+
validateSafeNameWithLang(name, lang),
|
|
3530
|
+
tr(lang, "cli", "validation.context.ideaName"),
|
|
3531
|
+
lang
|
|
3532
|
+
);
|
|
3533
|
+
let component = (options.component || "").trim().toLowerCase();
|
|
3534
|
+
if (component && projectType === "single") {
|
|
3535
|
+
throw createCliError(
|
|
3536
|
+
"INVALID_ARGUMENT",
|
|
3537
|
+
"`--component` can only be used in multi mode."
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
if (projectType === "multi" && component) {
|
|
3541
|
+
assertAllowedComponent(component, configuredComponents);
|
|
3542
|
+
}
|
|
3543
|
+
return withFileLock(
|
|
3544
|
+
getDocsLockPath(docsDir),
|
|
3545
|
+
async () => {
|
|
3546
|
+
const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
|
|
3547
|
+
const ideasDir = path13.join(docsDir, "ideas");
|
|
3548
|
+
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3549
|
+
const ideaPath = path13.join(ideasDir, ideaFileName);
|
|
3550
|
+
if (await fs.pathExists(ideaPath)) {
|
|
3551
|
+
throw createCliError(
|
|
3552
|
+
"INVALID_ARGUMENT",
|
|
3553
|
+
tr(lang, "cli", "idea.fileExists", { path: ideaPath })
|
|
3554
|
+
);
|
|
3555
|
+
}
|
|
3556
|
+
const templatePath = path13.join(
|
|
3557
|
+
getTemplatesDir(),
|
|
3558
|
+
lang,
|
|
3559
|
+
"common",
|
|
3560
|
+
"ideas",
|
|
3561
|
+
"idea.md"
|
|
3562
|
+
);
|
|
3563
|
+
if (!await fs.pathExists(templatePath)) {
|
|
3564
|
+
throw createCliError(
|
|
3565
|
+
"DOCS_NOT_FOUND",
|
|
3566
|
+
tr(lang, "cli", "idea.templateNotFound")
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3569
|
+
await fs.mkdir(ideasDir, { recursive: true });
|
|
3570
|
+
const template = await fs.readFile(templatePath, "utf-8");
|
|
3571
|
+
const content = applyIdeaTemplate(template, {
|
|
3572
|
+
ideaId,
|
|
3573
|
+
name,
|
|
3574
|
+
description: options.desc || "",
|
|
3575
|
+
component: component || "-",
|
|
3576
|
+
created: getLocalDateString()
|
|
3577
|
+
});
|
|
3578
|
+
await fs.writeFile(ideaPath, content, "utf-8");
|
|
3579
|
+
if (!options.json) {
|
|
3580
|
+
console.log();
|
|
3581
|
+
console.log(chalk9.green(tr(lang, "cli", "idea.created", { path: ideaPath })));
|
|
3582
|
+
console.log();
|
|
3583
|
+
console.log(chalk9.blue(tr(lang, "cli", "idea.nextStepsTitle")));
|
|
3584
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps1")));
|
|
3585
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps2", { ideaId })));
|
|
3586
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps3")));
|
|
3587
|
+
console.log();
|
|
3588
|
+
}
|
|
3589
|
+
return {
|
|
3590
|
+
ideaId,
|
|
3591
|
+
ideaName: name,
|
|
3592
|
+
component: component || void 0,
|
|
3593
|
+
ideaPath,
|
|
3594
|
+
ideaPathFromDocs: path13.relative(docsDir, ideaPath)
|
|
3595
|
+
};
|
|
3596
|
+
},
|
|
3597
|
+
{ owner: "idea" }
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
function validateProvidedIdeaId(id, lang) {
|
|
3601
|
+
assertValid(
|
|
3602
|
+
validateIdeaIdWithLang(id, lang),
|
|
3603
|
+
tr(lang, "cli", "validation.context.ideaId"),
|
|
3604
|
+
lang
|
|
3605
|
+
);
|
|
3606
|
+
return id;
|
|
3607
|
+
}
|
|
3608
|
+
function applyIdeaTemplate(template, values) {
|
|
3609
|
+
return template.replaceAll("{idea-id}", values.ideaId).replaceAll("{idea-name}", values.name).replaceAll("{YYYY-MM-DD}", values.created).replaceAll("{{description}}", values.description).replaceAll("{component}", values.component);
|
|
3610
|
+
}
|
|
3611
|
+
async function getNextIdeaId(docsDir) {
|
|
3612
|
+
const ideasDir = path13.join(docsDir, "ideas");
|
|
3613
|
+
let max = 0;
|
|
3614
|
+
if (await fs.pathExists(ideasDir)) {
|
|
3615
|
+
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3616
|
+
for (const entry of entries) {
|
|
3617
|
+
if (!entry.isFile()) continue;
|
|
3618
|
+
const match = entry.name.match(/^I(\d+)-/);
|
|
3619
|
+
if (!match) continue;
|
|
3620
|
+
const num = parseInt(match[1], 10);
|
|
3621
|
+
if (num > max) max = num;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
const next = max + 1;
|
|
3625
|
+
const width = Math.max(3, String(next).length);
|
|
3626
|
+
return `I${String(next).padStart(width, "0")}`;
|
|
3627
|
+
}
|
|
3323
3628
|
var DefaultCommandAdapter = class {
|
|
3324
3629
|
execSync(command, options) {
|
|
3325
3630
|
return execSync(command, options);
|
|
@@ -3579,15 +3884,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3579
3884
|
2. \uB9AC\uBDF0 \uBC94\uC704\uB97C \uBD84\uB9AC\uD574 \uD655\uC778\uD558\uC138\uC694.
|
|
3580
3885
|
- main \uAE30\uC900: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3581
3886
|
- worktree \uAE30\uC900: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3582
|
-
3. \uAD6C\uD604\uC774 feature \uC758\uB3C4\uC5D0 \uB9DE\uB294\uC9C0 \uD3C9\uAC00\uD558\uC138\uC694. \uD2B9\uD788 \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, \`residualRisks\`\uB97C \uAD6C\uCCB4\uC801\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.
|
|
3583
|
-
4. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694.
|
|
3887
|
+
3. \uAD6C\uD604\uC774 feature \uC758\uB3C4\uC5D0 \uB9DE\uB294\uC9C0 \uD3C9\uAC00\uD558\uC138\uC694. \uD2B9\uD788 \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, \`residualRisks\`, \`approvalRationale\`\uB97C \uAD6C\uCCB4\uC801\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.
|
|
3888
|
+
4. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uC99D\uAC70\uC5D0\uB294 \uBC18\uB4DC\uC2DC \`baseSha\`, \`headSha\`, \`changedFiles\`, \`reviewedFiles\`, \`riskSummaries\`(blocking/important/minor)\uAC00 \uD3EC\uD568\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4.
|
|
3584
3889
|
5. \uAE30\uBCF8 \uBCA0\uC774\uC2A4\uB77C\uC778\uC740 '${fallbackText}'\uC774\uBA70, 'create-pr' \uBB38\uC11C\uC758 'Pre-PR \uAE30\uBCF8 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8' \uC139\uC158\uC744 \uC218\uD589\uD558\uC138\uC694.
|
|
3585
3890
|
6. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: ${skills.length > 0 ? skills.join(", ") : "\uC5C6\uC74C"} \uB85C \uC2EC\uD654 \uAC80\uD1A0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
|
|
3586
3891
|
7. \uCD94\uAC00 \uAC80\uC99D\uC774 \uAF2D \uD544\uC694\uD560 \uB54C\uB9CC audit/\uD0C0\uAE43 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694. \uBCC4\uB3C4 \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uCD94\uAC00 \uC0DD\uC131\uB3C4 \uAF2D \uD544\uC694\uD560 \uB54C\uB9CC \uD558\uC138\uC694.
|
|
3587
3892
|
8. \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uD55C\uB3C4\uC5D0 \uAC78\uB9AC\uBA74 \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uC5D0\uC11C \uB9AC\uBDF0\uB97C \uC774\uC5B4\uAC00\uACE0, \uC774\uBBF8 \uC218\uC9D1\uD55C \uACB0\uACFC\uB9CC \uC815\uB9AC\uD574\uB3C4 \uB429\uB2C8\uB2E4.
|
|
3588
3893
|
9. \uC2E4\uD589\uD55C \uBA85\uB839\uC774 \uC788\uC73C\uBA74 \`commandsExecuted\`\uC5D0 \uAE30\uB85D\uD558\uC138\uC694.
|
|
3589
|
-
10. \uC9C0\uC801\uC0AC\uD56D\uC774 \uB0A8\uC544 \uC788\uC73C\uBA74 \uBA3C\uC800 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested' \uB85C \uAE30\uB85D\uD558\uC138\uC694. \uB2E8, \`workflow.prePrReview.evidenceMode=any\` \uC774\uACE0 \uC2E4\uD589 \uC99D\uAC70 \uAC15\uC81C\uAC00 \uC5C6\uC73C\uBA74 \`--evidence\` \uC5C6\uC774 \uC9C1\uC811 \uAE30\uB85D\uD574\uB3C4 \uB429\uB2C8\uB2E4.
|
|
3590
|
-
11. \uC218\uC815/\uC7AC\uAC80\uC99D \uD6C4 \uCD5C\uC885 \uC2B9\uC778 \uC2DC\uC810\uC5D0 'npx lee-spec-kit pre-pr-review <feature> --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694.
|
|
3894
|
+
10. \uC9C0\uC801\uC0AC\uD56D\uC774 \uB0A8\uC544 \uC788\uC73C\uBA74 \uBA3C\uC800 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested' \uB85C \uAE30\uB85D\uD558\uC138\uC694. \uB2E8, \`workflow.prePrReview.evidenceMode=any\` \uC774\uACE0 \uC2E4\uD589 \uC99D\uAC70 \uAC15\uC81C\uAC00 \uC5C6\uC73C\uBA74 \`--evidence\` \uC5C6\uC774 \uC9C1\uC811 \uAE30\uB85D\uD574\uB3C4 \uB429\uB2C8\uB2E4.
|
|
3895
|
+
11. \uC218\uC815/\uC7AC\uAC80\uC99D \uD6C4 \uCD5C\uC885 \uC2B9\uC778 \uC2DC\uC810\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uAD6C\uC870\uD654\uB41C evidence\uC640 \uD568\uAED8 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694. approve\uB294 evidence \uC5C6\uC774 \uAE30\uB85D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`;
|
|
3591
3896
|
}
|
|
3592
3897
|
return `Conduct a pre-PR code review.
|
|
3593
3898
|
0. Reuse the existing helper/sub-agent for this feature review if one already exists. Default to a single helper agent.
|
|
@@ -3595,15 +3900,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3595
3900
|
2. Split and check the review scope.
|
|
3596
3901
|
- Main scope: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3597
3902
|
- Worktree scope: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3598
|
-
3. Evaluate whether the implementation actually fits the feature intent. Capture concrete \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, and \`
|
|
3599
|
-
4. Generate a 'review-trace.json' file for all changed files, including \`findingCount\`, \`blockingFindings\`,
|
|
3903
|
+
3. Evaluate whether the implementation actually fits the feature intent. Capture concrete \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, \`residualRisks\`, and \`approvalRationale\`, and explicitly set \`specAlignmentChecked\`.
|
|
3904
|
+
4. Generate a 'review-trace.json' file for all changed files, including \`baseSha\`, \`headSha\`, \`changedFiles\`, \`reviewedFiles\`, \`riskSummaries\` (blocking/important/minor), \`findingCount\`, \`blockingFindings\`, per-file risk/security/perf/maintainability evaluations, and specific \`fileLine\` locators.
|
|
3600
3905
|
5. The baseline is '${fallbackText}'. Always perform the 'Pre-PR Core Checklist' section of the 'create-pr' document.
|
|
3601
3906
|
6. Priority skills: ${skills.length > 0 ? skills.join(", ") : "None"} for deeper technical review.
|
|
3602
3907
|
7. Run extra audit/targeted verification only when the review truly needs more evidence. Spawn additional helper agents only when necessary.
|
|
3603
3908
|
8. If helper-agent quota is exhausted, continue the review in the main agent and just keep the evidence consistent.
|
|
3604
3909
|
9. Record commands in \`commandsExecuted\` only when you actually ran them.
|
|
3605
|
-
10. If unresolved findings remain, first record them with 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested'. When \`workflow.prePrReview.evidenceMode=any\` and execution evidence is not enforced, direct record mode without \`--evidence\` is also valid.
|
|
3606
|
-
11. After fixes and re-validation, run 'npx lee-spec-kit pre-pr-review <feature> --decision approve' for final pre-PR approval.
|
|
3910
|
+
10. If unresolved findings remain, first record them with 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested'. When \`workflow.prePrReview.evidenceMode=any\` and execution evidence is not enforced, direct record mode without \`--evidence\` is also valid.
|
|
3911
|
+
11. After fixes and re-validation, run 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision approve' for final pre-PR approval. Approve never records without structured evidence.`;
|
|
3607
3912
|
}
|
|
3608
3913
|
function getCodeReviewPrompt(lang) {
|
|
3609
3914
|
if (lang === "ko") {
|
|
@@ -3743,7 +4048,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
3743
4048
|
}
|
|
3744
4049
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
3745
4050
|
try {
|
|
3746
|
-
const raw =
|
|
4051
|
+
const raw = fs10.readFileSync(evidencePath, "utf-8");
|
|
3747
4052
|
const parsed = JSON.parse(raw);
|
|
3748
4053
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
3749
4054
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -3759,31 +4064,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
3759
4064
|
const candidates = [];
|
|
3760
4065
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
3761
4066
|
if (explicit && explicit !== "-") {
|
|
3762
|
-
if (
|
|
4067
|
+
if (path13.isAbsolute(explicit)) {
|
|
3763
4068
|
candidates.push(explicit);
|
|
3764
4069
|
} else {
|
|
3765
|
-
candidates.push(
|
|
3766
|
-
candidates.push(
|
|
4070
|
+
candidates.push(path13.resolve(feature.path, explicit));
|
|
4071
|
+
candidates.push(path13.resolve(docsRoot, explicit));
|
|
3767
4072
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
3768
4073
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
3769
4074
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
3770
4075
|
if (withoutDocsPrefix) {
|
|
3771
|
-
candidates.push(
|
|
4076
|
+
candidates.push(path13.resolve(docsRoot, withoutDocsPrefix));
|
|
3772
4077
|
}
|
|
3773
4078
|
}
|
|
3774
4079
|
}
|
|
3775
4080
|
}
|
|
3776
|
-
candidates.push(
|
|
3777
|
-
candidates.push(
|
|
4081
|
+
candidates.push(path13.join(feature.path, "review-trace.json"));
|
|
4082
|
+
candidates.push(path13.join(docsRoot, "review-trace.json"));
|
|
3778
4083
|
const seen = /* @__PURE__ */ new Set();
|
|
3779
4084
|
for (const candidate of candidates) {
|
|
3780
|
-
const abs =
|
|
4085
|
+
const abs = path13.resolve(candidate);
|
|
3781
4086
|
if (seen.has(abs)) continue;
|
|
3782
4087
|
seen.add(abs);
|
|
3783
|
-
if (!
|
|
4088
|
+
if (!fs10.existsSync(abs)) continue;
|
|
3784
4089
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
3785
4090
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
3786
|
-
const rel =
|
|
4091
|
+
const rel = path13.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
3787
4092
|
if (rel && !rel.startsWith("../")) {
|
|
3788
4093
|
return rel;
|
|
3789
4094
|
}
|
|
@@ -3824,8 +4129,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
3824
4129
|
}
|
|
3825
4130
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
3826
4131
|
if (!projectGitCwd) return null;
|
|
3827
|
-
const normalized =
|
|
3828
|
-
const marker = `${
|
|
4132
|
+
const normalized = path13.resolve(projectGitCwd);
|
|
4133
|
+
const marker = `${path13.sep}.worktrees${path13.sep}`;
|
|
3829
4134
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
3830
4135
|
if (markerIndex <= 0) return null;
|
|
3831
4136
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -3868,7 +4173,7 @@ function toTaskKey(rawTitle) {
|
|
|
3868
4173
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
3869
4174
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
3870
4175
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
3871
|
-
|
|
4176
|
+
path13.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
3872
4177
|
);
|
|
3873
4178
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
3874
4179
|
"diff",
|
|
@@ -3943,7 +4248,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
3943
4248
|
return { pass: true };
|
|
3944
4249
|
}
|
|
3945
4250
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
3946
|
-
const relativeDocsDir =
|
|
4251
|
+
const relativeDocsDir = path13.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
3947
4252
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
3948
4253
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
3949
4254
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -4325,9 +4630,9 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4325
4630
|
const isPrePrReviewCurrent = (f) => prePrReviewPolicy.enabled && workflowPolicy.requirePr && f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && !f.git.docsHasCommitRequiredChanges && !f.git.projectHasUncommittedChanges && (!isPrMetadataConfigured(f) || !f.pr.link) && !isPrePrReviewSatisfied(f, prePrReviewPolicy);
|
|
4326
4631
|
const isPrePrReviewMetadataMissing = (f) => isPrePrReviewCurrent(f) && !f.docs.prePrReviewFieldExists;
|
|
4327
4632
|
const isPrePrReviewFixRequired = (f) => isPrePrReviewCurrent(f) && !!f.prePrReview.decisionOutcome && f.prePrReview.decisionOutcome !== "approve";
|
|
4328
|
-
const isPrePrReviewRun = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status !== "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4329
|
-
const isPrePrReviewRunning = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status === "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4330
|
-
const isPrePrReviewRecord = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) &&
|
|
4633
|
+
const isPrePrReviewRun = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status !== "Running" && !resolvePrePrReviewEvidencePath(f);
|
|
4634
|
+
const isPrePrReviewRunning = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status === "Running" && !resolvePrePrReviewEvidencePath(f);
|
|
4635
|
+
const isPrePrReviewRecord = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && !!resolvePrePrReviewEvidencePath(f);
|
|
4331
4636
|
const getPrePrReviewMetadataActions = () => [
|
|
4332
4637
|
{
|
|
4333
4638
|
type: "instruction",
|
|
@@ -4371,7 +4676,7 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4371
4676
|
cmd: buildSelfCliCommand(buildPrePrReviewRunCommandArgs(f))
|
|
4372
4677
|
}
|
|
4373
4678
|
];
|
|
4374
|
-
const
|
|
4679
|
+
const getPrePrReviewInProgressActions = () => [
|
|
4375
4680
|
{
|
|
4376
4681
|
type: "instruction",
|
|
4377
4682
|
category: "pre_pr_review_run",
|
|
@@ -4909,15 +5214,15 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4909
5214
|
actions: (f) => getPrePrReviewRunActions(f)
|
|
4910
5215
|
},
|
|
4911
5216
|
{
|
|
4912
|
-
id: "
|
|
5217
|
+
id: "pre_pr_review_in_progress",
|
|
4913
5218
|
phase: "running",
|
|
4914
5219
|
owner: "subagent",
|
|
4915
5220
|
category: "pre_pr_review_run",
|
|
4916
5221
|
when: (f) => isPrePrReviewRunning(f),
|
|
4917
|
-
actions: () =>
|
|
5222
|
+
actions: () => getPrePrReviewInProgressActions()
|
|
4918
5223
|
},
|
|
4919
5224
|
{
|
|
4920
|
-
id: "
|
|
5225
|
+
id: "pre_pr_review_record_pending",
|
|
4921
5226
|
phase: "record",
|
|
4922
5227
|
owner: "main",
|
|
4923
5228
|
category: "pre_pr_review_record",
|
|
@@ -5559,17 +5864,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
5559
5864
|
}
|
|
5560
5865
|
}
|
|
5561
5866
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
5562
|
-
var WORKTREE_MARKER = `${
|
|
5867
|
+
var WORKTREE_MARKER = `${path13.sep}.worktrees${path13.sep}`;
|
|
5563
5868
|
function resetContextGitCaches() {
|
|
5564
5869
|
GIT_WORKTREE_CACHE.clear();
|
|
5565
5870
|
}
|
|
5566
5871
|
function isManagedWorktreePath(cwd) {
|
|
5567
5872
|
if (!cwd) return false;
|
|
5568
|
-
const normalized =
|
|
5873
|
+
const normalized = path13.resolve(cwd);
|
|
5569
5874
|
return normalized.includes(WORKTREE_MARKER);
|
|
5570
5875
|
}
|
|
5571
5876
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
5572
|
-
const normalized =
|
|
5877
|
+
const normalized = path13.resolve(cwd);
|
|
5573
5878
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
5574
5879
|
if (markerIndex <= 0) return normalized;
|
|
5575
5880
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -5588,7 +5893,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
5588
5893
|
}
|
|
5589
5894
|
function listGitWorktrees(ctx, cwd) {
|
|
5590
5895
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
5591
|
-
const cacheKey =
|
|
5896
|
+
const cacheKey = path13.resolve(topLevel);
|
|
5592
5897
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
5593
5898
|
if (cached) return cached;
|
|
5594
5899
|
try {
|
|
@@ -5689,12 +5994,12 @@ function countDocumentLines(content) {
|
|
|
5689
5994
|
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
5690
5995
|
return lines.length;
|
|
5691
5996
|
}
|
|
5692
|
-
function
|
|
5997
|
+
function escapeRegExp2(value) {
|
|
5693
5998
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5694
5999
|
}
|
|
5695
6000
|
function extractSpecValue(content, key) {
|
|
5696
6001
|
const regex = new RegExp(
|
|
5697
|
-
`^\\s*-\\s*\\*\\*${
|
|
6002
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
5698
6003
|
"m"
|
|
5699
6004
|
);
|
|
5700
6005
|
const match = content.match(regex);
|
|
@@ -5702,7 +6007,7 @@ function extractSpecValue(content, key) {
|
|
|
5702
6007
|
}
|
|
5703
6008
|
function hasSpecKey(content, key) {
|
|
5704
6009
|
const regex = new RegExp(
|
|
5705
|
-
`^\\s*-\\s*\\*\\*${
|
|
6010
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:`,
|
|
5706
6011
|
"m"
|
|
5707
6012
|
);
|
|
5708
6013
|
return regex.test(content);
|
|
@@ -5894,7 +6199,7 @@ function splitReviewLogSections(content, headerRegex) {
|
|
|
5894
6199
|
}
|
|
5895
6200
|
function collectStructuredReviewEntries(section, keys) {
|
|
5896
6201
|
const lines = section.split("\n");
|
|
5897
|
-
const escaped = keys.map((key) =>
|
|
6202
|
+
const escaped = keys.map((key) => escapeRegExp2(key));
|
|
5898
6203
|
const fieldRegex = new RegExp(
|
|
5899
6204
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*(.*)$`,
|
|
5900
6205
|
"i"
|
|
@@ -6076,17 +6381,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6076
6381
|
if (!evidencePath) return [];
|
|
6077
6382
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6078
6383
|
const candidates = /* @__PURE__ */ new Set();
|
|
6079
|
-
if (
|
|
6080
|
-
candidates.add(
|
|
6384
|
+
if (path13.isAbsolute(evidencePath)) {
|
|
6385
|
+
candidates.add(path13.resolve(evidencePath));
|
|
6081
6386
|
} else {
|
|
6082
|
-
candidates.add(
|
|
6083
|
-
candidates.add(
|
|
6084
|
-
candidates.add(
|
|
6387
|
+
candidates.add(path13.resolve(context.featurePath, evidencePath));
|
|
6388
|
+
candidates.add(path13.resolve(context.docsDir, evidencePath));
|
|
6389
|
+
candidates.add(path13.resolve(path13.dirname(context.docsDir), evidencePath));
|
|
6085
6390
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6086
6391
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6087
6392
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6088
6393
|
if (withoutDocsPrefix) {
|
|
6089
|
-
candidates.add(
|
|
6394
|
+
candidates.add(path13.resolve(context.docsDir, withoutDocsPrefix));
|
|
6090
6395
|
}
|
|
6091
6396
|
}
|
|
6092
6397
|
}
|
|
@@ -6148,13 +6453,13 @@ function parsePrLink(value) {
|
|
|
6148
6453
|
return trimmed;
|
|
6149
6454
|
}
|
|
6150
6455
|
function normalizeGitPath(value) {
|
|
6151
|
-
return value.split(
|
|
6456
|
+
return value.split(path13.sep).join("/");
|
|
6152
6457
|
}
|
|
6153
6458
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6154
|
-
const relativeDocsDir =
|
|
6459
|
+
const relativeDocsDir = path13.relative(projectGitCwd, docsDir);
|
|
6155
6460
|
if (!relativeDocsDir) return [];
|
|
6156
|
-
if (
|
|
6157
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6461
|
+
if (path13.isAbsolute(relativeDocsDir)) return [];
|
|
6462
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path13.sep}`)) {
|
|
6158
6463
|
return [];
|
|
6159
6464
|
}
|
|
6160
6465
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6192,7 +6497,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6192
6497
|
const seen = /* @__PURE__ */ new Set();
|
|
6193
6498
|
const out = [];
|
|
6194
6499
|
for (const name of names) {
|
|
6195
|
-
const candidate =
|
|
6500
|
+
const candidate = path13.resolve(projectRoot, ".worktrees", name);
|
|
6196
6501
|
if (seen.has(candidate)) continue;
|
|
6197
6502
|
seen.add(candidate);
|
|
6198
6503
|
out.push(candidate);
|
|
@@ -6206,7 +6511,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6206
6511
|
slug,
|
|
6207
6512
|
folderName
|
|
6208
6513
|
)) {
|
|
6209
|
-
if (!
|
|
6514
|
+
if (!fs10.existsSync(candidate)) continue;
|
|
6210
6515
|
return candidate;
|
|
6211
6516
|
}
|
|
6212
6517
|
return void 0;
|
|
@@ -6236,7 +6541,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6236
6541
|
slug,
|
|
6237
6542
|
folderName
|
|
6238
6543
|
)) {
|
|
6239
|
-
if (!
|
|
6544
|
+
if (!fs10.existsSync(candidate)) continue;
|
|
6240
6545
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6241
6546
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6242
6547
|
return {
|
|
@@ -6377,10 +6682,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6377
6682
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6378
6683
|
candidates.map((candidate) => {
|
|
6379
6684
|
if (!candidate) return "";
|
|
6380
|
-
if (!
|
|
6381
|
-
const relative =
|
|
6685
|
+
if (!path13.isAbsolute(candidate)) return candidate;
|
|
6686
|
+
const relative = path13.relative(projectGitCwd, candidate);
|
|
6382
6687
|
if (!relative) return "";
|
|
6383
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
6688
|
+
if (relative === ".." || relative.startsWith(`..${path13.sep}`))
|
|
6384
6689
|
return "";
|
|
6385
6690
|
return relative;
|
|
6386
6691
|
}).filter(Boolean)
|
|
@@ -6394,7 +6699,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6394
6699
|
if (cached) return [...cached];
|
|
6395
6700
|
const existing = [];
|
|
6396
6701
|
for (const candidate of normalizedCandidates) {
|
|
6397
|
-
if (await ctx.fs.pathExists(
|
|
6702
|
+
if (await ctx.fs.pathExists(path13.join(projectGitCwd, candidate))) {
|
|
6398
6703
|
existing.push(candidate);
|
|
6399
6704
|
}
|
|
6400
6705
|
}
|
|
@@ -6482,16 +6787,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6482
6787
|
const lang = options.lang;
|
|
6483
6788
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
6484
6789
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
6485
|
-
const folderName =
|
|
6790
|
+
const folderName = path13.basename(featurePath);
|
|
6486
6791
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6487
6792
|
const id = match?.[1];
|
|
6488
6793
|
const slug = match?.[2] || folderName;
|
|
6489
|
-
const specPath =
|
|
6490
|
-
const planPath =
|
|
6491
|
-
const tasksPath =
|
|
6492
|
-
const decisionsPath =
|
|
6493
|
-
const issueDocPath =
|
|
6494
|
-
const prDocPath =
|
|
6794
|
+
const specPath = path13.join(featurePath, "spec.md");
|
|
6795
|
+
const planPath = path13.join(featurePath, "plan.md");
|
|
6796
|
+
const tasksPath = path13.join(featurePath, "tasks.md");
|
|
6797
|
+
const decisionsPath = path13.join(featurePath, "decisions.md");
|
|
6798
|
+
const issueDocPath = path13.join(featurePath, "issue.md");
|
|
6799
|
+
const prDocPath = path13.join(featurePath, "pr.md");
|
|
6495
6800
|
let specStatus;
|
|
6496
6801
|
let issueNumber;
|
|
6497
6802
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -6753,7 +7058,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6753
7058
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
6754
7059
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
6755
7060
|
}
|
|
6756
|
-
const relativeFeaturePathFromDocs =
|
|
7061
|
+
const relativeFeaturePathFromDocs = path13.relative(
|
|
6757
7062
|
context.docsDir,
|
|
6758
7063
|
featurePath
|
|
6759
7064
|
);
|
|
@@ -7116,7 +7421,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7116
7421
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7117
7422
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7118
7423
|
return dirs.filter(
|
|
7119
|
-
(value) =>
|
|
7424
|
+
(value) => path13.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7120
7425
|
);
|
|
7121
7426
|
}
|
|
7122
7427
|
function normalizeRelPath(value) {
|
|
@@ -7257,7 +7562,7 @@ async function scanFeatures(ctx) {
|
|
|
7257
7562
|
if (config.projectType === "single") {
|
|
7258
7563
|
const featureDirs = await listFeatureDirs(
|
|
7259
7564
|
ctx,
|
|
7260
|
-
|
|
7565
|
+
path13.join(config.docsDir, "features")
|
|
7261
7566
|
);
|
|
7262
7567
|
componentFeatureDirs.set("single", featureDirs);
|
|
7263
7568
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7269,14 +7574,14 @@ async function scanFeatures(ctx) {
|
|
|
7269
7574
|
for (const component of components) {
|
|
7270
7575
|
const componentDirs = await listFeatureDirs(
|
|
7271
7576
|
ctx,
|
|
7272
|
-
|
|
7577
|
+
path13.join(config.docsDir, "features", component)
|
|
7273
7578
|
);
|
|
7274
7579
|
componentFeatureDirs.set(component, componentDirs);
|
|
7275
7580
|
allFeatureDirs.push(...componentDirs);
|
|
7276
7581
|
}
|
|
7277
7582
|
}
|
|
7278
7583
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7279
|
-
(dir) => normalizeRelPath(
|
|
7584
|
+
(dir) => normalizeRelPath(path13.relative(config.docsDir, dir))
|
|
7280
7585
|
);
|
|
7281
7586
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7282
7587
|
ctx,
|
|
@@ -7293,7 +7598,7 @@ async function scanFeatures(ctx) {
|
|
|
7293
7598
|
const parsed = await Promise.all(
|
|
7294
7599
|
target.dirs.map(async (dir) => {
|
|
7295
7600
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7296
|
-
|
|
7601
|
+
path13.relative(config.docsDir, dir)
|
|
7297
7602
|
);
|
|
7298
7603
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7299
7604
|
return parseFeature(
|
|
@@ -7345,8 +7650,8 @@ function statusCommand(program2) {
|
|
|
7345
7650
|
const cliError = toCliError(error);
|
|
7346
7651
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7347
7652
|
console.error(
|
|
7348
|
-
|
|
7349
|
-
|
|
7653
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7654
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7350
7655
|
);
|
|
7351
7656
|
printCliErrorSuggestions(suggestions, lang);
|
|
7352
7657
|
process.exitCode = 1;
|
|
@@ -7363,13 +7668,13 @@ async function runStatus(options) {
|
|
|
7363
7668
|
);
|
|
7364
7669
|
}
|
|
7365
7670
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7366
|
-
const featuresDir =
|
|
7671
|
+
const featuresDir = path13.join(docsDir, "features");
|
|
7367
7672
|
const scan = await scanFeatures(ctx);
|
|
7368
7673
|
const features = [];
|
|
7369
7674
|
const idMap = /* @__PURE__ */ new Map();
|
|
7370
7675
|
for (const f of scan.features) {
|
|
7371
7676
|
const id = f.id || "UNKNOWN";
|
|
7372
|
-
const relPath =
|
|
7677
|
+
const relPath = path13.relative(docsDir, f.path);
|
|
7373
7678
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7374
7679
|
idMap.get(id).push(relPath);
|
|
7375
7680
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7443,7 +7748,7 @@ async function runStatus(options) {
|
|
|
7443
7748
|
return;
|
|
7444
7749
|
}
|
|
7445
7750
|
if (features.length === 0) {
|
|
7446
|
-
console.log(
|
|
7751
|
+
console.log(chalk9.yellow(tr(lang, "cli", "status.noFeatures")));
|
|
7447
7752
|
return;
|
|
7448
7753
|
}
|
|
7449
7754
|
features.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -7453,14 +7758,14 @@ async function runStatus(options) {
|
|
|
7453
7758
|
console.log(header);
|
|
7454
7759
|
console.log(separator);
|
|
7455
7760
|
for (const f of features) {
|
|
7456
|
-
const statusColor = f.status === "WORKFLOW_DONE" ?
|
|
7761
|
+
const statusColor = f.status === "WORKFLOW_DONE" ? chalk9.green : f.status === "DONE" ? chalk9.cyan : f.status === "DOING" ? chalk9.yellow : chalk9.gray;
|
|
7457
7762
|
console.log(
|
|
7458
7763
|
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.step} | ${f.substate} | ${f.path} |`
|
|
7459
7764
|
);
|
|
7460
7765
|
}
|
|
7461
7766
|
console.log();
|
|
7462
7767
|
if (options.write) {
|
|
7463
|
-
const outputPath =
|
|
7768
|
+
const outputPath = path13.join(featuresDir, "status.md");
|
|
7464
7769
|
const date = getLocalDateString();
|
|
7465
7770
|
const content = [
|
|
7466
7771
|
"# Feature Status",
|
|
@@ -7477,22 +7782,22 @@ async function runStatus(options) {
|
|
|
7477
7782
|
].join("\n");
|
|
7478
7783
|
await ctx.fs.writeFile(outputPath, content, "utf-8");
|
|
7479
7784
|
console.log(
|
|
7480
|
-
|
|
7785
|
+
chalk9.green(tr(lang, "cli", "status.wrote", { path: outputPath }))
|
|
7481
7786
|
);
|
|
7482
7787
|
}
|
|
7483
7788
|
}
|
|
7484
|
-
function
|
|
7789
|
+
function escapeRegExp3(value) {
|
|
7485
7790
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7486
7791
|
}
|
|
7487
7792
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7488
7793
|
try {
|
|
7489
|
-
const specPath =
|
|
7794
|
+
const specPath = path13.join(featureDir, "spec.md");
|
|
7490
7795
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7491
7796
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7492
7797
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
7493
7798
|
for (const key of keys) {
|
|
7494
7799
|
const regex = new RegExp(
|
|
7495
|
-
`^\\s*-\\s*\\*\\*${
|
|
7800
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
7496
7801
|
"m"
|
|
7497
7802
|
);
|
|
7498
7803
|
const match = content.match(regex);
|
|
@@ -7514,15 +7819,15 @@ function updateCommand(program2) {
|
|
|
7514
7819
|
const config = await getConfig(process.cwd());
|
|
7515
7820
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
7516
7821
|
if (error instanceof Error && error.message === "canceled") {
|
|
7517
|
-
console.log(
|
|
7822
|
+
console.log(chalk9.yellow(`
|
|
7518
7823
|
${tr(lang, "cli", "common.canceled")}`));
|
|
7519
7824
|
return;
|
|
7520
7825
|
}
|
|
7521
7826
|
const cliError = toCliError(error);
|
|
7522
7827
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7523
7828
|
console.error(
|
|
7524
|
-
|
|
7525
|
-
|
|
7829
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7830
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7526
7831
|
);
|
|
7527
7832
|
printCliErrorSuggestions(suggestions, lang);
|
|
7528
7833
|
process.exitCode = 1;
|
|
@@ -7552,26 +7857,26 @@ async function runUpdate(options) {
|
|
|
7552
7857
|
const updateAgentsMd = options.agentsMd || !hasExplicitSelection;
|
|
7553
7858
|
const updateTemplates = options.templates || !hasExplicitSelection;
|
|
7554
7859
|
const agentsMode = options.skills && !options.agents ? "skills" : "all";
|
|
7555
|
-
console.log(
|
|
7556
|
-
console.log(
|
|
7860
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.start")));
|
|
7861
|
+
console.log(chalk9.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
|
|
7557
7862
|
console.log(
|
|
7558
|
-
|
|
7863
|
+
chalk9.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
|
|
7559
7864
|
);
|
|
7560
7865
|
console.log();
|
|
7561
7866
|
let updatedCount = 0;
|
|
7562
7867
|
if (updateAgents) {
|
|
7563
7868
|
if (agentsMode === "skills") {
|
|
7564
|
-
console.log(
|
|
7869
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingSkills")));
|
|
7565
7870
|
console.log(
|
|
7566
|
-
|
|
7871
|
+
chalk9.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
|
|
7567
7872
|
);
|
|
7568
|
-
console.log(
|
|
7873
|
+
console.log(chalk9.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
|
|
7569
7874
|
} else {
|
|
7570
|
-
console.log(
|
|
7875
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
7571
7876
|
}
|
|
7572
7877
|
if (agentsMode === "all") {
|
|
7573
|
-
const commonAgentsBase =
|
|
7574
|
-
const targetAgentsBase =
|
|
7878
|
+
const commonAgentsBase = path13.join(templatesDir, lang, "common", "agents");
|
|
7879
|
+
const targetAgentsBase = path13.join(docsDir, "agents");
|
|
7575
7880
|
const commonAgents = commonAgentsBase;
|
|
7576
7881
|
const targetAgents = targetAgentsBase;
|
|
7577
7882
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -7599,7 +7904,7 @@ async function runUpdate(options) {
|
|
|
7599
7904
|
updatedCount += count;
|
|
7600
7905
|
}
|
|
7601
7906
|
console.log(
|
|
7602
|
-
|
|
7907
|
+
chalk9.green(
|
|
7603
7908
|
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
7604
7909
|
)
|
|
7605
7910
|
);
|
|
@@ -7618,13 +7923,13 @@ async function runUpdate(options) {
|
|
|
7618
7923
|
}
|
|
7619
7924
|
}
|
|
7620
7925
|
if (updateTemplates) {
|
|
7621
|
-
console.log(
|
|
7622
|
-
console.log(
|
|
7926
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
7927
|
+
console.log(chalk9.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
|
|
7623
7928
|
}
|
|
7624
7929
|
const pruned = await pruneEngineManagedDocs(docsDir);
|
|
7625
7930
|
if (pruned.length > 0) {
|
|
7626
7931
|
console.log(
|
|
7627
|
-
|
|
7932
|
+
chalk9.gray(
|
|
7628
7933
|
` - ${tr(lang, "cli", "update.engineManagedPruned", {
|
|
7629
7934
|
count: pruned.length
|
|
7630
7935
|
})}`
|
|
@@ -7634,18 +7939,18 @@ async function runUpdate(options) {
|
|
|
7634
7939
|
console.log();
|
|
7635
7940
|
if (configBackfill.changed) {
|
|
7636
7941
|
console.log(
|
|
7637
|
-
|
|
7942
|
+
chalk9.gray(
|
|
7638
7943
|
` - ${tr(lang, "cli", "update.fileUpdated", { file: ".lee-spec-kit.json" })}`
|
|
7639
7944
|
)
|
|
7640
7945
|
);
|
|
7641
7946
|
console.log(
|
|
7642
|
-
|
|
7947
|
+
chalk9.gray(
|
|
7643
7948
|
` (${configBackfill.changedPaths.join(", ")})`
|
|
7644
7949
|
)
|
|
7645
7950
|
);
|
|
7646
7951
|
}
|
|
7647
7952
|
console.log(
|
|
7648
|
-
|
|
7953
|
+
chalk9.green(`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`)
|
|
7649
7954
|
);
|
|
7650
7955
|
},
|
|
7651
7956
|
{ owner: "update" }
|
|
@@ -7669,21 +7974,21 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
7669
7974
|
const targets = /* @__PURE__ */ new Set();
|
|
7670
7975
|
const docsRepo = config.docsRepo ?? "embedded";
|
|
7671
7976
|
if (docsRepo === "embedded") {
|
|
7672
|
-
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) ||
|
|
7673
|
-
targets.add(
|
|
7977
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path13.resolve(config.docsDir, "..");
|
|
7978
|
+
targets.add(path13.join(repoRoot, "AGENTS.md"));
|
|
7674
7979
|
return [...targets];
|
|
7675
7980
|
}
|
|
7676
|
-
targets.add(
|
|
7981
|
+
targets.add(path13.join(config.docsDir, "AGENTS.md"));
|
|
7677
7982
|
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
7678
7983
|
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
7679
7984
|
for (const rawRoot of rawRoots) {
|
|
7680
7985
|
const value = String(rawRoot || "").trim();
|
|
7681
7986
|
if (!value) continue;
|
|
7682
|
-
const resolved =
|
|
7987
|
+
const resolved = path13.resolve(baseDir, value);
|
|
7683
7988
|
if (!await fs.pathExists(resolved)) continue;
|
|
7684
7989
|
const stat = await fs.stat(resolved);
|
|
7685
7990
|
if (!stat.isDirectory()) continue;
|
|
7686
|
-
targets.add(
|
|
7991
|
+
targets.add(path13.join(resolved, "AGENTS.md"));
|
|
7687
7992
|
}
|
|
7688
7993
|
return [...targets];
|
|
7689
7994
|
}
|
|
@@ -7723,7 +8028,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
7723
8028
|
return [...deduped];
|
|
7724
8029
|
}
|
|
7725
8030
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
7726
|
-
const configPath =
|
|
8031
|
+
const configPath = path13.join(docsDir, ".lee-spec-kit.json");
|
|
7727
8032
|
if (!await fs.pathExists(configPath)) {
|
|
7728
8033
|
return { changed: false, changedPaths: [] };
|
|
7729
8034
|
}
|
|
@@ -7846,8 +8151,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7846
8151
|
const files = await fs.readdir(sourceDir);
|
|
7847
8152
|
let updatedCount = 0;
|
|
7848
8153
|
for (const file of files) {
|
|
7849
|
-
const sourcePath =
|
|
7850
|
-
const targetPath =
|
|
8154
|
+
const sourcePath = path13.join(sourceDir, file);
|
|
8155
|
+
const targetPath = path13.join(targetDir, file);
|
|
7851
8156
|
const stat = await fs.stat(sourcePath);
|
|
7852
8157
|
if (stat.isFile()) {
|
|
7853
8158
|
if (protectedFiles.has(file)) {
|
|
@@ -7865,7 +8170,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7865
8170
|
}
|
|
7866
8171
|
if (!force) {
|
|
7867
8172
|
console.log(
|
|
7868
|
-
|
|
8173
|
+
chalk9.yellow(
|
|
7869
8174
|
` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
|
|
7870
8175
|
)
|
|
7871
8176
|
);
|
|
@@ -7875,7 +8180,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7875
8180
|
if (shouldUpdate) {
|
|
7876
8181
|
await fs.writeFile(targetPath, sourceContent);
|
|
7877
8182
|
console.log(
|
|
7878
|
-
|
|
8183
|
+
chalk9.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
|
|
7879
8184
|
);
|
|
7880
8185
|
updatedCount++;
|
|
7881
8186
|
}
|
|
@@ -7930,7 +8235,7 @@ function extractPorcelainPaths(line) {
|
|
|
7930
8235
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
7931
8236
|
const top = getGitTopLevel2(docsDir);
|
|
7932
8237
|
if (!top) return null;
|
|
7933
|
-
const rel =
|
|
8238
|
+
const rel = path13.relative(top, docsDir) || ".";
|
|
7934
8239
|
try {
|
|
7935
8240
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
7936
8241
|
cwd: top,
|
|
@@ -7942,7 +8247,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
7942
8247
|
}
|
|
7943
8248
|
const ignoredRelPaths = new Set(
|
|
7944
8249
|
ignoredAbsPaths.map(
|
|
7945
|
-
(absPath) => normalizeGitPath2(
|
|
8250
|
+
(absPath) => normalizeGitPath2(path13.relative(top, absPath) || ".")
|
|
7946
8251
|
)
|
|
7947
8252
|
);
|
|
7948
8253
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -7980,7 +8285,7 @@ function configCommand(program2) {
|
|
|
7980
8285
|
if (error instanceof Error && error.message === "canceled") {
|
|
7981
8286
|
const config2 = await getConfig(process.cwd());
|
|
7982
8287
|
const lang2 = config2?.lang ?? DEFAULT_LANG;
|
|
7983
|
-
console.log(
|
|
8288
|
+
console.log(chalk9.yellow(`
|
|
7984
8289
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
7985
8290
|
return;
|
|
7986
8291
|
}
|
|
@@ -7989,8 +8294,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
7989
8294
|
const cliError = toCliError(error);
|
|
7990
8295
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7991
8296
|
console.error(
|
|
7992
|
-
|
|
7993
|
-
|
|
8297
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
8298
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7994
8299
|
);
|
|
7995
8300
|
printCliErrorSuggestions(suggestions, lang);
|
|
7996
8301
|
process.exitCode = 1;
|
|
@@ -8000,7 +8305,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
8000
8305
|
}
|
|
8001
8306
|
async function runConfig(options) {
|
|
8002
8307
|
const cwd = process.cwd();
|
|
8003
|
-
const targetCwd = options.dir ?
|
|
8308
|
+
const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
|
|
8004
8309
|
const config = await getConfig(targetCwd);
|
|
8005
8310
|
if (!config) {
|
|
8006
8311
|
throw createCliError(
|
|
@@ -8008,13 +8313,13 @@ async function runConfig(options) {
|
|
|
8008
8313
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
8009
8314
|
);
|
|
8010
8315
|
}
|
|
8011
|
-
const configPath =
|
|
8316
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
8012
8317
|
if (!options.projectRoot) {
|
|
8013
8318
|
console.log();
|
|
8014
|
-
console.log(
|
|
8319
|
+
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
8015
8320
|
console.log();
|
|
8016
8321
|
console.log(
|
|
8017
|
-
|
|
8322
|
+
chalk9.gray(
|
|
8018
8323
|
` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
|
|
8019
8324
|
)
|
|
8020
8325
|
);
|
|
@@ -8031,7 +8336,7 @@ async function runConfig(options) {
|
|
|
8031
8336
|
const configFile = await fs.readJson(configPath);
|
|
8032
8337
|
if (configFile.docsRepo !== "standalone") {
|
|
8033
8338
|
console.log(
|
|
8034
|
-
|
|
8339
|
+
chalk9.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
|
|
8035
8340
|
);
|
|
8036
8341
|
return;
|
|
8037
8342
|
}
|
|
@@ -8081,7 +8386,7 @@ async function runConfig(options) {
|
|
|
8081
8386
|
currentRoot[targetComponent] = projectRoot;
|
|
8082
8387
|
configFile.projectRoot = currentRoot;
|
|
8083
8388
|
console.log(
|
|
8084
|
-
|
|
8389
|
+
chalk9.green(
|
|
8085
8390
|
tr(config.lang, "cli", "config.projectRootSet", {
|
|
8086
8391
|
repo: targetComponent.toUpperCase(),
|
|
8087
8392
|
path: projectRoot
|
|
@@ -8097,7 +8402,7 @@ async function runConfig(options) {
|
|
|
8097
8402
|
}
|
|
8098
8403
|
configFile.projectRoot = projectRoot;
|
|
8099
8404
|
console.log(
|
|
8100
|
-
|
|
8405
|
+
chalk9.green(
|
|
8101
8406
|
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
8102
8407
|
path: projectRoot
|
|
8103
8408
|
})
|
|
@@ -8114,47 +8419,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8114
8419
|
{
|
|
8115
8420
|
id: "agents",
|
|
8116
8421
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8117
|
-
relativePath: (_, lang) =>
|
|
8422
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "agents.md")
|
|
8118
8423
|
},
|
|
8119
8424
|
{
|
|
8120
8425
|
id: "git-workflow",
|
|
8121
8426
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8122
|
-
relativePath: (_, lang) =>
|
|
8427
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "git-workflow.md")
|
|
8123
8428
|
},
|
|
8124
8429
|
{
|
|
8125
8430
|
id: "issue-doc",
|
|
8126
8431
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8127
|
-
relativePath: (_, lang) =>
|
|
8432
|
+
relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8128
8433
|
},
|
|
8129
8434
|
{
|
|
8130
8435
|
id: "pr-doc",
|
|
8131
8436
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8132
|
-
relativePath: (_, lang) =>
|
|
8437
|
+
relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8133
8438
|
},
|
|
8134
8439
|
{
|
|
8135
8440
|
id: "create-feature",
|
|
8136
8441
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8137
|
-
relativePath: (_, lang) =>
|
|
8442
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8138
8443
|
},
|
|
8139
8444
|
{
|
|
8140
8445
|
id: "execute-task",
|
|
8141
8446
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8142
|
-
relativePath: (_, lang) =>
|
|
8447
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8143
8448
|
},
|
|
8144
8449
|
{
|
|
8145
8450
|
id: "create-issue",
|
|
8146
8451
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8147
|
-
relativePath: (_, lang) =>
|
|
8452
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8148
8453
|
},
|
|
8149
8454
|
{
|
|
8150
8455
|
id: "create-pr",
|
|
8151
8456
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8152
|
-
relativePath: (_, lang) =>
|
|
8457
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8153
8458
|
},
|
|
8154
8459
|
{
|
|
8155
8460
|
id: "split-feature",
|
|
8156
8461
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8157
|
-
relativePath: (_, lang) =>
|
|
8462
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8158
8463
|
}
|
|
8159
8464
|
];
|
|
8160
8465
|
var DOC_FOLLOWUPS = {
|
|
@@ -8251,7 +8556,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8251
8556
|
id: doc.id,
|
|
8252
8557
|
title: doc.title[lang],
|
|
8253
8558
|
relativePath,
|
|
8254
|
-
absolutePath:
|
|
8559
|
+
absolutePath: path13.join(templatesDir, relativePath)
|
|
8255
8560
|
};
|
|
8256
8561
|
});
|
|
8257
8562
|
}
|
|
@@ -8672,7 +8977,7 @@ function getActionExecutionMetadata(action) {
|
|
|
8672
8977
|
return {
|
|
8673
8978
|
handoffOnly: true,
|
|
8674
8979
|
advancesWorkflow: false,
|
|
8675
|
-
nextMainState: "
|
|
8980
|
+
nextMainState: "pre_pr_review_in_progress"
|
|
8676
8981
|
};
|
|
8677
8982
|
}
|
|
8678
8983
|
return null;
|
|
@@ -8830,7 +9135,7 @@ function buildDelegatedActionContract(state) {
|
|
|
8830
9135
|
const feature = state.matchedFeature;
|
|
8831
9136
|
const substateId = feature?.currentSubstateId;
|
|
8832
9137
|
if (!feature || !substateId) return null;
|
|
8833
|
-
if (substateId === "pre_pr_review_run" || substateId === "
|
|
9138
|
+
if (substateId === "pre_pr_review_run" || substateId === "pre_pr_review_in_progress") {
|
|
8834
9139
|
return {
|
|
8835
9140
|
required: true,
|
|
8836
9141
|
mode: "command",
|
|
@@ -8839,8 +9144,8 @@ function buildDelegatedActionContract(state) {
|
|
|
8839
9144
|
delegatedWorkRequired: true,
|
|
8840
9145
|
handoffOnly: true,
|
|
8841
9146
|
advancesWorkflow: false,
|
|
8842
|
-
doNotReapproveSameLabel: substateId === "
|
|
8843
|
-
nextMainState: "
|
|
9147
|
+
doNotReapproveSameLabel: substateId === "pre_pr_review_in_progress",
|
|
9148
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
8844
9149
|
reuseKey: `pre-pr:${feature.folderName}`,
|
|
8845
9150
|
evidenceFile: "review-trace.json",
|
|
8846
9151
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
@@ -8851,7 +9156,7 @@ function buildDelegatedActionContract(state) {
|
|
|
8851
9156
|
),
|
|
8852
9157
|
approve: buildPrePrRecordCommand(feature, "approve")
|
|
8853
9158
|
},
|
|
8854
|
-
guidance: substateId === "
|
|
9159
|
+
guidance: substateId === "pre_pr_review_in_progress" ? "A pre-PR review is already in progress. Reuse or resume the delegated review, generate structured review evidence, then record the result with pre-pr-review. Do not re-approve the same label." : "After approval, spawn_agent first and hand off the delegated pre-PR review. Handoff-only execution only prepares the review session; continue the delegated review immediately after approval."
|
|
8855
9160
|
};
|
|
8856
9161
|
}
|
|
8857
9162
|
if (substateId === "code_review_run" || substateId === "code_review_running") {
|
|
@@ -9083,12 +9388,12 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
9083
9388
|
if (suggestionOptions.length === 0) return;
|
|
9084
9389
|
const finalPrompt = buildSuggestionFinalPrompt(lang, suggestionOptions);
|
|
9085
9390
|
console.log(
|
|
9086
|
-
|
|
9391
|
+
chalk9.green(chalk9.bold(`\u{1F449} ${tr(lang, "cli", "context.suggestionHeader")}`))
|
|
9087
9392
|
);
|
|
9088
9393
|
suggestionOptions.forEach((option) => {
|
|
9089
9394
|
console.log(` ${option.label}: ${option.summary}`);
|
|
9090
9395
|
console.log(
|
|
9091
|
-
|
|
9396
|
+
chalk9.gray(
|
|
9092
9397
|
` \u21B3 ${tr(lang, "cli", "context.suggestionCommandHint", {
|
|
9093
9398
|
command: option.command
|
|
9094
9399
|
})}`
|
|
@@ -9096,7 +9401,7 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
9096
9401
|
);
|
|
9097
9402
|
});
|
|
9098
9403
|
if (finalPrompt) {
|
|
9099
|
-
console.log(
|
|
9404
|
+
console.log(chalk9.cyan(` \u21B3 ${finalPrompt}`));
|
|
9100
9405
|
}
|
|
9101
9406
|
}
|
|
9102
9407
|
function buildRequiredDocHints(actionOptions) {
|
|
@@ -9288,7 +9593,7 @@ function getApprovalSessionId() {
|
|
|
9288
9593
|
function getApprovalTicketPaths(config) {
|
|
9289
9594
|
return {
|
|
9290
9595
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
9291
|
-
legacyPath:
|
|
9596
|
+
legacyPath: path13.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
9292
9597
|
};
|
|
9293
9598
|
}
|
|
9294
9599
|
async function loadApprovalTicketStore(storePath) {
|
|
@@ -9302,7 +9607,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9302
9607
|
}
|
|
9303
9608
|
}
|
|
9304
9609
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9305
|
-
await fs.ensureDir(
|
|
9610
|
+
await fs.ensureDir(path13.dirname(storePath));
|
|
9306
9611
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9307
9612
|
}
|
|
9308
9613
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -9633,10 +9938,10 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9633
9938
|
return;
|
|
9634
9939
|
}
|
|
9635
9940
|
console.log();
|
|
9636
|
-
console.log(
|
|
9637
|
-
console.log(
|
|
9941
|
+
console.log(chalk9.green(`\u2705 Approved option: ${parsedLabel}`));
|
|
9942
|
+
console.log(chalk9.gray(` - Action: ${freshSelected.detail}`));
|
|
9638
9943
|
if (userRequest) {
|
|
9639
|
-
console.log(
|
|
9944
|
+
console.log(chalk9.gray(` - User request: ${userRequest}`));
|
|
9640
9945
|
}
|
|
9641
9946
|
if (selectedAction.type === "command") {
|
|
9642
9947
|
const selectedComponent = selectionOptions.component || "";
|
|
@@ -9647,24 +9952,24 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9647
9952
|
`--ticket ${ticket.token}`
|
|
9648
9953
|
);
|
|
9649
9954
|
console.log(
|
|
9650
|
-
|
|
9955
|
+
chalk9.gray(
|
|
9651
9956
|
` - Ticket: ${ticket.token} (expires: ${ticket.expiresAt})`
|
|
9652
9957
|
)
|
|
9653
9958
|
);
|
|
9654
9959
|
} else {
|
|
9655
9960
|
executeCommand = executeCommand.replace(" [--ticket <TICKET>]", "");
|
|
9656
9961
|
}
|
|
9657
|
-
console.log(
|
|
9962
|
+
console.log(chalk9.gray(` - Run with: ${executeCommand}`));
|
|
9658
9963
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9659
9964
|
console.log(
|
|
9660
|
-
|
|
9965
|
+
chalk9.gray(
|
|
9661
9966
|
" - This command prepares a handoff only; complete the delegated work and update workflow evidence before re-running context."
|
|
9662
9967
|
)
|
|
9663
9968
|
);
|
|
9664
9969
|
}
|
|
9665
9970
|
} else {
|
|
9666
9971
|
console.log(
|
|
9667
|
-
|
|
9972
|
+
chalk9.gray(" - Instruction-only action (no command execution).")
|
|
9668
9973
|
);
|
|
9669
9974
|
}
|
|
9670
9975
|
console.log();
|
|
@@ -9715,19 +10020,19 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9715
10020
|
}
|
|
9716
10021
|
console.log();
|
|
9717
10022
|
console.log(
|
|
9718
|
-
|
|
10023
|
+
chalk9.yellow(`\u26A0\uFE0F Approved label ${parsedLabel} is instruction-only.`)
|
|
9719
10024
|
);
|
|
9720
10025
|
if (userRequest) {
|
|
9721
|
-
console.log(
|
|
10026
|
+
console.log(chalk9.gray(` User request: ${userRequest}`));
|
|
9722
10027
|
}
|
|
9723
|
-
console.log(
|
|
10028
|
+
console.log(chalk9.gray(` ${selectedAction.message}`));
|
|
9724
10029
|
console.log();
|
|
9725
10030
|
return;
|
|
9726
10031
|
}
|
|
9727
10032
|
if (!jsonMode) {
|
|
9728
10033
|
console.log();
|
|
9729
|
-
console.log(
|
|
9730
|
-
console.log(
|
|
10034
|
+
console.log(chalk9.blue(`\u25B6 Executing option ${parsedLabel}...`));
|
|
10035
|
+
console.log(chalk9.gray(` ${selectedAction.cmd}`));
|
|
9731
10036
|
console.log();
|
|
9732
10037
|
}
|
|
9733
10038
|
try {
|
|
@@ -9789,14 +10094,14 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9789
10094
|
}
|
|
9790
10095
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9791
10096
|
console.log(
|
|
9792
|
-
|
|
10097
|
+
chalk9.yellow(
|
|
9793
10098
|
"Prepared handoff only. Complete the delegated work and update workflow evidence before re-running context."
|
|
9794
10099
|
)
|
|
9795
10100
|
);
|
|
9796
10101
|
console.log();
|
|
9797
10102
|
return;
|
|
9798
10103
|
}
|
|
9799
|
-
console.log(
|
|
10104
|
+
console.log(chalk9.green(`\u2705 Executed option ${parsedLabel}.`));
|
|
9800
10105
|
console.log();
|
|
9801
10106
|
} catch (error) {
|
|
9802
10107
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -9844,8 +10149,8 @@ function contextCommand(program2) {
|
|
|
9844
10149
|
);
|
|
9845
10150
|
} else {
|
|
9846
10151
|
console.error(
|
|
9847
|
-
|
|
9848
|
-
|
|
10152
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
10153
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
9849
10154
|
);
|
|
9850
10155
|
printCliErrorSuggestions(suggestions, lang);
|
|
9851
10156
|
}
|
|
@@ -10214,11 +10519,11 @@ async function runContext(featureName, options) {
|
|
|
10214
10519
|
return;
|
|
10215
10520
|
}
|
|
10216
10521
|
console.log();
|
|
10217
|
-
console.log(
|
|
10522
|
+
console.log(chalk9.bold(tr(lang, "cli", "context.header")));
|
|
10218
10523
|
if (config.projectType === "single") {
|
|
10219
10524
|
if (state.branches.project.single) {
|
|
10220
10525
|
console.log(
|
|
10221
|
-
|
|
10526
|
+
chalk9.gray(
|
|
10222
10527
|
` (Detected from Project Branch: ${state.branches.project.single})`
|
|
10223
10528
|
)
|
|
10224
10529
|
);
|
|
@@ -10227,7 +10532,7 @@ async function runContext(featureName, options) {
|
|
|
10227
10532
|
const branchName = state.branches.project[selectedComponent] || "";
|
|
10228
10533
|
if (branchName) {
|
|
10229
10534
|
console.log(
|
|
10230
|
-
|
|
10535
|
+
chalk9.gray(
|
|
10231
10536
|
` (Detected from Project Branch: ${selectedComponent.toUpperCase()} ${branchName})`
|
|
10232
10537
|
)
|
|
10233
10538
|
);
|
|
@@ -10236,37 +10541,37 @@ async function runContext(featureName, options) {
|
|
|
10236
10541
|
const parts = Object.entries(state.branches.project).filter(([key, value]) => key !== "single" && !!value).map(([key, value]) => `${key.toUpperCase()} ${value}`);
|
|
10237
10542
|
if (parts.length > 0) {
|
|
10238
10543
|
console.log(
|
|
10239
|
-
|
|
10544
|
+
chalk9.gray(` (Detected from Project Branch: ${parts.join(" / ")})`)
|
|
10240
10545
|
);
|
|
10241
10546
|
}
|
|
10242
10547
|
}
|
|
10243
10548
|
if (config.docsRepo === "standalone" && state.branches.docs) {
|
|
10244
|
-
console.log(
|
|
10549
|
+
console.log(chalk9.gray(` (Docs Branch: ${state.branches.docs})`));
|
|
10245
10550
|
}
|
|
10246
|
-
console.log(
|
|
10551
|
+
console.log(chalk9.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
10247
10552
|
console.log();
|
|
10248
10553
|
if (state.features.length === 0) {
|
|
10249
|
-
console.log(
|
|
10554
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10250
10555
|
console.log();
|
|
10251
10556
|
printSuggestionOptions(lang, suggestionOptions);
|
|
10252
10557
|
console.log();
|
|
10253
10558
|
return;
|
|
10254
10559
|
}
|
|
10255
10560
|
if (state.warnings.length > 0) {
|
|
10256
|
-
console.log(
|
|
10257
|
-
state.warnings.forEach((w) => console.log(
|
|
10561
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.envWarnings")));
|
|
10562
|
+
state.warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
10258
10563
|
console.log();
|
|
10259
10564
|
}
|
|
10260
10565
|
if (state.targetFeatures.length === 0) {
|
|
10261
|
-
console.log(
|
|
10566
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10262
10567
|
if (state.status === "no_open") {
|
|
10263
10568
|
console.log(
|
|
10264
|
-
|
|
10569
|
+
chalk9.gray(
|
|
10265
10570
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10266
10571
|
)
|
|
10267
10572
|
);
|
|
10268
10573
|
console.log(
|
|
10269
|
-
|
|
10574
|
+
chalk9.gray(
|
|
10270
10575
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10271
10576
|
)
|
|
10272
10577
|
);
|
|
@@ -10279,7 +10584,7 @@ async function runContext(featureName, options) {
|
|
|
10279
10584
|
if (state.targetFeatures.length > 1) {
|
|
10280
10585
|
if (state.selectionMode === "open") {
|
|
10281
10586
|
console.log(
|
|
10282
|
-
|
|
10587
|
+
chalk9.gray(
|
|
10283
10588
|
` ${tr(lang, "cli", "context.openFallbackSummary", {
|
|
10284
10589
|
inProgress: state.inProgressFeatures.length,
|
|
10285
10590
|
readyToClose: state.readyToCloseFeatures.length,
|
|
@@ -10291,7 +10596,7 @@ async function runContext(featureName, options) {
|
|
|
10291
10596
|
}
|
|
10292
10597
|
if (state.selectionMode === "open") {
|
|
10293
10598
|
console.log(
|
|
10294
|
-
|
|
10599
|
+
chalk9.blue(
|
|
10295
10600
|
`\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${state.inProgressFeatures.length})`
|
|
10296
10601
|
)
|
|
10297
10602
|
);
|
|
@@ -10303,14 +10608,14 @@ async function runContext(featureName, options) {
|
|
|
10303
10608
|
workflowPolicy,
|
|
10304
10609
|
prePrReviewPolicy
|
|
10305
10610
|
);
|
|
10306
|
-
const typeStr = config.projectType === "multi" ?
|
|
10611
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10307
10612
|
console.log(
|
|
10308
|
-
` \u2022 ${
|
|
10613
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10309
10614
|
);
|
|
10310
10615
|
});
|
|
10311
10616
|
console.log();
|
|
10312
10617
|
console.log(
|
|
10313
|
-
|
|
10618
|
+
chalk9.blue(
|
|
10314
10619
|
`\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${state.readyToCloseFeatures.length})`
|
|
10315
10620
|
)
|
|
10316
10621
|
);
|
|
@@ -10322,14 +10627,14 @@ async function runContext(featureName, options) {
|
|
|
10322
10627
|
workflowPolicy,
|
|
10323
10628
|
prePrReviewPolicy
|
|
10324
10629
|
);
|
|
10325
|
-
const typeStr = config.projectType === "multi" ?
|
|
10630
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10326
10631
|
console.log(
|
|
10327
|
-
` \u2022 ${
|
|
10632
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10328
10633
|
);
|
|
10329
10634
|
});
|
|
10330
10635
|
} else {
|
|
10331
10636
|
const title = state.selectionMode === "all" ? `\u{1F539} ${state.targetFeatures.length} Features:` : state.selectionMode === "done" ? `\u{1F539} ${state.targetFeatures.length} Done Features:` : `\u{1F539} ${state.targetFeatures.length} Features Detected:`;
|
|
10332
|
-
console.log(
|
|
10637
|
+
console.log(chalk9.blue(title));
|
|
10333
10638
|
console.log();
|
|
10334
10639
|
state.targetFeatures.forEach((f2) => {
|
|
10335
10640
|
const stepName2 = getListLabel(
|
|
@@ -10339,24 +10644,24 @@ async function runContext(featureName, options) {
|
|
|
10339
10644
|
workflowPolicy,
|
|
10340
10645
|
prePrReviewPolicy
|
|
10341
10646
|
);
|
|
10342
|
-
const typeStr = config.projectType === "multi" ?
|
|
10647
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10343
10648
|
console.log(
|
|
10344
|
-
` \u2022 ${
|
|
10649
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10345
10650
|
);
|
|
10346
10651
|
});
|
|
10347
10652
|
}
|
|
10348
10653
|
console.log();
|
|
10349
|
-
console.log(
|
|
10654
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.tipDetails")));
|
|
10350
10655
|
const selectorTip = config.projectType === "multi" ? selectedComponent ? ` $ npx lee-spec-kit context <slug|F001|F001-slug> --component ${selectedComponent}` : " $ npx lee-spec-kit context <slug|F001|F001-slug> [--component <component>]" : " $ npx lee-spec-kit context <slug|F001|F001-slug>";
|
|
10351
|
-
console.log(
|
|
10656
|
+
console.log(chalk9.gray(selectorTip));
|
|
10352
10657
|
if (state.selectionMode === "open") {
|
|
10353
10658
|
console.log(
|
|
10354
|
-
|
|
10659
|
+
chalk9.gray(
|
|
10355
10660
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10356
10661
|
)
|
|
10357
10662
|
);
|
|
10358
10663
|
console.log(
|
|
10359
|
-
|
|
10664
|
+
chalk9.gray(
|
|
10360
10665
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10361
10666
|
)
|
|
10362
10667
|
);
|
|
@@ -10368,47 +10673,47 @@ async function runContext(featureName, options) {
|
|
|
10368
10673
|
}
|
|
10369
10674
|
const f = state.targetFeatures[0];
|
|
10370
10675
|
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
10371
|
-
const checkTag = (requiresUserCheck) => requiresUserCheck ?
|
|
10676
|
+
const checkTag = (requiresUserCheck) => requiresUserCheck ? chalk9.yellow(tr(lang, "cli", "context.checkRequired")) : "";
|
|
10372
10677
|
const hasCheckAction = approvalRequired;
|
|
10373
10678
|
console.log(
|
|
10374
|
-
`\u{1F539} Feature: ${
|
|
10679
|
+
`\u{1F539} Feature: ${chalk9.bold(f.folderName)} ${config.projectType === "multi" ? chalk9.cyan(`(${f.type})`) : ""}`
|
|
10375
10680
|
);
|
|
10376
10681
|
console.log(
|
|
10377
|
-
` \u2022 Completion: ${f.completion.implementationDone ?
|
|
10682
|
+
` \u2022 Completion: ${f.completion.implementationDone ? chalk9.green("Implementation \u2705") : chalk9.gray("Implementation \u25EF")} / ${f.completion.workflowDone ? chalk9.green("Workflow \u2705") : chalk9.yellow("Workflow \u25EF")}`
|
|
10378
10683
|
);
|
|
10379
10684
|
if (f.issueNumber) {
|
|
10380
10685
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10381
10686
|
}
|
|
10382
|
-
console.log(` \u2022 Path: ${
|
|
10687
|
+
console.log(` \u2022 Path: ${path13.relative(cwd, f.path)}`);
|
|
10383
10688
|
if (f.git.projectBranch) {
|
|
10384
10689
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10385
10690
|
}
|
|
10386
10691
|
console.log();
|
|
10387
10692
|
console.log(
|
|
10388
|
-
`\u{1F539} Progress: ${
|
|
10693
|
+
`\u{1F539} Progress: ${chalk9.yellow(`Step ${f.currentStep}. ${stepName}`)}`
|
|
10389
10694
|
);
|
|
10390
10695
|
if (f.activeTask) {
|
|
10391
10696
|
console.log(
|
|
10392
|
-
` \u2022 Active Task: ${
|
|
10697
|
+
` \u2022 Active Task: ${chalk9.yellow(`[${f.activeTask.status}]`)} ${f.activeTask.title}`
|
|
10393
10698
|
);
|
|
10394
10699
|
} else if (f.nextTodoTask && f.currentStep === 10) {
|
|
10395
10700
|
console.log(
|
|
10396
|
-
` \u2022 Next TODO: ${
|
|
10701
|
+
` \u2022 Next TODO: ${chalk9.gray(`[${f.nextTodoTask.status}]`)} ${f.nextTodoTask.title}`
|
|
10397
10702
|
);
|
|
10398
10703
|
}
|
|
10399
10704
|
printChecklist(f, stepDefinitions);
|
|
10400
10705
|
if (f.warnings.length > 0) {
|
|
10401
10706
|
console.log();
|
|
10402
|
-
console.log(
|
|
10403
|
-
f.warnings.forEach((w) => console.log(
|
|
10707
|
+
console.log(chalk9.yellow("\u26A0\uFE0F Feature Warnings:"));
|
|
10708
|
+
f.warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
10404
10709
|
}
|
|
10405
10710
|
console.log();
|
|
10406
|
-
console.log(
|
|
10711
|
+
console.log(chalk9.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
10407
10712
|
if (hasCheckAction) {
|
|
10408
|
-
console.log(
|
|
10713
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.checkPolicyHint")));
|
|
10409
10714
|
}
|
|
10410
10715
|
if (!f.actions || f.actions.length === 0) {
|
|
10411
|
-
console.log(`\u{1F449} Next Action: ${
|
|
10716
|
+
console.log(`\u{1F449} Next Action: ${chalk9.green(chalk9.bold(f.nextAction))}`);
|
|
10412
10717
|
console.log();
|
|
10413
10718
|
return;
|
|
10414
10719
|
}
|
|
@@ -10421,7 +10726,7 @@ async function runContext(featureName, options) {
|
|
|
10421
10726
|
f.currentSubstateOwner
|
|
10422
10727
|
);
|
|
10423
10728
|
const showOptionLabels = hasCheckAction;
|
|
10424
|
-
console.log(
|
|
10729
|
+
console.log(chalk9.green(chalk9.bold("\u{1F449} Next Options (Atomic):")));
|
|
10425
10730
|
let hasDocsCommand = false;
|
|
10426
10731
|
actionOptions.forEach((option) => {
|
|
10427
10732
|
const requiresCheck = option.action.requiresUserCheck;
|
|
@@ -10434,26 +10739,26 @@ async function runContext(featureName, options) {
|
|
|
10434
10739
|
});
|
|
10435
10740
|
if (hasDocsCommand) {
|
|
10436
10741
|
console.log(
|
|
10437
|
-
|
|
10742
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.tipDocsCommitRules")}`)
|
|
10438
10743
|
);
|
|
10439
10744
|
}
|
|
10440
10745
|
if (hasCommandOption && longRunningDelegation.shouldDelegate) {
|
|
10441
10746
|
console.log(
|
|
10442
|
-
|
|
10747
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.subAgentOrchestrationHint")}`)
|
|
10443
10748
|
);
|
|
10444
10749
|
}
|
|
10445
10750
|
if (hasCheckAction) {
|
|
10446
10751
|
console.log(
|
|
10447
|
-
|
|
10752
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionOptionHint")}`)
|
|
10448
10753
|
);
|
|
10449
10754
|
console.log(
|
|
10450
|
-
|
|
10755
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionExplainHint")}`)
|
|
10451
10756
|
);
|
|
10452
10757
|
}
|
|
10453
10758
|
if (requiredDocs.length > 0) {
|
|
10454
10759
|
for (const requiredDoc of requiredDocs) {
|
|
10455
10760
|
console.log(
|
|
10456
|
-
|
|
10761
|
+
chalk9.gray(
|
|
10457
10762
|
` \u21B3 ${tr(lang, "cli", "context.readBuiltinDocFirst", {
|
|
10458
10763
|
command: requiredDoc.command
|
|
10459
10764
|
})}`
|
|
@@ -10462,9 +10767,9 @@ async function runContext(featureName, options) {
|
|
|
10462
10767
|
}
|
|
10463
10768
|
}
|
|
10464
10769
|
if (autoRunPlan.available) {
|
|
10465
|
-
console.log(
|
|
10770
|
+
console.log(chalk9.gray(` \u21B3 ${autoRunPlan.summary}`));
|
|
10466
10771
|
console.log(
|
|
10467
|
-
|
|
10772
|
+
chalk9.gray(
|
|
10468
10773
|
` \u21B3 ${tr(lang, "cli", "context.autoRunCommandHint", {
|
|
10469
10774
|
command: autoRunPlan.command
|
|
10470
10775
|
})}`
|
|
@@ -10484,16 +10789,16 @@ async function runContext(featureName, options) {
|
|
|
10484
10789
|
selectedComponent,
|
|
10485
10790
|
true
|
|
10486
10791
|
);
|
|
10487
|
-
console.log(
|
|
10792
|
+
console.log(chalk9.cyan(` \u21B3 ${finalApprovalPrompt}`));
|
|
10488
10793
|
console.log(
|
|
10489
|
-
|
|
10794
|
+
chalk9.gray(
|
|
10490
10795
|
` \u21B3 ${tr(lang, "cli", "context.finalLabelCommandHint", {
|
|
10491
10796
|
command: approveCommand
|
|
10492
10797
|
})}`
|
|
10493
10798
|
)
|
|
10494
10799
|
);
|
|
10495
10800
|
console.log(
|
|
10496
|
-
|
|
10801
|
+
chalk9.gray(
|
|
10497
10802
|
` \u21B3 ${tr(lang, "cli", "context.finalTicketCommandHint", {
|
|
10498
10803
|
command: executeCommand
|
|
10499
10804
|
})}`
|
|
@@ -10507,8 +10812,8 @@ function printChecklist(f, stepDefinitions) {
|
|
|
10507
10812
|
checklistSteps.forEach((definition) => {
|
|
10508
10813
|
const done = definition.checklist.done(f);
|
|
10509
10814
|
const detail = definition.checklist.detail?.(f) ?? "";
|
|
10510
|
-
const mark = done ?
|
|
10511
|
-
const label = definition.step === f.currentStep ?
|
|
10815
|
+
const mark = done ? chalk9.green("\u2705") : chalk9.gray("\u25EF");
|
|
10816
|
+
const label = definition.step === f.currentStep ? chalk9.bold(definition.name) : definition.name;
|
|
10512
10817
|
console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
|
|
10513
10818
|
});
|
|
10514
10819
|
}
|
|
@@ -10531,7 +10836,7 @@ function extractTitleAfterId(line, id) {
|
|
|
10531
10836
|
return cleaned ? cleaned : void 0;
|
|
10532
10837
|
}
|
|
10533
10838
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
10534
|
-
const prdDir =
|
|
10839
|
+
const prdDir = path13.join(docsDir, "prd");
|
|
10535
10840
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
10536
10841
|
extensions: [".md"],
|
|
10537
10842
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -10539,7 +10844,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10539
10844
|
const definitions = /* @__PURE__ */ new Map();
|
|
10540
10845
|
const duplicates = [];
|
|
10541
10846
|
for (const filePath of files) {
|
|
10542
|
-
if (
|
|
10847
|
+
if (path13.basename(filePath).toLowerCase() === "readme.md") {
|
|
10543
10848
|
continue;
|
|
10544
10849
|
}
|
|
10545
10850
|
let content = "";
|
|
@@ -10548,7 +10853,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10548
10853
|
} catch {
|
|
10549
10854
|
continue;
|
|
10550
10855
|
}
|
|
10551
|
-
const relFile = normalizeRelPath2(
|
|
10856
|
+
const relFile = normalizeRelPath2(path13.relative(docsDir, filePath));
|
|
10552
10857
|
const lines = content.split(/\r?\n/);
|
|
10553
10858
|
let inCodeBlock = false;
|
|
10554
10859
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -10623,7 +10928,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
10623
10928
|
]);
|
|
10624
10929
|
function formatPath(cwd, p) {
|
|
10625
10930
|
if (!p) return "";
|
|
10626
|
-
return
|
|
10931
|
+
return path13.isAbsolute(p) ? path13.relative(cwd, p) : p;
|
|
10627
10932
|
}
|
|
10628
10933
|
function detectPlaceholders(content) {
|
|
10629
10934
|
const patterns = [
|
|
@@ -10782,7 +11087,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10782
11087
|
const placeholderContext = {
|
|
10783
11088
|
projectName: config.projectName,
|
|
10784
11089
|
featureName: f.slug,
|
|
10785
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11090
|
+
featurePath: f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path),
|
|
10786
11091
|
repoType: f.type,
|
|
10787
11092
|
featureNumber
|
|
10788
11093
|
};
|
|
@@ -10792,7 +11097,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10792
11097
|
"tasks.md"
|
|
10793
11098
|
];
|
|
10794
11099
|
for (const file of files) {
|
|
10795
|
-
const fullPath =
|
|
11100
|
+
const fullPath = path13.join(f.path, file);
|
|
10796
11101
|
if (!await fs.pathExists(fullPath)) continue;
|
|
10797
11102
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
10798
11103
|
let next = original;
|
|
@@ -10845,7 +11150,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10845
11150
|
const issues = [];
|
|
10846
11151
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
10847
11152
|
for (const dir of requiredDirs) {
|
|
10848
|
-
const p =
|
|
11153
|
+
const p = path13.join(config.docsDir, dir);
|
|
10849
11154
|
if (!await fs.pathExists(p)) {
|
|
10850
11155
|
issues.push({
|
|
10851
11156
|
level: "error",
|
|
@@ -10857,7 +11162,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10857
11162
|
});
|
|
10858
11163
|
}
|
|
10859
11164
|
}
|
|
10860
|
-
const configPath =
|
|
11165
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
10861
11166
|
if (!await fs.pathExists(configPath)) {
|
|
10862
11167
|
issues.push({
|
|
10863
11168
|
level: "warn",
|
|
@@ -10885,7 +11190,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10885
11190
|
}
|
|
10886
11191
|
const idMap = /* @__PURE__ */ new Map();
|
|
10887
11192
|
for (const f of features) {
|
|
10888
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11193
|
+
const rel = f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path);
|
|
10889
11194
|
const id = f.id || "UNKNOWN";
|
|
10890
11195
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
10891
11196
|
idMap.get(id).push(rel);
|
|
@@ -10893,7 +11198,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10893
11198
|
if (!isInitialTemplateState) {
|
|
10894
11199
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
10895
11200
|
for (const file of featureDocs) {
|
|
10896
|
-
const p =
|
|
11201
|
+
const p = path13.join(f.path, file);
|
|
10897
11202
|
if (!await fs.pathExists(p)) continue;
|
|
10898
11203
|
const content = await fs.readFile(p, "utf-8");
|
|
10899
11204
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10908,7 +11213,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10908
11213
|
});
|
|
10909
11214
|
}
|
|
10910
11215
|
if (decisionsPlaceholderMode !== "off") {
|
|
10911
|
-
const decisionsPath =
|
|
11216
|
+
const decisionsPath = path13.join(f.path, "decisions.md");
|
|
10912
11217
|
if (await fs.pathExists(decisionsPath)) {
|
|
10913
11218
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
10914
11219
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10937,7 +11242,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10937
11242
|
level: "warn",
|
|
10938
11243
|
code: "spec_status_unset",
|
|
10939
11244
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
10940
|
-
path: formatPath(cwd,
|
|
11245
|
+
path: formatPath(cwd, path13.join(f.path, "spec.md"))
|
|
10941
11246
|
});
|
|
10942
11247
|
}
|
|
10943
11248
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -10945,7 +11250,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10945
11250
|
level: "warn",
|
|
10946
11251
|
code: "plan_status_unset",
|
|
10947
11252
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
10948
|
-
path: formatPath(cwd,
|
|
11253
|
+
path: formatPath(cwd, path13.join(f.path, "plan.md"))
|
|
10949
11254
|
});
|
|
10950
11255
|
}
|
|
10951
11256
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -10953,11 +11258,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10953
11258
|
level: "warn",
|
|
10954
11259
|
code: "tasks_empty",
|
|
10955
11260
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
10956
|
-
path: formatPath(cwd,
|
|
11261
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10957
11262
|
});
|
|
10958
11263
|
}
|
|
10959
11264
|
if (f.docs.tasksExists) {
|
|
10960
|
-
const tasksPath =
|
|
11265
|
+
const tasksPath = path13.join(f.path, "tasks.md");
|
|
10961
11266
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
10962
11267
|
const unknownPrdTags = [...new Set(
|
|
10963
11268
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -10979,7 +11284,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10979
11284
|
level: "warn",
|
|
10980
11285
|
code: "tasks_doc_status_missing",
|
|
10981
11286
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
10982
|
-
path: formatPath(cwd,
|
|
11287
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10983
11288
|
});
|
|
10984
11289
|
}
|
|
10985
11290
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -10987,7 +11292,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10987
11292
|
level: "warn",
|
|
10988
11293
|
code: "tasks_doc_status_unset",
|
|
10989
11294
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
10990
|
-
path: formatPath(cwd,
|
|
11295
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10991
11296
|
});
|
|
10992
11297
|
}
|
|
10993
11298
|
}
|
|
@@ -11011,7 +11316,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11011
11316
|
level: "warn",
|
|
11012
11317
|
code: "missing_feature_id",
|
|
11013
11318
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
11014
|
-
path: formatPath(cwd,
|
|
11319
|
+
path: formatPath(cwd, path13.join(config.docsDir, p))
|
|
11015
11320
|
});
|
|
11016
11321
|
}
|
|
11017
11322
|
return issues;
|
|
@@ -11141,25 +11446,25 @@ function doctorCommand(program2) {
|
|
|
11141
11446
|
return;
|
|
11142
11447
|
}
|
|
11143
11448
|
console.log();
|
|
11144
|
-
console.log(
|
|
11145
|
-
console.log(
|
|
11146
|
-
console.log(
|
|
11147
|
-
console.log(
|
|
11449
|
+
console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
|
|
11450
|
+
console.log(chalk9.gray(`- Docs: ${path13.relative(cwd, docsDir)}`));
|
|
11451
|
+
console.log(chalk9.gray(`- Type: ${projectType}`));
|
|
11452
|
+
console.log(chalk9.gray(`- Lang: ${lang}`));
|
|
11148
11453
|
console.log();
|
|
11149
11454
|
if (warnings.length > 0) {
|
|
11150
|
-
console.log(
|
|
11151
|
-
warnings.forEach((w) => console.log(
|
|
11455
|
+
console.log(chalk9.yellow(tr(lang, "cli", "doctor.envWarnings")));
|
|
11456
|
+
warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
11152
11457
|
console.log();
|
|
11153
11458
|
}
|
|
11154
11459
|
if (fixResult) {
|
|
11155
11460
|
const label = fixResult.dryRun ? "\u{1F9EA} Doctor Fix (dry-run)" : "\u{1F6E0}\uFE0F Doctor Fix";
|
|
11156
11461
|
console.log(
|
|
11157
|
-
|
|
11462
|
+
chalk9.blue(`${label}: ${fixResult.changedFiles} file(s)`)
|
|
11158
11463
|
);
|
|
11159
11464
|
if (fixResult.changedFiles > 0) {
|
|
11160
11465
|
fixResult.entries.forEach(
|
|
11161
11466
|
(entry) => console.log(
|
|
11162
|
-
|
|
11467
|
+
chalk9.gray(` - ${entry.path}: ${entry.changes.join(", ")}`)
|
|
11163
11468
|
)
|
|
11164
11469
|
);
|
|
11165
11470
|
}
|
|
@@ -11169,51 +11474,51 @@ function doctorCommand(program2) {
|
|
|
11169
11474
|
const warns = issues.filter((i) => i.level === "warn");
|
|
11170
11475
|
const infos = issues.filter((i) => i.level === "info");
|
|
11171
11476
|
if (!hasIssues && infos.length === 0) {
|
|
11172
|
-
console.log(
|
|
11477
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11173
11478
|
console.log();
|
|
11174
11479
|
return;
|
|
11175
11480
|
}
|
|
11176
11481
|
if (!hasIssues && infos.length > 0) {
|
|
11177
|
-
console.log(
|
|
11482
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11178
11483
|
console.log();
|
|
11179
11484
|
}
|
|
11180
11485
|
if (errors.length > 0) {
|
|
11181
11486
|
console.log(
|
|
11182
|
-
|
|
11487
|
+
chalk9.red(
|
|
11183
11488
|
`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`
|
|
11184
11489
|
)
|
|
11185
11490
|
);
|
|
11186
11491
|
errors.forEach(
|
|
11187
11492
|
(i) => console.log(
|
|
11188
|
-
|
|
11493
|
+
chalk9.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11189
11494
|
)
|
|
11190
11495
|
);
|
|
11191
11496
|
console.log();
|
|
11192
11497
|
}
|
|
11193
11498
|
if (warns.length > 0) {
|
|
11194
11499
|
console.log(
|
|
11195
|
-
|
|
11500
|
+
chalk9.yellow(
|
|
11196
11501
|
`\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
|
|
11197
11502
|
)
|
|
11198
11503
|
);
|
|
11199
11504
|
warns.forEach(
|
|
11200
11505
|
(i) => console.log(
|
|
11201
|
-
|
|
11506
|
+
chalk9.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11202
11507
|
)
|
|
11203
11508
|
);
|
|
11204
11509
|
console.log();
|
|
11205
11510
|
}
|
|
11206
11511
|
if (infos.length > 0) {
|
|
11207
|
-
console.log(
|
|
11512
|
+
console.log(chalk9.blue(`\u2139\uFE0F Info (${infos.length})`));
|
|
11208
11513
|
infos.forEach(
|
|
11209
11514
|
(i) => console.log(
|
|
11210
|
-
|
|
11515
|
+
chalk9.blue(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11211
11516
|
)
|
|
11212
11517
|
);
|
|
11213
11518
|
console.log();
|
|
11214
11519
|
}
|
|
11215
11520
|
console.log(
|
|
11216
|
-
|
|
11521
|
+
chalk9.gray(
|
|
11217
11522
|
tr(lang, "cli", "doctor.tipJson", {
|
|
11218
11523
|
strictFlag: options.strict ? " --strict" : ""
|
|
11219
11524
|
})
|
|
@@ -11242,8 +11547,8 @@ function doctorCommand(program2) {
|
|
|
11242
11547
|
);
|
|
11243
11548
|
} else {
|
|
11244
11549
|
console.error(
|
|
11245
|
-
|
|
11246
|
-
|
|
11550
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11551
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11247
11552
|
);
|
|
11248
11553
|
printCliErrorSuggestions(suggestions, lang);
|
|
11249
11554
|
}
|
|
@@ -11272,8 +11577,8 @@ function viewCommand(program2) {
|
|
|
11272
11577
|
);
|
|
11273
11578
|
} else {
|
|
11274
11579
|
console.error(
|
|
11275
|
-
|
|
11276
|
-
|
|
11580
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11581
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11277
11582
|
);
|
|
11278
11583
|
printCliErrorSuggestions(suggestions, lang);
|
|
11279
11584
|
}
|
|
@@ -11322,49 +11627,49 @@ async function runView(featureName, options) {
|
|
|
11322
11627
|
return;
|
|
11323
11628
|
}
|
|
11324
11629
|
console.log();
|
|
11325
|
-
console.log(
|
|
11326
|
-
console.log(
|
|
11630
|
+
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11631
|
+
console.log(chalk9.gray(`- Docs: ${path13.relative(cwd, config.docsDir)}`));
|
|
11327
11632
|
console.log(
|
|
11328
|
-
|
|
11633
|
+
chalk9.gray(
|
|
11329
11634
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
11330
11635
|
)
|
|
11331
11636
|
);
|
|
11332
11637
|
console.log(
|
|
11333
|
-
|
|
11638
|
+
chalk9.gray(
|
|
11334
11639
|
`- In Progress: ${state.inProgressFeatures.length}, Ready To Close: ${state.readyToCloseFeatures.length}`
|
|
11335
11640
|
)
|
|
11336
11641
|
);
|
|
11337
11642
|
if (state.warnings.length > 0) {
|
|
11338
11643
|
console.log();
|
|
11339
|
-
console.log(
|
|
11644
|
+
console.log(chalk9.yellow("\u26A0\uFE0F Environment warnings:"));
|
|
11340
11645
|
for (const warning of state.warnings) {
|
|
11341
|
-
console.log(
|
|
11646
|
+
console.log(chalk9.yellow(` - ${warning}`));
|
|
11342
11647
|
}
|
|
11343
11648
|
}
|
|
11344
11649
|
if (state.features.length === 0) {
|
|
11345
11650
|
console.log();
|
|
11346
|
-
console.log(
|
|
11347
|
-
console.log(
|
|
11651
|
+
console.log(chalk9.yellow("No features found."));
|
|
11652
|
+
console.log(chalk9.gray("Try: npx lee-spec-kit feature <name>"));
|
|
11348
11653
|
console.log();
|
|
11349
11654
|
return;
|
|
11350
11655
|
}
|
|
11351
11656
|
if (!state.matchedFeature) {
|
|
11352
11657
|
console.log();
|
|
11353
11658
|
console.log(
|
|
11354
|
-
|
|
11659
|
+
chalk9.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`)
|
|
11355
11660
|
);
|
|
11356
11661
|
const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
|
|
11357
11662
|
for (const f2 of rows) {
|
|
11358
|
-
const statusText = f2.completion.workflowDone ?
|
|
11663
|
+
const statusText = f2.completion.workflowDone ? chalk9.green("WORKFLOW_DONE") : f2.completion.implementationDone ? chalk9.cyan("DONE") : chalk9.yellow("IN_PROGRESS");
|
|
11359
11664
|
const substateText = f2.currentSubstateId ? `${f2.currentSubstateId} (${f2.currentSubstateOwner ?? "-"} / ${f2.currentSubstatePhase ?? "-"})` : "-";
|
|
11360
11665
|
console.log(
|
|
11361
11666
|
`- ${f2.folderName} (${f2.tasks.done}/${f2.tasks.total}) ${statusText} step:${f2.currentStep} substate:${substateText}`
|
|
11362
11667
|
);
|
|
11363
|
-
console.log(
|
|
11668
|
+
console.log(chalk9.gray(` next: ${f2.nextAction}`));
|
|
11364
11669
|
}
|
|
11365
11670
|
console.log();
|
|
11366
11671
|
const selectorTip = config.projectType === "multi" ? selectedComponent ? `Tip: npx lee-spec-kit view <slug|F001|F001-slug> --component ${selectedComponent}` : "Tip: npx lee-spec-kit view <slug|F001|F001-slug> [--component <component>]" : "Tip: npx lee-spec-kit view <slug|F001|F001-slug>";
|
|
11367
|
-
console.log(
|
|
11672
|
+
console.log(chalk9.gray(selectorTip));
|
|
11368
11673
|
console.log();
|
|
11369
11674
|
return;
|
|
11370
11675
|
}
|
|
@@ -11372,7 +11677,7 @@ async function runView(featureName, options) {
|
|
|
11372
11677
|
const completion = `${f.tasks.done}/${f.tasks.total}`;
|
|
11373
11678
|
const stateLabel = f.completion.workflowDone ? "WORKFLOW_DONE" : f.completion.implementationDone ? "DONE" : "IN_PROGRESS";
|
|
11374
11679
|
console.log();
|
|
11375
|
-
console.log(
|
|
11680
|
+
console.log(chalk9.blue(`Feature: ${chalk9.bold(f.folderName)}`));
|
|
11376
11681
|
console.log(`- State: ${stateLabel}`);
|
|
11377
11682
|
console.log(`- Progress: ${completion}`);
|
|
11378
11683
|
console.log(`- Step: ${f.currentStep}`);
|
|
@@ -11383,14 +11688,14 @@ async function runView(featureName, options) {
|
|
|
11383
11688
|
console.log(`- Next: ${nextSummary}`);
|
|
11384
11689
|
if (state.actionOptions.length > 0) {
|
|
11385
11690
|
console.log();
|
|
11386
|
-
console.log(
|
|
11691
|
+
console.log(chalk9.green("Atomic options:"));
|
|
11387
11692
|
for (const option of state.actionOptions) {
|
|
11388
11693
|
const lang = config.lang ?? DEFAULT_LANG;
|
|
11389
11694
|
const requiresCheck = option.action.requiresUserCheck ? tr(lang, "cli", "context.checkRequired") : "";
|
|
11390
11695
|
console.log(` ${option.label}. ${requiresCheck}${option.detail}`);
|
|
11391
11696
|
}
|
|
11392
11697
|
console.log(
|
|
11393
|
-
|
|
11698
|
+
chalk9.gray(
|
|
11394
11699
|
`Approve with: npx lee-spec-kit context ${f.folderName} --approve <LABEL>`
|
|
11395
11700
|
)
|
|
11396
11701
|
);
|
|
@@ -11408,13 +11713,13 @@ function normalizeRunId(raw) {
|
|
|
11408
11713
|
return value;
|
|
11409
11714
|
}
|
|
11410
11715
|
function getFlowRunBaseDir(cwd) {
|
|
11411
|
-
return
|
|
11716
|
+
return path13.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11412
11717
|
}
|
|
11413
11718
|
function getFlowRunPath(cwd, runId) {
|
|
11414
|
-
return
|
|
11719
|
+
return path13.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11415
11720
|
}
|
|
11416
11721
|
function getFlowRunLockPath(cwd, runId) {
|
|
11417
|
-
return
|
|
11722
|
+
return path13.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11418
11723
|
}
|
|
11419
11724
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11420
11725
|
const normalized = normalizeRunId(runId);
|
|
@@ -11440,7 +11745,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11440
11745
|
}
|
|
11441
11746
|
async function writeFlowRunRecord(cwd, record) {
|
|
11442
11747
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
11443
|
-
await fs.ensureDir(
|
|
11748
|
+
await fs.ensureDir(path13.dirname(filePath));
|
|
11444
11749
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
11445
11750
|
}
|
|
11446
11751
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -12235,8 +12540,8 @@ function flowCommand(program2) {
|
|
|
12235
12540
|
);
|
|
12236
12541
|
} else {
|
|
12237
12542
|
console.error(
|
|
12238
|
-
|
|
12239
|
-
|
|
12543
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
12544
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
12240
12545
|
);
|
|
12241
12546
|
printCliErrorSuggestions(suggestions, lang);
|
|
12242
12547
|
}
|
|
@@ -12555,16 +12860,16 @@ async function runFlow(featureName, options) {
|
|
|
12555
12860
|
return;
|
|
12556
12861
|
}
|
|
12557
12862
|
console.log();
|
|
12558
|
-
console.log(
|
|
12863
|
+
console.log(chalk9.bold("\u{1F501} Flow Summary"));
|
|
12559
12864
|
console.log(
|
|
12560
|
-
|
|
12865
|
+
chalk9.gray(
|
|
12561
12866
|
`- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
|
|
12562
12867
|
)
|
|
12563
12868
|
);
|
|
12564
12869
|
if (approvalResult && typeof approvalResult === "object") {
|
|
12565
12870
|
const result = approvalResult;
|
|
12566
12871
|
console.log(
|
|
12567
|
-
|
|
12872
|
+
chalk9.gray(
|
|
12568
12873
|
`- Approval: ${result.status ?? "unknown"} (${result.reasonCode ?? "-"})`
|
|
12569
12874
|
)
|
|
12570
12875
|
);
|
|
@@ -12572,18 +12877,18 @@ async function runFlow(featureName, options) {
|
|
|
12572
12877
|
if (autoRun) {
|
|
12573
12878
|
const presetSuffix = autoRun.preset ? `, preset ${autoRun.preset}` : "";
|
|
12574
12879
|
console.log(
|
|
12575
|
-
|
|
12880
|
+
chalk9.gray(
|
|
12576
12881
|
`- Auto: ${autoRun.status} (${autoRun.reasonCode}), iterations ${autoRun.iterations}, executions ${autoRun.executions.length}${presetSuffix}`
|
|
12577
12882
|
)
|
|
12578
12883
|
);
|
|
12579
|
-
console.log(
|
|
12884
|
+
console.log(chalk9.gray(`- Auto resume: ${autoRun.resume.flowCommand}`));
|
|
12580
12885
|
if (autoRun.run) {
|
|
12581
12886
|
console.log(
|
|
12582
|
-
|
|
12887
|
+
chalk9.gray(
|
|
12583
12888
|
`- Auto run: ${autoRun.run.mode} ${autoRun.run.runId} (${autoRun.run.status})`
|
|
12584
12889
|
)
|
|
12585
12890
|
);
|
|
12586
|
-
console.log(
|
|
12891
|
+
console.log(chalk9.gray(`- Resume with: ${autoRun.run.resumeCommand}`));
|
|
12587
12892
|
}
|
|
12588
12893
|
}
|
|
12589
12894
|
const agentOrchestration = buildAgentOrchestrationPolicy2(
|
|
@@ -12591,21 +12896,21 @@ async function runFlow(featureName, options) {
|
|
|
12591
12896
|
resolvedFeatureName || null
|
|
12592
12897
|
);
|
|
12593
12898
|
console.log(
|
|
12594
|
-
|
|
12899
|
+
chalk9.gray(
|
|
12595
12900
|
`- Orchestration: ${agentOrchestration.mode}, prefer substate-owner delegation and fall back to legacy long-running categories`
|
|
12596
12901
|
)
|
|
12597
12902
|
);
|
|
12598
12903
|
const statusCounts = statusReport.counts;
|
|
12599
12904
|
const doctorCounts = doctorReport.counts;
|
|
12600
|
-
console.log(
|
|
12905
|
+
console.log(chalk9.gray(`- Status features: ${statusCounts?.features ?? 0}`));
|
|
12601
12906
|
console.log(
|
|
12602
|
-
|
|
12907
|
+
chalk9.gray(
|
|
12603
12908
|
`- Doctor issues: ${doctorCounts?.issues ?? 0} (errors ${doctorCounts?.errors ?? 0}, warnings ${doctorCounts?.warnings ?? 0})`
|
|
12604
12909
|
)
|
|
12605
12910
|
);
|
|
12606
12911
|
if (strictChecks) {
|
|
12607
|
-
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ?
|
|
12608
|
-
console.log(
|
|
12912
|
+
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ? chalk9.green("PASS") : chalk9.red("FAIL");
|
|
12913
|
+
console.log(chalk9.gray(`- Strict checks: ${strictLabel}`));
|
|
12609
12914
|
}
|
|
12610
12915
|
console.log();
|
|
12611
12916
|
if (autoRun?.status === "gate_reached" && autoRun.gate?.userFacingLines?.length) {
|
|
@@ -12613,7 +12918,7 @@ async function runFlow(featureName, options) {
|
|
|
12613
12918
|
console.log(line);
|
|
12614
12919
|
}
|
|
12615
12920
|
console.log(
|
|
12616
|
-
|
|
12921
|
+
chalk9.gray(
|
|
12617
12922
|
"Auto gate reached. Reply with one of the labels shown above (example: A OK)."
|
|
12618
12923
|
)
|
|
12619
12924
|
);
|
|
@@ -12624,15 +12929,15 @@ async function runFlow(featureName, options) {
|
|
|
12624
12929
|
}
|
|
12625
12930
|
if (after.matchedFeature) {
|
|
12626
12931
|
console.log(
|
|
12627
|
-
|
|
12932
|
+
chalk9.blue(
|
|
12628
12933
|
`Next: npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}`
|
|
12629
12934
|
)
|
|
12630
12935
|
);
|
|
12631
12936
|
} else {
|
|
12632
|
-
console.log(
|
|
12937
|
+
console.log(chalk9.blue(`Next: npx lee-spec-kit context${componentHint}`));
|
|
12633
12938
|
}
|
|
12634
12939
|
console.log(
|
|
12635
|
-
|
|
12940
|
+
chalk9.gray(
|
|
12636
12941
|
"Tip: add --approve <LABEL> [--execute] to run the selected atomic action."
|
|
12637
12942
|
)
|
|
12638
12943
|
);
|
|
@@ -12751,27 +13056,27 @@ function tg(lang, key, vars = {}) {
|
|
|
12751
13056
|
function detectGithubCliLangSync(cwd) {
|
|
12752
13057
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
12753
13058
|
const startDirs = [
|
|
12754
|
-
explicitDocsDir ?
|
|
12755
|
-
|
|
13059
|
+
explicitDocsDir ? path13.resolve(explicitDocsDir) : "",
|
|
13060
|
+
path13.resolve(cwd)
|
|
12756
13061
|
].filter(Boolean);
|
|
12757
13062
|
const scanOrder = [];
|
|
12758
13063
|
const seen = /* @__PURE__ */ new Set();
|
|
12759
13064
|
for (const start of startDirs) {
|
|
12760
13065
|
let current = start;
|
|
12761
13066
|
while (true) {
|
|
12762
|
-
const abs =
|
|
13067
|
+
const abs = path13.resolve(current);
|
|
12763
13068
|
if (!seen.has(abs)) {
|
|
12764
13069
|
scanOrder.push(abs);
|
|
12765
13070
|
seen.add(abs);
|
|
12766
13071
|
}
|
|
12767
|
-
const parent =
|
|
13072
|
+
const parent = path13.dirname(abs);
|
|
12768
13073
|
if (parent === abs) break;
|
|
12769
13074
|
current = parent;
|
|
12770
13075
|
}
|
|
12771
13076
|
}
|
|
12772
13077
|
for (const base of scanOrder) {
|
|
12773
|
-
for (const docsDir of [
|
|
12774
|
-
const configPath =
|
|
13078
|
+
for (const docsDir of [path13.join(base, "docs"), base]) {
|
|
13079
|
+
const configPath = path13.join(docsDir, ".lee-spec-kit.json");
|
|
12775
13080
|
if (fs.existsSync(configPath)) {
|
|
12776
13081
|
try {
|
|
12777
13082
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -12780,11 +13085,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
12780
13085
|
} catch {
|
|
12781
13086
|
}
|
|
12782
13087
|
}
|
|
12783
|
-
const agentsPath =
|
|
12784
|
-
const featuresPath =
|
|
13088
|
+
const agentsPath = path13.join(docsDir, "agents");
|
|
13089
|
+
const featuresPath = path13.join(docsDir, "features");
|
|
12785
13090
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
12786
13091
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
12787
|
-
const file =
|
|
13092
|
+
const file = path13.join(agentsPath, probe);
|
|
12788
13093
|
if (!fs.existsSync(file)) continue;
|
|
12789
13094
|
try {
|
|
12790
13095
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -12804,13 +13109,13 @@ function parseLabels(raw, lang) {
|
|
|
12804
13109
|
}
|
|
12805
13110
|
return [...new Set(labels)];
|
|
12806
13111
|
}
|
|
12807
|
-
function
|
|
13112
|
+
function escapeRegExp4(value) {
|
|
12808
13113
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12809
13114
|
}
|
|
12810
13115
|
function extractDraftMetadataValue(content, keys) {
|
|
12811
13116
|
for (const key of keys) {
|
|
12812
13117
|
const re = new RegExp(
|
|
12813
|
-
`^\\s*-\\s*\\*\\*${
|
|
13118
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
|
|
12814
13119
|
"mi"
|
|
12815
13120
|
);
|
|
12816
13121
|
const match = content.match(re);
|
|
@@ -12889,7 +13194,7 @@ async function prepareGithubBody(params) {
|
|
|
12889
13194
|
};
|
|
12890
13195
|
}
|
|
12891
13196
|
}
|
|
12892
|
-
await fs.ensureDir(
|
|
13197
|
+
await fs.ensureDir(path13.dirname(defaultBodyFile));
|
|
12893
13198
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
12894
13199
|
return {
|
|
12895
13200
|
body: generatedBody,
|
|
@@ -12929,7 +13234,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12929
13234
|
};
|
|
12930
13235
|
const hasMetadataField = (field) => {
|
|
12931
13236
|
const re = new RegExp(
|
|
12932
|
-
`^\\s*-\\s*\\*\\*${
|
|
13237
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(field)}\\*\\*\\s*:`,
|
|
12933
13238
|
"m"
|
|
12934
13239
|
);
|
|
12935
13240
|
return re.test(body);
|
|
@@ -12959,7 +13264,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12959
13264
|
}
|
|
12960
13265
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
12961
13266
|
const missing = relativePaths.filter(
|
|
12962
|
-
(relativePath) => !fs.existsSync(
|
|
13267
|
+
(relativePath) => !fs.existsSync(path13.join(docsDir, relativePath))
|
|
12963
13268
|
);
|
|
12964
13269
|
if (missing.length > 0) {
|
|
12965
13270
|
throw createCliError(
|
|
@@ -12969,18 +13274,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
12969
13274
|
}
|
|
12970
13275
|
}
|
|
12971
13276
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
12972
|
-
const key = `${
|
|
13277
|
+
const key = `${path13.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
12973
13278
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
12974
13279
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
12975
13280
|
}
|
|
12976
13281
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
12977
|
-
const selected = raw?.trim() ||
|
|
13282
|
+
const selected = raw?.trim() || path13.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
12978
13283
|
assertValid(
|
|
12979
13284
|
validatePathWithLang(selected, lang),
|
|
12980
13285
|
`github.${kind}.bodyFile`,
|
|
12981
13286
|
lang
|
|
12982
13287
|
);
|
|
12983
|
-
return
|
|
13288
|
+
return path13.resolve(selected);
|
|
12984
13289
|
}
|
|
12985
13290
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
12986
13291
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -13817,7 +14122,7 @@ function stripMarkdownCodeContexts(body) {
|
|
|
13817
14122
|
function hasIssueClosingKeyword(body, issueNumber) {
|
|
13818
14123
|
if (!issueNumber) return false;
|
|
13819
14124
|
const cleaned = stripMarkdownCodeContexts(body);
|
|
13820
|
-
const issue =
|
|
14125
|
+
const issue = escapeRegExp4(issueNumber);
|
|
13821
14126
|
const closeKeywordRegex = new RegExp(
|
|
13822
14127
|
`\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
|
|
13823
14128
|
"i"
|
|
@@ -14035,7 +14340,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
14035
14340
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
14036
14341
|
const uniqueRelativePaths = [
|
|
14037
14342
|
...new Set(
|
|
14038
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14343
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path13.relative(cwd, absPath) || absPath)
|
|
14039
14344
|
)
|
|
14040
14345
|
];
|
|
14041
14346
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14231,15 +14536,15 @@ function githubCommand(program2) {
|
|
|
14231
14536
|
config.lang
|
|
14232
14537
|
);
|
|
14233
14538
|
const specContent = await fs.readFile(
|
|
14234
|
-
|
|
14539
|
+
path13.join(config.docsDir, paths.specPath),
|
|
14235
14540
|
"utf-8"
|
|
14236
14541
|
);
|
|
14237
14542
|
const planContent = await fs.readFile(
|
|
14238
|
-
|
|
14543
|
+
path13.join(config.docsDir, paths.planPath),
|
|
14239
14544
|
"utf-8"
|
|
14240
14545
|
);
|
|
14241
14546
|
const tasksContent = await fs.readFile(
|
|
14242
|
-
|
|
14547
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14243
14548
|
"utf-8"
|
|
14244
14549
|
);
|
|
14245
14550
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14282,7 +14587,7 @@ function githubCommand(program2) {
|
|
|
14282
14587
|
create: options.create,
|
|
14283
14588
|
explicitBodyFile,
|
|
14284
14589
|
defaultBodyFile,
|
|
14285
|
-
workflowDraftPath:
|
|
14590
|
+
workflowDraftPath: path13.join(config.docsDir, paths.issuePath),
|
|
14286
14591
|
generatedBody,
|
|
14287
14592
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
14288
14593
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -14298,7 +14603,7 @@ function githubCommand(program2) {
|
|
|
14298
14603
|
`${feature.type}-issue-sanitized`,
|
|
14299
14604
|
config.lang
|
|
14300
14605
|
);
|
|
14301
|
-
await fs.ensureDir(
|
|
14606
|
+
await fs.ensureDir(path13.dirname(sanitizedBodyFile));
|
|
14302
14607
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14303
14608
|
bodyFile = sanitizedBodyFile;
|
|
14304
14609
|
}
|
|
@@ -14347,12 +14652,12 @@ function githubCommand(program2) {
|
|
|
14347
14652
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
14348
14653
|
if (syncedIssueNumber) {
|
|
14349
14654
|
const synced = syncTasksIssueMetadata(
|
|
14350
|
-
|
|
14655
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14351
14656
|
syncedIssueNumber,
|
|
14352
14657
|
config.lang
|
|
14353
14658
|
);
|
|
14354
14659
|
const draftSynced = syncIssueDraftMetadata(
|
|
14355
|
-
|
|
14660
|
+
path13.join(config.docsDir, paths.issuePath),
|
|
14356
14661
|
syncedIssueNumber
|
|
14357
14662
|
);
|
|
14358
14663
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -14380,31 +14685,31 @@ function githubCommand(program2) {
|
|
|
14380
14685
|
return;
|
|
14381
14686
|
}
|
|
14382
14687
|
console.log();
|
|
14383
|
-
console.log(
|
|
14688
|
+
console.log(chalk9.bold(tg(config.lang, "issueHeader")));
|
|
14384
14689
|
console.log(
|
|
14385
|
-
|
|
14690
|
+
chalk9.gray(
|
|
14386
14691
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14387
14692
|
)
|
|
14388
14693
|
);
|
|
14389
14694
|
console.log(
|
|
14390
|
-
|
|
14695
|
+
chalk9.gray(
|
|
14391
14696
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14392
14697
|
)
|
|
14393
14698
|
);
|
|
14394
14699
|
console.log(
|
|
14395
|
-
|
|
14700
|
+
chalk9.gray(
|
|
14396
14701
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14397
14702
|
)
|
|
14398
14703
|
);
|
|
14399
14704
|
if (issueUrl) {
|
|
14400
14705
|
console.log(
|
|
14401
|
-
|
|
14706
|
+
chalk9.green(
|
|
14402
14707
|
tg(config.lang, "issueCreated", { url: issueUrl })
|
|
14403
14708
|
)
|
|
14404
14709
|
);
|
|
14405
14710
|
} else {
|
|
14406
14711
|
console.log(
|
|
14407
|
-
|
|
14712
|
+
chalk9.blue(tg(config.lang, "issueTemplateGenerated"))
|
|
14408
14713
|
);
|
|
14409
14714
|
}
|
|
14410
14715
|
console.log();
|
|
@@ -14423,8 +14728,8 @@ function githubCommand(program2) {
|
|
|
14423
14728
|
);
|
|
14424
14729
|
} else {
|
|
14425
14730
|
console.error(
|
|
14426
|
-
|
|
14427
|
-
|
|
14731
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
14732
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14428
14733
|
);
|
|
14429
14734
|
printCliErrorSuggestions(suggestions, lang);
|
|
14430
14735
|
}
|
|
@@ -14463,13 +14768,13 @@ function githubCommand(program2) {
|
|
|
14463
14768
|
config.lang
|
|
14464
14769
|
);
|
|
14465
14770
|
const specContent = await fs.readFile(
|
|
14466
|
-
|
|
14771
|
+
path13.join(config.docsDir, paths.specPath),
|
|
14467
14772
|
"utf-8"
|
|
14468
14773
|
);
|
|
14469
|
-
const planPath =
|
|
14774
|
+
const planPath = path13.join(config.docsDir, paths.planPath);
|
|
14470
14775
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
14471
14776
|
const tasksContent = await fs.readFile(
|
|
14472
|
-
|
|
14777
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14473
14778
|
"utf-8"
|
|
14474
14779
|
);
|
|
14475
14780
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14517,7 +14822,7 @@ function githubCommand(program2) {
|
|
|
14517
14822
|
create: options.create,
|
|
14518
14823
|
explicitBodyFile,
|
|
14519
14824
|
defaultBodyFile,
|
|
14520
|
-
workflowDraftPath:
|
|
14825
|
+
workflowDraftPath: path13.join(config.docsDir, paths.prPath),
|
|
14521
14826
|
generatedBody,
|
|
14522
14827
|
requiredSections: getRequiredPrSections(config.lang),
|
|
14523
14828
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -14535,7 +14840,7 @@ function githubCommand(program2) {
|
|
|
14535
14840
|
`${feature.type}-pr-sanitized`,
|
|
14536
14841
|
config.lang
|
|
14537
14842
|
);
|
|
14538
|
-
await fs.ensureDir(
|
|
14843
|
+
await fs.ensureDir(path13.dirname(sanitizedBodyFile));
|
|
14539
14844
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14540
14845
|
bodyFile = sanitizedBodyFile;
|
|
14541
14846
|
}
|
|
@@ -14563,7 +14868,7 @@ function githubCommand(program2) {
|
|
|
14563
14868
|
if (preparedBody.source === "generated") {
|
|
14564
14869
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
14565
14870
|
} else {
|
|
14566
|
-
await fs.ensureDir(
|
|
14871
|
+
await fs.ensureDir(path13.dirname(fallbackBodyFile));
|
|
14567
14872
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
14568
14873
|
bodyFile = fallbackBodyFile;
|
|
14569
14874
|
}
|
|
@@ -14624,13 +14929,13 @@ function githubCommand(program2) {
|
|
|
14624
14929
|
}
|
|
14625
14930
|
if (prUrl && options.syncTasks !== false) {
|
|
14626
14931
|
const syncedTasks = syncTasksPrMetadata(
|
|
14627
|
-
|
|
14932
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14628
14933
|
prUrl,
|
|
14629
14934
|
"Review",
|
|
14630
14935
|
config.lang
|
|
14631
14936
|
);
|
|
14632
14937
|
const syncedDraft = syncPrDraftMetadata(
|
|
14633
|
-
|
|
14938
|
+
path13.join(config.docsDir, paths.prPath),
|
|
14634
14939
|
prUrl,
|
|
14635
14940
|
"Review"
|
|
14636
14941
|
);
|
|
@@ -14671,13 +14976,13 @@ function githubCommand(program2) {
|
|
|
14671
14976
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
14672
14977
|
if (prUrl && options.syncTasks !== false) {
|
|
14673
14978
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
14674
|
-
|
|
14979
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14675
14980
|
prUrl,
|
|
14676
14981
|
"Approved",
|
|
14677
14982
|
config.lang
|
|
14678
14983
|
);
|
|
14679
14984
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
14680
|
-
|
|
14985
|
+
path13.join(config.docsDir, paths.prPath),
|
|
14681
14986
|
prUrl,
|
|
14682
14987
|
"Approved"
|
|
14683
14988
|
);
|
|
@@ -14761,35 +15066,35 @@ function githubCommand(program2) {
|
|
|
14761
15066
|
return;
|
|
14762
15067
|
}
|
|
14763
15068
|
console.log();
|
|
14764
|
-
console.log(
|
|
15069
|
+
console.log(chalk9.bold(tg(config.lang, "prHeader")));
|
|
14765
15070
|
console.log(
|
|
14766
|
-
|
|
15071
|
+
chalk9.gray(
|
|
14767
15072
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14768
15073
|
)
|
|
14769
15074
|
);
|
|
14770
15075
|
console.log(
|
|
14771
|
-
|
|
15076
|
+
chalk9.gray(
|
|
14772
15077
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14773
15078
|
)
|
|
14774
15079
|
);
|
|
14775
15080
|
console.log(
|
|
14776
|
-
|
|
15081
|
+
chalk9.gray(
|
|
14777
15082
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14778
15083
|
)
|
|
14779
15084
|
);
|
|
14780
15085
|
if (prUrl) {
|
|
14781
15086
|
console.log(
|
|
14782
|
-
|
|
15087
|
+
chalk9.gray(`- ${tg(config.lang, "labelPr")}: ${prUrl}`)
|
|
14783
15088
|
);
|
|
14784
15089
|
}
|
|
14785
15090
|
if (syncChanged) {
|
|
14786
15091
|
console.log(
|
|
14787
|
-
|
|
15092
|
+
chalk9.green(tg(config.lang, "prTasksSynced"))
|
|
14788
15093
|
);
|
|
14789
15094
|
}
|
|
14790
15095
|
if (options.merge) {
|
|
14791
15096
|
console.log(
|
|
14792
|
-
|
|
15097
|
+
chalk9.green(
|
|
14793
15098
|
tg(config.lang, "prMerged", {
|
|
14794
15099
|
attempts: mergedAttempts ?? 1
|
|
14795
15100
|
})
|
|
@@ -14797,15 +15102,15 @@ function githubCommand(program2) {
|
|
|
14797
15102
|
);
|
|
14798
15103
|
if (mergeAlreadyMerged) {
|
|
14799
15104
|
console.log(
|
|
14800
|
-
|
|
15105
|
+
chalk9.yellow(tg(config.lang, "prAlreadyMergedNotice"))
|
|
14801
15106
|
);
|
|
14802
15107
|
}
|
|
14803
15108
|
for (const warning of postMergeWarnings) {
|
|
14804
|
-
console.log(
|
|
15109
|
+
console.log(chalk9.yellow(`\u26A0\uFE0F ${warning}`));
|
|
14805
15110
|
}
|
|
14806
15111
|
} else if (!options.create) {
|
|
14807
15112
|
console.log(
|
|
14808
|
-
|
|
15113
|
+
chalk9.blue(tg(config.lang, "prTemplateGenerated"))
|
|
14809
15114
|
);
|
|
14810
15115
|
}
|
|
14811
15116
|
console.log();
|
|
@@ -14824,8 +15129,8 @@ function githubCommand(program2) {
|
|
|
14824
15129
|
);
|
|
14825
15130
|
} else {
|
|
14826
15131
|
console.error(
|
|
14827
|
-
|
|
14828
|
-
|
|
15132
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15133
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14829
15134
|
);
|
|
14830
15135
|
printCliErrorSuggestions(suggestions, lang);
|
|
14831
15136
|
}
|
|
@@ -14883,10 +15188,10 @@ function docsCommand(program2) {
|
|
|
14883
15188
|
return;
|
|
14884
15189
|
}
|
|
14885
15190
|
console.log();
|
|
14886
|
-
console.log(
|
|
15191
|
+
console.log(chalk9.bold(tr(config.lang, "cli", "docs.listHeader")));
|
|
14887
15192
|
for (const doc of docsList) {
|
|
14888
15193
|
console.log(`- ${doc.id}: ${doc.title}`);
|
|
14889
|
-
console.log(
|
|
15194
|
+
console.log(chalk9.gray(` ${doc.command}`));
|
|
14890
15195
|
}
|
|
14891
15196
|
console.log();
|
|
14892
15197
|
} catch (error) {
|
|
@@ -14905,8 +15210,8 @@ function docsCommand(program2) {
|
|
|
14905
15210
|
);
|
|
14906
15211
|
} else {
|
|
14907
15212
|
console.error(
|
|
14908
|
-
|
|
14909
|
-
|
|
15213
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15214
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14910
15215
|
);
|
|
14911
15216
|
printCliErrorSuggestions(suggestions, lang);
|
|
14912
15217
|
}
|
|
@@ -14948,25 +15253,25 @@ function docsCommand(program2) {
|
|
|
14948
15253
|
);
|
|
14949
15254
|
return;
|
|
14950
15255
|
}
|
|
14951
|
-
const relativeFromCwd =
|
|
15256
|
+
const relativeFromCwd = path13.relative(process.cwd(), loaded.entry.absolutePath);
|
|
14952
15257
|
console.log();
|
|
14953
|
-
console.log(
|
|
15258
|
+
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
14954
15259
|
console.log(
|
|
14955
|
-
|
|
15260
|
+
chalk9.gray(
|
|
14956
15261
|
`${tr(config.lang, "cli", "docs.sourceLabel")}: ${relativeFromCwd || loaded.entry.absolutePath}`
|
|
14957
15262
|
)
|
|
14958
15263
|
);
|
|
14959
15264
|
console.log(
|
|
14960
|
-
|
|
15265
|
+
chalk9.gray(`${tr(config.lang, "cli", "docs.hashLabel")}: ${loaded.hash}`)
|
|
14961
15266
|
);
|
|
14962
15267
|
console.log();
|
|
14963
15268
|
process.stdout.write(loaded.content.endsWith("\n") ? loaded.content : `${loaded.content}
|
|
14964
15269
|
`);
|
|
14965
15270
|
if (followups.length > 0) {
|
|
14966
15271
|
console.log();
|
|
14967
|
-
console.log(
|
|
15272
|
+
console.log(chalk9.blue(`${tr(config.lang, "cli", "docs.nextDocs")}:`));
|
|
14968
15273
|
for (const followup of followups) {
|
|
14969
|
-
console.log(
|
|
15274
|
+
console.log(chalk9.gray(`- ${followup.command}`));
|
|
14970
15275
|
}
|
|
14971
15276
|
console.log();
|
|
14972
15277
|
}
|
|
@@ -14986,8 +15291,8 @@ function docsCommand(program2) {
|
|
|
14986
15291
|
);
|
|
14987
15292
|
} else {
|
|
14988
15293
|
console.error(
|
|
14989
|
-
|
|
14990
|
-
|
|
15294
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15295
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14991
15296
|
);
|
|
14992
15297
|
printCliErrorSuggestions(suggestions, lang);
|
|
14993
15298
|
}
|
|
@@ -15016,8 +15321,8 @@ function detectCommand(program2) {
|
|
|
15016
15321
|
);
|
|
15017
15322
|
} else {
|
|
15018
15323
|
console.error(
|
|
15019
|
-
|
|
15020
|
-
|
|
15324
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15325
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
15021
15326
|
);
|
|
15022
15327
|
printCliErrorSuggestions(suggestions, lang);
|
|
15023
15328
|
}
|
|
@@ -15028,7 +15333,7 @@ function detectCommand(program2) {
|
|
|
15028
15333
|
}
|
|
15029
15334
|
async function runDetect(options) {
|
|
15030
15335
|
const cwd = process.cwd();
|
|
15031
|
-
const targetCwd = options.dir ?
|
|
15336
|
+
const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
|
|
15032
15337
|
const config = await getConfig(targetCwd);
|
|
15033
15338
|
const detected = !!config;
|
|
15034
15339
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -15055,7 +15360,7 @@ async function runDetect(options) {
|
|
|
15055
15360
|
);
|
|
15056
15361
|
return;
|
|
15057
15362
|
}
|
|
15058
|
-
const configPath2 =
|
|
15363
|
+
const configPath2 = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
15059
15364
|
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
15060
15365
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
15061
15366
|
console.log(
|
|
@@ -15081,26 +15386,26 @@ async function runDetect(options) {
|
|
|
15081
15386
|
}
|
|
15082
15387
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
15083
15388
|
console.log();
|
|
15084
|
-
console.log(
|
|
15085
|
-
console.log(
|
|
15389
|
+
console.log(chalk9.blue(tr(lang, "cli", "detect.header")));
|
|
15390
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelTarget")}: ${targetCwd}`));
|
|
15086
15391
|
if (!config) {
|
|
15087
|
-
console.log(
|
|
15088
|
-
console.log(
|
|
15392
|
+
console.log(chalk9.yellow(`- ${tr(lang, "cli", "detect.resultNotDetected")}`));
|
|
15393
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.notDetectedHint")}`));
|
|
15089
15394
|
console.log();
|
|
15090
15395
|
return;
|
|
15091
15396
|
}
|
|
15092
|
-
const configPath =
|
|
15397
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
15093
15398
|
const configFilePresent = await fs.pathExists(configPath);
|
|
15094
15399
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
15095
|
-
console.log(
|
|
15096
|
-
console.log(
|
|
15400
|
+
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
15401
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${config.docsDir}`));
|
|
15097
15402
|
console.log(
|
|
15098
|
-
|
|
15403
|
+
chalk9.gray(
|
|
15099
15404
|
`- ${tr(lang, "cli", "detect.labelConfigPath")}: ${configFilePresent ? configPath : "-"}`
|
|
15100
15405
|
)
|
|
15101
15406
|
);
|
|
15102
15407
|
console.log(
|
|
15103
|
-
|
|
15408
|
+
chalk9.gray(
|
|
15104
15409
|
`- ${tr(lang, "cli", "detect.labelSource")}: ${tr(
|
|
15105
15410
|
lang,
|
|
15106
15411
|
"cli",
|
|
@@ -15109,14 +15414,14 @@ async function runDetect(options) {
|
|
|
15109
15414
|
)
|
|
15110
15415
|
);
|
|
15111
15416
|
console.log(
|
|
15112
|
-
|
|
15417
|
+
chalk9.gray(
|
|
15113
15418
|
`- ${tr(lang, "cli", "detect.labelProjectType")}: ${config.projectType}`
|
|
15114
15419
|
)
|
|
15115
15420
|
);
|
|
15116
|
-
console.log(
|
|
15421
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelLang")}: ${config.lang}`));
|
|
15117
15422
|
if (config.projectName) {
|
|
15118
15423
|
console.log(
|
|
15119
|
-
|
|
15424
|
+
chalk9.gray(
|
|
15120
15425
|
`- ${tr(lang, "cli", "detect.labelProjectName")}: ${config.projectName}`
|
|
15121
15426
|
)
|
|
15122
15427
|
);
|
|
@@ -15166,19 +15471,19 @@ function hasTemplateMarkers(content) {
|
|
|
15166
15471
|
return patterns.some((pattern) => pattern.test(content));
|
|
15167
15472
|
}
|
|
15168
15473
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15169
|
-
const featuresRoot =
|
|
15474
|
+
const featuresRoot = path13.join(docsDir, "features");
|
|
15170
15475
|
if (projectType === "single") {
|
|
15171
15476
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15172
|
-
return dirs.filter((value) =>
|
|
15477
|
+
return dirs.filter((value) => path13.basename(value) !== "feature-base").length;
|
|
15173
15478
|
}
|
|
15174
15479
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15175
15480
|
let total = 0;
|
|
15176
15481
|
for (const componentDir of components) {
|
|
15177
|
-
const componentName =
|
|
15482
|
+
const componentName = path13.basename(componentDir).trim().toLowerCase();
|
|
15178
15483
|
if (!componentName || componentName === "feature-base") continue;
|
|
15179
15484
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15180
15485
|
total += dirs.filter(
|
|
15181
|
-
(value) =>
|
|
15486
|
+
(value) => path13.basename(value) !== "feature-base"
|
|
15182
15487
|
).length;
|
|
15183
15488
|
}
|
|
15184
15489
|
return total;
|
|
@@ -15190,7 +15495,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15190
15495
|
ignoreDirs: ["node_modules"]
|
|
15191
15496
|
});
|
|
15192
15497
|
return files.some(
|
|
15193
|
-
(absolutePath) =>
|
|
15498
|
+
(absolutePath) => path13.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15194
15499
|
);
|
|
15195
15500
|
}
|
|
15196
15501
|
function finalizeChecks(checks) {
|
|
@@ -15207,17 +15512,17 @@ function finalizeChecks(checks) {
|
|
|
15207
15512
|
function printOnboardResult(lang, result) {
|
|
15208
15513
|
console.log();
|
|
15209
15514
|
console.log(
|
|
15210
|
-
|
|
15515
|
+
chalk9.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks"))
|
|
15211
15516
|
);
|
|
15212
15517
|
for (const check of result.checks) {
|
|
15213
|
-
const mark = check.status === "ok" ?
|
|
15214
|
-
const level = check.status === "ok" ?
|
|
15518
|
+
const mark = check.status === "ok" ? chalk9.green("\u2705") : check.status === "warn" ? chalk9.yellow("\u26A0\uFE0F") : chalk9.red("\u274C");
|
|
15519
|
+
const level = check.status === "ok" ? chalk9.green("OK") : check.status === "warn" ? chalk9.yellow("WARN") : chalk9.red("BLOCK");
|
|
15215
15520
|
console.log(`${mark} [${level}] ${check.title}`);
|
|
15216
15521
|
console.log(` ${check.message}`);
|
|
15217
|
-
if (check.path) console.log(
|
|
15522
|
+
if (check.path) console.log(chalk9.gray(` path: ${check.path}`));
|
|
15218
15523
|
if (check.suggestedCommand) {
|
|
15219
15524
|
console.log(
|
|
15220
|
-
|
|
15525
|
+
chalk9.gray(
|
|
15221
15526
|
` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`
|
|
15222
15527
|
)
|
|
15223
15528
|
);
|
|
@@ -15225,7 +15530,7 @@ function printOnboardResult(lang, result) {
|
|
|
15225
15530
|
}
|
|
15226
15531
|
console.log();
|
|
15227
15532
|
console.log(
|
|
15228
|
-
|
|
15533
|
+
chalk9.bold(
|
|
15229
15534
|
t(
|
|
15230
15535
|
lang,
|
|
15231
15536
|
`\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
|
|
@@ -15235,13 +15540,13 @@ function printOnboardResult(lang, result) {
|
|
|
15235
15540
|
);
|
|
15236
15541
|
if (result.status === "ready") {
|
|
15237
15542
|
console.log(
|
|
15238
|
-
|
|
15543
|
+
chalk9.green(
|
|
15239
15544
|
t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")
|
|
15240
15545
|
)
|
|
15241
15546
|
);
|
|
15242
15547
|
} else if (result.status === "needs_action") {
|
|
15243
15548
|
console.log(
|
|
15244
|
-
|
|
15549
|
+
chalk9.yellow(
|
|
15245
15550
|
t(
|
|
15246
15551
|
lang,
|
|
15247
15552
|
"\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15251,7 +15556,7 @@ function printOnboardResult(lang, result) {
|
|
|
15251
15556
|
);
|
|
15252
15557
|
} else {
|
|
15253
15558
|
console.log(
|
|
15254
|
-
|
|
15559
|
+
chalk9.red(
|
|
15255
15560
|
t(
|
|
15256
15561
|
lang,
|
|
15257
15562
|
"\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15359,7 +15664,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15359
15664
|
});
|
|
15360
15665
|
}
|
|
15361
15666
|
}
|
|
15362
|
-
const constitutionPath =
|
|
15667
|
+
const constitutionPath = path13.join(docsDir, "agents", "constitution.md");
|
|
15363
15668
|
if (!await fs.pathExists(constitutionPath)) {
|
|
15364
15669
|
checks.push({
|
|
15365
15670
|
id: "constitution_exists",
|
|
@@ -15401,7 +15706,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15401
15706
|
});
|
|
15402
15707
|
}
|
|
15403
15708
|
}
|
|
15404
|
-
const customPath =
|
|
15709
|
+
const customPath = path13.join(docsDir, "agents", "custom.md");
|
|
15405
15710
|
if (await fs.pathExists(customPath)) {
|
|
15406
15711
|
const content = await fs.readFile(customPath, "utf-8");
|
|
15407
15712
|
if (hasTemplateMarkers(content)) {
|
|
@@ -15430,7 +15735,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15430
15735
|
});
|
|
15431
15736
|
}
|
|
15432
15737
|
}
|
|
15433
|
-
const prdDir =
|
|
15738
|
+
const prdDir = path13.join(docsDir, "prd");
|
|
15434
15739
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
15435
15740
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
15436
15741
|
if (!prdReady) {
|
|
@@ -15448,7 +15753,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15448
15753
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
15449
15754
|
),
|
|
15450
15755
|
path: prdDir,
|
|
15451
|
-
suggestedCommand: `touch ${quotePath(
|
|
15756
|
+
suggestedCommand: `touch ${quotePath(path13.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
15452
15757
|
});
|
|
15453
15758
|
} else {
|
|
15454
15759
|
checks.push({
|
|
@@ -15589,8 +15894,8 @@ function onboardCommand(program2) {
|
|
|
15589
15894
|
);
|
|
15590
15895
|
} else {
|
|
15591
15896
|
console.error(
|
|
15592
|
-
|
|
15593
|
-
|
|
15897
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15898
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
15594
15899
|
);
|
|
15595
15900
|
printCliErrorSuggestions(suggestions, lang);
|
|
15596
15901
|
}
|
|
@@ -15633,6 +15938,14 @@ function asNonEmptyString(value, fallback) {
|
|
|
15633
15938
|
const trimmed = value.trim();
|
|
15634
15939
|
return trimmed || fallback;
|
|
15635
15940
|
}
|
|
15941
|
+
function asRequiredNonEmptyString(value, field) {
|
|
15942
|
+
const normalized = asNonEmptyString(value, "");
|
|
15943
|
+
if (normalized) return normalized;
|
|
15944
|
+
throw createCliError(
|
|
15945
|
+
"VALIDATION_FAILED",
|
|
15946
|
+
`Evidence JSON ${field} is required.`
|
|
15947
|
+
);
|
|
15948
|
+
}
|
|
15636
15949
|
function asRequiredBoolean(value, field) {
|
|
15637
15950
|
if (typeof value === "boolean") return value;
|
|
15638
15951
|
throw createCliError(
|
|
@@ -15711,6 +16024,61 @@ function normalizeCommandsExecuted(value) {
|
|
|
15711
16024
|
return entry.trim();
|
|
15712
16025
|
});
|
|
15713
16026
|
}
|
|
16027
|
+
function normalizeRequiredPathList(value, field) {
|
|
16028
|
+
if (!Array.isArray(value)) {
|
|
16029
|
+
throw createCliError(
|
|
16030
|
+
"VALIDATION_FAILED",
|
|
16031
|
+
`Evidence JSON ${field} must be a non-empty array of repo-relative paths.`
|
|
16032
|
+
);
|
|
16033
|
+
}
|
|
16034
|
+
const out = value.map((entry, index) => {
|
|
16035
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
16036
|
+
throw createCliError(
|
|
16037
|
+
"VALIDATION_FAILED",
|
|
16038
|
+
`Evidence JSON ${field}[${index}] must be a non-empty repo-relative path string.`
|
|
16039
|
+
);
|
|
16040
|
+
}
|
|
16041
|
+
return normalizeGitPath3(entry);
|
|
16042
|
+
}).filter(Boolean);
|
|
16043
|
+
if (out.length === 0) {
|
|
16044
|
+
throw createCliError(
|
|
16045
|
+
"VALIDATION_FAILED",
|
|
16046
|
+
`Evidence JSON ${field} must include at least one repo-relative path.`
|
|
16047
|
+
);
|
|
16048
|
+
}
|
|
16049
|
+
return uniquePaths(out);
|
|
16050
|
+
}
|
|
16051
|
+
function normalizeRiskSummaries(value) {
|
|
16052
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16053
|
+
throw createCliError(
|
|
16054
|
+
"VALIDATION_FAILED",
|
|
16055
|
+
'Evidence JSON "riskSummaries" is required and must include blocking, important, and minor fields.'
|
|
16056
|
+
);
|
|
16057
|
+
}
|
|
16058
|
+
const riskSummaries = value;
|
|
16059
|
+
return {
|
|
16060
|
+
blocking: asRequiredReviewField(
|
|
16061
|
+
riskSummaries.blocking,
|
|
16062
|
+
'"riskSummaries.blocking"',
|
|
16063
|
+
{ allowExplicitNone: true }
|
|
16064
|
+
),
|
|
16065
|
+
important: asRequiredReviewField(
|
|
16066
|
+
riskSummaries.important,
|
|
16067
|
+
'"riskSummaries.important"',
|
|
16068
|
+
{ allowExplicitNone: true }
|
|
16069
|
+
),
|
|
16070
|
+
minor: asRequiredReviewField(
|
|
16071
|
+
riskSummaries.minor,
|
|
16072
|
+
'"riskSummaries.minor"',
|
|
16073
|
+
{ allowExplicitNone: true }
|
|
16074
|
+
)
|
|
16075
|
+
};
|
|
16076
|
+
}
|
|
16077
|
+
function formatMissingPathGuidance(field, missingFiles) {
|
|
16078
|
+
return `Evidence JSON ${field} is missing these repo-relative paths:
|
|
16079
|
+
${missingFiles.map((file) => `- ${file}`).join("\n")}
|
|
16080
|
+
Use paths relative to the project git root, matching files.path entries.`;
|
|
16081
|
+
}
|
|
15714
16082
|
function normalizeGitPath3(value) {
|
|
15715
16083
|
return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
15716
16084
|
}
|
|
@@ -15823,7 +16191,7 @@ var PrePrReviewValidator = class {
|
|
|
15823
16191
|
return result.evidence;
|
|
15824
16192
|
}
|
|
15825
16193
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
15826
|
-
const fullPath =
|
|
16194
|
+
const fullPath = path13.resolve(evidencePath);
|
|
15827
16195
|
if (!await fs.pathExists(fullPath)) {
|
|
15828
16196
|
throw createCliError(
|
|
15829
16197
|
"INVALID_ARGUMENT",
|
|
@@ -15866,6 +16234,27 @@ var PrePrReviewValidator = class {
|
|
|
15866
16234
|
evidence.blockingFindings,
|
|
15867
16235
|
'"blockingFindings"'
|
|
15868
16236
|
),
|
|
16237
|
+
baseSha: asRequiredNonEmptyString(evidence.baseSha, '"baseSha"'),
|
|
16238
|
+
headSha: asRequiredNonEmptyString(evidence.headSha, '"headSha"'),
|
|
16239
|
+
changedFiles: normalizeRequiredPathList(
|
|
16240
|
+
evidence.changedFiles,
|
|
16241
|
+
'"changedFiles"'
|
|
16242
|
+
),
|
|
16243
|
+
reviewedFiles: normalizeRequiredPathList(
|
|
16244
|
+
evidence.reviewedFiles,
|
|
16245
|
+
'"reviewedFiles"'
|
|
16246
|
+
),
|
|
16247
|
+
riskSummaries: normalizeRiskSummaries(evidence.riskSummaries),
|
|
16248
|
+
approvalRationale: asRequiredReviewField(
|
|
16249
|
+
evidence.approvalRationale,
|
|
16250
|
+
'"approvalRationale"'
|
|
16251
|
+
),
|
|
16252
|
+
coverage: {
|
|
16253
|
+
changedFileCount: 0,
|
|
16254
|
+
reviewedFileCount: 0,
|
|
16255
|
+
reviewCoverageRatio: 0,
|
|
16256
|
+
missingFiles: []
|
|
16257
|
+
},
|
|
15869
16258
|
files: normalizeEvidenceFiles(evidence.files),
|
|
15870
16259
|
residualRisks: normalizeResidualRisks(evidence.residualRisks),
|
|
15871
16260
|
commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
|
|
@@ -15900,9 +16289,9 @@ var PrePrReviewValidator = class {
|
|
|
15900
16289
|
]);
|
|
15901
16290
|
const reviewedFiles = new Set(
|
|
15902
16291
|
normalizedEvidence.files.map(
|
|
15903
|
-
(f) =>
|
|
16292
|
+
(f) => path13.relative(
|
|
15904
16293
|
projectRoot,
|
|
15905
|
-
|
|
16294
|
+
path13.resolve(projectRoot, f.path)
|
|
15906
16295
|
)
|
|
15907
16296
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
15908
16297
|
);
|
|
@@ -15914,6 +16303,36 @@ var PrePrReviewValidator = class {
|
|
|
15914
16303
|
${missingFiles.map((f) => `- ${f}`).join("\n")}`
|
|
15915
16304
|
);
|
|
15916
16305
|
}
|
|
16306
|
+
const changedFilesInEvidence = new Set(
|
|
16307
|
+
normalizedEvidence.changedFiles.map((entry) => normalizeGitPath3(entry))
|
|
16308
|
+
);
|
|
16309
|
+
const missingChangedFiles = changedFiles.filter(
|
|
16310
|
+
(file) => !changedFilesInEvidence.has(file)
|
|
16311
|
+
);
|
|
16312
|
+
if (missingChangedFiles.length > 0) {
|
|
16313
|
+
throw createCliError(
|
|
16314
|
+
"VALIDATION_FAILED",
|
|
16315
|
+
formatMissingPathGuidance('"changedFiles"', missingChangedFiles)
|
|
16316
|
+
);
|
|
16317
|
+
}
|
|
16318
|
+
const reviewedFilesInEvidence = new Set(
|
|
16319
|
+
normalizedEvidence.reviewedFiles.map((entry) => normalizeGitPath3(entry))
|
|
16320
|
+
);
|
|
16321
|
+
const missingReviewedFiles = changedFiles.filter(
|
|
16322
|
+
(file) => !reviewedFilesInEvidence.has(file)
|
|
16323
|
+
);
|
|
16324
|
+
if (missingReviewedFiles.length > 0) {
|
|
16325
|
+
throw createCliError(
|
|
16326
|
+
"VALIDATION_FAILED",
|
|
16327
|
+
formatMissingPathGuidance('"reviewedFiles"', missingReviewedFiles)
|
|
16328
|
+
);
|
|
16329
|
+
}
|
|
16330
|
+
normalizedEvidence.coverage = {
|
|
16331
|
+
changedFileCount: changedFiles.length,
|
|
16332
|
+
reviewedFileCount: normalizedEvidence.reviewedFiles.length,
|
|
16333
|
+
reviewCoverageRatio: changedFiles.length === 0 ? 1 : normalizedEvidence.reviewedFiles.length / changedFiles.length,
|
|
16334
|
+
missingFiles: missingReviewedFiles
|
|
16335
|
+
};
|
|
15917
16336
|
return {
|
|
15918
16337
|
evidence: normalizedEvidence,
|
|
15919
16338
|
scope
|
|
@@ -16048,11 +16467,27 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
|
|
|
16048
16467
|
specAlignmentChecked: true,
|
|
16049
16468
|
findingCount: 0,
|
|
16050
16469
|
blockingFindings: 0,
|
|
16470
|
+
baseSha: "unrecorded",
|
|
16471
|
+
headSha: "unrecorded",
|
|
16472
|
+
changedFiles: [],
|
|
16473
|
+
reviewedFiles: [],
|
|
16474
|
+
riskSummaries: {
|
|
16475
|
+
blocking: "none",
|
|
16476
|
+
important: "manual direct record without structured evidence",
|
|
16477
|
+
minor: "manual direct record without structured evidence"
|
|
16478
|
+
},
|
|
16479
|
+
approvalRationale: "manual direct record without structured evidence; not valid for approve",
|
|
16480
|
+
coverage: {
|
|
16481
|
+
changedFileCount: 0,
|
|
16482
|
+
reviewedFileCount: 0,
|
|
16483
|
+
reviewCoverageRatio: 0,
|
|
16484
|
+
missingFiles: []
|
|
16485
|
+
},
|
|
16051
16486
|
files: [],
|
|
16052
16487
|
residualRisks: ["none"],
|
|
16053
16488
|
commandsExecuted: []
|
|
16054
16489
|
};
|
|
16055
|
-
function
|
|
16490
|
+
function escapeRegExp5(value) {
|
|
16056
16491
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16057
16492
|
}
|
|
16058
16493
|
function normalizeDecision(raw) {
|
|
@@ -16066,14 +16501,14 @@ function normalizeDecision(raw) {
|
|
|
16066
16501
|
return null;
|
|
16067
16502
|
}
|
|
16068
16503
|
function findSpecLineIndex(lines, keys) {
|
|
16069
|
-
const escaped = keys.map((key) =>
|
|
16504
|
+
const escaped = keys.map((key) => escapeRegExp5(key));
|
|
16070
16505
|
const re = new RegExp(
|
|
16071
16506
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16072
16507
|
);
|
|
16073
16508
|
return lines.findIndex((line) => re.test(line));
|
|
16074
16509
|
}
|
|
16075
16510
|
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
16076
|
-
const escaped = keys.map((key) =>
|
|
16511
|
+
const escaped = keys.map((key) => escapeRegExp5(key));
|
|
16077
16512
|
const re = new RegExp(
|
|
16078
16513
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16079
16514
|
);
|
|
@@ -16163,11 +16598,28 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
|
|
|
16163
16598
|
- **Spec Alignment Checked**: ${input.evidence.specAlignmentChecked ? "yes" : "no"}
|
|
16164
16599
|
- **Finding Count**: ${input.evidence.findingCount}
|
|
16165
16600
|
- **Blocking Findings**: ${input.evidence.blockingFindings}
|
|
16601
|
+
- **Base SHA**: ${input.evidence.baseSha}
|
|
16602
|
+
- **Head SHA**: ${input.evidence.headSha}
|
|
16603
|
+
- **Approval Rationale**: ${input.evidence.approvalRationale}
|
|
16166
16604
|
${commandsRun}
|
|
16167
16605
|
|
|
16168
16606
|
- **Residual Risks**:
|
|
16169
16607
|
${residualRisksSection}
|
|
16170
16608
|
|
|
16609
|
+
- **Risk Summaries**:
|
|
16610
|
+
- Blocking: ${input.evidence.riskSummaries.blocking}
|
|
16611
|
+
- Important: ${input.evidence.riskSummaries.important}
|
|
16612
|
+
- Minor: ${input.evidence.riskSummaries.minor}
|
|
16613
|
+
|
|
16614
|
+
- **Evidence Coverage**:
|
|
16615
|
+
- Changed File Count: ${input.evidence.coverage.changedFileCount}
|
|
16616
|
+
- Reviewed File Count: ${input.evidence.coverage.reviewedFileCount}
|
|
16617
|
+
- Review Coverage Ratio: ${input.evidence.coverage.reviewCoverageRatio.toFixed(2)}
|
|
16618
|
+
- Reviewed Files:
|
|
16619
|
+
${input.evidence.reviewedFiles.length > 0 ? input.evidence.reviewedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)"}
|
|
16620
|
+
- Evidence Changed Files:
|
|
16621
|
+
${input.evidence.changedFiles.length > 0 ? input.evidence.changedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)"}
|
|
16622
|
+
|
|
16171
16623
|
- **Review Scope**:
|
|
16172
16624
|
- **Main Base Ref**: ${input.scope.baseRef}
|
|
16173
16625
|
- **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
|
|
@@ -16221,7 +16673,7 @@ function prePrReviewCommand(program2) {
|
|
|
16221
16673
|
})
|
|
16222
16674
|
);
|
|
16223
16675
|
} else {
|
|
16224
|
-
console.error(
|
|
16676
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16225
16677
|
printCliErrorSuggestions(suggestions, lang);
|
|
16226
16678
|
}
|
|
16227
16679
|
process.exitCode = 1;
|
|
@@ -16233,7 +16685,7 @@ function prePrReviewCommand(program2) {
|
|
|
16233
16685
|
"Decision outcome: approve | changes_requested | blocked"
|
|
16234
16686
|
).option("--note <text>", "Decision note text").option(
|
|
16235
16687
|
"--evidence <path>",
|
|
16236
|
-
"
|
|
16688
|
+
"Structured review evidence path (for example review-trace.json); required for approve and whenever policy demands it"
|
|
16237
16689
|
).option("--json", "Output JSON").action(
|
|
16238
16690
|
async (featureName, options) => {
|
|
16239
16691
|
try {
|
|
@@ -16253,7 +16705,7 @@ function prePrReviewCommand(program2) {
|
|
|
16253
16705
|
})
|
|
16254
16706
|
);
|
|
16255
16707
|
} else {
|
|
16256
|
-
console.error(
|
|
16708
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16257
16709
|
printCliErrorSuggestions(suggestions, lang);
|
|
16258
16710
|
}
|
|
16259
16711
|
process.exitCode = 1;
|
|
@@ -16300,7 +16752,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16300
16752
|
);
|
|
16301
16753
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16302
16754
|
const preferred = getPreferredKeys(config.lang);
|
|
16303
|
-
const tasksPath =
|
|
16755
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16304
16756
|
let tasksUpdated = false;
|
|
16305
16757
|
if (await fs.pathExists(tasksPath)) {
|
|
16306
16758
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16351,7 +16803,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16351
16803
|
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
16352
16804
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
16353
16805
|
delegatedWorkRequired: true,
|
|
16354
|
-
nextMainState: "
|
|
16806
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
16355
16807
|
evidenceFile: "review-trace.json",
|
|
16356
16808
|
tasksUpdated,
|
|
16357
16809
|
tasksPath,
|
|
@@ -16370,26 +16822,26 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16370
16822
|
console.log(prompt);
|
|
16371
16823
|
console.log();
|
|
16372
16824
|
console.log(
|
|
16373
|
-
|
|
16374
|
-
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. review-trace.json\uC744 \uC9C1\uC811 \uC0DD\uC131\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the review handoff. It does not
|
|
16825
|
+
chalk9.yellow(
|
|
16826
|
+
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. review-trace.json\uC744 \uC9C1\uC811 \uC0DD\uC131\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the review handoff. It does not complete the review or advance workflow state by itself."
|
|
16375
16827
|
)
|
|
16376
16828
|
);
|
|
16377
16829
|
console.log(
|
|
16378
|
-
|
|
16830
|
+
chalk9.gray(
|
|
16379
16831
|
config.lang === "ko" ? "- \uAE30\uC874 pre-PR \uB9AC\uBDF0 \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC788\uC73C\uBA74 \uC7AC\uC0AC\uC6A9\uD558\uACE0, \uAE30\uBCF8\uC740 1\uAC1C\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694." : "- Reuse the existing pre-PR helper agent if one already exists; default to a single helper agent."
|
|
16380
16832
|
)
|
|
16381
16833
|
);
|
|
16382
16834
|
console.log(
|
|
16383
|
-
|
|
16835
|
+
chalk9.gray(
|
|
16384
16836
|
config.lang === "ko" ? "- \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uD55C\uB3C4\uC5D0 \uAC78\uB9AC\uBA74 \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uC5D0\uC11C \uB9AC\uBDF0\uB97C \uC774\uC5B4\uAC00\uC138\uC694." : "- If helper-agent quota is exhausted, continue the review in the main agent."
|
|
16385
16837
|
)
|
|
16386
16838
|
);
|
|
16387
16839
|
console.log(`Reuse key: pre-pr:${featureRef}`);
|
|
16388
16840
|
console.log(`Suggested parallelism: 1`);
|
|
16389
|
-
console.log(`Next main state:
|
|
16841
|
+
console.log(`Next main state: pre_pr_review_in_progress`);
|
|
16390
16842
|
console.log(`Evidence file: review-trace.json`);
|
|
16391
16843
|
console.log(
|
|
16392
|
-
config.lang === "ko" ? "Next required: delegated review\uB97C \uC774\uC5B4\uC11C \uC218\uD589\uD558\uACE0 review-trace.json\uC744 \uB9CC\uB4E0 \uB4A4 pre-pr-review\uB85C \uAE30\uB85D" : "Next required: continue the delegated review, generate review-trace.json, then record the result with pre-pr-review"
|
|
16844
|
+
config.lang === "ko" ? "Next required: delegated review\uB97C \uC774\uC5B4\uC11C \uC218\uD589\uD558\uACE0 \uAD6C\uC870\uD654\uB41C review-trace.json\uC744 \uB9CC\uB4E0 \uB4A4 pre-pr-review\uB85C \uAE30\uB85D" : "Next required: continue the delegated review, generate structured review-trace.json, then record the result with pre-pr-review"
|
|
16393
16845
|
);
|
|
16394
16846
|
if (tasksUpdated) {
|
|
16395
16847
|
console.log(`tasks.md updated: ${tasksPath}`);
|
|
@@ -16411,7 +16863,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16411
16863
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
16412
16864
|
);
|
|
16413
16865
|
}
|
|
16414
|
-
const tasksPath =
|
|
16866
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16415
16867
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
16416
16868
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16417
16869
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -16444,6 +16896,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
16444
16896
|
"`--evidence <path>` is required when workflow.prePrReview.enforceExecutionEvidence=true."
|
|
16445
16897
|
);
|
|
16446
16898
|
}
|
|
16899
|
+
if (decision === "approve" && !options.evidence) {
|
|
16900
|
+
throw createCliError(
|
|
16901
|
+
"INVALID_ARGUMENT",
|
|
16902
|
+
"`--evidence <path>` is required for approve decisions. Generate structured review evidence first, for example `--evidence review-trace.json`."
|
|
16903
|
+
);
|
|
16904
|
+
}
|
|
16447
16905
|
if (options.evidence) {
|
|
16448
16906
|
const validator = new PrePrReviewValidator(ctx);
|
|
16449
16907
|
const validationResult = await validator.validateEvidenceWithScope(
|
|
@@ -16503,7 +16961,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16503
16961
|
}
|
|
16504
16962
|
}
|
|
16505
16963
|
}
|
|
16506
|
-
const decisionsPath =
|
|
16964
|
+
const decisionsPath = path13.join(feature.path, "decisions.md");
|
|
16507
16965
|
const decisionLogEntry = buildReportContent({
|
|
16508
16966
|
folderName: feature.folderName,
|
|
16509
16967
|
date,
|
|
@@ -16519,9 +16977,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
16519
16977
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
16520
16978
|
}
|
|
16521
16979
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
16522
|
-
|
|
16980
|
+
path13.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
16523
16981
|
);
|
|
16524
|
-
const evidencePath =
|
|
16982
|
+
const evidencePath = path13.basename(config.docsDir) === "docs" ? normalizePathForDoc(path13.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
16525
16983
|
let nextTasks = tasksContent;
|
|
16526
16984
|
nextTasks = upsertSpecLine(
|
|
16527
16985
|
nextTasks,
|
|
@@ -16568,26 +17026,26 @@ async function runPrePrReview(featureName, options) {
|
|
|
16568
17026
|
return;
|
|
16569
17027
|
}
|
|
16570
17028
|
console.log();
|
|
16571
|
-
console.log(
|
|
16572
|
-
console.log(
|
|
16573
|
-
console.log(
|
|
17029
|
+
console.log(chalk9.green(`\u2705 pre-pr-review completed: ${feature.folderName}`));
|
|
17030
|
+
console.log(chalk9.gray(`- Decision: ${decision}`));
|
|
17031
|
+
console.log(chalk9.gray(`- Decisions log: ${decisionsPath}`));
|
|
16574
17032
|
if (nextTasks !== tasksContent) {
|
|
16575
|
-
console.log(
|
|
17033
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
16576
17034
|
}
|
|
16577
17035
|
console.log();
|
|
16578
17036
|
}
|
|
16579
|
-
function
|
|
17037
|
+
function escapeRegExp6(value) {
|
|
16580
17038
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16581
17039
|
}
|
|
16582
17040
|
function findSpecLineIndex2(lines, keys) {
|
|
16583
|
-
const escaped = keys.map((key) =>
|
|
17041
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16584
17042
|
const re = new RegExp(
|
|
16585
17043
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16586
17044
|
);
|
|
16587
17045
|
return lines.findIndex((line) => re.test(line));
|
|
16588
17046
|
}
|
|
16589
17047
|
function replaceSpecLine2(line, keys, preferredKey, value) {
|
|
16590
|
-
const escaped = keys.map((key) =>
|
|
17048
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16591
17049
|
const re = new RegExp(
|
|
16592
17050
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16593
17051
|
);
|
|
@@ -16662,7 +17120,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16662
17120
|
);
|
|
16663
17121
|
}
|
|
16664
17122
|
const feature = state.matchedFeature;
|
|
16665
|
-
const tasksPath =
|
|
17123
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16666
17124
|
let tasksUpdated = false;
|
|
16667
17125
|
if (await fs.pathExists(tasksPath)) {
|
|
16668
17126
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16697,7 +17155,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16697
17155
|
nextMainState: "code_review_running",
|
|
16698
17156
|
tasksUpdated,
|
|
16699
17157
|
tasksPath,
|
|
16700
|
-
decisionsPath:
|
|
17158
|
+
decisionsPath: path13.join(feature.path, "decisions.md"),
|
|
16701
17159
|
prompt,
|
|
16702
17160
|
recordedAt: getLocalDateString()
|
|
16703
17161
|
};
|
|
@@ -16708,30 +17166,30 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16708
17166
|
console.log(prompt);
|
|
16709
17167
|
console.log();
|
|
16710
17168
|
console.log(
|
|
16711
|
-
|
|
17169
|
+
chalk9.yellow(
|
|
16712
17170
|
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 PR \uB9AC\uBDF0 \uCF54\uBA58\uD2B8 \uB300\uC751\uC6A9 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. \uCF54\uBA58\uD2B8\uB97C \uC9C1\uC811 \uC77D\uC5B4\uC624\uAC70\uB098 evidence/decision\uC744 \uC790\uB3D9 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC73C\uBA70, \uC0C1\uD0DC\uB3C4 \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares a handoff for addressing PR review comments. It does not fetch comments automatically, record review evidence/decision, or advance workflow state by itself."
|
|
16713
17171
|
)
|
|
16714
17172
|
);
|
|
16715
|
-
console.log(
|
|
16716
|
-
console.log(
|
|
16717
|
-
console.log(
|
|
16718
|
-
console.log(
|
|
17173
|
+
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17174
|
+
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17175
|
+
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17176
|
+
console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
|
|
16719
17177
|
console.log(
|
|
16720
|
-
|
|
17178
|
+
chalk9.gray(
|
|
16721
17179
|
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
16722
17180
|
)
|
|
16723
17181
|
);
|
|
16724
|
-
console.log(
|
|
17182
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
16725
17183
|
if (tasksUpdated) {
|
|
16726
|
-
console.log(
|
|
17184
|
+
console.log(chalk9.gray(`- tasks.md updated: ${payload.tasksPath}`));
|
|
16727
17185
|
console.log(
|
|
16728
|
-
|
|
17186
|
+
chalk9.gray(
|
|
16729
17187
|
config.lang === "ko" ? "- PR \uB9AC\uBDF0 \uC0C1\uD0DC: Running" : "- PR Review status: Running"
|
|
16730
17188
|
)
|
|
16731
17189
|
);
|
|
16732
17190
|
}
|
|
16733
|
-
console.log(
|
|
16734
|
-
console.log(
|
|
17191
|
+
console.log(chalk9.gray(`- tasks.md: ${payload.tasksPath}`));
|
|
17192
|
+
console.log(chalk9.gray(`- decisions.md: ${payload.decisionsPath}`));
|
|
16735
17193
|
}
|
|
16736
17194
|
function codeReviewRunCommand(program2) {
|
|
16737
17195
|
program2.command("code-review-run [feature-name]").description("Prepare a PR review execution handoff for sub-agent work").option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(
|
|
@@ -16753,7 +17211,7 @@ function codeReviewRunCommand(program2) {
|
|
|
16753
17211
|
})
|
|
16754
17212
|
);
|
|
16755
17213
|
} else {
|
|
16756
|
-
console.error(
|
|
17214
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16757
17215
|
printCliErrorSuggestions(suggestions, lang);
|
|
16758
17216
|
}
|
|
16759
17217
|
process.exitCode = 1;
|
|
@@ -16788,8 +17246,8 @@ function requirementsCommand(program2) {
|
|
|
16788
17246
|
const lang = ctx?.config?.lang ?? DEFAULT_LANG;
|
|
16789
17247
|
const cliError = toCliError(error);
|
|
16790
17248
|
console.error(
|
|
16791
|
-
|
|
16792
|
-
|
|
17249
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
17250
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
16793
17251
|
);
|
|
16794
17252
|
process.exitCode = 1;
|
|
16795
17253
|
}
|
|
@@ -16822,7 +17280,7 @@ async function runRequirements(options) {
|
|
|
16822
17280
|
}
|
|
16823
17281
|
for (const feature of scan.features) {
|
|
16824
17282
|
if (!feature.docs.tasksExists) continue;
|
|
16825
|
-
const tasksPath =
|
|
17283
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16826
17284
|
let tasksContent = "";
|
|
16827
17285
|
try {
|
|
16828
17286
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -16956,10 +17414,10 @@ async function runRequirements(options) {
|
|
|
16956
17414
|
process.stdout.write(`${lines.join("\n")}
|
|
16957
17415
|
`);
|
|
16958
17416
|
if (options.write) {
|
|
16959
|
-
const outputPath =
|
|
17417
|
+
const outputPath = path13.join(docsDir, "prd", "status.md");
|
|
16960
17418
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
16961
17419
|
`, "utf-8");
|
|
16962
|
-
console.log(
|
|
17420
|
+
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
16963
17421
|
}
|
|
16964
17422
|
if (options.strict && issuesFound) process.exitCode = 1;
|
|
16965
17423
|
}
|
|
@@ -17041,7 +17499,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
17041
17499
|
}
|
|
17042
17500
|
async function runTaskRun(featureName, options) {
|
|
17043
17501
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
17044
|
-
const tasksPath =
|
|
17502
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
17045
17503
|
if (!await fs.pathExists(tasksPath)) {
|
|
17046
17504
|
throw createCliError(
|
|
17047
17505
|
"PRECONDITION_FAILED",
|
|
@@ -17112,20 +17570,20 @@ async function runTaskRun(featureName, options) {
|
|
|
17112
17570
|
}
|
|
17113
17571
|
console.log(prompt);
|
|
17114
17572
|
console.log();
|
|
17115
|
-
console.log(
|
|
17116
|
-
console.log(
|
|
17117
|
-
console.log(
|
|
17118
|
-
console.log(
|
|
17573
|
+
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17574
|
+
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17575
|
+
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17576
|
+
console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
|
|
17119
17577
|
console.log(
|
|
17120
|
-
|
|
17578
|
+
chalk9.gray(
|
|
17121
17579
|
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
17122
17580
|
)
|
|
17123
17581
|
);
|
|
17124
|
-
console.log(
|
|
17582
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17125
17583
|
if (tasksUpdated) {
|
|
17126
17584
|
console.log();
|
|
17127
|
-
console.log(
|
|
17128
|
-
console.log(
|
|
17585
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
17586
|
+
console.log(chalk9.gray(`- task status: TODO -> DOING`));
|
|
17129
17587
|
}
|
|
17130
17588
|
}
|
|
17131
17589
|
function taskRunCommand(program2) {
|
|
@@ -17149,7 +17607,7 @@ function taskRunCommand(program2) {
|
|
|
17149
17607
|
})
|
|
17150
17608
|
);
|
|
17151
17609
|
} else {
|
|
17152
|
-
console.error(
|
|
17610
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
17153
17611
|
printCliErrorSuggestions(suggestions, lang);
|
|
17154
17612
|
}
|
|
17155
17613
|
process.exitCode = 1;
|
|
@@ -17200,7 +17658,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
17200
17658
|
}
|
|
17201
17659
|
async function runTaskComplete(featureName, options) {
|
|
17202
17660
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
17203
|
-
const tasksPath =
|
|
17661
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
17204
17662
|
if (!await fs.pathExists(tasksPath)) {
|
|
17205
17663
|
throw createCliError(
|
|
17206
17664
|
"PRECONDITION_FAILED",
|
|
@@ -17260,13 +17718,13 @@ async function runTaskComplete(featureName, options) {
|
|
|
17260
17718
|
return;
|
|
17261
17719
|
}
|
|
17262
17720
|
console.log(
|
|
17263
|
-
|
|
17721
|
+
chalk9.green(
|
|
17264
17722
|
`Marked task ${resolvedTask.taskId} as DONE for ${feature.folderName}.`
|
|
17265
17723
|
)
|
|
17266
17724
|
);
|
|
17267
|
-
console.log(
|
|
17268
|
-
console.log(
|
|
17269
|
-
console.log(
|
|
17725
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
17726
|
+
console.log(chalk9.gray(`- status: ${resolvedTask.status} -> DONE`));
|
|
17727
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17270
17728
|
}
|
|
17271
17729
|
function taskCompleteCommand(program2) {
|
|
17272
17730
|
program2.command("task-complete [feature-name]").description("Mark the active DOING/REVIEW task as DONE").option("--component <component>", "Component name for multi projects").option("--task <task-id>", "Explicit task id to mark DONE").option("--json", "Output JSON").action(
|
|
@@ -17288,7 +17746,7 @@ function taskCompleteCommand(program2) {
|
|
|
17288
17746
|
})
|
|
17289
17747
|
);
|
|
17290
17748
|
} else {
|
|
17291
|
-
console.error(
|
|
17749
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
17292
17750
|
printCliErrorSuggestions(suggestions, lang);
|
|
17293
17751
|
}
|
|
17294
17752
|
process.exitCode = 1;
|
|
@@ -17335,15 +17793,15 @@ function getBanner(opts) {
|
|
|
17335
17793
|
${version}
|
|
17336
17794
|
` : "\n";
|
|
17337
17795
|
if (process.stdout.isTTY) {
|
|
17338
|
-
return `${
|
|
17796
|
+
return `${chalk9.cyan(ascii)}${chalk9.gray(footer)}`;
|
|
17339
17797
|
}
|
|
17340
17798
|
return `${ascii}${footer}`;
|
|
17341
17799
|
}
|
|
17342
|
-
var CACHE_FILE =
|
|
17800
|
+
var CACHE_FILE = path13.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
17343
17801
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
17344
17802
|
function getCurrentVersion() {
|
|
17345
17803
|
try {
|
|
17346
|
-
const packageJsonPath =
|
|
17804
|
+
const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
|
|
17347
17805
|
if (fs.existsSync(packageJsonPath)) {
|
|
17348
17806
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17349
17807
|
return pkg.version;
|
|
@@ -17377,8 +17835,8 @@ function resolveUpdateNoticeLang() {
|
|
|
17377
17835
|
}
|
|
17378
17836
|
function printUpdateNotice(current, latest, lang) {
|
|
17379
17837
|
console.log();
|
|
17380
|
-
console.log(
|
|
17381
|
-
console.log(
|
|
17838
|
+
console.log(chalk9.yellow(tr(lang, "cli", "versionCheck.noticeAvailable", { latest, current })));
|
|
17839
|
+
console.log(chalk9.gray(tr(lang, "cli", "versionCheck.updateCommand")));
|
|
17382
17840
|
console.log();
|
|
17383
17841
|
}
|
|
17384
17842
|
function spawnBackgroundVersionCheck() {
|
|
@@ -17447,7 +17905,7 @@ function shouldCheckForUpdates() {
|
|
|
17447
17905
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
17448
17906
|
function getCliVersion() {
|
|
17449
17907
|
try {
|
|
17450
|
-
const packageJsonPath =
|
|
17908
|
+
const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
|
|
17451
17909
|
if (fs.existsSync(packageJsonPath)) {
|
|
17452
17910
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17453
17911
|
if (pkg?.version) return String(pkg.version);
|
|
@@ -17465,6 +17923,7 @@ if (shouldShowBanner()) {
|
|
|
17465
17923
|
}
|
|
17466
17924
|
initCommand(program);
|
|
17467
17925
|
featureCommand(program);
|
|
17926
|
+
ideaCommand(program);
|
|
17468
17927
|
statusCommand(program);
|
|
17469
17928
|
updateCommand(program);
|
|
17470
17929
|
configCommand(program);
|