lee-spec-kit 0.7.0 → 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/README.en.md +1 -0
- package/dist/index.js +1200 -570
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +6 -0
- package/templates/en/common/agents/agents.md +2 -1
- package/templates/en/common/agents/skills/execute-task.md +2 -1
- 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/agents/agents.md +2 -1
- package/templates/ko/common/agents/skills/execute-task.md +2 -1
- 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
|
}
|
|
@@ -1990,6 +2029,7 @@ In approval-waiting state:
|
|
|
1990
2029
|
3. Do not paraphrase or omit these lines.
|
|
1991
2030
|
4. Prefer \`approvalRequest.userFacingLines\` as the source for user-facing approval text.
|
|
1992
2031
|
5. Prefer \`matchedFeature.currentSubstateOwner\` plus \`agentOrchestration.subAgentHandoff\` as the delegation SSOT. Treat \`currentActionShouldDelegate\` as a compatibility mirror for older consumers.
|
|
2032
|
+
6. When \`matchedFeature.currentSubstateOwner="subagent"\` and \`agentOrchestration.subAgentHandoff.required=true\` with \`mode="command"\`, call \`spawn_agent\` first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, continue the delegated work immediately and do not re-open the same approval label.
|
|
1993
2033
|
|
|
1994
2034
|
In non-approval state (progress updates, analysis, tool execution logs, unrelated Q&A):
|
|
1995
2035
|
|
|
@@ -2004,23 +2044,35 @@ If approval is still pending after answering an unrelated question:
|
|
|
2004
2044
|
- \`actionOptions[*].approvalPrompt\` (label meaning included), and
|
|
2005
2045
|
- \`approvalRequest.finalPrompt\` (format line).
|
|
2006
2046
|
- Never output \`finalPrompt\` alone without the matching \`A: ...\` prompt.`;
|
|
2007
|
-
function
|
|
2047
|
+
function renderManagedSegment(lang, docsRepo) {
|
|
2008
2048
|
return `${LEE_SPEC_KIT_AGENTS_BEGIN}
|
|
2009
2049
|
${CANONICAL_LEE_SPEC_KIT_AGENTS_TEXT}
|
|
2010
|
-
${LEE_SPEC_KIT_AGENTS_END}
|
|
2050
|
+
${LEE_SPEC_KIT_AGENTS_END}`;
|
|
2051
|
+
}
|
|
2052
|
+
function renderManagedBlock(lang, docsRepo) {
|
|
2053
|
+
return `${renderManagedSegment()}
|
|
2011
2054
|
|
|
2012
2055
|
`;
|
|
2013
2056
|
}
|
|
2014
2057
|
async function upsertLeeSpecKitAgentsMd(filePath, options) {
|
|
2015
2058
|
const block = renderManagedBlock(options.lang, options.docsRepo);
|
|
2059
|
+
const segment = renderManagedSegment(options.lang, options.docsRepo);
|
|
2016
2060
|
const exists = await fs.pathExists(filePath);
|
|
2017
2061
|
if (!exists) {
|
|
2018
2062
|
await fs.writeFile(filePath, block, "utf-8");
|
|
2019
2063
|
return { changed: true, action: "created" };
|
|
2020
2064
|
}
|
|
2021
2065
|
const current = await fs.readFile(filePath, "utf-8");
|
|
2022
|
-
|
|
2023
|
-
|
|
2066
|
+
const beginIndex = current.indexOf(LEE_SPEC_KIT_AGENTS_BEGIN);
|
|
2067
|
+
const endIndex = current.indexOf(LEE_SPEC_KIT_AGENTS_END);
|
|
2068
|
+
if (beginIndex !== -1 && endIndex !== -1 && beginIndex <= endIndex) {
|
|
2069
|
+
const replaceEnd = endIndex + LEE_SPEC_KIT_AGENTS_END.length;
|
|
2070
|
+
const next2 = `${current.slice(0, beginIndex)}${segment}${current.slice(replaceEnd)}`;
|
|
2071
|
+
if (next2 === current) {
|
|
2072
|
+
return { changed: false, action: "noop" };
|
|
2073
|
+
}
|
|
2074
|
+
await fs.writeFile(filePath, next2, "utf-8");
|
|
2075
|
+
return { changed: true, action: "updated" };
|
|
2024
2076
|
}
|
|
2025
2077
|
let next = current;
|
|
2026
2078
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
@@ -2158,7 +2210,7 @@ function initCommand(program2) {
|
|
|
2158
2210
|
} catch (error) {
|
|
2159
2211
|
if (error instanceof Error && error.message === "canceled") {
|
|
2160
2212
|
const lang2 = options.lang ?? DEFAULT_LANG;
|
|
2161
|
-
console.log(
|
|
2213
|
+
console.log(chalk9.yellow(`
|
|
2162
2214
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
2163
2215
|
return;
|
|
2164
2216
|
}
|
|
@@ -2166,8 +2218,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2166
2218
|
const cliError = toCliError(error);
|
|
2167
2219
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
2168
2220
|
console.error(
|
|
2169
|
-
|
|
2170
|
-
|
|
2221
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
2222
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
2171
2223
|
);
|
|
2172
2224
|
printCliErrorSuggestions(suggestions, lang);
|
|
2173
2225
|
process.exitCode = 1;
|
|
@@ -2177,7 +2229,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2177
2229
|
}
|
|
2178
2230
|
async function runInit(options) {
|
|
2179
2231
|
const cwd = process.cwd();
|
|
2180
|
-
const defaultName =
|
|
2232
|
+
const defaultName = path13.basename(cwd);
|
|
2181
2233
|
let projectName = options.name || defaultName;
|
|
2182
2234
|
let projectType = options.type;
|
|
2183
2235
|
let components = parseComponentsOption(options.components);
|
|
@@ -2188,7 +2240,7 @@ async function runInit(options) {
|
|
|
2188
2240
|
let docsRemote = options.docsRemote;
|
|
2189
2241
|
let projectRoot;
|
|
2190
2242
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
2191
|
-
const targetDir =
|
|
2243
|
+
const targetDir = path13.resolve(cwd, options.dir || "./docs");
|
|
2192
2244
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
2193
2245
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
2194
2246
|
throw createCliError(
|
|
@@ -2228,18 +2280,18 @@ async function runInit(options) {
|
|
|
2228
2280
|
}
|
|
2229
2281
|
console.log();
|
|
2230
2282
|
console.log(
|
|
2231
|
-
|
|
2283
|
+
chalk9.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
|
|
2232
2284
|
);
|
|
2233
2285
|
if (isInsideGitRepo) {
|
|
2234
|
-
console.log(
|
|
2286
|
+
console.log(chalk9.green(tr(lang, "cli", "init.gitDetected")));
|
|
2235
2287
|
console.log();
|
|
2236
|
-
console.log(
|
|
2237
|
-
console.log(
|
|
2238
|
-
console.log(
|
|
2239
|
-
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")));
|
|
2240
2292
|
} else {
|
|
2241
|
-
console.log(
|
|
2242
|
-
console.log(
|
|
2293
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.gitNotDetected")));
|
|
2294
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
|
|
2243
2295
|
}
|
|
2244
2296
|
console.log();
|
|
2245
2297
|
const response = await prompts(
|
|
@@ -2561,31 +2613,31 @@ async function runInit(options) {
|
|
|
2561
2613
|
initial: false
|
|
2562
2614
|
});
|
|
2563
2615
|
if (!overwrite) {
|
|
2564
|
-
console.log(
|
|
2616
|
+
console.log(chalk9.yellow(tr(lang, "cli", "common.canceled")));
|
|
2565
2617
|
return;
|
|
2566
2618
|
}
|
|
2567
2619
|
}
|
|
2568
2620
|
}
|
|
2569
2621
|
}
|
|
2570
2622
|
console.log();
|
|
2571
|
-
console.log(
|
|
2623
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
2572
2624
|
console.log(
|
|
2573
|
-
|
|
2625
|
+
chalk9.gray(
|
|
2574
2626
|
` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
|
|
2575
2627
|
)
|
|
2576
2628
|
);
|
|
2577
2629
|
console.log(
|
|
2578
|
-
|
|
2630
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
|
|
2579
2631
|
);
|
|
2580
2632
|
console.log(
|
|
2581
|
-
|
|
2633
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`)
|
|
2582
2634
|
);
|
|
2583
2635
|
console.log(
|
|
2584
|
-
|
|
2636
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
2585
2637
|
);
|
|
2586
2638
|
console.log();
|
|
2587
2639
|
const templatesDir = getTemplatesDir();
|
|
2588
|
-
const commonPath =
|
|
2640
|
+
const commonPath = path13.join(templatesDir, lang, "common");
|
|
2589
2641
|
if (!await fs.pathExists(commonPath)) {
|
|
2590
2642
|
throw new Error(
|
|
2591
2643
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2594,11 +2646,11 @@ async function runInit(options) {
|
|
|
2594
2646
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
2595
2647
|
await copyTemplates(fsAdapter, commonPath, targetDir);
|
|
2596
2648
|
if (projectType === "multi") {
|
|
2597
|
-
const featuresRoot =
|
|
2649
|
+
const featuresRoot = path13.join(targetDir, "features");
|
|
2598
2650
|
for (const component of components) {
|
|
2599
|
-
const componentDir =
|
|
2651
|
+
const componentDir = path13.join(featuresRoot, component);
|
|
2600
2652
|
await fs.ensureDir(componentDir);
|
|
2601
|
-
const readmePath =
|
|
2653
|
+
const readmePath = path13.join(componentDir, "README.md");
|
|
2602
2654
|
if (!await fs.pathExists(readmePath)) {
|
|
2603
2655
|
await fs.writeFile(
|
|
2604
2656
|
readmePath,
|
|
@@ -2659,20 +2711,20 @@ async function runInit(options) {
|
|
|
2659
2711
|
config.projectRoot = projectRoot;
|
|
2660
2712
|
}
|
|
2661
2713
|
}
|
|
2662
|
-
const configPath =
|
|
2714
|
+
const configPath = path13.join(targetDir, ".lee-spec-kit.json");
|
|
2663
2715
|
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
2664
2716
|
const extraCommitPathsAbs = [];
|
|
2665
2717
|
try {
|
|
2666
2718
|
if (docsRepo === "embedded") {
|
|
2667
2719
|
const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
|
|
2668
|
-
const agentsMdPath =
|
|
2720
|
+
const agentsMdPath = path13.join(repoRoot, "AGENTS.md");
|
|
2669
2721
|
const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
|
|
2670
2722
|
lang,
|
|
2671
2723
|
docsRepo
|
|
2672
2724
|
});
|
|
2673
2725
|
if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
|
|
2674
2726
|
} else {
|
|
2675
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2727
|
+
await upsertLeeSpecKitAgentsMd(path13.join(targetDir, "AGENTS.md"), {
|
|
2676
2728
|
lang,
|
|
2677
2729
|
docsRepo
|
|
2678
2730
|
});
|
|
@@ -2682,16 +2734,16 @@ async function runInit(options) {
|
|
|
2682
2734
|
} else if (projectRoot && typeof projectRoot === "object") {
|
|
2683
2735
|
roots.push(...Object.values(projectRoot));
|
|
2684
2736
|
}
|
|
2685
|
-
const resolvedCwd =
|
|
2737
|
+
const resolvedCwd = path13.resolve(cwd);
|
|
2686
2738
|
for (const raw of roots) {
|
|
2687
2739
|
const value = String(raw || "").trim();
|
|
2688
2740
|
if (!value) continue;
|
|
2689
|
-
const abs =
|
|
2690
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
2741
|
+
const abs = path13.resolve(cwd, value);
|
|
2742
|
+
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path13.sep}`)) {
|
|
2691
2743
|
if (await fs.pathExists(abs)) {
|
|
2692
2744
|
const stat = await fs.stat(abs);
|
|
2693
2745
|
if (stat.isDirectory()) {
|
|
2694
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2746
|
+
await upsertLeeSpecKitAgentsMd(path13.join(abs, "AGENTS.md"), {
|
|
2695
2747
|
lang,
|
|
2696
2748
|
docsRepo
|
|
2697
2749
|
});
|
|
@@ -2702,7 +2754,7 @@ async function runInit(options) {
|
|
|
2702
2754
|
}
|
|
2703
2755
|
} catch {
|
|
2704
2756
|
}
|
|
2705
|
-
console.log(
|
|
2757
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
2706
2758
|
console.log();
|
|
2707
2759
|
await initGit(
|
|
2708
2760
|
cwd,
|
|
@@ -2713,14 +2765,14 @@ async function runInit(options) {
|
|
|
2713
2765
|
docsRemote,
|
|
2714
2766
|
extraCommitPathsAbs
|
|
2715
2767
|
);
|
|
2716
|
-
console.log(
|
|
2768
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
|
|
2717
2769
|
console.log(
|
|
2718
|
-
|
|
2770
|
+
chalk9.gray(
|
|
2719
2771
|
tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir })
|
|
2720
2772
|
)
|
|
2721
2773
|
);
|
|
2722
|
-
console.log(
|
|
2723
|
-
console.log(
|
|
2774
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
2775
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps3")));
|
|
2724
2776
|
console.log();
|
|
2725
2777
|
},
|
|
2726
2778
|
{ owner: "init" }
|
|
@@ -2775,31 +2827,31 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2775
2827
|
try {
|
|
2776
2828
|
runGitOrThrow(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
|
|
2777
2829
|
console.log(
|
|
2778
|
-
|
|
2830
|
+
chalk9.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit"))
|
|
2779
2831
|
);
|
|
2780
2832
|
} catch {
|
|
2781
|
-
console.log(
|
|
2833
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2782
2834
|
runGitOrThrow(["init"], gitWorkdir);
|
|
2783
2835
|
}
|
|
2784
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2836
|
+
const relativePath = docsRepo === "standalone" ? "." : path13.relative(gitWorkdir, targetDir);
|
|
2785
2837
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2786
2838
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2787
|
-
console.log(
|
|
2788
|
-
console.log(
|
|
2839
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
|
|
2840
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.warn.commitManually")));
|
|
2789
2841
|
console.log();
|
|
2790
2842
|
return;
|
|
2791
2843
|
}
|
|
2792
2844
|
if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
|
|
2793
2845
|
const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
|
|
2794
2846
|
console.log(
|
|
2795
|
-
|
|
2847
|
+
chalk9.yellow(
|
|
2796
2848
|
tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
|
|
2797
2849
|
path: repoRelativePath
|
|
2798
2850
|
})
|
|
2799
2851
|
)
|
|
2800
2852
|
);
|
|
2801
2853
|
console.log(
|
|
2802
|
-
|
|
2854
|
+
chalk9.gray(
|
|
2803
2855
|
tr(lang, "cli", "init.warn.docsPathIgnoredHint", {
|
|
2804
2856
|
path: repoRelativePath
|
|
2805
2857
|
})
|
|
@@ -2808,7 +2860,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2808
2860
|
console.log();
|
|
2809
2861
|
return;
|
|
2810
2862
|
}
|
|
2811
|
-
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("../"));
|
|
2812
2864
|
const pathsToStage = [relativePath, ...extraRelativePaths];
|
|
2813
2865
|
for (const p of pathsToStage) {
|
|
2814
2866
|
runGitOrThrow(["add", p], gitWorkdir);
|
|
@@ -2827,34 +2879,34 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2827
2879
|
try {
|
|
2828
2880
|
runGitOrThrow(["remote", "add", "origin", docsRemote], gitWorkdir);
|
|
2829
2881
|
console.log(
|
|
2830
|
-
|
|
2882
|
+
chalk9.green(
|
|
2831
2883
|
tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote })
|
|
2832
2884
|
)
|
|
2833
2885
|
);
|
|
2834
2886
|
} catch {
|
|
2835
|
-
console.log(
|
|
2887
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
|
|
2836
2888
|
}
|
|
2837
2889
|
}
|
|
2838
|
-
console.log(
|
|
2890
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
|
|
2839
2891
|
console.log();
|
|
2840
2892
|
} catch {
|
|
2841
|
-
console.log(
|
|
2893
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.skipGitInit")));
|
|
2842
2894
|
console.log();
|
|
2843
2895
|
}
|
|
2844
2896
|
}
|
|
2845
2897
|
function getAncestorDirs(startDir) {
|
|
2846
2898
|
const dirs = [];
|
|
2847
|
-
let current =
|
|
2899
|
+
let current = path13.resolve(startDir);
|
|
2848
2900
|
while (true) {
|
|
2849
2901
|
dirs.push(current);
|
|
2850
|
-
const parent =
|
|
2902
|
+
const parent = path13.dirname(current);
|
|
2851
2903
|
if (parent === current) break;
|
|
2852
2904
|
current = parent;
|
|
2853
2905
|
}
|
|
2854
2906
|
return dirs;
|
|
2855
2907
|
}
|
|
2856
2908
|
function hasWorkspaceBoundary(dir) {
|
|
2857
|
-
return fs.existsSync(
|
|
2909
|
+
return fs.existsSync(path13.join(dir, "package.json")) || fs.existsSync(path13.join(dir, ".git"));
|
|
2858
2910
|
}
|
|
2859
2911
|
function getSearchBaseDirs(cwd) {
|
|
2860
2912
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2870,7 +2922,7 @@ function normalizeComponentKeys(value) {
|
|
|
2870
2922
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2871
2923
|
}
|
|
2872
2924
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2873
|
-
const featuresPath =
|
|
2925
|
+
const featuresPath = path13.join(docsDir, "features");
|
|
2874
2926
|
if (!await fs.pathExists(featuresPath)) return [];
|
|
2875
2927
|
const entries = await fs.readdir(featuresPath, { withFileTypes: true });
|
|
2876
2928
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2881,21 +2933,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2881
2933
|
async function getConfig(cwd) {
|
|
2882
2934
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2883
2935
|
const baseDirs = [
|
|
2884
|
-
...explicitDocsDir ? [
|
|
2936
|
+
...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
|
|
2885
2937
|
...getSearchBaseDirs(cwd)
|
|
2886
2938
|
];
|
|
2887
2939
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2888
2940
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2889
2941
|
for (const baseDir of baseDirs) {
|
|
2890
|
-
const resolvedBaseDir =
|
|
2942
|
+
const resolvedBaseDir = path13.resolve(baseDir);
|
|
2891
2943
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2892
2944
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2893
|
-
const possibleDocsDirs = [
|
|
2945
|
+
const possibleDocsDirs = [path13.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2894
2946
|
for (const docsDir of possibleDocsDirs) {
|
|
2895
|
-
const resolvedDocsDir =
|
|
2947
|
+
const resolvedDocsDir = path13.resolve(docsDir);
|
|
2896
2948
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2897
2949
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2898
|
-
const configPath =
|
|
2950
|
+
const configPath = path13.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2899
2951
|
if (await fs.pathExists(configPath)) {
|
|
2900
2952
|
try {
|
|
2901
2953
|
const configFile = await fs.readJson(configPath);
|
|
@@ -2925,16 +2977,16 @@ async function getConfig(cwd) {
|
|
|
2925
2977
|
} catch {
|
|
2926
2978
|
}
|
|
2927
2979
|
}
|
|
2928
|
-
const agentsPath =
|
|
2929
|
-
const featuresPath =
|
|
2980
|
+
const agentsPath = path13.join(resolvedDocsDir, "agents");
|
|
2981
|
+
const featuresPath = path13.join(resolvedDocsDir, "features");
|
|
2930
2982
|
if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
|
|
2931
2983
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2932
2984
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2933
2985
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2934
2986
|
const langProbeCandidates = [
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2987
|
+
path13.join(agentsPath, "custom.md"),
|
|
2988
|
+
path13.join(agentsPath, "constitution.md"),
|
|
2989
|
+
path13.join(agentsPath, "agents.md")
|
|
2938
2990
|
];
|
|
2939
2991
|
let lang = "en";
|
|
2940
2992
|
for (const candidate of langProbeCandidates) {
|
|
@@ -3005,18 +3057,18 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
3005
3057
|
await fs.writeFile(filePath, transform(content), "utf-8");
|
|
3006
3058
|
}
|
|
3007
3059
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
3008
|
-
await patchMarkdownIfExists(
|
|
3060
|
+
await patchMarkdownIfExists(path13.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3009
3061
|
await patchMarkdownIfExists(
|
|
3010
|
-
|
|
3062
|
+
path13.join(featureDir, "tasks.md"),
|
|
3011
3063
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3012
3064
|
);
|
|
3013
|
-
await fs.remove(
|
|
3014
|
-
await fs.remove(
|
|
3065
|
+
await fs.remove(path13.join(featureDir, "issue.md"));
|
|
3066
|
+
await fs.remove(path13.join(featureDir, "pr.md"));
|
|
3015
3067
|
}
|
|
3016
3068
|
|
|
3017
3069
|
// src/commands/feature.ts
|
|
3018
3070
|
function featureCommand(program2) {
|
|
3019
|
-
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) => {
|
|
3020
3072
|
try {
|
|
3021
3073
|
const result = await runFeature(name, options);
|
|
3022
3074
|
if (options.json) {
|
|
@@ -3049,7 +3101,7 @@ function featureCommand(program2) {
|
|
|
3049
3101
|
);
|
|
3050
3102
|
return;
|
|
3051
3103
|
}
|
|
3052
|
-
console.log(
|
|
3104
|
+
console.log(chalk9.yellow(`
|
|
3053
3105
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
3054
3106
|
return;
|
|
3055
3107
|
}
|
|
@@ -3070,8 +3122,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
3070
3122
|
return;
|
|
3071
3123
|
}
|
|
3072
3124
|
console.error(
|
|
3073
|
-
|
|
3074
|
-
|
|
3125
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
3126
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
3075
3127
|
);
|
|
3076
3128
|
printCliErrorSuggestions(suggestions, lang);
|
|
3077
3129
|
process.exitCode = 1;
|
|
@@ -3097,6 +3149,7 @@ async function runFeature(name, options) {
|
|
|
3097
3149
|
projectType,
|
|
3098
3150
|
config.components
|
|
3099
3151
|
);
|
|
3152
|
+
const linkedIdea = options.idea ? await resolveIdeaReference(docsDir, options.idea, lang) : null;
|
|
3100
3153
|
assertValid(
|
|
3101
3154
|
validateSafeNameWithLang(name, lang),
|
|
3102
3155
|
tr(lang, "cli", "validation.context.featureName"),
|
|
@@ -3162,19 +3215,19 @@ async function runFeature(name, options) {
|
|
|
3162
3215
|
}
|
|
3163
3216
|
let featuresDir;
|
|
3164
3217
|
if (projectType === "multi") {
|
|
3165
|
-
featuresDir =
|
|
3218
|
+
featuresDir = path13.join(docsDir, "features", component);
|
|
3166
3219
|
} else {
|
|
3167
|
-
featuresDir =
|
|
3220
|
+
featuresDir = path13.join(docsDir, "features");
|
|
3168
3221
|
}
|
|
3169
3222
|
const featureFolderName = `${featureId}-${name}`;
|
|
3170
|
-
const featureDir =
|
|
3223
|
+
const featureDir = path13.join(featuresDir, featureFolderName);
|
|
3171
3224
|
if (await fs.pathExists(featureDir)) {
|
|
3172
3225
|
throw createCliError(
|
|
3173
3226
|
"INVALID_ARGUMENT",
|
|
3174
3227
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3175
3228
|
);
|
|
3176
3229
|
}
|
|
3177
|
-
const featureBasePath =
|
|
3230
|
+
const featureBasePath = path13.join(
|
|
3178
3231
|
getTemplatesDir(),
|
|
3179
3232
|
lang,
|
|
3180
3233
|
"common",
|
|
@@ -3219,23 +3272,30 @@ async function runFeature(name, options) {
|
|
|
3219
3272
|
}
|
|
3220
3273
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
3221
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
|
+
}
|
|
3222
3282
|
if (config.workflow?.mode === "local") {
|
|
3223
3283
|
await applyLocalWorkflowTemplateToFeatureDir(featureDir, lang);
|
|
3224
3284
|
}
|
|
3225
3285
|
if (!options.json) {
|
|
3226
3286
|
console.log();
|
|
3227
3287
|
console.log(
|
|
3228
|
-
|
|
3288
|
+
chalk9.green(tr(lang, "cli", "feature.created", { path: featureDir }))
|
|
3229
3289
|
);
|
|
3230
3290
|
console.log();
|
|
3231
|
-
console.log(
|
|
3291
|
+
console.log(chalk9.blue(tr(lang, "cli", "feature.nextStepsTitle")));
|
|
3232
3292
|
console.log(
|
|
3233
|
-
|
|
3293
|
+
chalk9.gray(
|
|
3234
3294
|
tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
|
|
3235
3295
|
)
|
|
3236
3296
|
);
|
|
3237
|
-
console.log(
|
|
3238
|
-
console.log(
|
|
3297
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps2")));
|
|
3298
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps3")));
|
|
3239
3299
|
console.log();
|
|
3240
3300
|
}
|
|
3241
3301
|
return {
|
|
@@ -3243,18 +3303,115 @@ async function runFeature(name, options) {
|
|
|
3243
3303
|
featureName: name,
|
|
3244
3304
|
component: projectType === "multi" ? component : void 0,
|
|
3245
3305
|
featurePath: featureDir,
|
|
3246
|
-
featurePathFromDocs:
|
|
3306
|
+
featurePathFromDocs: path13.relative(docsDir, featureDir)
|
|
3247
3307
|
};
|
|
3248
3308
|
},
|
|
3249
3309
|
{ owner: "feature" }
|
|
3250
3310
|
);
|
|
3251
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
|
+
}
|
|
3252
3409
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3253
3410
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3254
3411
|
const candidates = [
|
|
3255
|
-
...explicitDocsDir ? [
|
|
3256
|
-
|
|
3257
|
-
|
|
3412
|
+
...explicitDocsDir ? [path13.resolve(explicitDocsDir)] : [],
|
|
3413
|
+
path13.resolve(cwd, "docs"),
|
|
3414
|
+
path13.resolve(cwd)
|
|
3258
3415
|
];
|
|
3259
3416
|
const endAt = Date.now() + timeoutMs;
|
|
3260
3417
|
while (Date.now() < endAt) {
|
|
@@ -3281,12 +3438,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3281
3438
|
return getConfig(cwd);
|
|
3282
3439
|
}
|
|
3283
3440
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3284
|
-
const featuresDir =
|
|
3441
|
+
const featuresDir = path13.join(docsDir, "features");
|
|
3285
3442
|
let max = 0;
|
|
3286
3443
|
const scanDirs = [];
|
|
3287
3444
|
if (projectType === "multi") {
|
|
3288
3445
|
scanDirs.push(
|
|
3289
|
-
...components.map((component) =>
|
|
3446
|
+
...components.map((component) => path13.join(featuresDir, component))
|
|
3290
3447
|
);
|
|
3291
3448
|
} else {
|
|
3292
3449
|
scanDirs.push(featuresDir);
|
|
@@ -3307,6 +3464,167 @@ async function getNextFeatureId(docsDir, projectType, components) {
|
|
|
3307
3464
|
const width = Math.max(3, String(next).length);
|
|
3308
3465
|
return `F${String(next).padStart(width, "0")}`;
|
|
3309
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
|
+
}
|
|
3310
3628
|
var DefaultCommandAdapter = class {
|
|
3311
3629
|
execSync(command, options) {
|
|
3312
3630
|
return execSync(command, options);
|
|
@@ -3566,15 +3884,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3566
3884
|
2. \uB9AC\uBDF0 \uBC94\uC704\uB97C \uBD84\uB9AC\uD574 \uD655\uC778\uD558\uC138\uC694.
|
|
3567
3885
|
- main \uAE30\uC900: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3568
3886
|
- worktree \uAE30\uC900: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3569
|
-
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.
|
|
3570
|
-
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.
|
|
3571
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.
|
|
3572
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.
|
|
3573
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.
|
|
3574
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.
|
|
3575
3893
|
9. \uC2E4\uD589\uD55C \uBA85\uB839\uC774 \uC788\uC73C\uBA74 \`commandsExecuted\`\uC5D0 \uAE30\uB85D\uD558\uC138\uC694.
|
|
3576
|
-
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.
|
|
3577
|
-
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.`;
|
|
3578
3896
|
}
|
|
3579
3897
|
return `Conduct a pre-PR code review.
|
|
3580
3898
|
0. Reuse the existing helper/sub-agent for this feature review if one already exists. Default to a single helper agent.
|
|
@@ -3582,15 +3900,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3582
3900
|
2. Split and check the review scope.
|
|
3583
3901
|
- Main scope: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3584
3902
|
- Worktree scope: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3585
|
-
3. Evaluate whether the implementation actually fits the feature intent. Capture concrete \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, and \`
|
|
3586
|
-
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.
|
|
3587
3905
|
5. The baseline is '${fallbackText}'. Always perform the 'Pre-PR Core Checklist' section of the 'create-pr' document.
|
|
3588
3906
|
6. Priority skills: ${skills.length > 0 ? skills.join(", ") : "None"} for deeper technical review.
|
|
3589
3907
|
7. Run extra audit/targeted verification only when the review truly needs more evidence. Spawn additional helper agents only when necessary.
|
|
3590
3908
|
8. If helper-agent quota is exhausted, continue the review in the main agent and just keep the evidence consistent.
|
|
3591
3909
|
9. Record commands in \`commandsExecuted\` only when you actually ran them.
|
|
3592
|
-
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.
|
|
3593
|
-
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.`;
|
|
3594
3912
|
}
|
|
3595
3913
|
function getCodeReviewPrompt(lang) {
|
|
3596
3914
|
if (lang === "ko") {
|
|
@@ -3730,7 +4048,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
3730
4048
|
}
|
|
3731
4049
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
3732
4050
|
try {
|
|
3733
|
-
const raw =
|
|
4051
|
+
const raw = fs10.readFileSync(evidencePath, "utf-8");
|
|
3734
4052
|
const parsed = JSON.parse(raw);
|
|
3735
4053
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
3736
4054
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -3746,31 +4064,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
3746
4064
|
const candidates = [];
|
|
3747
4065
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
3748
4066
|
if (explicit && explicit !== "-") {
|
|
3749
|
-
if (
|
|
4067
|
+
if (path13.isAbsolute(explicit)) {
|
|
3750
4068
|
candidates.push(explicit);
|
|
3751
4069
|
} else {
|
|
3752
|
-
candidates.push(
|
|
3753
|
-
candidates.push(
|
|
4070
|
+
candidates.push(path13.resolve(feature.path, explicit));
|
|
4071
|
+
candidates.push(path13.resolve(docsRoot, explicit));
|
|
3754
4072
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
3755
4073
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
3756
4074
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
3757
4075
|
if (withoutDocsPrefix) {
|
|
3758
|
-
candidates.push(
|
|
4076
|
+
candidates.push(path13.resolve(docsRoot, withoutDocsPrefix));
|
|
3759
4077
|
}
|
|
3760
4078
|
}
|
|
3761
4079
|
}
|
|
3762
4080
|
}
|
|
3763
|
-
candidates.push(
|
|
3764
|
-
candidates.push(
|
|
4081
|
+
candidates.push(path13.join(feature.path, "review-trace.json"));
|
|
4082
|
+
candidates.push(path13.join(docsRoot, "review-trace.json"));
|
|
3765
4083
|
const seen = /* @__PURE__ */ new Set();
|
|
3766
4084
|
for (const candidate of candidates) {
|
|
3767
|
-
const abs =
|
|
4085
|
+
const abs = path13.resolve(candidate);
|
|
3768
4086
|
if (seen.has(abs)) continue;
|
|
3769
4087
|
seen.add(abs);
|
|
3770
|
-
if (!
|
|
4088
|
+
if (!fs10.existsSync(abs)) continue;
|
|
3771
4089
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
3772
4090
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
3773
|
-
const rel =
|
|
4091
|
+
const rel = path13.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
3774
4092
|
if (rel && !rel.startsWith("../")) {
|
|
3775
4093
|
return rel;
|
|
3776
4094
|
}
|
|
@@ -3811,8 +4129,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
3811
4129
|
}
|
|
3812
4130
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
3813
4131
|
if (!projectGitCwd) return null;
|
|
3814
|
-
const normalized =
|
|
3815
|
-
const marker = `${
|
|
4132
|
+
const normalized = path13.resolve(projectGitCwd);
|
|
4133
|
+
const marker = `${path13.sep}.worktrees${path13.sep}`;
|
|
3816
4134
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
3817
4135
|
if (markerIndex <= 0) return null;
|
|
3818
4136
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -3855,7 +4173,7 @@ function toTaskKey(rawTitle) {
|
|
|
3855
4173
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
3856
4174
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
3857
4175
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
3858
|
-
|
|
4176
|
+
path13.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
3859
4177
|
);
|
|
3860
4178
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
3861
4179
|
"diff",
|
|
@@ -3930,7 +4248,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
3930
4248
|
return { pass: true };
|
|
3931
4249
|
}
|
|
3932
4250
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
3933
|
-
const relativeDocsDir =
|
|
4251
|
+
const relativeDocsDir = path13.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
3934
4252
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
3935
4253
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
3936
4254
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -4312,9 +4630,9 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4312
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);
|
|
4313
4631
|
const isPrePrReviewMetadataMissing = (f) => isPrePrReviewCurrent(f) && !f.docs.prePrReviewFieldExists;
|
|
4314
4632
|
const isPrePrReviewFixRequired = (f) => isPrePrReviewCurrent(f) && !!f.prePrReview.decisionOutcome && f.prePrReview.decisionOutcome !== "approve";
|
|
4315
|
-
const isPrePrReviewRun = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status !== "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4316
|
-
const isPrePrReviewRunning = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status === "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4317
|
-
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);
|
|
4318
4636
|
const getPrePrReviewMetadataActions = () => [
|
|
4319
4637
|
{
|
|
4320
4638
|
type: "instruction",
|
|
@@ -4358,7 +4676,7 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4358
4676
|
cmd: buildSelfCliCommand(buildPrePrReviewRunCommandArgs(f))
|
|
4359
4677
|
}
|
|
4360
4678
|
];
|
|
4361
|
-
const
|
|
4679
|
+
const getPrePrReviewInProgressActions = () => [
|
|
4362
4680
|
{
|
|
4363
4681
|
type: "instruction",
|
|
4364
4682
|
category: "pre_pr_review_run",
|
|
@@ -4896,15 +5214,15 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4896
5214
|
actions: (f) => getPrePrReviewRunActions(f)
|
|
4897
5215
|
},
|
|
4898
5216
|
{
|
|
4899
|
-
id: "
|
|
5217
|
+
id: "pre_pr_review_in_progress",
|
|
4900
5218
|
phase: "running",
|
|
4901
5219
|
owner: "subagent",
|
|
4902
5220
|
category: "pre_pr_review_run",
|
|
4903
5221
|
when: (f) => isPrePrReviewRunning(f),
|
|
4904
|
-
actions: () =>
|
|
5222
|
+
actions: () => getPrePrReviewInProgressActions()
|
|
4905
5223
|
},
|
|
4906
5224
|
{
|
|
4907
|
-
id: "
|
|
5225
|
+
id: "pre_pr_review_record_pending",
|
|
4908
5226
|
phase: "record",
|
|
4909
5227
|
owner: "main",
|
|
4910
5228
|
category: "pre_pr_review_record",
|
|
@@ -5546,17 +5864,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
5546
5864
|
}
|
|
5547
5865
|
}
|
|
5548
5866
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
5549
|
-
var WORKTREE_MARKER = `${
|
|
5867
|
+
var WORKTREE_MARKER = `${path13.sep}.worktrees${path13.sep}`;
|
|
5550
5868
|
function resetContextGitCaches() {
|
|
5551
5869
|
GIT_WORKTREE_CACHE.clear();
|
|
5552
5870
|
}
|
|
5553
5871
|
function isManagedWorktreePath(cwd) {
|
|
5554
5872
|
if (!cwd) return false;
|
|
5555
|
-
const normalized =
|
|
5873
|
+
const normalized = path13.resolve(cwd);
|
|
5556
5874
|
return normalized.includes(WORKTREE_MARKER);
|
|
5557
5875
|
}
|
|
5558
5876
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
5559
|
-
const normalized =
|
|
5877
|
+
const normalized = path13.resolve(cwd);
|
|
5560
5878
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
5561
5879
|
if (markerIndex <= 0) return normalized;
|
|
5562
5880
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -5575,7 +5893,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
5575
5893
|
}
|
|
5576
5894
|
function listGitWorktrees(ctx, cwd) {
|
|
5577
5895
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
5578
|
-
const cacheKey =
|
|
5896
|
+
const cacheKey = path13.resolve(topLevel);
|
|
5579
5897
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
5580
5898
|
if (cached) return cached;
|
|
5581
5899
|
try {
|
|
@@ -5676,12 +5994,12 @@ function countDocumentLines(content) {
|
|
|
5676
5994
|
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
5677
5995
|
return lines.length;
|
|
5678
5996
|
}
|
|
5679
|
-
function
|
|
5997
|
+
function escapeRegExp2(value) {
|
|
5680
5998
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5681
5999
|
}
|
|
5682
6000
|
function extractSpecValue(content, key) {
|
|
5683
6001
|
const regex = new RegExp(
|
|
5684
|
-
`^\\s*-\\s*\\*\\*${
|
|
6002
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
5685
6003
|
"m"
|
|
5686
6004
|
);
|
|
5687
6005
|
const match = content.match(regex);
|
|
@@ -5689,7 +6007,7 @@ function extractSpecValue(content, key) {
|
|
|
5689
6007
|
}
|
|
5690
6008
|
function hasSpecKey(content, key) {
|
|
5691
6009
|
const regex = new RegExp(
|
|
5692
|
-
`^\\s*-\\s*\\*\\*${
|
|
6010
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:`,
|
|
5693
6011
|
"m"
|
|
5694
6012
|
);
|
|
5695
6013
|
return regex.test(content);
|
|
@@ -5881,7 +6199,7 @@ function splitReviewLogSections(content, headerRegex) {
|
|
|
5881
6199
|
}
|
|
5882
6200
|
function collectStructuredReviewEntries(section, keys) {
|
|
5883
6201
|
const lines = section.split("\n");
|
|
5884
|
-
const escaped = keys.map((key) =>
|
|
6202
|
+
const escaped = keys.map((key) => escapeRegExp2(key));
|
|
5885
6203
|
const fieldRegex = new RegExp(
|
|
5886
6204
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*(.*)$`,
|
|
5887
6205
|
"i"
|
|
@@ -6063,17 +6381,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6063
6381
|
if (!evidencePath) return [];
|
|
6064
6382
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6065
6383
|
const candidates = /* @__PURE__ */ new Set();
|
|
6066
|
-
if (
|
|
6067
|
-
candidates.add(
|
|
6384
|
+
if (path13.isAbsolute(evidencePath)) {
|
|
6385
|
+
candidates.add(path13.resolve(evidencePath));
|
|
6068
6386
|
} else {
|
|
6069
|
-
candidates.add(
|
|
6070
|
-
candidates.add(
|
|
6071
|
-
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));
|
|
6072
6390
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6073
6391
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6074
6392
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6075
6393
|
if (withoutDocsPrefix) {
|
|
6076
|
-
candidates.add(
|
|
6394
|
+
candidates.add(path13.resolve(context.docsDir, withoutDocsPrefix));
|
|
6077
6395
|
}
|
|
6078
6396
|
}
|
|
6079
6397
|
}
|
|
@@ -6135,13 +6453,13 @@ function parsePrLink(value) {
|
|
|
6135
6453
|
return trimmed;
|
|
6136
6454
|
}
|
|
6137
6455
|
function normalizeGitPath(value) {
|
|
6138
|
-
return value.split(
|
|
6456
|
+
return value.split(path13.sep).join("/");
|
|
6139
6457
|
}
|
|
6140
6458
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6141
|
-
const relativeDocsDir =
|
|
6459
|
+
const relativeDocsDir = path13.relative(projectGitCwd, docsDir);
|
|
6142
6460
|
if (!relativeDocsDir) return [];
|
|
6143
|
-
if (
|
|
6144
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6461
|
+
if (path13.isAbsolute(relativeDocsDir)) return [];
|
|
6462
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path13.sep}`)) {
|
|
6145
6463
|
return [];
|
|
6146
6464
|
}
|
|
6147
6465
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6179,7 +6497,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6179
6497
|
const seen = /* @__PURE__ */ new Set();
|
|
6180
6498
|
const out = [];
|
|
6181
6499
|
for (const name of names) {
|
|
6182
|
-
const candidate =
|
|
6500
|
+
const candidate = path13.resolve(projectRoot, ".worktrees", name);
|
|
6183
6501
|
if (seen.has(candidate)) continue;
|
|
6184
6502
|
seen.add(candidate);
|
|
6185
6503
|
out.push(candidate);
|
|
@@ -6193,7 +6511,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6193
6511
|
slug,
|
|
6194
6512
|
folderName
|
|
6195
6513
|
)) {
|
|
6196
|
-
if (!
|
|
6514
|
+
if (!fs10.existsSync(candidate)) continue;
|
|
6197
6515
|
return candidate;
|
|
6198
6516
|
}
|
|
6199
6517
|
return void 0;
|
|
@@ -6223,7 +6541,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6223
6541
|
slug,
|
|
6224
6542
|
folderName
|
|
6225
6543
|
)) {
|
|
6226
|
-
if (!
|
|
6544
|
+
if (!fs10.existsSync(candidate)) continue;
|
|
6227
6545
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6228
6546
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6229
6547
|
return {
|
|
@@ -6364,10 +6682,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6364
6682
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6365
6683
|
candidates.map((candidate) => {
|
|
6366
6684
|
if (!candidate) return "";
|
|
6367
|
-
if (!
|
|
6368
|
-
const relative =
|
|
6685
|
+
if (!path13.isAbsolute(candidate)) return candidate;
|
|
6686
|
+
const relative = path13.relative(projectGitCwd, candidate);
|
|
6369
6687
|
if (!relative) return "";
|
|
6370
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
6688
|
+
if (relative === ".." || relative.startsWith(`..${path13.sep}`))
|
|
6371
6689
|
return "";
|
|
6372
6690
|
return relative;
|
|
6373
6691
|
}).filter(Boolean)
|
|
@@ -6381,7 +6699,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6381
6699
|
if (cached) return [...cached];
|
|
6382
6700
|
const existing = [];
|
|
6383
6701
|
for (const candidate of normalizedCandidates) {
|
|
6384
|
-
if (await ctx.fs.pathExists(
|
|
6702
|
+
if (await ctx.fs.pathExists(path13.join(projectGitCwd, candidate))) {
|
|
6385
6703
|
existing.push(candidate);
|
|
6386
6704
|
}
|
|
6387
6705
|
}
|
|
@@ -6469,16 +6787,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6469
6787
|
const lang = options.lang;
|
|
6470
6788
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
6471
6789
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
6472
|
-
const folderName =
|
|
6790
|
+
const folderName = path13.basename(featurePath);
|
|
6473
6791
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6474
6792
|
const id = match?.[1];
|
|
6475
6793
|
const slug = match?.[2] || folderName;
|
|
6476
|
-
const specPath =
|
|
6477
|
-
const planPath =
|
|
6478
|
-
const tasksPath =
|
|
6479
|
-
const decisionsPath =
|
|
6480
|
-
const issueDocPath =
|
|
6481
|
-
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");
|
|
6482
6800
|
let specStatus;
|
|
6483
6801
|
let issueNumber;
|
|
6484
6802
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -6740,7 +7058,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6740
7058
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
6741
7059
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
6742
7060
|
}
|
|
6743
|
-
const relativeFeaturePathFromDocs =
|
|
7061
|
+
const relativeFeaturePathFromDocs = path13.relative(
|
|
6744
7062
|
context.docsDir,
|
|
6745
7063
|
featurePath
|
|
6746
7064
|
);
|
|
@@ -7103,7 +7421,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7103
7421
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7104
7422
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7105
7423
|
return dirs.filter(
|
|
7106
|
-
(value) =>
|
|
7424
|
+
(value) => path13.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7107
7425
|
);
|
|
7108
7426
|
}
|
|
7109
7427
|
function normalizeRelPath(value) {
|
|
@@ -7244,7 +7562,7 @@ async function scanFeatures(ctx) {
|
|
|
7244
7562
|
if (config.projectType === "single") {
|
|
7245
7563
|
const featureDirs = await listFeatureDirs(
|
|
7246
7564
|
ctx,
|
|
7247
|
-
|
|
7565
|
+
path13.join(config.docsDir, "features")
|
|
7248
7566
|
);
|
|
7249
7567
|
componentFeatureDirs.set("single", featureDirs);
|
|
7250
7568
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7256,14 +7574,14 @@ async function scanFeatures(ctx) {
|
|
|
7256
7574
|
for (const component of components) {
|
|
7257
7575
|
const componentDirs = await listFeatureDirs(
|
|
7258
7576
|
ctx,
|
|
7259
|
-
|
|
7577
|
+
path13.join(config.docsDir, "features", component)
|
|
7260
7578
|
);
|
|
7261
7579
|
componentFeatureDirs.set(component, componentDirs);
|
|
7262
7580
|
allFeatureDirs.push(...componentDirs);
|
|
7263
7581
|
}
|
|
7264
7582
|
}
|
|
7265
7583
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7266
|
-
(dir) => normalizeRelPath(
|
|
7584
|
+
(dir) => normalizeRelPath(path13.relative(config.docsDir, dir))
|
|
7267
7585
|
);
|
|
7268
7586
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7269
7587
|
ctx,
|
|
@@ -7280,7 +7598,7 @@ async function scanFeatures(ctx) {
|
|
|
7280
7598
|
const parsed = await Promise.all(
|
|
7281
7599
|
target.dirs.map(async (dir) => {
|
|
7282
7600
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7283
|
-
|
|
7601
|
+
path13.relative(config.docsDir, dir)
|
|
7284
7602
|
);
|
|
7285
7603
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7286
7604
|
return parseFeature(
|
|
@@ -7332,8 +7650,8 @@ function statusCommand(program2) {
|
|
|
7332
7650
|
const cliError = toCliError(error);
|
|
7333
7651
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7334
7652
|
console.error(
|
|
7335
|
-
|
|
7336
|
-
|
|
7653
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7654
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7337
7655
|
);
|
|
7338
7656
|
printCliErrorSuggestions(suggestions, lang);
|
|
7339
7657
|
process.exitCode = 1;
|
|
@@ -7350,13 +7668,13 @@ async function runStatus(options) {
|
|
|
7350
7668
|
);
|
|
7351
7669
|
}
|
|
7352
7670
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7353
|
-
const featuresDir =
|
|
7671
|
+
const featuresDir = path13.join(docsDir, "features");
|
|
7354
7672
|
const scan = await scanFeatures(ctx);
|
|
7355
7673
|
const features = [];
|
|
7356
7674
|
const idMap = /* @__PURE__ */ new Map();
|
|
7357
7675
|
for (const f of scan.features) {
|
|
7358
7676
|
const id = f.id || "UNKNOWN";
|
|
7359
|
-
const relPath =
|
|
7677
|
+
const relPath = path13.relative(docsDir, f.path);
|
|
7360
7678
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7361
7679
|
idMap.get(id).push(relPath);
|
|
7362
7680
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7430,7 +7748,7 @@ async function runStatus(options) {
|
|
|
7430
7748
|
return;
|
|
7431
7749
|
}
|
|
7432
7750
|
if (features.length === 0) {
|
|
7433
|
-
console.log(
|
|
7751
|
+
console.log(chalk9.yellow(tr(lang, "cli", "status.noFeatures")));
|
|
7434
7752
|
return;
|
|
7435
7753
|
}
|
|
7436
7754
|
features.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -7440,14 +7758,14 @@ async function runStatus(options) {
|
|
|
7440
7758
|
console.log(header);
|
|
7441
7759
|
console.log(separator);
|
|
7442
7760
|
for (const f of features) {
|
|
7443
|
-
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;
|
|
7444
7762
|
console.log(
|
|
7445
7763
|
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.step} | ${f.substate} | ${f.path} |`
|
|
7446
7764
|
);
|
|
7447
7765
|
}
|
|
7448
7766
|
console.log();
|
|
7449
7767
|
if (options.write) {
|
|
7450
|
-
const outputPath =
|
|
7768
|
+
const outputPath = path13.join(featuresDir, "status.md");
|
|
7451
7769
|
const date = getLocalDateString();
|
|
7452
7770
|
const content = [
|
|
7453
7771
|
"# Feature Status",
|
|
@@ -7464,22 +7782,22 @@ async function runStatus(options) {
|
|
|
7464
7782
|
].join("\n");
|
|
7465
7783
|
await ctx.fs.writeFile(outputPath, content, "utf-8");
|
|
7466
7784
|
console.log(
|
|
7467
|
-
|
|
7785
|
+
chalk9.green(tr(lang, "cli", "status.wrote", { path: outputPath }))
|
|
7468
7786
|
);
|
|
7469
7787
|
}
|
|
7470
7788
|
}
|
|
7471
|
-
function
|
|
7789
|
+
function escapeRegExp3(value) {
|
|
7472
7790
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7473
7791
|
}
|
|
7474
7792
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7475
7793
|
try {
|
|
7476
|
-
const specPath =
|
|
7794
|
+
const specPath = path13.join(featureDir, "spec.md");
|
|
7477
7795
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7478
7796
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7479
7797
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
7480
7798
|
for (const key of keys) {
|
|
7481
7799
|
const regex = new RegExp(
|
|
7482
|
-
`^\\s*-\\s*\\*\\*${
|
|
7800
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
7483
7801
|
"m"
|
|
7484
7802
|
);
|
|
7485
7803
|
const match = content.match(regex);
|
|
@@ -7491,7 +7809,7 @@ async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallb
|
|
|
7491
7809
|
return fallbackSlug || fallbackFolderName;
|
|
7492
7810
|
}
|
|
7493
7811
|
function updateCommand(program2) {
|
|
7494
|
-
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Cleanup legacy agents/skills copies (CLI-managed)").option("--templates", "Cleanup legacy feature-base copies (CLI-managed)").option(
|
|
7812
|
+
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--agents-md", "Sync project-scoped AGENTS.md entrypoint").option("--skills", "Cleanup legacy agents/skills copies (CLI-managed)").option("--templates", "Cleanup legacy feature-base copies (CLI-managed)").option(
|
|
7495
7813
|
"-f, --force",
|
|
7496
7814
|
"Force overwrite even if docs has uncommitted changes"
|
|
7497
7815
|
).action(async (options) => {
|
|
@@ -7501,15 +7819,15 @@ function updateCommand(program2) {
|
|
|
7501
7819
|
const config = await getConfig(process.cwd());
|
|
7502
7820
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
7503
7821
|
if (error instanceof Error && error.message === "canceled") {
|
|
7504
|
-
console.log(
|
|
7822
|
+
console.log(chalk9.yellow(`
|
|
7505
7823
|
${tr(lang, "cli", "common.canceled")}`));
|
|
7506
7824
|
return;
|
|
7507
7825
|
}
|
|
7508
7826
|
const cliError = toCliError(error);
|
|
7509
7827
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7510
7828
|
console.error(
|
|
7511
|
-
|
|
7512
|
-
|
|
7829
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7830
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7513
7831
|
);
|
|
7514
7832
|
printCliErrorSuggestions(suggestions, lang);
|
|
7515
7833
|
process.exitCode = 1;
|
|
@@ -7534,30 +7852,31 @@ async function runUpdate(options) {
|
|
|
7534
7852
|
const docsLockPath = getDocsLockPath(docsDir);
|
|
7535
7853
|
const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang, [docsLockPath]);
|
|
7536
7854
|
const configBackfill = await backfillMissingConfigDefaults(docsDir);
|
|
7537
|
-
const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
|
|
7855
|
+
const hasExplicitSelection = !!(options.agents || options.agentsMd || options.skills || options.templates);
|
|
7538
7856
|
const updateAgents = options.agents || options.skills || !hasExplicitSelection;
|
|
7857
|
+
const updateAgentsMd = options.agentsMd || !hasExplicitSelection;
|
|
7539
7858
|
const updateTemplates = options.templates || !hasExplicitSelection;
|
|
7540
7859
|
const agentsMode = options.skills && !options.agents ? "skills" : "all";
|
|
7541
|
-
console.log(
|
|
7542
|
-
console.log(
|
|
7860
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.start")));
|
|
7861
|
+
console.log(chalk9.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
|
|
7543
7862
|
console.log(
|
|
7544
|
-
|
|
7863
|
+
chalk9.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
|
|
7545
7864
|
);
|
|
7546
7865
|
console.log();
|
|
7547
7866
|
let updatedCount = 0;
|
|
7548
7867
|
if (updateAgents) {
|
|
7549
7868
|
if (agentsMode === "skills") {
|
|
7550
|
-
console.log(
|
|
7869
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingSkills")));
|
|
7551
7870
|
console.log(
|
|
7552
|
-
|
|
7871
|
+
chalk9.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
|
|
7553
7872
|
);
|
|
7554
|
-
console.log(
|
|
7873
|
+
console.log(chalk9.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
|
|
7555
7874
|
} else {
|
|
7556
|
-
console.log(
|
|
7875
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
7557
7876
|
}
|
|
7558
7877
|
if (agentsMode === "all") {
|
|
7559
|
-
const commonAgentsBase =
|
|
7560
|
-
const targetAgentsBase =
|
|
7878
|
+
const commonAgentsBase = path13.join(templatesDir, lang, "common", "agents");
|
|
7879
|
+
const targetAgentsBase = path13.join(docsDir, "agents");
|
|
7561
7880
|
const commonAgents = commonAgentsBase;
|
|
7562
7881
|
const targetAgents = targetAgentsBase;
|
|
7563
7882
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -7585,20 +7904,32 @@ async function runUpdate(options) {
|
|
|
7585
7904
|
updatedCount += count;
|
|
7586
7905
|
}
|
|
7587
7906
|
console.log(
|
|
7588
|
-
|
|
7907
|
+
chalk9.green(
|
|
7589
7908
|
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
7590
7909
|
)
|
|
7591
7910
|
);
|
|
7592
7911
|
}
|
|
7593
7912
|
}
|
|
7913
|
+
if (updateAgentsMd) {
|
|
7914
|
+
const agentsMdTargets = await collectAgentsMdTargets(cwd, config);
|
|
7915
|
+
for (const target of agentsMdTargets) {
|
|
7916
|
+
const result = await upsertLeeSpecKitAgentsMd(target, {
|
|
7917
|
+
lang,
|
|
7918
|
+
docsRepo: config.docsRepo ?? "embedded"
|
|
7919
|
+
});
|
|
7920
|
+
if (result.changed) {
|
|
7921
|
+
updatedCount += 1;
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7594
7925
|
if (updateTemplates) {
|
|
7595
|
-
console.log(
|
|
7596
|
-
console.log(
|
|
7926
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
7927
|
+
console.log(chalk9.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
|
|
7597
7928
|
}
|
|
7598
7929
|
const pruned = await pruneEngineManagedDocs(docsDir);
|
|
7599
7930
|
if (pruned.length > 0) {
|
|
7600
7931
|
console.log(
|
|
7601
|
-
|
|
7932
|
+
chalk9.gray(
|
|
7602
7933
|
` - ${tr(lang, "cli", "update.engineManagedPruned", {
|
|
7603
7934
|
count: pruned.length
|
|
7604
7935
|
})}`
|
|
@@ -7608,23 +7939,59 @@ async function runUpdate(options) {
|
|
|
7608
7939
|
console.log();
|
|
7609
7940
|
if (configBackfill.changed) {
|
|
7610
7941
|
console.log(
|
|
7611
|
-
|
|
7942
|
+
chalk9.gray(
|
|
7612
7943
|
` - ${tr(lang, "cli", "update.fileUpdated", { file: ".lee-spec-kit.json" })}`
|
|
7613
7944
|
)
|
|
7614
7945
|
);
|
|
7615
7946
|
console.log(
|
|
7616
|
-
|
|
7947
|
+
chalk9.gray(
|
|
7617
7948
|
` (${configBackfill.changedPaths.join(", ")})`
|
|
7618
7949
|
)
|
|
7619
7950
|
);
|
|
7620
7951
|
}
|
|
7621
7952
|
console.log(
|
|
7622
|
-
|
|
7953
|
+
chalk9.green(`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`)
|
|
7623
7954
|
);
|
|
7624
7955
|
},
|
|
7625
7956
|
{ owner: "update" }
|
|
7626
7957
|
);
|
|
7627
7958
|
}
|
|
7959
|
+
function getGitTopLevelOrNull2(cwd) {
|
|
7960
|
+
try {
|
|
7961
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
7962
|
+
cwd,
|
|
7963
|
+
encoding: "utf-8",
|
|
7964
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
7965
|
+
});
|
|
7966
|
+
const value = String(out || "").trim();
|
|
7967
|
+
return value ? value : null;
|
|
7968
|
+
} catch {
|
|
7969
|
+
return null;
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
async function collectAgentsMdTargets(cwd, config) {
|
|
7973
|
+
if (!config) return [];
|
|
7974
|
+
const targets = /* @__PURE__ */ new Set();
|
|
7975
|
+
const docsRepo = config.docsRepo ?? "embedded";
|
|
7976
|
+
if (docsRepo === "embedded") {
|
|
7977
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path13.resolve(config.docsDir, "..");
|
|
7978
|
+
targets.add(path13.join(repoRoot, "AGENTS.md"));
|
|
7979
|
+
return [...targets];
|
|
7980
|
+
}
|
|
7981
|
+
targets.add(path13.join(config.docsDir, "AGENTS.md"));
|
|
7982
|
+
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
7983
|
+
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
7984
|
+
for (const rawRoot of rawRoots) {
|
|
7985
|
+
const value = String(rawRoot || "").trim();
|
|
7986
|
+
if (!value) continue;
|
|
7987
|
+
const resolved = path13.resolve(baseDir, value);
|
|
7988
|
+
if (!await fs.pathExists(resolved)) continue;
|
|
7989
|
+
const stat = await fs.stat(resolved);
|
|
7990
|
+
if (!stat.isDirectory()) continue;
|
|
7991
|
+
targets.add(path13.join(resolved, "AGENTS.md"));
|
|
7992
|
+
}
|
|
7993
|
+
return [...targets];
|
|
7994
|
+
}
|
|
7628
7995
|
function isPlainObject(value) {
|
|
7629
7996
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7630
7997
|
}
|
|
@@ -7661,7 +8028,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
7661
8028
|
return [...deduped];
|
|
7662
8029
|
}
|
|
7663
8030
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
7664
|
-
const configPath =
|
|
8031
|
+
const configPath = path13.join(docsDir, ".lee-spec-kit.json");
|
|
7665
8032
|
if (!await fs.pathExists(configPath)) {
|
|
7666
8033
|
return { changed: false, changedPaths: [] };
|
|
7667
8034
|
}
|
|
@@ -7784,8 +8151,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7784
8151
|
const files = await fs.readdir(sourceDir);
|
|
7785
8152
|
let updatedCount = 0;
|
|
7786
8153
|
for (const file of files) {
|
|
7787
|
-
const sourcePath =
|
|
7788
|
-
const targetPath =
|
|
8154
|
+
const sourcePath = path13.join(sourceDir, file);
|
|
8155
|
+
const targetPath = path13.join(targetDir, file);
|
|
7789
8156
|
const stat = await fs.stat(sourcePath);
|
|
7790
8157
|
if (stat.isFile()) {
|
|
7791
8158
|
if (protectedFiles.has(file)) {
|
|
@@ -7803,7 +8170,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7803
8170
|
}
|
|
7804
8171
|
if (!force) {
|
|
7805
8172
|
console.log(
|
|
7806
|
-
|
|
8173
|
+
chalk9.yellow(
|
|
7807
8174
|
` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
|
|
7808
8175
|
)
|
|
7809
8176
|
);
|
|
@@ -7813,7 +8180,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7813
8180
|
if (shouldUpdate) {
|
|
7814
8181
|
await fs.writeFile(targetPath, sourceContent);
|
|
7815
8182
|
console.log(
|
|
7816
|
-
|
|
8183
|
+
chalk9.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
|
|
7817
8184
|
);
|
|
7818
8185
|
updatedCount++;
|
|
7819
8186
|
}
|
|
@@ -7868,7 +8235,7 @@ function extractPorcelainPaths(line) {
|
|
|
7868
8235
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
7869
8236
|
const top = getGitTopLevel2(docsDir);
|
|
7870
8237
|
if (!top) return null;
|
|
7871
|
-
const rel =
|
|
8238
|
+
const rel = path13.relative(top, docsDir) || ".";
|
|
7872
8239
|
try {
|
|
7873
8240
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
7874
8241
|
cwd: top,
|
|
@@ -7880,7 +8247,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
7880
8247
|
}
|
|
7881
8248
|
const ignoredRelPaths = new Set(
|
|
7882
8249
|
ignoredAbsPaths.map(
|
|
7883
|
-
(absPath) => normalizeGitPath2(
|
|
8250
|
+
(absPath) => normalizeGitPath2(path13.relative(top, absPath) || ".")
|
|
7884
8251
|
)
|
|
7885
8252
|
);
|
|
7886
8253
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -7918,7 +8285,7 @@ function configCommand(program2) {
|
|
|
7918
8285
|
if (error instanceof Error && error.message === "canceled") {
|
|
7919
8286
|
const config2 = await getConfig(process.cwd());
|
|
7920
8287
|
const lang2 = config2?.lang ?? DEFAULT_LANG;
|
|
7921
|
-
console.log(
|
|
8288
|
+
console.log(chalk9.yellow(`
|
|
7922
8289
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
7923
8290
|
return;
|
|
7924
8291
|
}
|
|
@@ -7927,8 +8294,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
7927
8294
|
const cliError = toCliError(error);
|
|
7928
8295
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7929
8296
|
console.error(
|
|
7930
|
-
|
|
7931
|
-
|
|
8297
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
8298
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7932
8299
|
);
|
|
7933
8300
|
printCliErrorSuggestions(suggestions, lang);
|
|
7934
8301
|
process.exitCode = 1;
|
|
@@ -7938,7 +8305,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
7938
8305
|
}
|
|
7939
8306
|
async function runConfig(options) {
|
|
7940
8307
|
const cwd = process.cwd();
|
|
7941
|
-
const targetCwd = options.dir ?
|
|
8308
|
+
const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
|
|
7942
8309
|
const config = await getConfig(targetCwd);
|
|
7943
8310
|
if (!config) {
|
|
7944
8311
|
throw createCliError(
|
|
@@ -7946,13 +8313,13 @@ async function runConfig(options) {
|
|
|
7946
8313
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
7947
8314
|
);
|
|
7948
8315
|
}
|
|
7949
|
-
const configPath =
|
|
8316
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
7950
8317
|
if (!options.projectRoot) {
|
|
7951
8318
|
console.log();
|
|
7952
|
-
console.log(
|
|
8319
|
+
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
7953
8320
|
console.log();
|
|
7954
8321
|
console.log(
|
|
7955
|
-
|
|
8322
|
+
chalk9.gray(
|
|
7956
8323
|
` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
|
|
7957
8324
|
)
|
|
7958
8325
|
);
|
|
@@ -7969,7 +8336,7 @@ async function runConfig(options) {
|
|
|
7969
8336
|
const configFile = await fs.readJson(configPath);
|
|
7970
8337
|
if (configFile.docsRepo !== "standalone") {
|
|
7971
8338
|
console.log(
|
|
7972
|
-
|
|
8339
|
+
chalk9.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
|
|
7973
8340
|
);
|
|
7974
8341
|
return;
|
|
7975
8342
|
}
|
|
@@ -8019,7 +8386,7 @@ async function runConfig(options) {
|
|
|
8019
8386
|
currentRoot[targetComponent] = projectRoot;
|
|
8020
8387
|
configFile.projectRoot = currentRoot;
|
|
8021
8388
|
console.log(
|
|
8022
|
-
|
|
8389
|
+
chalk9.green(
|
|
8023
8390
|
tr(config.lang, "cli", "config.projectRootSet", {
|
|
8024
8391
|
repo: targetComponent.toUpperCase(),
|
|
8025
8392
|
path: projectRoot
|
|
@@ -8035,7 +8402,7 @@ async function runConfig(options) {
|
|
|
8035
8402
|
}
|
|
8036
8403
|
configFile.projectRoot = projectRoot;
|
|
8037
8404
|
console.log(
|
|
8038
|
-
|
|
8405
|
+
chalk9.green(
|
|
8039
8406
|
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
8040
8407
|
path: projectRoot
|
|
8041
8408
|
})
|
|
@@ -8052,47 +8419,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8052
8419
|
{
|
|
8053
8420
|
id: "agents",
|
|
8054
8421
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8055
|
-
relativePath: (_, lang) =>
|
|
8422
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "agents.md")
|
|
8056
8423
|
},
|
|
8057
8424
|
{
|
|
8058
8425
|
id: "git-workflow",
|
|
8059
8426
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8060
|
-
relativePath: (_, lang) =>
|
|
8427
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "git-workflow.md")
|
|
8061
8428
|
},
|
|
8062
8429
|
{
|
|
8063
8430
|
id: "issue-doc",
|
|
8064
8431
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8065
|
-
relativePath: (_, lang) =>
|
|
8432
|
+
relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8066
8433
|
},
|
|
8067
8434
|
{
|
|
8068
8435
|
id: "pr-doc",
|
|
8069
8436
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8070
|
-
relativePath: (_, lang) =>
|
|
8437
|
+
relativePath: (_, lang) => path13.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8071
8438
|
},
|
|
8072
8439
|
{
|
|
8073
8440
|
id: "create-feature",
|
|
8074
8441
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8075
|
-
relativePath: (_, lang) =>
|
|
8442
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8076
8443
|
},
|
|
8077
8444
|
{
|
|
8078
8445
|
id: "execute-task",
|
|
8079
8446
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8080
|
-
relativePath: (_, lang) =>
|
|
8447
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8081
8448
|
},
|
|
8082
8449
|
{
|
|
8083
8450
|
id: "create-issue",
|
|
8084
8451
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8085
|
-
relativePath: (_, lang) =>
|
|
8452
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8086
8453
|
},
|
|
8087
8454
|
{
|
|
8088
8455
|
id: "create-pr",
|
|
8089
8456
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8090
|
-
relativePath: (_, lang) =>
|
|
8457
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8091
8458
|
},
|
|
8092
8459
|
{
|
|
8093
8460
|
id: "split-feature",
|
|
8094
8461
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8095
|
-
relativePath: (_, lang) =>
|
|
8462
|
+
relativePath: (_, lang) => path13.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8096
8463
|
}
|
|
8097
8464
|
];
|
|
8098
8465
|
var DOC_FOLLOWUPS = {
|
|
@@ -8189,7 +8556,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8189
8556
|
id: doc.id,
|
|
8190
8557
|
title: doc.title[lang],
|
|
8191
8558
|
relativePath,
|
|
8192
|
-
absolutePath:
|
|
8559
|
+
absolutePath: path13.join(templatesDir, relativePath)
|
|
8193
8560
|
};
|
|
8194
8561
|
});
|
|
8195
8562
|
}
|
|
@@ -8610,7 +8977,7 @@ function getActionExecutionMetadata(action) {
|
|
|
8610
8977
|
return {
|
|
8611
8978
|
handoffOnly: true,
|
|
8612
8979
|
advancesWorkflow: false,
|
|
8613
|
-
nextMainState: "
|
|
8980
|
+
nextMainState: "pre_pr_review_in_progress"
|
|
8614
8981
|
};
|
|
8615
8982
|
}
|
|
8616
8983
|
return null;
|
|
@@ -8749,6 +9116,73 @@ function buildApprovalCommand(state, featureName, selectedComponent, execute) {
|
|
|
8749
9116
|
}
|
|
8750
9117
|
return `npx lee-spec-kit context ${featureRef}${componentArg} --approve <LABEL>`;
|
|
8751
9118
|
}
|
|
9119
|
+
function buildFeatureComponentArgs(feature) {
|
|
9120
|
+
return feature.type && feature.type !== "single" ? ["--component", feature.type] : [];
|
|
9121
|
+
}
|
|
9122
|
+
function buildPrePrRecordCommand(feature, decision) {
|
|
9123
|
+
const args = [
|
|
9124
|
+
"pre-pr-review",
|
|
9125
|
+
feature.folderName,
|
|
9126
|
+
...buildFeatureComponentArgs(feature),
|
|
9127
|
+
"--evidence",
|
|
9128
|
+
"review-trace.json",
|
|
9129
|
+
"--decision",
|
|
9130
|
+
decision
|
|
9131
|
+
];
|
|
9132
|
+
return `npx lee-spec-kit ${args.join(" ")}`;
|
|
9133
|
+
}
|
|
9134
|
+
function buildDelegatedActionContract(state) {
|
|
9135
|
+
const feature = state.matchedFeature;
|
|
9136
|
+
const substateId = feature?.currentSubstateId;
|
|
9137
|
+
if (!feature || !substateId) return null;
|
|
9138
|
+
if (substateId === "pre_pr_review_run" || substateId === "pre_pr_review_in_progress") {
|
|
9139
|
+
return {
|
|
9140
|
+
required: true,
|
|
9141
|
+
mode: "command",
|
|
9142
|
+
category: "pre_pr_review_run",
|
|
9143
|
+
currentSubstateId: substateId,
|
|
9144
|
+
delegatedWorkRequired: true,
|
|
9145
|
+
handoffOnly: true,
|
|
9146
|
+
advancesWorkflow: false,
|
|
9147
|
+
doNotReapproveSameLabel: substateId === "pre_pr_review_in_progress",
|
|
9148
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
9149
|
+
reuseKey: `pre-pr:${feature.folderName}`,
|
|
9150
|
+
evidenceFile: "review-trace.json",
|
|
9151
|
+
nextStepRequirement: "generate_review_trace_then_record",
|
|
9152
|
+
recordCommands: {
|
|
9153
|
+
changesRequested: buildPrePrRecordCommand(
|
|
9154
|
+
feature,
|
|
9155
|
+
"changes_requested"
|
|
9156
|
+
),
|
|
9157
|
+
approve: buildPrePrRecordCommand(feature, "approve")
|
|
9158
|
+
},
|
|
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."
|
|
9160
|
+
};
|
|
9161
|
+
}
|
|
9162
|
+
if (substateId === "code_review_run" || substateId === "code_review_running") {
|
|
9163
|
+
return {
|
|
9164
|
+
required: true,
|
|
9165
|
+
mode: "command",
|
|
9166
|
+
category: "code_review_run",
|
|
9167
|
+
currentSubstateId: substateId,
|
|
9168
|
+
delegatedWorkRequired: true,
|
|
9169
|
+
handoffOnly: true,
|
|
9170
|
+
advancesWorkflow: false,
|
|
9171
|
+
doNotReapproveSameLabel: substateId === "code_review_running",
|
|
9172
|
+
nextMainState: "code_review_running",
|
|
9173
|
+
reuseKey: `code-review:${feature.folderName}`,
|
|
9174
|
+
guidance: substateId === "code_review_running" ? "A PR review handoff is already prepared. Reuse or resume the delegated review-fix work, then refresh PR Review Evidence and PR Review Decision before continuing. Do not re-approve the same label." : "After approval, spawn_agent first and hand off the delegated PR review-fix work. Handoff-only execution only prepares the delegated session; continue the delegated work immediately after approval."
|
|
9175
|
+
};
|
|
9176
|
+
}
|
|
9177
|
+
return null;
|
|
9178
|
+
}
|
|
9179
|
+
function buildDelegatedApprovalGuidance(handoffRequired, handoffMode) {
|
|
9180
|
+
const base = "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`).";
|
|
9181
|
+
const delegatedCommand = handoffRequired && handoffMode === "command" ? ' When `matchedFeature.currentSubstateOwner="subagent"` and `agentOrchestration.subAgentHandoff.required=true` with `mode="command"`, call spawn_agent first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, `--execute` only prepares the handoff; continue the delegated work immediately and do not re-approve the same label.' : "";
|
|
9182
|
+
const nonDelegated = " For non-delegated command actions, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed.";
|
|
9183
|
+
const orchestration = ' Use main-agent orchestration: keep short steps in main agent. Prefer `matchedFeature.currentSubstateOwner` + `agentOrchestration.subAgentHandoff` as the delegation SSOT; `currentActionShouldDelegate` is a compatibility mirror. Delegate auto-run only when `agentOrchestration.subAgentHandoff.required=true` with `mode="auto_run"`.';
|
|
9184
|
+
return `${base}${delegatedCommand}${nonDelegated}${orchestration}`;
|
|
9185
|
+
}
|
|
8752
9186
|
function buildFinalApprovalPrompt(lang, actionOptions) {
|
|
8753
9187
|
if (actionOptions.length === 0) return "";
|
|
8754
9188
|
const labels = listLabels(actionOptions);
|
|
@@ -8954,12 +9388,12 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
8954
9388
|
if (suggestionOptions.length === 0) return;
|
|
8955
9389
|
const finalPrompt = buildSuggestionFinalPrompt(lang, suggestionOptions);
|
|
8956
9390
|
console.log(
|
|
8957
|
-
|
|
9391
|
+
chalk9.green(chalk9.bold(`\u{1F449} ${tr(lang, "cli", "context.suggestionHeader")}`))
|
|
8958
9392
|
);
|
|
8959
9393
|
suggestionOptions.forEach((option) => {
|
|
8960
9394
|
console.log(` ${option.label}: ${option.summary}`);
|
|
8961
9395
|
console.log(
|
|
8962
|
-
|
|
9396
|
+
chalk9.gray(
|
|
8963
9397
|
` \u21B3 ${tr(lang, "cli", "context.suggestionCommandHint", {
|
|
8964
9398
|
command: option.command
|
|
8965
9399
|
})}`
|
|
@@ -8967,7 +9401,7 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
8967
9401
|
);
|
|
8968
9402
|
});
|
|
8969
9403
|
if (finalPrompt) {
|
|
8970
|
-
console.log(
|
|
9404
|
+
console.log(chalk9.cyan(` \u21B3 ${finalPrompt}`));
|
|
8971
9405
|
}
|
|
8972
9406
|
}
|
|
8973
9407
|
function buildRequiredDocHints(actionOptions) {
|
|
@@ -9159,7 +9593,7 @@ function getApprovalSessionId() {
|
|
|
9159
9593
|
function getApprovalTicketPaths(config) {
|
|
9160
9594
|
return {
|
|
9161
9595
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
9162
|
-
legacyPath:
|
|
9596
|
+
legacyPath: path13.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
9163
9597
|
};
|
|
9164
9598
|
}
|
|
9165
9599
|
async function loadApprovalTicketStore(storePath) {
|
|
@@ -9173,7 +9607,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9173
9607
|
}
|
|
9174
9608
|
}
|
|
9175
9609
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9176
|
-
await fs.ensureDir(
|
|
9610
|
+
await fs.ensureDir(path13.dirname(storePath));
|
|
9177
9611
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9178
9612
|
}
|
|
9179
9613
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -9358,6 +9792,28 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
9358
9792
|
}
|
|
9359
9793
|
return getProjectExecutionLockPath(action.cwd);
|
|
9360
9794
|
}
|
|
9795
|
+
function buildApprovedHandoffMetadata(action, featureRef) {
|
|
9796
|
+
if (action.category === "pre_pr_review_run") {
|
|
9797
|
+
return {
|
|
9798
|
+
delegatedWorkRequired: true,
|
|
9799
|
+
doNotReapproveSameLabel: true,
|
|
9800
|
+
reuseKey: `pre-pr:${featureRef}`,
|
|
9801
|
+
nextStepRequirement: "generate_review_trace_then_record",
|
|
9802
|
+
evidenceFile: "review-trace.json"
|
|
9803
|
+
};
|
|
9804
|
+
}
|
|
9805
|
+
if (action.category === "code_review_run") {
|
|
9806
|
+
return {
|
|
9807
|
+
delegatedWorkRequired: true,
|
|
9808
|
+
doNotReapproveSameLabel: true,
|
|
9809
|
+
reuseKey: `code-review:${featureRef}`
|
|
9810
|
+
};
|
|
9811
|
+
}
|
|
9812
|
+
return {
|
|
9813
|
+
delegatedWorkRequired: true,
|
|
9814
|
+
doNotReapproveSameLabel: true
|
|
9815
|
+
};
|
|
9816
|
+
}
|
|
9361
9817
|
async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
|
|
9362
9818
|
const approval = options.approve || "";
|
|
9363
9819
|
const ticketToken = (options.ticket || "").trim();
|
|
@@ -9482,10 +9938,10 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9482
9938
|
return;
|
|
9483
9939
|
}
|
|
9484
9940
|
console.log();
|
|
9485
|
-
console.log(
|
|
9486
|
-
console.log(
|
|
9941
|
+
console.log(chalk9.green(`\u2705 Approved option: ${parsedLabel}`));
|
|
9942
|
+
console.log(chalk9.gray(` - Action: ${freshSelected.detail}`));
|
|
9487
9943
|
if (userRequest) {
|
|
9488
|
-
console.log(
|
|
9944
|
+
console.log(chalk9.gray(` - User request: ${userRequest}`));
|
|
9489
9945
|
}
|
|
9490
9946
|
if (selectedAction.type === "command") {
|
|
9491
9947
|
const selectedComponent = selectionOptions.component || "";
|
|
@@ -9496,24 +9952,24 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9496
9952
|
`--ticket ${ticket.token}`
|
|
9497
9953
|
);
|
|
9498
9954
|
console.log(
|
|
9499
|
-
|
|
9955
|
+
chalk9.gray(
|
|
9500
9956
|
` - Ticket: ${ticket.token} (expires: ${ticket.expiresAt})`
|
|
9501
9957
|
)
|
|
9502
9958
|
);
|
|
9503
9959
|
} else {
|
|
9504
9960
|
executeCommand = executeCommand.replace(" [--ticket <TICKET>]", "");
|
|
9505
9961
|
}
|
|
9506
|
-
console.log(
|
|
9962
|
+
console.log(chalk9.gray(` - Run with: ${executeCommand}`));
|
|
9507
9963
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9508
9964
|
console.log(
|
|
9509
|
-
|
|
9965
|
+
chalk9.gray(
|
|
9510
9966
|
" - This command prepares a handoff only; complete the delegated work and update workflow evidence before re-running context."
|
|
9511
9967
|
)
|
|
9512
9968
|
);
|
|
9513
9969
|
}
|
|
9514
9970
|
} else {
|
|
9515
9971
|
console.log(
|
|
9516
|
-
|
|
9972
|
+
chalk9.gray(" - Instruction-only action (no command execution).")
|
|
9517
9973
|
);
|
|
9518
9974
|
}
|
|
9519
9975
|
console.log();
|
|
@@ -9564,19 +10020,19 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9564
10020
|
}
|
|
9565
10021
|
console.log();
|
|
9566
10022
|
console.log(
|
|
9567
|
-
|
|
10023
|
+
chalk9.yellow(`\u26A0\uFE0F Approved label ${parsedLabel} is instruction-only.`)
|
|
9568
10024
|
);
|
|
9569
10025
|
if (userRequest) {
|
|
9570
|
-
console.log(
|
|
10026
|
+
console.log(chalk9.gray(` User request: ${userRequest}`));
|
|
9571
10027
|
}
|
|
9572
|
-
console.log(
|
|
10028
|
+
console.log(chalk9.gray(` ${selectedAction.message}`));
|
|
9573
10029
|
console.log();
|
|
9574
10030
|
return;
|
|
9575
10031
|
}
|
|
9576
10032
|
if (!jsonMode) {
|
|
9577
10033
|
console.log();
|
|
9578
|
-
console.log(
|
|
9579
|
-
console.log(
|
|
10034
|
+
console.log(chalk9.blue(`\u25B6 Executing option ${parsedLabel}...`));
|
|
10035
|
+
console.log(chalk9.gray(` ${selectedAction.cmd}`));
|
|
9580
10036
|
console.log();
|
|
9581
10037
|
}
|
|
9582
10038
|
try {
|
|
@@ -9588,6 +10044,10 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9588
10044
|
);
|
|
9589
10045
|
if (jsonMode) {
|
|
9590
10046
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
10047
|
+
const handoffMetadata = buildApprovedHandoffMetadata(
|
|
10048
|
+
selectedAction,
|
|
10049
|
+
freshState.matchedFeature?.folderName ?? featureRef
|
|
10050
|
+
);
|
|
9591
10051
|
console.log(
|
|
9592
10052
|
JSON.stringify(
|
|
9593
10053
|
{
|
|
@@ -9602,6 +10062,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9602
10062
|
handoffOnly: true,
|
|
9603
10063
|
advancesWorkflow: false,
|
|
9604
10064
|
nextMainState: executionMetadata.nextMainState,
|
|
10065
|
+
...handoffMetadata,
|
|
9605
10066
|
stdout: execResult.stdout?.trim() || void 0,
|
|
9606
10067
|
stderr: execResult.stderr?.trim() || void 0
|
|
9607
10068
|
},
|
|
@@ -9633,14 +10094,14 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9633
10094
|
}
|
|
9634
10095
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9635
10096
|
console.log(
|
|
9636
|
-
|
|
10097
|
+
chalk9.yellow(
|
|
9637
10098
|
"Prepared handoff only. Complete the delegated work and update workflow evidence before re-running context."
|
|
9638
10099
|
)
|
|
9639
10100
|
);
|
|
9640
10101
|
console.log();
|
|
9641
10102
|
return;
|
|
9642
10103
|
}
|
|
9643
|
-
console.log(
|
|
10104
|
+
console.log(chalk9.green(`\u2705 Executed option ${parsedLabel}.`));
|
|
9644
10105
|
console.log();
|
|
9645
10106
|
} catch (error) {
|
|
9646
10107
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -9688,8 +10149,8 @@ function contextCommand(program2) {
|
|
|
9688
10149
|
);
|
|
9689
10150
|
} else {
|
|
9690
10151
|
console.error(
|
|
9691
|
-
|
|
9692
|
-
|
|
10152
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
10153
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
9693
10154
|
);
|
|
9694
10155
|
printCliErrorSuggestions(suggestions, lang);
|
|
9695
10156
|
}
|
|
@@ -9782,6 +10243,11 @@ async function runContext(featureName, options) {
|
|
|
9782
10243
|
state.matchedFeature?.folderName || null,
|
|
9783
10244
|
state.matchedFeature?.currentSubstateOwner
|
|
9784
10245
|
);
|
|
10246
|
+
const delegatedAction = buildDelegatedActionContract(state);
|
|
10247
|
+
const approvalGuidance = buildDelegatedApprovalGuidance(
|
|
10248
|
+
agentOrchestration.subAgentHandoff.required,
|
|
10249
|
+
agentOrchestration.subAgentHandoff.mode
|
|
10250
|
+
);
|
|
9785
10251
|
if (options.approve || options.execute) {
|
|
9786
10252
|
await runApprovedOption(
|
|
9787
10253
|
state,
|
|
@@ -9840,6 +10306,7 @@ async function runContext(featureName, options) {
|
|
|
9840
10306
|
agentOrchestration: {
|
|
9841
10307
|
subAgentHandoff: agentOrchestration.subAgentHandoff
|
|
9842
10308
|
},
|
|
10309
|
+
delegatedAction,
|
|
9843
10310
|
autoRun: {
|
|
9844
10311
|
available: autoRunPlan.available,
|
|
9845
10312
|
policyEligible: autoRunPlan.policyEligible,
|
|
@@ -9975,13 +10442,14 @@ async function runContext(featureName, options) {
|
|
|
9975
10442
|
"actionOptions[].detail",
|
|
9976
10443
|
"actionOptions[].approvalPrompt"
|
|
9977
10444
|
] : [],
|
|
9978
|
-
recommendation:
|
|
10445
|
+
recommendation: approvalGuidance,
|
|
9979
10446
|
oneApprovalPerAction: approvalRequired,
|
|
9980
10447
|
requireFreshContext: true,
|
|
9981
10448
|
contextVersion: state.contextVersion,
|
|
9982
10449
|
config: config.approval ?? { mode: "builtin" }
|
|
9983
10450
|
},
|
|
9984
10451
|
agentOrchestration,
|
|
10452
|
+
delegatedAction,
|
|
9985
10453
|
autoRun: {
|
|
9986
10454
|
available: autoRunPlan.available,
|
|
9987
10455
|
policyEligible: autoRunPlan.policyEligible,
|
|
@@ -9995,7 +10463,10 @@ async function runContext(featureName, options) {
|
|
|
9995
10463
|
guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories.'
|
|
9996
10464
|
},
|
|
9997
10465
|
approvalRequest: {
|
|
9998
|
-
guidance:
|
|
10466
|
+
guidance: approvalGuidance.replace(
|
|
10467
|
+
"Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user.",
|
|
10468
|
+
"User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`."
|
|
10469
|
+
),
|
|
9999
10470
|
required: approvalRequired,
|
|
10000
10471
|
finalPrompt: finalApprovalPrompt,
|
|
10001
10472
|
userFacingLines: approvalUserFacingLines,
|
|
@@ -10048,11 +10519,11 @@ async function runContext(featureName, options) {
|
|
|
10048
10519
|
return;
|
|
10049
10520
|
}
|
|
10050
10521
|
console.log();
|
|
10051
|
-
console.log(
|
|
10522
|
+
console.log(chalk9.bold(tr(lang, "cli", "context.header")));
|
|
10052
10523
|
if (config.projectType === "single") {
|
|
10053
10524
|
if (state.branches.project.single) {
|
|
10054
10525
|
console.log(
|
|
10055
|
-
|
|
10526
|
+
chalk9.gray(
|
|
10056
10527
|
` (Detected from Project Branch: ${state.branches.project.single})`
|
|
10057
10528
|
)
|
|
10058
10529
|
);
|
|
@@ -10061,7 +10532,7 @@ async function runContext(featureName, options) {
|
|
|
10061
10532
|
const branchName = state.branches.project[selectedComponent] || "";
|
|
10062
10533
|
if (branchName) {
|
|
10063
10534
|
console.log(
|
|
10064
|
-
|
|
10535
|
+
chalk9.gray(
|
|
10065
10536
|
` (Detected from Project Branch: ${selectedComponent.toUpperCase()} ${branchName})`
|
|
10066
10537
|
)
|
|
10067
10538
|
);
|
|
@@ -10070,37 +10541,37 @@ async function runContext(featureName, options) {
|
|
|
10070
10541
|
const parts = Object.entries(state.branches.project).filter(([key, value]) => key !== "single" && !!value).map(([key, value]) => `${key.toUpperCase()} ${value}`);
|
|
10071
10542
|
if (parts.length > 0) {
|
|
10072
10543
|
console.log(
|
|
10073
|
-
|
|
10544
|
+
chalk9.gray(` (Detected from Project Branch: ${parts.join(" / ")})`)
|
|
10074
10545
|
);
|
|
10075
10546
|
}
|
|
10076
10547
|
}
|
|
10077
10548
|
if (config.docsRepo === "standalone" && state.branches.docs) {
|
|
10078
|
-
console.log(
|
|
10549
|
+
console.log(chalk9.gray(` (Docs Branch: ${state.branches.docs})`));
|
|
10079
10550
|
}
|
|
10080
|
-
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"));
|
|
10081
10552
|
console.log();
|
|
10082
10553
|
if (state.features.length === 0) {
|
|
10083
|
-
console.log(
|
|
10554
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10084
10555
|
console.log();
|
|
10085
10556
|
printSuggestionOptions(lang, suggestionOptions);
|
|
10086
10557
|
console.log();
|
|
10087
10558
|
return;
|
|
10088
10559
|
}
|
|
10089
10560
|
if (state.warnings.length > 0) {
|
|
10090
|
-
console.log(
|
|
10091
|
-
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}`)));
|
|
10092
10563
|
console.log();
|
|
10093
10564
|
}
|
|
10094
10565
|
if (state.targetFeatures.length === 0) {
|
|
10095
|
-
console.log(
|
|
10566
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10096
10567
|
if (state.status === "no_open") {
|
|
10097
10568
|
console.log(
|
|
10098
|
-
|
|
10569
|
+
chalk9.gray(
|
|
10099
10570
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10100
10571
|
)
|
|
10101
10572
|
);
|
|
10102
10573
|
console.log(
|
|
10103
|
-
|
|
10574
|
+
chalk9.gray(
|
|
10104
10575
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10105
10576
|
)
|
|
10106
10577
|
);
|
|
@@ -10113,7 +10584,7 @@ async function runContext(featureName, options) {
|
|
|
10113
10584
|
if (state.targetFeatures.length > 1) {
|
|
10114
10585
|
if (state.selectionMode === "open") {
|
|
10115
10586
|
console.log(
|
|
10116
|
-
|
|
10587
|
+
chalk9.gray(
|
|
10117
10588
|
` ${tr(lang, "cli", "context.openFallbackSummary", {
|
|
10118
10589
|
inProgress: state.inProgressFeatures.length,
|
|
10119
10590
|
readyToClose: state.readyToCloseFeatures.length,
|
|
@@ -10125,7 +10596,7 @@ async function runContext(featureName, options) {
|
|
|
10125
10596
|
}
|
|
10126
10597
|
if (state.selectionMode === "open") {
|
|
10127
10598
|
console.log(
|
|
10128
|
-
|
|
10599
|
+
chalk9.blue(
|
|
10129
10600
|
`\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${state.inProgressFeatures.length})`
|
|
10130
10601
|
)
|
|
10131
10602
|
);
|
|
@@ -10137,14 +10608,14 @@ async function runContext(featureName, options) {
|
|
|
10137
10608
|
workflowPolicy,
|
|
10138
10609
|
prePrReviewPolicy
|
|
10139
10610
|
);
|
|
10140
|
-
const typeStr = config.projectType === "multi" ?
|
|
10611
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10141
10612
|
console.log(
|
|
10142
|
-
` \u2022 ${
|
|
10613
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10143
10614
|
);
|
|
10144
10615
|
});
|
|
10145
10616
|
console.log();
|
|
10146
10617
|
console.log(
|
|
10147
|
-
|
|
10618
|
+
chalk9.blue(
|
|
10148
10619
|
`\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${state.readyToCloseFeatures.length})`
|
|
10149
10620
|
)
|
|
10150
10621
|
);
|
|
@@ -10156,14 +10627,14 @@ async function runContext(featureName, options) {
|
|
|
10156
10627
|
workflowPolicy,
|
|
10157
10628
|
prePrReviewPolicy
|
|
10158
10629
|
);
|
|
10159
|
-
const typeStr = config.projectType === "multi" ?
|
|
10630
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10160
10631
|
console.log(
|
|
10161
|
-
` \u2022 ${
|
|
10632
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10162
10633
|
);
|
|
10163
10634
|
});
|
|
10164
10635
|
} else {
|
|
10165
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:`;
|
|
10166
|
-
console.log(
|
|
10637
|
+
console.log(chalk9.blue(title));
|
|
10167
10638
|
console.log();
|
|
10168
10639
|
state.targetFeatures.forEach((f2) => {
|
|
10169
10640
|
const stepName2 = getListLabel(
|
|
@@ -10173,24 +10644,24 @@ async function runContext(featureName, options) {
|
|
|
10173
10644
|
workflowPolicy,
|
|
10174
10645
|
prePrReviewPolicy
|
|
10175
10646
|
);
|
|
10176
|
-
const typeStr = config.projectType === "multi" ?
|
|
10647
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10177
10648
|
console.log(
|
|
10178
|
-
` \u2022 ${
|
|
10649
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10179
10650
|
);
|
|
10180
10651
|
});
|
|
10181
10652
|
}
|
|
10182
10653
|
console.log();
|
|
10183
|
-
console.log(
|
|
10654
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.tipDetails")));
|
|
10184
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>";
|
|
10185
|
-
console.log(
|
|
10656
|
+
console.log(chalk9.gray(selectorTip));
|
|
10186
10657
|
if (state.selectionMode === "open") {
|
|
10187
10658
|
console.log(
|
|
10188
|
-
|
|
10659
|
+
chalk9.gray(
|
|
10189
10660
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10190
10661
|
)
|
|
10191
10662
|
);
|
|
10192
10663
|
console.log(
|
|
10193
|
-
|
|
10664
|
+
chalk9.gray(
|
|
10194
10665
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10195
10666
|
)
|
|
10196
10667
|
);
|
|
@@ -10202,47 +10673,47 @@ async function runContext(featureName, options) {
|
|
|
10202
10673
|
}
|
|
10203
10674
|
const f = state.targetFeatures[0];
|
|
10204
10675
|
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
10205
|
-
const checkTag = (requiresUserCheck) => requiresUserCheck ?
|
|
10676
|
+
const checkTag = (requiresUserCheck) => requiresUserCheck ? chalk9.yellow(tr(lang, "cli", "context.checkRequired")) : "";
|
|
10206
10677
|
const hasCheckAction = approvalRequired;
|
|
10207
10678
|
console.log(
|
|
10208
|
-
`\u{1F539} Feature: ${
|
|
10679
|
+
`\u{1F539} Feature: ${chalk9.bold(f.folderName)} ${config.projectType === "multi" ? chalk9.cyan(`(${f.type})`) : ""}`
|
|
10209
10680
|
);
|
|
10210
10681
|
console.log(
|
|
10211
|
-
` \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")}`
|
|
10212
10683
|
);
|
|
10213
10684
|
if (f.issueNumber) {
|
|
10214
10685
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10215
10686
|
}
|
|
10216
|
-
console.log(` \u2022 Path: ${
|
|
10687
|
+
console.log(` \u2022 Path: ${path13.relative(cwd, f.path)}`);
|
|
10217
10688
|
if (f.git.projectBranch) {
|
|
10218
10689
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10219
10690
|
}
|
|
10220
10691
|
console.log();
|
|
10221
10692
|
console.log(
|
|
10222
|
-
`\u{1F539} Progress: ${
|
|
10693
|
+
`\u{1F539} Progress: ${chalk9.yellow(`Step ${f.currentStep}. ${stepName}`)}`
|
|
10223
10694
|
);
|
|
10224
10695
|
if (f.activeTask) {
|
|
10225
10696
|
console.log(
|
|
10226
|
-
` \u2022 Active Task: ${
|
|
10697
|
+
` \u2022 Active Task: ${chalk9.yellow(`[${f.activeTask.status}]`)} ${f.activeTask.title}`
|
|
10227
10698
|
);
|
|
10228
10699
|
} else if (f.nextTodoTask && f.currentStep === 10) {
|
|
10229
10700
|
console.log(
|
|
10230
|
-
` \u2022 Next TODO: ${
|
|
10701
|
+
` \u2022 Next TODO: ${chalk9.gray(`[${f.nextTodoTask.status}]`)} ${f.nextTodoTask.title}`
|
|
10231
10702
|
);
|
|
10232
10703
|
}
|
|
10233
10704
|
printChecklist(f, stepDefinitions);
|
|
10234
10705
|
if (f.warnings.length > 0) {
|
|
10235
10706
|
console.log();
|
|
10236
|
-
console.log(
|
|
10237
|
-
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}`)));
|
|
10238
10709
|
}
|
|
10239
10710
|
console.log();
|
|
10240
|
-
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"));
|
|
10241
10712
|
if (hasCheckAction) {
|
|
10242
|
-
console.log(
|
|
10713
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.checkPolicyHint")));
|
|
10243
10714
|
}
|
|
10244
10715
|
if (!f.actions || f.actions.length === 0) {
|
|
10245
|
-
console.log(`\u{1F449} Next Action: ${
|
|
10716
|
+
console.log(`\u{1F449} Next Action: ${chalk9.green(chalk9.bold(f.nextAction))}`);
|
|
10246
10717
|
console.log();
|
|
10247
10718
|
return;
|
|
10248
10719
|
}
|
|
@@ -10255,7 +10726,7 @@ async function runContext(featureName, options) {
|
|
|
10255
10726
|
f.currentSubstateOwner
|
|
10256
10727
|
);
|
|
10257
10728
|
const showOptionLabels = hasCheckAction;
|
|
10258
|
-
console.log(
|
|
10729
|
+
console.log(chalk9.green(chalk9.bold("\u{1F449} Next Options (Atomic):")));
|
|
10259
10730
|
let hasDocsCommand = false;
|
|
10260
10731
|
actionOptions.forEach((option) => {
|
|
10261
10732
|
const requiresCheck = option.action.requiresUserCheck;
|
|
@@ -10268,26 +10739,26 @@ async function runContext(featureName, options) {
|
|
|
10268
10739
|
});
|
|
10269
10740
|
if (hasDocsCommand) {
|
|
10270
10741
|
console.log(
|
|
10271
|
-
|
|
10742
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.tipDocsCommitRules")}`)
|
|
10272
10743
|
);
|
|
10273
10744
|
}
|
|
10274
10745
|
if (hasCommandOption && longRunningDelegation.shouldDelegate) {
|
|
10275
10746
|
console.log(
|
|
10276
|
-
|
|
10747
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.subAgentOrchestrationHint")}`)
|
|
10277
10748
|
);
|
|
10278
10749
|
}
|
|
10279
10750
|
if (hasCheckAction) {
|
|
10280
10751
|
console.log(
|
|
10281
|
-
|
|
10752
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionOptionHint")}`)
|
|
10282
10753
|
);
|
|
10283
10754
|
console.log(
|
|
10284
|
-
|
|
10755
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionExplainHint")}`)
|
|
10285
10756
|
);
|
|
10286
10757
|
}
|
|
10287
10758
|
if (requiredDocs.length > 0) {
|
|
10288
10759
|
for (const requiredDoc of requiredDocs) {
|
|
10289
10760
|
console.log(
|
|
10290
|
-
|
|
10761
|
+
chalk9.gray(
|
|
10291
10762
|
` \u21B3 ${tr(lang, "cli", "context.readBuiltinDocFirst", {
|
|
10292
10763
|
command: requiredDoc.command
|
|
10293
10764
|
})}`
|
|
@@ -10296,9 +10767,9 @@ async function runContext(featureName, options) {
|
|
|
10296
10767
|
}
|
|
10297
10768
|
}
|
|
10298
10769
|
if (autoRunPlan.available) {
|
|
10299
|
-
console.log(
|
|
10770
|
+
console.log(chalk9.gray(` \u21B3 ${autoRunPlan.summary}`));
|
|
10300
10771
|
console.log(
|
|
10301
|
-
|
|
10772
|
+
chalk9.gray(
|
|
10302
10773
|
` \u21B3 ${tr(lang, "cli", "context.autoRunCommandHint", {
|
|
10303
10774
|
command: autoRunPlan.command
|
|
10304
10775
|
})}`
|
|
@@ -10318,16 +10789,16 @@ async function runContext(featureName, options) {
|
|
|
10318
10789
|
selectedComponent,
|
|
10319
10790
|
true
|
|
10320
10791
|
);
|
|
10321
|
-
console.log(
|
|
10792
|
+
console.log(chalk9.cyan(` \u21B3 ${finalApprovalPrompt}`));
|
|
10322
10793
|
console.log(
|
|
10323
|
-
|
|
10794
|
+
chalk9.gray(
|
|
10324
10795
|
` \u21B3 ${tr(lang, "cli", "context.finalLabelCommandHint", {
|
|
10325
10796
|
command: approveCommand
|
|
10326
10797
|
})}`
|
|
10327
10798
|
)
|
|
10328
10799
|
);
|
|
10329
10800
|
console.log(
|
|
10330
|
-
|
|
10801
|
+
chalk9.gray(
|
|
10331
10802
|
` \u21B3 ${tr(lang, "cli", "context.finalTicketCommandHint", {
|
|
10332
10803
|
command: executeCommand
|
|
10333
10804
|
})}`
|
|
@@ -10341,8 +10812,8 @@ function printChecklist(f, stepDefinitions) {
|
|
|
10341
10812
|
checklistSteps.forEach((definition) => {
|
|
10342
10813
|
const done = definition.checklist.done(f);
|
|
10343
10814
|
const detail = definition.checklist.detail?.(f) ?? "";
|
|
10344
|
-
const mark = done ?
|
|
10345
|
-
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;
|
|
10346
10817
|
console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
|
|
10347
10818
|
});
|
|
10348
10819
|
}
|
|
@@ -10365,7 +10836,7 @@ function extractTitleAfterId(line, id) {
|
|
|
10365
10836
|
return cleaned ? cleaned : void 0;
|
|
10366
10837
|
}
|
|
10367
10838
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
10368
|
-
const prdDir =
|
|
10839
|
+
const prdDir = path13.join(docsDir, "prd");
|
|
10369
10840
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
10370
10841
|
extensions: [".md"],
|
|
10371
10842
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -10373,7 +10844,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10373
10844
|
const definitions = /* @__PURE__ */ new Map();
|
|
10374
10845
|
const duplicates = [];
|
|
10375
10846
|
for (const filePath of files) {
|
|
10376
|
-
if (
|
|
10847
|
+
if (path13.basename(filePath).toLowerCase() === "readme.md") {
|
|
10377
10848
|
continue;
|
|
10378
10849
|
}
|
|
10379
10850
|
let content = "";
|
|
@@ -10382,7 +10853,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10382
10853
|
} catch {
|
|
10383
10854
|
continue;
|
|
10384
10855
|
}
|
|
10385
|
-
const relFile = normalizeRelPath2(
|
|
10856
|
+
const relFile = normalizeRelPath2(path13.relative(docsDir, filePath));
|
|
10386
10857
|
const lines = content.split(/\r?\n/);
|
|
10387
10858
|
let inCodeBlock = false;
|
|
10388
10859
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -10457,7 +10928,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
10457
10928
|
]);
|
|
10458
10929
|
function formatPath(cwd, p) {
|
|
10459
10930
|
if (!p) return "";
|
|
10460
|
-
return
|
|
10931
|
+
return path13.isAbsolute(p) ? path13.relative(cwd, p) : p;
|
|
10461
10932
|
}
|
|
10462
10933
|
function detectPlaceholders(content) {
|
|
10463
10934
|
const patterns = [
|
|
@@ -10616,7 +11087,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10616
11087
|
const placeholderContext = {
|
|
10617
11088
|
projectName: config.projectName,
|
|
10618
11089
|
featureName: f.slug,
|
|
10619
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11090
|
+
featurePath: f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path),
|
|
10620
11091
|
repoType: f.type,
|
|
10621
11092
|
featureNumber
|
|
10622
11093
|
};
|
|
@@ -10626,7 +11097,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10626
11097
|
"tasks.md"
|
|
10627
11098
|
];
|
|
10628
11099
|
for (const file of files) {
|
|
10629
|
-
const fullPath =
|
|
11100
|
+
const fullPath = path13.join(f.path, file);
|
|
10630
11101
|
if (!await fs.pathExists(fullPath)) continue;
|
|
10631
11102
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
10632
11103
|
let next = original;
|
|
@@ -10679,7 +11150,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10679
11150
|
const issues = [];
|
|
10680
11151
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
10681
11152
|
for (const dir of requiredDirs) {
|
|
10682
|
-
const p =
|
|
11153
|
+
const p = path13.join(config.docsDir, dir);
|
|
10683
11154
|
if (!await fs.pathExists(p)) {
|
|
10684
11155
|
issues.push({
|
|
10685
11156
|
level: "error",
|
|
@@ -10691,7 +11162,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10691
11162
|
});
|
|
10692
11163
|
}
|
|
10693
11164
|
}
|
|
10694
|
-
const configPath =
|
|
11165
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
10695
11166
|
if (!await fs.pathExists(configPath)) {
|
|
10696
11167
|
issues.push({
|
|
10697
11168
|
level: "warn",
|
|
@@ -10719,7 +11190,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10719
11190
|
}
|
|
10720
11191
|
const idMap = /* @__PURE__ */ new Map();
|
|
10721
11192
|
for (const f of features) {
|
|
10722
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11193
|
+
const rel = f.docs.featurePathFromDocs || path13.relative(config.docsDir, f.path);
|
|
10723
11194
|
const id = f.id || "UNKNOWN";
|
|
10724
11195
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
10725
11196
|
idMap.get(id).push(rel);
|
|
@@ -10727,7 +11198,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10727
11198
|
if (!isInitialTemplateState) {
|
|
10728
11199
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
10729
11200
|
for (const file of featureDocs) {
|
|
10730
|
-
const p =
|
|
11201
|
+
const p = path13.join(f.path, file);
|
|
10731
11202
|
if (!await fs.pathExists(p)) continue;
|
|
10732
11203
|
const content = await fs.readFile(p, "utf-8");
|
|
10733
11204
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10742,7 +11213,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10742
11213
|
});
|
|
10743
11214
|
}
|
|
10744
11215
|
if (decisionsPlaceholderMode !== "off") {
|
|
10745
|
-
const decisionsPath =
|
|
11216
|
+
const decisionsPath = path13.join(f.path, "decisions.md");
|
|
10746
11217
|
if (await fs.pathExists(decisionsPath)) {
|
|
10747
11218
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
10748
11219
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10771,7 +11242,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10771
11242
|
level: "warn",
|
|
10772
11243
|
code: "spec_status_unset",
|
|
10773
11244
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
10774
|
-
path: formatPath(cwd,
|
|
11245
|
+
path: formatPath(cwd, path13.join(f.path, "spec.md"))
|
|
10775
11246
|
});
|
|
10776
11247
|
}
|
|
10777
11248
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -10779,7 +11250,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10779
11250
|
level: "warn",
|
|
10780
11251
|
code: "plan_status_unset",
|
|
10781
11252
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
10782
|
-
path: formatPath(cwd,
|
|
11253
|
+
path: formatPath(cwd, path13.join(f.path, "plan.md"))
|
|
10783
11254
|
});
|
|
10784
11255
|
}
|
|
10785
11256
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -10787,11 +11258,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10787
11258
|
level: "warn",
|
|
10788
11259
|
code: "tasks_empty",
|
|
10789
11260
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
10790
|
-
path: formatPath(cwd,
|
|
11261
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10791
11262
|
});
|
|
10792
11263
|
}
|
|
10793
11264
|
if (f.docs.tasksExists) {
|
|
10794
|
-
const tasksPath =
|
|
11265
|
+
const tasksPath = path13.join(f.path, "tasks.md");
|
|
10795
11266
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
10796
11267
|
const unknownPrdTags = [...new Set(
|
|
10797
11268
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -10813,7 +11284,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10813
11284
|
level: "warn",
|
|
10814
11285
|
code: "tasks_doc_status_missing",
|
|
10815
11286
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
10816
|
-
path: formatPath(cwd,
|
|
11287
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10817
11288
|
});
|
|
10818
11289
|
}
|
|
10819
11290
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -10821,7 +11292,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10821
11292
|
level: "warn",
|
|
10822
11293
|
code: "tasks_doc_status_unset",
|
|
10823
11294
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
10824
|
-
path: formatPath(cwd,
|
|
11295
|
+
path: formatPath(cwd, path13.join(f.path, "tasks.md"))
|
|
10825
11296
|
});
|
|
10826
11297
|
}
|
|
10827
11298
|
}
|
|
@@ -10845,7 +11316,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10845
11316
|
level: "warn",
|
|
10846
11317
|
code: "missing_feature_id",
|
|
10847
11318
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
10848
|
-
path: formatPath(cwd,
|
|
11319
|
+
path: formatPath(cwd, path13.join(config.docsDir, p))
|
|
10849
11320
|
});
|
|
10850
11321
|
}
|
|
10851
11322
|
return issues;
|
|
@@ -10975,25 +11446,25 @@ function doctorCommand(program2) {
|
|
|
10975
11446
|
return;
|
|
10976
11447
|
}
|
|
10977
11448
|
console.log();
|
|
10978
|
-
console.log(
|
|
10979
|
-
console.log(
|
|
10980
|
-
console.log(
|
|
10981
|
-
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}`));
|
|
10982
11453
|
console.log();
|
|
10983
11454
|
if (warnings.length > 0) {
|
|
10984
|
-
console.log(
|
|
10985
|
-
warnings.forEach((w) => console.log(
|
|
11455
|
+
console.log(chalk9.yellow(tr(lang, "cli", "doctor.envWarnings")));
|
|
11456
|
+
warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
10986
11457
|
console.log();
|
|
10987
11458
|
}
|
|
10988
11459
|
if (fixResult) {
|
|
10989
11460
|
const label = fixResult.dryRun ? "\u{1F9EA} Doctor Fix (dry-run)" : "\u{1F6E0}\uFE0F Doctor Fix";
|
|
10990
11461
|
console.log(
|
|
10991
|
-
|
|
11462
|
+
chalk9.blue(`${label}: ${fixResult.changedFiles} file(s)`)
|
|
10992
11463
|
);
|
|
10993
11464
|
if (fixResult.changedFiles > 0) {
|
|
10994
11465
|
fixResult.entries.forEach(
|
|
10995
11466
|
(entry) => console.log(
|
|
10996
|
-
|
|
11467
|
+
chalk9.gray(` - ${entry.path}: ${entry.changes.join(", ")}`)
|
|
10997
11468
|
)
|
|
10998
11469
|
);
|
|
10999
11470
|
}
|
|
@@ -11003,51 +11474,51 @@ function doctorCommand(program2) {
|
|
|
11003
11474
|
const warns = issues.filter((i) => i.level === "warn");
|
|
11004
11475
|
const infos = issues.filter((i) => i.level === "info");
|
|
11005
11476
|
if (!hasIssues && infos.length === 0) {
|
|
11006
|
-
console.log(
|
|
11477
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11007
11478
|
console.log();
|
|
11008
11479
|
return;
|
|
11009
11480
|
}
|
|
11010
11481
|
if (!hasIssues && infos.length > 0) {
|
|
11011
|
-
console.log(
|
|
11482
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11012
11483
|
console.log();
|
|
11013
11484
|
}
|
|
11014
11485
|
if (errors.length > 0) {
|
|
11015
11486
|
console.log(
|
|
11016
|
-
|
|
11487
|
+
chalk9.red(
|
|
11017
11488
|
`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`
|
|
11018
11489
|
)
|
|
11019
11490
|
);
|
|
11020
11491
|
errors.forEach(
|
|
11021
11492
|
(i) => console.log(
|
|
11022
|
-
|
|
11493
|
+
chalk9.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11023
11494
|
)
|
|
11024
11495
|
);
|
|
11025
11496
|
console.log();
|
|
11026
11497
|
}
|
|
11027
11498
|
if (warns.length > 0) {
|
|
11028
11499
|
console.log(
|
|
11029
|
-
|
|
11500
|
+
chalk9.yellow(
|
|
11030
11501
|
`\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
|
|
11031
11502
|
)
|
|
11032
11503
|
);
|
|
11033
11504
|
warns.forEach(
|
|
11034
11505
|
(i) => console.log(
|
|
11035
|
-
|
|
11506
|
+
chalk9.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11036
11507
|
)
|
|
11037
11508
|
);
|
|
11038
11509
|
console.log();
|
|
11039
11510
|
}
|
|
11040
11511
|
if (infos.length > 0) {
|
|
11041
|
-
console.log(
|
|
11512
|
+
console.log(chalk9.blue(`\u2139\uFE0F Info (${infos.length})`));
|
|
11042
11513
|
infos.forEach(
|
|
11043
11514
|
(i) => console.log(
|
|
11044
|
-
|
|
11515
|
+
chalk9.blue(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11045
11516
|
)
|
|
11046
11517
|
);
|
|
11047
11518
|
console.log();
|
|
11048
11519
|
}
|
|
11049
11520
|
console.log(
|
|
11050
|
-
|
|
11521
|
+
chalk9.gray(
|
|
11051
11522
|
tr(lang, "cli", "doctor.tipJson", {
|
|
11052
11523
|
strictFlag: options.strict ? " --strict" : ""
|
|
11053
11524
|
})
|
|
@@ -11076,8 +11547,8 @@ function doctorCommand(program2) {
|
|
|
11076
11547
|
);
|
|
11077
11548
|
} else {
|
|
11078
11549
|
console.error(
|
|
11079
|
-
|
|
11080
|
-
|
|
11550
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11551
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11081
11552
|
);
|
|
11082
11553
|
printCliErrorSuggestions(suggestions, lang);
|
|
11083
11554
|
}
|
|
@@ -11106,8 +11577,8 @@ function viewCommand(program2) {
|
|
|
11106
11577
|
);
|
|
11107
11578
|
} else {
|
|
11108
11579
|
console.error(
|
|
11109
|
-
|
|
11110
|
-
|
|
11580
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11581
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11111
11582
|
);
|
|
11112
11583
|
printCliErrorSuggestions(suggestions, lang);
|
|
11113
11584
|
}
|
|
@@ -11156,49 +11627,49 @@ async function runView(featureName, options) {
|
|
|
11156
11627
|
return;
|
|
11157
11628
|
}
|
|
11158
11629
|
console.log();
|
|
11159
|
-
console.log(
|
|
11160
|
-
console.log(
|
|
11630
|
+
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11631
|
+
console.log(chalk9.gray(`- Docs: ${path13.relative(cwd, config.docsDir)}`));
|
|
11161
11632
|
console.log(
|
|
11162
|
-
|
|
11633
|
+
chalk9.gray(
|
|
11163
11634
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
11164
11635
|
)
|
|
11165
11636
|
);
|
|
11166
11637
|
console.log(
|
|
11167
|
-
|
|
11638
|
+
chalk9.gray(
|
|
11168
11639
|
`- In Progress: ${state.inProgressFeatures.length}, Ready To Close: ${state.readyToCloseFeatures.length}`
|
|
11169
11640
|
)
|
|
11170
11641
|
);
|
|
11171
11642
|
if (state.warnings.length > 0) {
|
|
11172
11643
|
console.log();
|
|
11173
|
-
console.log(
|
|
11644
|
+
console.log(chalk9.yellow("\u26A0\uFE0F Environment warnings:"));
|
|
11174
11645
|
for (const warning of state.warnings) {
|
|
11175
|
-
console.log(
|
|
11646
|
+
console.log(chalk9.yellow(` - ${warning}`));
|
|
11176
11647
|
}
|
|
11177
11648
|
}
|
|
11178
11649
|
if (state.features.length === 0) {
|
|
11179
11650
|
console.log();
|
|
11180
|
-
console.log(
|
|
11181
|
-
console.log(
|
|
11651
|
+
console.log(chalk9.yellow("No features found."));
|
|
11652
|
+
console.log(chalk9.gray("Try: npx lee-spec-kit feature <name>"));
|
|
11182
11653
|
console.log();
|
|
11183
11654
|
return;
|
|
11184
11655
|
}
|
|
11185
11656
|
if (!state.matchedFeature) {
|
|
11186
11657
|
console.log();
|
|
11187
11658
|
console.log(
|
|
11188
|
-
|
|
11659
|
+
chalk9.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`)
|
|
11189
11660
|
);
|
|
11190
11661
|
const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
|
|
11191
11662
|
for (const f2 of rows) {
|
|
11192
|
-
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");
|
|
11193
11664
|
const substateText = f2.currentSubstateId ? `${f2.currentSubstateId} (${f2.currentSubstateOwner ?? "-"} / ${f2.currentSubstatePhase ?? "-"})` : "-";
|
|
11194
11665
|
console.log(
|
|
11195
11666
|
`- ${f2.folderName} (${f2.tasks.done}/${f2.tasks.total}) ${statusText} step:${f2.currentStep} substate:${substateText}`
|
|
11196
11667
|
);
|
|
11197
|
-
console.log(
|
|
11668
|
+
console.log(chalk9.gray(` next: ${f2.nextAction}`));
|
|
11198
11669
|
}
|
|
11199
11670
|
console.log();
|
|
11200
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>";
|
|
11201
|
-
console.log(
|
|
11672
|
+
console.log(chalk9.gray(selectorTip));
|
|
11202
11673
|
console.log();
|
|
11203
11674
|
return;
|
|
11204
11675
|
}
|
|
@@ -11206,7 +11677,7 @@ async function runView(featureName, options) {
|
|
|
11206
11677
|
const completion = `${f.tasks.done}/${f.tasks.total}`;
|
|
11207
11678
|
const stateLabel = f.completion.workflowDone ? "WORKFLOW_DONE" : f.completion.implementationDone ? "DONE" : "IN_PROGRESS";
|
|
11208
11679
|
console.log();
|
|
11209
|
-
console.log(
|
|
11680
|
+
console.log(chalk9.blue(`Feature: ${chalk9.bold(f.folderName)}`));
|
|
11210
11681
|
console.log(`- State: ${stateLabel}`);
|
|
11211
11682
|
console.log(`- Progress: ${completion}`);
|
|
11212
11683
|
console.log(`- Step: ${f.currentStep}`);
|
|
@@ -11217,14 +11688,14 @@ async function runView(featureName, options) {
|
|
|
11217
11688
|
console.log(`- Next: ${nextSummary}`);
|
|
11218
11689
|
if (state.actionOptions.length > 0) {
|
|
11219
11690
|
console.log();
|
|
11220
|
-
console.log(
|
|
11691
|
+
console.log(chalk9.green("Atomic options:"));
|
|
11221
11692
|
for (const option of state.actionOptions) {
|
|
11222
11693
|
const lang = config.lang ?? DEFAULT_LANG;
|
|
11223
11694
|
const requiresCheck = option.action.requiresUserCheck ? tr(lang, "cli", "context.checkRequired") : "";
|
|
11224
11695
|
console.log(` ${option.label}. ${requiresCheck}${option.detail}`);
|
|
11225
11696
|
}
|
|
11226
11697
|
console.log(
|
|
11227
|
-
|
|
11698
|
+
chalk9.gray(
|
|
11228
11699
|
`Approve with: npx lee-spec-kit context ${f.folderName} --approve <LABEL>`
|
|
11229
11700
|
)
|
|
11230
11701
|
);
|
|
@@ -11242,13 +11713,13 @@ function normalizeRunId(raw) {
|
|
|
11242
11713
|
return value;
|
|
11243
11714
|
}
|
|
11244
11715
|
function getFlowRunBaseDir(cwd) {
|
|
11245
|
-
return
|
|
11716
|
+
return path13.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11246
11717
|
}
|
|
11247
11718
|
function getFlowRunPath(cwd, runId) {
|
|
11248
|
-
return
|
|
11719
|
+
return path13.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11249
11720
|
}
|
|
11250
11721
|
function getFlowRunLockPath(cwd, runId) {
|
|
11251
|
-
return
|
|
11722
|
+
return path13.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11252
11723
|
}
|
|
11253
11724
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11254
11725
|
const normalized = normalizeRunId(runId);
|
|
@@ -11274,7 +11745,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11274
11745
|
}
|
|
11275
11746
|
async function writeFlowRunRecord(cwd, record) {
|
|
11276
11747
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
11277
|
-
await fs.ensureDir(
|
|
11748
|
+
await fs.ensureDir(path13.dirname(filePath));
|
|
11278
11749
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
11279
11750
|
}
|
|
11280
11751
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -11403,6 +11874,7 @@ function toCompactFlowContextSnapshot(state) {
|
|
|
11403
11874
|
actionOptions: state.actionOptions.map(
|
|
11404
11875
|
(option) => toCompactFlowActionOption(option)
|
|
11405
11876
|
),
|
|
11877
|
+
delegatedAction: buildDelegatedActionContract(state),
|
|
11406
11878
|
primaryActionLabel: primaryAction?.label ?? null,
|
|
11407
11879
|
primaryActionType: primaryAction?.action.type ?? null,
|
|
11408
11880
|
primaryActionCategory: primaryAction?.action.category ?? null,
|
|
@@ -12068,8 +12540,8 @@ function flowCommand(program2) {
|
|
|
12068
12540
|
);
|
|
12069
12541
|
} else {
|
|
12070
12542
|
console.error(
|
|
12071
|
-
|
|
12072
|
-
|
|
12543
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
12544
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
12073
12545
|
);
|
|
12074
12546
|
printCliErrorSuggestions(suggestions, lang);
|
|
12075
12547
|
}
|
|
@@ -12388,16 +12860,16 @@ async function runFlow(featureName, options) {
|
|
|
12388
12860
|
return;
|
|
12389
12861
|
}
|
|
12390
12862
|
console.log();
|
|
12391
|
-
console.log(
|
|
12863
|
+
console.log(chalk9.bold("\u{1F501} Flow Summary"));
|
|
12392
12864
|
console.log(
|
|
12393
|
-
|
|
12865
|
+
chalk9.gray(
|
|
12394
12866
|
`- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
|
|
12395
12867
|
)
|
|
12396
12868
|
);
|
|
12397
12869
|
if (approvalResult && typeof approvalResult === "object") {
|
|
12398
12870
|
const result = approvalResult;
|
|
12399
12871
|
console.log(
|
|
12400
|
-
|
|
12872
|
+
chalk9.gray(
|
|
12401
12873
|
`- Approval: ${result.status ?? "unknown"} (${result.reasonCode ?? "-"})`
|
|
12402
12874
|
)
|
|
12403
12875
|
);
|
|
@@ -12405,18 +12877,18 @@ async function runFlow(featureName, options) {
|
|
|
12405
12877
|
if (autoRun) {
|
|
12406
12878
|
const presetSuffix = autoRun.preset ? `, preset ${autoRun.preset}` : "";
|
|
12407
12879
|
console.log(
|
|
12408
|
-
|
|
12880
|
+
chalk9.gray(
|
|
12409
12881
|
`- Auto: ${autoRun.status} (${autoRun.reasonCode}), iterations ${autoRun.iterations}, executions ${autoRun.executions.length}${presetSuffix}`
|
|
12410
12882
|
)
|
|
12411
12883
|
);
|
|
12412
|
-
console.log(
|
|
12884
|
+
console.log(chalk9.gray(`- Auto resume: ${autoRun.resume.flowCommand}`));
|
|
12413
12885
|
if (autoRun.run) {
|
|
12414
12886
|
console.log(
|
|
12415
|
-
|
|
12887
|
+
chalk9.gray(
|
|
12416
12888
|
`- Auto run: ${autoRun.run.mode} ${autoRun.run.runId} (${autoRun.run.status})`
|
|
12417
12889
|
)
|
|
12418
12890
|
);
|
|
12419
|
-
console.log(
|
|
12891
|
+
console.log(chalk9.gray(`- Resume with: ${autoRun.run.resumeCommand}`));
|
|
12420
12892
|
}
|
|
12421
12893
|
}
|
|
12422
12894
|
const agentOrchestration = buildAgentOrchestrationPolicy2(
|
|
@@ -12424,21 +12896,21 @@ async function runFlow(featureName, options) {
|
|
|
12424
12896
|
resolvedFeatureName || null
|
|
12425
12897
|
);
|
|
12426
12898
|
console.log(
|
|
12427
|
-
|
|
12899
|
+
chalk9.gray(
|
|
12428
12900
|
`- Orchestration: ${agentOrchestration.mode}, prefer substate-owner delegation and fall back to legacy long-running categories`
|
|
12429
12901
|
)
|
|
12430
12902
|
);
|
|
12431
12903
|
const statusCounts = statusReport.counts;
|
|
12432
12904
|
const doctorCounts = doctorReport.counts;
|
|
12433
|
-
console.log(
|
|
12905
|
+
console.log(chalk9.gray(`- Status features: ${statusCounts?.features ?? 0}`));
|
|
12434
12906
|
console.log(
|
|
12435
|
-
|
|
12907
|
+
chalk9.gray(
|
|
12436
12908
|
`- Doctor issues: ${doctorCounts?.issues ?? 0} (errors ${doctorCounts?.errors ?? 0}, warnings ${doctorCounts?.warnings ?? 0})`
|
|
12437
12909
|
)
|
|
12438
12910
|
);
|
|
12439
12911
|
if (strictChecks) {
|
|
12440
|
-
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ?
|
|
12441
|
-
console.log(
|
|
12912
|
+
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ? chalk9.green("PASS") : chalk9.red("FAIL");
|
|
12913
|
+
console.log(chalk9.gray(`- Strict checks: ${strictLabel}`));
|
|
12442
12914
|
}
|
|
12443
12915
|
console.log();
|
|
12444
12916
|
if (autoRun?.status === "gate_reached" && autoRun.gate?.userFacingLines?.length) {
|
|
@@ -12446,7 +12918,7 @@ async function runFlow(featureName, options) {
|
|
|
12446
12918
|
console.log(line);
|
|
12447
12919
|
}
|
|
12448
12920
|
console.log(
|
|
12449
|
-
|
|
12921
|
+
chalk9.gray(
|
|
12450
12922
|
"Auto gate reached. Reply with one of the labels shown above (example: A OK)."
|
|
12451
12923
|
)
|
|
12452
12924
|
);
|
|
@@ -12457,15 +12929,15 @@ async function runFlow(featureName, options) {
|
|
|
12457
12929
|
}
|
|
12458
12930
|
if (after.matchedFeature) {
|
|
12459
12931
|
console.log(
|
|
12460
|
-
|
|
12932
|
+
chalk9.blue(
|
|
12461
12933
|
`Next: npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}`
|
|
12462
12934
|
)
|
|
12463
12935
|
);
|
|
12464
12936
|
} else {
|
|
12465
|
-
console.log(
|
|
12937
|
+
console.log(chalk9.blue(`Next: npx lee-spec-kit context${componentHint}`));
|
|
12466
12938
|
}
|
|
12467
12939
|
console.log(
|
|
12468
|
-
|
|
12940
|
+
chalk9.gray(
|
|
12469
12941
|
"Tip: add --approve <LABEL> [--execute] to run the selected atomic action."
|
|
12470
12942
|
)
|
|
12471
12943
|
);
|
|
@@ -12584,27 +13056,27 @@ function tg(lang, key, vars = {}) {
|
|
|
12584
13056
|
function detectGithubCliLangSync(cwd) {
|
|
12585
13057
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
12586
13058
|
const startDirs = [
|
|
12587
|
-
explicitDocsDir ?
|
|
12588
|
-
|
|
13059
|
+
explicitDocsDir ? path13.resolve(explicitDocsDir) : "",
|
|
13060
|
+
path13.resolve(cwd)
|
|
12589
13061
|
].filter(Boolean);
|
|
12590
13062
|
const scanOrder = [];
|
|
12591
13063
|
const seen = /* @__PURE__ */ new Set();
|
|
12592
13064
|
for (const start of startDirs) {
|
|
12593
13065
|
let current = start;
|
|
12594
13066
|
while (true) {
|
|
12595
|
-
const abs =
|
|
13067
|
+
const abs = path13.resolve(current);
|
|
12596
13068
|
if (!seen.has(abs)) {
|
|
12597
13069
|
scanOrder.push(abs);
|
|
12598
13070
|
seen.add(abs);
|
|
12599
13071
|
}
|
|
12600
|
-
const parent =
|
|
13072
|
+
const parent = path13.dirname(abs);
|
|
12601
13073
|
if (parent === abs) break;
|
|
12602
13074
|
current = parent;
|
|
12603
13075
|
}
|
|
12604
13076
|
}
|
|
12605
13077
|
for (const base of scanOrder) {
|
|
12606
|
-
for (const docsDir of [
|
|
12607
|
-
const configPath =
|
|
13078
|
+
for (const docsDir of [path13.join(base, "docs"), base]) {
|
|
13079
|
+
const configPath = path13.join(docsDir, ".lee-spec-kit.json");
|
|
12608
13080
|
if (fs.existsSync(configPath)) {
|
|
12609
13081
|
try {
|
|
12610
13082
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -12613,11 +13085,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
12613
13085
|
} catch {
|
|
12614
13086
|
}
|
|
12615
13087
|
}
|
|
12616
|
-
const agentsPath =
|
|
12617
|
-
const featuresPath =
|
|
13088
|
+
const agentsPath = path13.join(docsDir, "agents");
|
|
13089
|
+
const featuresPath = path13.join(docsDir, "features");
|
|
12618
13090
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
12619
13091
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
12620
|
-
const file =
|
|
13092
|
+
const file = path13.join(agentsPath, probe);
|
|
12621
13093
|
if (!fs.existsSync(file)) continue;
|
|
12622
13094
|
try {
|
|
12623
13095
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -12637,13 +13109,13 @@ function parseLabels(raw, lang) {
|
|
|
12637
13109
|
}
|
|
12638
13110
|
return [...new Set(labels)];
|
|
12639
13111
|
}
|
|
12640
|
-
function
|
|
13112
|
+
function escapeRegExp4(value) {
|
|
12641
13113
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12642
13114
|
}
|
|
12643
13115
|
function extractDraftMetadataValue(content, keys) {
|
|
12644
13116
|
for (const key of keys) {
|
|
12645
13117
|
const re = new RegExp(
|
|
12646
|
-
`^\\s*-\\s*\\*\\*${
|
|
13118
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
|
|
12647
13119
|
"mi"
|
|
12648
13120
|
);
|
|
12649
13121
|
const match = content.match(re);
|
|
@@ -12722,7 +13194,7 @@ async function prepareGithubBody(params) {
|
|
|
12722
13194
|
};
|
|
12723
13195
|
}
|
|
12724
13196
|
}
|
|
12725
|
-
await fs.ensureDir(
|
|
13197
|
+
await fs.ensureDir(path13.dirname(defaultBodyFile));
|
|
12726
13198
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
12727
13199
|
return {
|
|
12728
13200
|
body: generatedBody,
|
|
@@ -12762,7 +13234,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12762
13234
|
};
|
|
12763
13235
|
const hasMetadataField = (field) => {
|
|
12764
13236
|
const re = new RegExp(
|
|
12765
|
-
`^\\s*-\\s*\\*\\*${
|
|
13237
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(field)}\\*\\*\\s*:`,
|
|
12766
13238
|
"m"
|
|
12767
13239
|
);
|
|
12768
13240
|
return re.test(body);
|
|
@@ -12792,7 +13264,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12792
13264
|
}
|
|
12793
13265
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
12794
13266
|
const missing = relativePaths.filter(
|
|
12795
|
-
(relativePath) => !fs.existsSync(
|
|
13267
|
+
(relativePath) => !fs.existsSync(path13.join(docsDir, relativePath))
|
|
12796
13268
|
);
|
|
12797
13269
|
if (missing.length > 0) {
|
|
12798
13270
|
throw createCliError(
|
|
@@ -12802,18 +13274,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
12802
13274
|
}
|
|
12803
13275
|
}
|
|
12804
13276
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
12805
|
-
const key = `${
|
|
13277
|
+
const key = `${path13.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
12806
13278
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
12807
13279
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
12808
13280
|
}
|
|
12809
13281
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
12810
|
-
const selected = raw?.trim() ||
|
|
13282
|
+
const selected = raw?.trim() || path13.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
12811
13283
|
assertValid(
|
|
12812
13284
|
validatePathWithLang(selected, lang),
|
|
12813
13285
|
`github.${kind}.bodyFile`,
|
|
12814
13286
|
lang
|
|
12815
13287
|
);
|
|
12816
|
-
return
|
|
13288
|
+
return path13.resolve(selected);
|
|
12817
13289
|
}
|
|
12818
13290
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
12819
13291
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -13650,7 +14122,7 @@ function stripMarkdownCodeContexts(body) {
|
|
|
13650
14122
|
function hasIssueClosingKeyword(body, issueNumber) {
|
|
13651
14123
|
if (!issueNumber) return false;
|
|
13652
14124
|
const cleaned = stripMarkdownCodeContexts(body);
|
|
13653
|
-
const issue =
|
|
14125
|
+
const issue = escapeRegExp4(issueNumber);
|
|
13654
14126
|
const closeKeywordRegex = new RegExp(
|
|
13655
14127
|
`\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
|
|
13656
14128
|
"i"
|
|
@@ -13868,7 +14340,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
13868
14340
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
13869
14341
|
const uniqueRelativePaths = [
|
|
13870
14342
|
...new Set(
|
|
13871
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14343
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path13.relative(cwd, absPath) || absPath)
|
|
13872
14344
|
)
|
|
13873
14345
|
];
|
|
13874
14346
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14064,15 +14536,15 @@ function githubCommand(program2) {
|
|
|
14064
14536
|
config.lang
|
|
14065
14537
|
);
|
|
14066
14538
|
const specContent = await fs.readFile(
|
|
14067
|
-
|
|
14539
|
+
path13.join(config.docsDir, paths.specPath),
|
|
14068
14540
|
"utf-8"
|
|
14069
14541
|
);
|
|
14070
14542
|
const planContent = await fs.readFile(
|
|
14071
|
-
|
|
14543
|
+
path13.join(config.docsDir, paths.planPath),
|
|
14072
14544
|
"utf-8"
|
|
14073
14545
|
);
|
|
14074
14546
|
const tasksContent = await fs.readFile(
|
|
14075
|
-
|
|
14547
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14076
14548
|
"utf-8"
|
|
14077
14549
|
);
|
|
14078
14550
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14115,7 +14587,7 @@ function githubCommand(program2) {
|
|
|
14115
14587
|
create: options.create,
|
|
14116
14588
|
explicitBodyFile,
|
|
14117
14589
|
defaultBodyFile,
|
|
14118
|
-
workflowDraftPath:
|
|
14590
|
+
workflowDraftPath: path13.join(config.docsDir, paths.issuePath),
|
|
14119
14591
|
generatedBody,
|
|
14120
14592
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
14121
14593
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -14131,7 +14603,7 @@ function githubCommand(program2) {
|
|
|
14131
14603
|
`${feature.type}-issue-sanitized`,
|
|
14132
14604
|
config.lang
|
|
14133
14605
|
);
|
|
14134
|
-
await fs.ensureDir(
|
|
14606
|
+
await fs.ensureDir(path13.dirname(sanitizedBodyFile));
|
|
14135
14607
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14136
14608
|
bodyFile = sanitizedBodyFile;
|
|
14137
14609
|
}
|
|
@@ -14180,12 +14652,12 @@ function githubCommand(program2) {
|
|
|
14180
14652
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
14181
14653
|
if (syncedIssueNumber) {
|
|
14182
14654
|
const synced = syncTasksIssueMetadata(
|
|
14183
|
-
|
|
14655
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14184
14656
|
syncedIssueNumber,
|
|
14185
14657
|
config.lang
|
|
14186
14658
|
);
|
|
14187
14659
|
const draftSynced = syncIssueDraftMetadata(
|
|
14188
|
-
|
|
14660
|
+
path13.join(config.docsDir, paths.issuePath),
|
|
14189
14661
|
syncedIssueNumber
|
|
14190
14662
|
);
|
|
14191
14663
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -14213,31 +14685,31 @@ function githubCommand(program2) {
|
|
|
14213
14685
|
return;
|
|
14214
14686
|
}
|
|
14215
14687
|
console.log();
|
|
14216
|
-
console.log(
|
|
14688
|
+
console.log(chalk9.bold(tg(config.lang, "issueHeader")));
|
|
14217
14689
|
console.log(
|
|
14218
|
-
|
|
14690
|
+
chalk9.gray(
|
|
14219
14691
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14220
14692
|
)
|
|
14221
14693
|
);
|
|
14222
14694
|
console.log(
|
|
14223
|
-
|
|
14695
|
+
chalk9.gray(
|
|
14224
14696
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14225
14697
|
)
|
|
14226
14698
|
);
|
|
14227
14699
|
console.log(
|
|
14228
|
-
|
|
14700
|
+
chalk9.gray(
|
|
14229
14701
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14230
14702
|
)
|
|
14231
14703
|
);
|
|
14232
14704
|
if (issueUrl) {
|
|
14233
14705
|
console.log(
|
|
14234
|
-
|
|
14706
|
+
chalk9.green(
|
|
14235
14707
|
tg(config.lang, "issueCreated", { url: issueUrl })
|
|
14236
14708
|
)
|
|
14237
14709
|
);
|
|
14238
14710
|
} else {
|
|
14239
14711
|
console.log(
|
|
14240
|
-
|
|
14712
|
+
chalk9.blue(tg(config.lang, "issueTemplateGenerated"))
|
|
14241
14713
|
);
|
|
14242
14714
|
}
|
|
14243
14715
|
console.log();
|
|
@@ -14256,8 +14728,8 @@ function githubCommand(program2) {
|
|
|
14256
14728
|
);
|
|
14257
14729
|
} else {
|
|
14258
14730
|
console.error(
|
|
14259
|
-
|
|
14260
|
-
|
|
14731
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
14732
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14261
14733
|
);
|
|
14262
14734
|
printCliErrorSuggestions(suggestions, lang);
|
|
14263
14735
|
}
|
|
@@ -14296,13 +14768,13 @@ function githubCommand(program2) {
|
|
|
14296
14768
|
config.lang
|
|
14297
14769
|
);
|
|
14298
14770
|
const specContent = await fs.readFile(
|
|
14299
|
-
|
|
14771
|
+
path13.join(config.docsDir, paths.specPath),
|
|
14300
14772
|
"utf-8"
|
|
14301
14773
|
);
|
|
14302
|
-
const planPath =
|
|
14774
|
+
const planPath = path13.join(config.docsDir, paths.planPath);
|
|
14303
14775
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
14304
14776
|
const tasksContent = await fs.readFile(
|
|
14305
|
-
|
|
14777
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14306
14778
|
"utf-8"
|
|
14307
14779
|
);
|
|
14308
14780
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14350,7 +14822,7 @@ function githubCommand(program2) {
|
|
|
14350
14822
|
create: options.create,
|
|
14351
14823
|
explicitBodyFile,
|
|
14352
14824
|
defaultBodyFile,
|
|
14353
|
-
workflowDraftPath:
|
|
14825
|
+
workflowDraftPath: path13.join(config.docsDir, paths.prPath),
|
|
14354
14826
|
generatedBody,
|
|
14355
14827
|
requiredSections: getRequiredPrSections(config.lang),
|
|
14356
14828
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -14368,7 +14840,7 @@ function githubCommand(program2) {
|
|
|
14368
14840
|
`${feature.type}-pr-sanitized`,
|
|
14369
14841
|
config.lang
|
|
14370
14842
|
);
|
|
14371
|
-
await fs.ensureDir(
|
|
14843
|
+
await fs.ensureDir(path13.dirname(sanitizedBodyFile));
|
|
14372
14844
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14373
14845
|
bodyFile = sanitizedBodyFile;
|
|
14374
14846
|
}
|
|
@@ -14396,7 +14868,7 @@ function githubCommand(program2) {
|
|
|
14396
14868
|
if (preparedBody.source === "generated") {
|
|
14397
14869
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
14398
14870
|
} else {
|
|
14399
|
-
await fs.ensureDir(
|
|
14871
|
+
await fs.ensureDir(path13.dirname(fallbackBodyFile));
|
|
14400
14872
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
14401
14873
|
bodyFile = fallbackBodyFile;
|
|
14402
14874
|
}
|
|
@@ -14457,13 +14929,13 @@ function githubCommand(program2) {
|
|
|
14457
14929
|
}
|
|
14458
14930
|
if (prUrl && options.syncTasks !== false) {
|
|
14459
14931
|
const syncedTasks = syncTasksPrMetadata(
|
|
14460
|
-
|
|
14932
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14461
14933
|
prUrl,
|
|
14462
14934
|
"Review",
|
|
14463
14935
|
config.lang
|
|
14464
14936
|
);
|
|
14465
14937
|
const syncedDraft = syncPrDraftMetadata(
|
|
14466
|
-
|
|
14938
|
+
path13.join(config.docsDir, paths.prPath),
|
|
14467
14939
|
prUrl,
|
|
14468
14940
|
"Review"
|
|
14469
14941
|
);
|
|
@@ -14504,13 +14976,13 @@ function githubCommand(program2) {
|
|
|
14504
14976
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
14505
14977
|
if (prUrl && options.syncTasks !== false) {
|
|
14506
14978
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
14507
|
-
|
|
14979
|
+
path13.join(config.docsDir, paths.tasksPath),
|
|
14508
14980
|
prUrl,
|
|
14509
14981
|
"Approved",
|
|
14510
14982
|
config.lang
|
|
14511
14983
|
);
|
|
14512
14984
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
14513
|
-
|
|
14985
|
+
path13.join(config.docsDir, paths.prPath),
|
|
14514
14986
|
prUrl,
|
|
14515
14987
|
"Approved"
|
|
14516
14988
|
);
|
|
@@ -14594,35 +15066,35 @@ function githubCommand(program2) {
|
|
|
14594
15066
|
return;
|
|
14595
15067
|
}
|
|
14596
15068
|
console.log();
|
|
14597
|
-
console.log(
|
|
15069
|
+
console.log(chalk9.bold(tg(config.lang, "prHeader")));
|
|
14598
15070
|
console.log(
|
|
14599
|
-
|
|
15071
|
+
chalk9.gray(
|
|
14600
15072
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14601
15073
|
)
|
|
14602
15074
|
);
|
|
14603
15075
|
console.log(
|
|
14604
|
-
|
|
15076
|
+
chalk9.gray(
|
|
14605
15077
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14606
15078
|
)
|
|
14607
15079
|
);
|
|
14608
15080
|
console.log(
|
|
14609
|
-
|
|
15081
|
+
chalk9.gray(
|
|
14610
15082
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14611
15083
|
)
|
|
14612
15084
|
);
|
|
14613
15085
|
if (prUrl) {
|
|
14614
15086
|
console.log(
|
|
14615
|
-
|
|
15087
|
+
chalk9.gray(`- ${tg(config.lang, "labelPr")}: ${prUrl}`)
|
|
14616
15088
|
);
|
|
14617
15089
|
}
|
|
14618
15090
|
if (syncChanged) {
|
|
14619
15091
|
console.log(
|
|
14620
|
-
|
|
15092
|
+
chalk9.green(tg(config.lang, "prTasksSynced"))
|
|
14621
15093
|
);
|
|
14622
15094
|
}
|
|
14623
15095
|
if (options.merge) {
|
|
14624
15096
|
console.log(
|
|
14625
|
-
|
|
15097
|
+
chalk9.green(
|
|
14626
15098
|
tg(config.lang, "prMerged", {
|
|
14627
15099
|
attempts: mergedAttempts ?? 1
|
|
14628
15100
|
})
|
|
@@ -14630,15 +15102,15 @@ function githubCommand(program2) {
|
|
|
14630
15102
|
);
|
|
14631
15103
|
if (mergeAlreadyMerged) {
|
|
14632
15104
|
console.log(
|
|
14633
|
-
|
|
15105
|
+
chalk9.yellow(tg(config.lang, "prAlreadyMergedNotice"))
|
|
14634
15106
|
);
|
|
14635
15107
|
}
|
|
14636
15108
|
for (const warning of postMergeWarnings) {
|
|
14637
|
-
console.log(
|
|
15109
|
+
console.log(chalk9.yellow(`\u26A0\uFE0F ${warning}`));
|
|
14638
15110
|
}
|
|
14639
15111
|
} else if (!options.create) {
|
|
14640
15112
|
console.log(
|
|
14641
|
-
|
|
15113
|
+
chalk9.blue(tg(config.lang, "prTemplateGenerated"))
|
|
14642
15114
|
);
|
|
14643
15115
|
}
|
|
14644
15116
|
console.log();
|
|
@@ -14657,8 +15129,8 @@ function githubCommand(program2) {
|
|
|
14657
15129
|
);
|
|
14658
15130
|
} else {
|
|
14659
15131
|
console.error(
|
|
14660
|
-
|
|
14661
|
-
|
|
15132
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15133
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14662
15134
|
);
|
|
14663
15135
|
printCliErrorSuggestions(suggestions, lang);
|
|
14664
15136
|
}
|
|
@@ -14716,10 +15188,10 @@ function docsCommand(program2) {
|
|
|
14716
15188
|
return;
|
|
14717
15189
|
}
|
|
14718
15190
|
console.log();
|
|
14719
|
-
console.log(
|
|
15191
|
+
console.log(chalk9.bold(tr(config.lang, "cli", "docs.listHeader")));
|
|
14720
15192
|
for (const doc of docsList) {
|
|
14721
15193
|
console.log(`- ${doc.id}: ${doc.title}`);
|
|
14722
|
-
console.log(
|
|
15194
|
+
console.log(chalk9.gray(` ${doc.command}`));
|
|
14723
15195
|
}
|
|
14724
15196
|
console.log();
|
|
14725
15197
|
} catch (error) {
|
|
@@ -14738,8 +15210,8 @@ function docsCommand(program2) {
|
|
|
14738
15210
|
);
|
|
14739
15211
|
} else {
|
|
14740
15212
|
console.error(
|
|
14741
|
-
|
|
14742
|
-
|
|
15213
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15214
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14743
15215
|
);
|
|
14744
15216
|
printCliErrorSuggestions(suggestions, lang);
|
|
14745
15217
|
}
|
|
@@ -14781,25 +15253,25 @@ function docsCommand(program2) {
|
|
|
14781
15253
|
);
|
|
14782
15254
|
return;
|
|
14783
15255
|
}
|
|
14784
|
-
const relativeFromCwd =
|
|
15256
|
+
const relativeFromCwd = path13.relative(process.cwd(), loaded.entry.absolutePath);
|
|
14785
15257
|
console.log();
|
|
14786
|
-
console.log(
|
|
15258
|
+
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
14787
15259
|
console.log(
|
|
14788
|
-
|
|
15260
|
+
chalk9.gray(
|
|
14789
15261
|
`${tr(config.lang, "cli", "docs.sourceLabel")}: ${relativeFromCwd || loaded.entry.absolutePath}`
|
|
14790
15262
|
)
|
|
14791
15263
|
);
|
|
14792
15264
|
console.log(
|
|
14793
|
-
|
|
15265
|
+
chalk9.gray(`${tr(config.lang, "cli", "docs.hashLabel")}: ${loaded.hash}`)
|
|
14794
15266
|
);
|
|
14795
15267
|
console.log();
|
|
14796
15268
|
process.stdout.write(loaded.content.endsWith("\n") ? loaded.content : `${loaded.content}
|
|
14797
15269
|
`);
|
|
14798
15270
|
if (followups.length > 0) {
|
|
14799
15271
|
console.log();
|
|
14800
|
-
console.log(
|
|
15272
|
+
console.log(chalk9.blue(`${tr(config.lang, "cli", "docs.nextDocs")}:`));
|
|
14801
15273
|
for (const followup of followups) {
|
|
14802
|
-
console.log(
|
|
15274
|
+
console.log(chalk9.gray(`- ${followup.command}`));
|
|
14803
15275
|
}
|
|
14804
15276
|
console.log();
|
|
14805
15277
|
}
|
|
@@ -14819,8 +15291,8 @@ function docsCommand(program2) {
|
|
|
14819
15291
|
);
|
|
14820
15292
|
} else {
|
|
14821
15293
|
console.error(
|
|
14822
|
-
|
|
14823
|
-
|
|
15294
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15295
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14824
15296
|
);
|
|
14825
15297
|
printCliErrorSuggestions(suggestions, lang);
|
|
14826
15298
|
}
|
|
@@ -14849,8 +15321,8 @@ function detectCommand(program2) {
|
|
|
14849
15321
|
);
|
|
14850
15322
|
} else {
|
|
14851
15323
|
console.error(
|
|
14852
|
-
|
|
14853
|
-
|
|
15324
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15325
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14854
15326
|
);
|
|
14855
15327
|
printCliErrorSuggestions(suggestions, lang);
|
|
14856
15328
|
}
|
|
@@ -14861,7 +15333,7 @@ function detectCommand(program2) {
|
|
|
14861
15333
|
}
|
|
14862
15334
|
async function runDetect(options) {
|
|
14863
15335
|
const cwd = process.cwd();
|
|
14864
|
-
const targetCwd = options.dir ?
|
|
15336
|
+
const targetCwd = options.dir ? path13.resolve(cwd, options.dir) : cwd;
|
|
14865
15337
|
const config = await getConfig(targetCwd);
|
|
14866
15338
|
const detected = !!config;
|
|
14867
15339
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -14888,7 +15360,7 @@ async function runDetect(options) {
|
|
|
14888
15360
|
);
|
|
14889
15361
|
return;
|
|
14890
15362
|
}
|
|
14891
|
-
const configPath2 =
|
|
15363
|
+
const configPath2 = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
14892
15364
|
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
14893
15365
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
14894
15366
|
console.log(
|
|
@@ -14914,26 +15386,26 @@ async function runDetect(options) {
|
|
|
14914
15386
|
}
|
|
14915
15387
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
14916
15388
|
console.log();
|
|
14917
|
-
console.log(
|
|
14918
|
-
console.log(
|
|
15389
|
+
console.log(chalk9.blue(tr(lang, "cli", "detect.header")));
|
|
15390
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelTarget")}: ${targetCwd}`));
|
|
14919
15391
|
if (!config) {
|
|
14920
|
-
console.log(
|
|
14921
|
-
console.log(
|
|
15392
|
+
console.log(chalk9.yellow(`- ${tr(lang, "cli", "detect.resultNotDetected")}`));
|
|
15393
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.notDetectedHint")}`));
|
|
14922
15394
|
console.log();
|
|
14923
15395
|
return;
|
|
14924
15396
|
}
|
|
14925
|
-
const configPath =
|
|
15397
|
+
const configPath = path13.join(config.docsDir, ".lee-spec-kit.json");
|
|
14926
15398
|
const configFilePresent = await fs.pathExists(configPath);
|
|
14927
15399
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
14928
|
-
console.log(
|
|
14929
|
-
console.log(
|
|
15400
|
+
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
15401
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${config.docsDir}`));
|
|
14930
15402
|
console.log(
|
|
14931
|
-
|
|
15403
|
+
chalk9.gray(
|
|
14932
15404
|
`- ${tr(lang, "cli", "detect.labelConfigPath")}: ${configFilePresent ? configPath : "-"}`
|
|
14933
15405
|
)
|
|
14934
15406
|
);
|
|
14935
15407
|
console.log(
|
|
14936
|
-
|
|
15408
|
+
chalk9.gray(
|
|
14937
15409
|
`- ${tr(lang, "cli", "detect.labelSource")}: ${tr(
|
|
14938
15410
|
lang,
|
|
14939
15411
|
"cli",
|
|
@@ -14942,14 +15414,14 @@ async function runDetect(options) {
|
|
|
14942
15414
|
)
|
|
14943
15415
|
);
|
|
14944
15416
|
console.log(
|
|
14945
|
-
|
|
15417
|
+
chalk9.gray(
|
|
14946
15418
|
`- ${tr(lang, "cli", "detect.labelProjectType")}: ${config.projectType}`
|
|
14947
15419
|
)
|
|
14948
15420
|
);
|
|
14949
|
-
console.log(
|
|
15421
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelLang")}: ${config.lang}`));
|
|
14950
15422
|
if (config.projectName) {
|
|
14951
15423
|
console.log(
|
|
14952
|
-
|
|
15424
|
+
chalk9.gray(
|
|
14953
15425
|
`- ${tr(lang, "cli", "detect.labelProjectName")}: ${config.projectName}`
|
|
14954
15426
|
)
|
|
14955
15427
|
);
|
|
@@ -14999,19 +15471,19 @@ function hasTemplateMarkers(content) {
|
|
|
14999
15471
|
return patterns.some((pattern) => pattern.test(content));
|
|
15000
15472
|
}
|
|
15001
15473
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15002
|
-
const featuresRoot =
|
|
15474
|
+
const featuresRoot = path13.join(docsDir, "features");
|
|
15003
15475
|
if (projectType === "single") {
|
|
15004
15476
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15005
|
-
return dirs.filter((value) =>
|
|
15477
|
+
return dirs.filter((value) => path13.basename(value) !== "feature-base").length;
|
|
15006
15478
|
}
|
|
15007
15479
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15008
15480
|
let total = 0;
|
|
15009
15481
|
for (const componentDir of components) {
|
|
15010
|
-
const componentName =
|
|
15482
|
+
const componentName = path13.basename(componentDir).trim().toLowerCase();
|
|
15011
15483
|
if (!componentName || componentName === "feature-base") continue;
|
|
15012
15484
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15013
15485
|
total += dirs.filter(
|
|
15014
|
-
(value) =>
|
|
15486
|
+
(value) => path13.basename(value) !== "feature-base"
|
|
15015
15487
|
).length;
|
|
15016
15488
|
}
|
|
15017
15489
|
return total;
|
|
@@ -15023,7 +15495,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15023
15495
|
ignoreDirs: ["node_modules"]
|
|
15024
15496
|
});
|
|
15025
15497
|
return files.some(
|
|
15026
|
-
(absolutePath) =>
|
|
15498
|
+
(absolutePath) => path13.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15027
15499
|
);
|
|
15028
15500
|
}
|
|
15029
15501
|
function finalizeChecks(checks) {
|
|
@@ -15040,17 +15512,17 @@ function finalizeChecks(checks) {
|
|
|
15040
15512
|
function printOnboardResult(lang, result) {
|
|
15041
15513
|
console.log();
|
|
15042
15514
|
console.log(
|
|
15043
|
-
|
|
15515
|
+
chalk9.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks"))
|
|
15044
15516
|
);
|
|
15045
15517
|
for (const check of result.checks) {
|
|
15046
|
-
const mark = check.status === "ok" ?
|
|
15047
|
-
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");
|
|
15048
15520
|
console.log(`${mark} [${level}] ${check.title}`);
|
|
15049
15521
|
console.log(` ${check.message}`);
|
|
15050
|
-
if (check.path) console.log(
|
|
15522
|
+
if (check.path) console.log(chalk9.gray(` path: ${check.path}`));
|
|
15051
15523
|
if (check.suggestedCommand) {
|
|
15052
15524
|
console.log(
|
|
15053
|
-
|
|
15525
|
+
chalk9.gray(
|
|
15054
15526
|
` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`
|
|
15055
15527
|
)
|
|
15056
15528
|
);
|
|
@@ -15058,7 +15530,7 @@ function printOnboardResult(lang, result) {
|
|
|
15058
15530
|
}
|
|
15059
15531
|
console.log();
|
|
15060
15532
|
console.log(
|
|
15061
|
-
|
|
15533
|
+
chalk9.bold(
|
|
15062
15534
|
t(
|
|
15063
15535
|
lang,
|
|
15064
15536
|
`\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
|
|
@@ -15068,13 +15540,13 @@ function printOnboardResult(lang, result) {
|
|
|
15068
15540
|
);
|
|
15069
15541
|
if (result.status === "ready") {
|
|
15070
15542
|
console.log(
|
|
15071
|
-
|
|
15543
|
+
chalk9.green(
|
|
15072
15544
|
t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")
|
|
15073
15545
|
)
|
|
15074
15546
|
);
|
|
15075
15547
|
} else if (result.status === "needs_action") {
|
|
15076
15548
|
console.log(
|
|
15077
|
-
|
|
15549
|
+
chalk9.yellow(
|
|
15078
15550
|
t(
|
|
15079
15551
|
lang,
|
|
15080
15552
|
"\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15084,7 +15556,7 @@ function printOnboardResult(lang, result) {
|
|
|
15084
15556
|
);
|
|
15085
15557
|
} else {
|
|
15086
15558
|
console.log(
|
|
15087
|
-
|
|
15559
|
+
chalk9.red(
|
|
15088
15560
|
t(
|
|
15089
15561
|
lang,
|
|
15090
15562
|
"\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15192,7 +15664,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15192
15664
|
});
|
|
15193
15665
|
}
|
|
15194
15666
|
}
|
|
15195
|
-
const constitutionPath =
|
|
15667
|
+
const constitutionPath = path13.join(docsDir, "agents", "constitution.md");
|
|
15196
15668
|
if (!await fs.pathExists(constitutionPath)) {
|
|
15197
15669
|
checks.push({
|
|
15198
15670
|
id: "constitution_exists",
|
|
@@ -15234,7 +15706,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15234
15706
|
});
|
|
15235
15707
|
}
|
|
15236
15708
|
}
|
|
15237
|
-
const customPath =
|
|
15709
|
+
const customPath = path13.join(docsDir, "agents", "custom.md");
|
|
15238
15710
|
if (await fs.pathExists(customPath)) {
|
|
15239
15711
|
const content = await fs.readFile(customPath, "utf-8");
|
|
15240
15712
|
if (hasTemplateMarkers(content)) {
|
|
@@ -15263,7 +15735,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15263
15735
|
});
|
|
15264
15736
|
}
|
|
15265
15737
|
}
|
|
15266
|
-
const prdDir =
|
|
15738
|
+
const prdDir = path13.join(docsDir, "prd");
|
|
15267
15739
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
15268
15740
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
15269
15741
|
if (!prdReady) {
|
|
@@ -15281,7 +15753,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15281
15753
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
15282
15754
|
),
|
|
15283
15755
|
path: prdDir,
|
|
15284
|
-
suggestedCommand: `touch ${quotePath(
|
|
15756
|
+
suggestedCommand: `touch ${quotePath(path13.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
15285
15757
|
});
|
|
15286
15758
|
} else {
|
|
15287
15759
|
checks.push({
|
|
@@ -15422,8 +15894,8 @@ function onboardCommand(program2) {
|
|
|
15422
15894
|
);
|
|
15423
15895
|
} else {
|
|
15424
15896
|
console.error(
|
|
15425
|
-
|
|
15426
|
-
|
|
15897
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15898
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
15427
15899
|
);
|
|
15428
15900
|
printCliErrorSuggestions(suggestions, lang);
|
|
15429
15901
|
}
|
|
@@ -15466,6 +15938,14 @@ function asNonEmptyString(value, fallback) {
|
|
|
15466
15938
|
const trimmed = value.trim();
|
|
15467
15939
|
return trimmed || fallback;
|
|
15468
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
|
+
}
|
|
15469
15949
|
function asRequiredBoolean(value, field) {
|
|
15470
15950
|
if (typeof value === "boolean") return value;
|
|
15471
15951
|
throw createCliError(
|
|
@@ -15544,6 +16024,61 @@ function normalizeCommandsExecuted(value) {
|
|
|
15544
16024
|
return entry.trim();
|
|
15545
16025
|
});
|
|
15546
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
|
+
}
|
|
15547
16082
|
function normalizeGitPath3(value) {
|
|
15548
16083
|
return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
15549
16084
|
}
|
|
@@ -15656,7 +16191,7 @@ var PrePrReviewValidator = class {
|
|
|
15656
16191
|
return result.evidence;
|
|
15657
16192
|
}
|
|
15658
16193
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
15659
|
-
const fullPath =
|
|
16194
|
+
const fullPath = path13.resolve(evidencePath);
|
|
15660
16195
|
if (!await fs.pathExists(fullPath)) {
|
|
15661
16196
|
throw createCliError(
|
|
15662
16197
|
"INVALID_ARGUMENT",
|
|
@@ -15699,6 +16234,27 @@ var PrePrReviewValidator = class {
|
|
|
15699
16234
|
evidence.blockingFindings,
|
|
15700
16235
|
'"blockingFindings"'
|
|
15701
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
|
+
},
|
|
15702
16258
|
files: normalizeEvidenceFiles(evidence.files),
|
|
15703
16259
|
residualRisks: normalizeResidualRisks(evidence.residualRisks),
|
|
15704
16260
|
commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
|
|
@@ -15733,9 +16289,9 @@ var PrePrReviewValidator = class {
|
|
|
15733
16289
|
]);
|
|
15734
16290
|
const reviewedFiles = new Set(
|
|
15735
16291
|
normalizedEvidence.files.map(
|
|
15736
|
-
(f) =>
|
|
16292
|
+
(f) => path13.relative(
|
|
15737
16293
|
projectRoot,
|
|
15738
|
-
|
|
16294
|
+
path13.resolve(projectRoot, f.path)
|
|
15739
16295
|
)
|
|
15740
16296
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
15741
16297
|
);
|
|
@@ -15747,6 +16303,36 @@ var PrePrReviewValidator = class {
|
|
|
15747
16303
|
${missingFiles.map((f) => `- ${f}`).join("\n")}`
|
|
15748
16304
|
);
|
|
15749
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
|
+
};
|
|
15750
16336
|
return {
|
|
15751
16337
|
evidence: normalizedEvidence,
|
|
15752
16338
|
scope
|
|
@@ -15881,11 +16467,27 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
|
|
|
15881
16467
|
specAlignmentChecked: true,
|
|
15882
16468
|
findingCount: 0,
|
|
15883
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
|
+
},
|
|
15884
16486
|
files: [],
|
|
15885
16487
|
residualRisks: ["none"],
|
|
15886
16488
|
commandsExecuted: []
|
|
15887
16489
|
};
|
|
15888
|
-
function
|
|
16490
|
+
function escapeRegExp5(value) {
|
|
15889
16491
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15890
16492
|
}
|
|
15891
16493
|
function normalizeDecision(raw) {
|
|
@@ -15899,14 +16501,14 @@ function normalizeDecision(raw) {
|
|
|
15899
16501
|
return null;
|
|
15900
16502
|
}
|
|
15901
16503
|
function findSpecLineIndex(lines, keys) {
|
|
15902
|
-
const escaped = keys.map((key) =>
|
|
16504
|
+
const escaped = keys.map((key) => escapeRegExp5(key));
|
|
15903
16505
|
const re = new RegExp(
|
|
15904
16506
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
15905
16507
|
);
|
|
15906
16508
|
return lines.findIndex((line) => re.test(line));
|
|
15907
16509
|
}
|
|
15908
16510
|
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
15909
|
-
const escaped = keys.map((key) =>
|
|
16511
|
+
const escaped = keys.map((key) => escapeRegExp5(key));
|
|
15910
16512
|
const re = new RegExp(
|
|
15911
16513
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
15912
16514
|
);
|
|
@@ -15996,11 +16598,28 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
|
|
|
15996
16598
|
- **Spec Alignment Checked**: ${input.evidence.specAlignmentChecked ? "yes" : "no"}
|
|
15997
16599
|
- **Finding Count**: ${input.evidence.findingCount}
|
|
15998
16600
|
- **Blocking Findings**: ${input.evidence.blockingFindings}
|
|
16601
|
+
- **Base SHA**: ${input.evidence.baseSha}
|
|
16602
|
+
- **Head SHA**: ${input.evidence.headSha}
|
|
16603
|
+
- **Approval Rationale**: ${input.evidence.approvalRationale}
|
|
15999
16604
|
${commandsRun}
|
|
16000
16605
|
|
|
16001
16606
|
- **Residual Risks**:
|
|
16002
16607
|
${residualRisksSection}
|
|
16003
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
|
+
|
|
16004
16623
|
- **Review Scope**:
|
|
16005
16624
|
- **Main Base Ref**: ${input.scope.baseRef}
|
|
16006
16625
|
- **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
|
|
@@ -16054,7 +16673,7 @@ function prePrReviewCommand(program2) {
|
|
|
16054
16673
|
})
|
|
16055
16674
|
);
|
|
16056
16675
|
} else {
|
|
16057
|
-
console.error(
|
|
16676
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16058
16677
|
printCliErrorSuggestions(suggestions, lang);
|
|
16059
16678
|
}
|
|
16060
16679
|
process.exitCode = 1;
|
|
@@ -16066,7 +16685,7 @@ function prePrReviewCommand(program2) {
|
|
|
16066
16685
|
"Decision outcome: approve | changes_requested | blocked"
|
|
16067
16686
|
).option("--note <text>", "Decision note text").option(
|
|
16068
16687
|
"--evidence <path>",
|
|
16069
|
-
"
|
|
16688
|
+
"Structured review evidence path (for example review-trace.json); required for approve and whenever policy demands it"
|
|
16070
16689
|
).option("--json", "Output JSON").action(
|
|
16071
16690
|
async (featureName, options) => {
|
|
16072
16691
|
try {
|
|
@@ -16086,7 +16705,7 @@ function prePrReviewCommand(program2) {
|
|
|
16086
16705
|
})
|
|
16087
16706
|
);
|
|
16088
16707
|
} else {
|
|
16089
|
-
console.error(
|
|
16708
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16090
16709
|
printCliErrorSuggestions(suggestions, lang);
|
|
16091
16710
|
}
|
|
16092
16711
|
process.exitCode = 1;
|
|
@@ -16133,7 +16752,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16133
16752
|
);
|
|
16134
16753
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16135
16754
|
const preferred = getPreferredKeys(config.lang);
|
|
16136
|
-
const tasksPath =
|
|
16755
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16137
16756
|
let tasksUpdated = false;
|
|
16138
16757
|
if (await fs.pathExists(tasksPath)) {
|
|
16139
16758
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16183,7 +16802,8 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16183
16802
|
suggestedParallelism: 1,
|
|
16184
16803
|
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
16185
16804
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
16186
|
-
|
|
16805
|
+
delegatedWorkRequired: true,
|
|
16806
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
16187
16807
|
evidenceFile: "review-trace.json",
|
|
16188
16808
|
tasksUpdated,
|
|
16189
16809
|
tasksPath,
|
|
@@ -16202,24 +16822,27 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16202
16822
|
console.log(prompt);
|
|
16203
16823
|
console.log();
|
|
16204
16824
|
console.log(
|
|
16205
|
-
|
|
16206
|
-
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."
|
|
16207
16827
|
)
|
|
16208
16828
|
);
|
|
16209
16829
|
console.log(
|
|
16210
|
-
|
|
16830
|
+
chalk9.gray(
|
|
16211
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."
|
|
16212
16832
|
)
|
|
16213
16833
|
);
|
|
16214
16834
|
console.log(
|
|
16215
|
-
|
|
16835
|
+
chalk9.gray(
|
|
16216
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."
|
|
16217
16837
|
)
|
|
16218
16838
|
);
|
|
16219
16839
|
console.log(`Reuse key: pre-pr:${featureRef}`);
|
|
16220
16840
|
console.log(`Suggested parallelism: 1`);
|
|
16221
|
-
console.log(`Next main state:
|
|
16841
|
+
console.log(`Next main state: pre_pr_review_in_progress`);
|
|
16222
16842
|
console.log(`Evidence file: review-trace.json`);
|
|
16843
|
+
console.log(
|
|
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"
|
|
16845
|
+
);
|
|
16223
16846
|
if (tasksUpdated) {
|
|
16224
16847
|
console.log(`tasks.md updated: ${tasksPath}`);
|
|
16225
16848
|
console.log(
|
|
@@ -16240,7 +16863,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16240
16863
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
16241
16864
|
);
|
|
16242
16865
|
}
|
|
16243
|
-
const tasksPath =
|
|
16866
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16244
16867
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
16245
16868
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16246
16869
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -16273,6 +16896,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
16273
16896
|
"`--evidence <path>` is required when workflow.prePrReview.enforceExecutionEvidence=true."
|
|
16274
16897
|
);
|
|
16275
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
|
+
}
|
|
16276
16905
|
if (options.evidence) {
|
|
16277
16906
|
const validator = new PrePrReviewValidator(ctx);
|
|
16278
16907
|
const validationResult = await validator.validateEvidenceWithScope(
|
|
@@ -16332,7 +16961,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16332
16961
|
}
|
|
16333
16962
|
}
|
|
16334
16963
|
}
|
|
16335
|
-
const decisionsPath =
|
|
16964
|
+
const decisionsPath = path13.join(feature.path, "decisions.md");
|
|
16336
16965
|
const decisionLogEntry = buildReportContent({
|
|
16337
16966
|
folderName: feature.folderName,
|
|
16338
16967
|
date,
|
|
@@ -16348,9 +16977,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
16348
16977
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
16349
16978
|
}
|
|
16350
16979
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
16351
|
-
|
|
16980
|
+
path13.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
16352
16981
|
);
|
|
16353
|
-
const evidencePath =
|
|
16982
|
+
const evidencePath = path13.basename(config.docsDir) === "docs" ? normalizePathForDoc(path13.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
16354
16983
|
let nextTasks = tasksContent;
|
|
16355
16984
|
nextTasks = upsertSpecLine(
|
|
16356
16985
|
nextTasks,
|
|
@@ -16397,26 +17026,26 @@ async function runPrePrReview(featureName, options) {
|
|
|
16397
17026
|
return;
|
|
16398
17027
|
}
|
|
16399
17028
|
console.log();
|
|
16400
|
-
console.log(
|
|
16401
|
-
console.log(
|
|
16402
|
-
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}`));
|
|
16403
17032
|
if (nextTasks !== tasksContent) {
|
|
16404
|
-
console.log(
|
|
17033
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
16405
17034
|
}
|
|
16406
17035
|
console.log();
|
|
16407
17036
|
}
|
|
16408
|
-
function
|
|
17037
|
+
function escapeRegExp6(value) {
|
|
16409
17038
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16410
17039
|
}
|
|
16411
17040
|
function findSpecLineIndex2(lines, keys) {
|
|
16412
|
-
const escaped = keys.map((key) =>
|
|
17041
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16413
17042
|
const re = new RegExp(
|
|
16414
17043
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16415
17044
|
);
|
|
16416
17045
|
return lines.findIndex((line) => re.test(line));
|
|
16417
17046
|
}
|
|
16418
17047
|
function replaceSpecLine2(line, keys, preferredKey, value) {
|
|
16419
|
-
const escaped = keys.map((key) =>
|
|
17048
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16420
17049
|
const re = new RegExp(
|
|
16421
17050
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16422
17051
|
);
|
|
@@ -16491,7 +17120,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16491
17120
|
);
|
|
16492
17121
|
}
|
|
16493
17122
|
const feature = state.matchedFeature;
|
|
16494
|
-
const tasksPath =
|
|
17123
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16495
17124
|
let tasksUpdated = false;
|
|
16496
17125
|
if (await fs.pathExists(tasksPath)) {
|
|
16497
17126
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16526,7 +17155,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16526
17155
|
nextMainState: "code_review_running",
|
|
16527
17156
|
tasksUpdated,
|
|
16528
17157
|
tasksPath,
|
|
16529
|
-
decisionsPath:
|
|
17158
|
+
decisionsPath: path13.join(feature.path, "decisions.md"),
|
|
16530
17159
|
prompt,
|
|
16531
17160
|
recordedAt: getLocalDateString()
|
|
16532
17161
|
};
|
|
@@ -16537,30 +17166,30 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16537
17166
|
console.log(prompt);
|
|
16538
17167
|
console.log();
|
|
16539
17168
|
console.log(
|
|
16540
|
-
|
|
17169
|
+
chalk9.yellow(
|
|
16541
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."
|
|
16542
17171
|
)
|
|
16543
17172
|
);
|
|
16544
|
-
console.log(
|
|
16545
|
-
console.log(
|
|
16546
|
-
console.log(
|
|
16547
|
-
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}`));
|
|
16548
17177
|
console.log(
|
|
16549
|
-
|
|
17178
|
+
chalk9.gray(
|
|
16550
17179
|
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
16551
17180
|
)
|
|
16552
17181
|
);
|
|
16553
|
-
console.log(
|
|
17182
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
16554
17183
|
if (tasksUpdated) {
|
|
16555
|
-
console.log(
|
|
17184
|
+
console.log(chalk9.gray(`- tasks.md updated: ${payload.tasksPath}`));
|
|
16556
17185
|
console.log(
|
|
16557
|
-
|
|
17186
|
+
chalk9.gray(
|
|
16558
17187
|
config.lang === "ko" ? "- PR \uB9AC\uBDF0 \uC0C1\uD0DC: Running" : "- PR Review status: Running"
|
|
16559
17188
|
)
|
|
16560
17189
|
);
|
|
16561
17190
|
}
|
|
16562
|
-
console.log(
|
|
16563
|
-
console.log(
|
|
17191
|
+
console.log(chalk9.gray(`- tasks.md: ${payload.tasksPath}`));
|
|
17192
|
+
console.log(chalk9.gray(`- decisions.md: ${payload.decisionsPath}`));
|
|
16564
17193
|
}
|
|
16565
17194
|
function codeReviewRunCommand(program2) {
|
|
16566
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(
|
|
@@ -16582,7 +17211,7 @@ function codeReviewRunCommand(program2) {
|
|
|
16582
17211
|
})
|
|
16583
17212
|
);
|
|
16584
17213
|
} else {
|
|
16585
|
-
console.error(
|
|
17214
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16586
17215
|
printCliErrorSuggestions(suggestions, lang);
|
|
16587
17216
|
}
|
|
16588
17217
|
process.exitCode = 1;
|
|
@@ -16617,8 +17246,8 @@ function requirementsCommand(program2) {
|
|
|
16617
17246
|
const lang = ctx?.config?.lang ?? DEFAULT_LANG;
|
|
16618
17247
|
const cliError = toCliError(error);
|
|
16619
17248
|
console.error(
|
|
16620
|
-
|
|
16621
|
-
|
|
17249
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
17250
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
16622
17251
|
);
|
|
16623
17252
|
process.exitCode = 1;
|
|
16624
17253
|
}
|
|
@@ -16651,7 +17280,7 @@ async function runRequirements(options) {
|
|
|
16651
17280
|
}
|
|
16652
17281
|
for (const feature of scan.features) {
|
|
16653
17282
|
if (!feature.docs.tasksExists) continue;
|
|
16654
|
-
const tasksPath =
|
|
17283
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16655
17284
|
let tasksContent = "";
|
|
16656
17285
|
try {
|
|
16657
17286
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -16785,10 +17414,10 @@ async function runRequirements(options) {
|
|
|
16785
17414
|
process.stdout.write(`${lines.join("\n")}
|
|
16786
17415
|
`);
|
|
16787
17416
|
if (options.write) {
|
|
16788
|
-
const outputPath =
|
|
17417
|
+
const outputPath = path13.join(docsDir, "prd", "status.md");
|
|
16789
17418
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
16790
17419
|
`, "utf-8");
|
|
16791
|
-
console.log(
|
|
17420
|
+
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
16792
17421
|
}
|
|
16793
17422
|
if (options.strict && issuesFound) process.exitCode = 1;
|
|
16794
17423
|
}
|
|
@@ -16870,7 +17499,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
16870
17499
|
}
|
|
16871
17500
|
async function runTaskRun(featureName, options) {
|
|
16872
17501
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
16873
|
-
const tasksPath =
|
|
17502
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
16874
17503
|
if (!await fs.pathExists(tasksPath)) {
|
|
16875
17504
|
throw createCliError(
|
|
16876
17505
|
"PRECONDITION_FAILED",
|
|
@@ -16941,20 +17570,20 @@ async function runTaskRun(featureName, options) {
|
|
|
16941
17570
|
}
|
|
16942
17571
|
console.log(prompt);
|
|
16943
17572
|
console.log();
|
|
16944
|
-
console.log(
|
|
16945
|
-
console.log(
|
|
16946
|
-
console.log(
|
|
16947
|
-
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}`));
|
|
16948
17577
|
console.log(
|
|
16949
|
-
|
|
17578
|
+
chalk9.gray(
|
|
16950
17579
|
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
16951
17580
|
)
|
|
16952
17581
|
);
|
|
16953
|
-
console.log(
|
|
17582
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
16954
17583
|
if (tasksUpdated) {
|
|
16955
17584
|
console.log();
|
|
16956
|
-
console.log(
|
|
16957
|
-
console.log(
|
|
17585
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
17586
|
+
console.log(chalk9.gray(`- task status: TODO -> DOING`));
|
|
16958
17587
|
}
|
|
16959
17588
|
}
|
|
16960
17589
|
function taskRunCommand(program2) {
|
|
@@ -16978,7 +17607,7 @@ function taskRunCommand(program2) {
|
|
|
16978
17607
|
})
|
|
16979
17608
|
);
|
|
16980
17609
|
} else {
|
|
16981
|
-
console.error(
|
|
17610
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16982
17611
|
printCliErrorSuggestions(suggestions, lang);
|
|
16983
17612
|
}
|
|
16984
17613
|
process.exitCode = 1;
|
|
@@ -17029,7 +17658,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
17029
17658
|
}
|
|
17030
17659
|
async function runTaskComplete(featureName, options) {
|
|
17031
17660
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
17032
|
-
const tasksPath =
|
|
17661
|
+
const tasksPath = path13.join(feature.path, "tasks.md");
|
|
17033
17662
|
if (!await fs.pathExists(tasksPath)) {
|
|
17034
17663
|
throw createCliError(
|
|
17035
17664
|
"PRECONDITION_FAILED",
|
|
@@ -17089,13 +17718,13 @@ async function runTaskComplete(featureName, options) {
|
|
|
17089
17718
|
return;
|
|
17090
17719
|
}
|
|
17091
17720
|
console.log(
|
|
17092
|
-
|
|
17721
|
+
chalk9.green(
|
|
17093
17722
|
`Marked task ${resolvedTask.taskId} as DONE for ${feature.folderName}.`
|
|
17094
17723
|
)
|
|
17095
17724
|
);
|
|
17096
|
-
console.log(
|
|
17097
|
-
console.log(
|
|
17098
|
-
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}`));
|
|
17099
17728
|
}
|
|
17100
17729
|
function taskCompleteCommand(program2) {
|
|
17101
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(
|
|
@@ -17117,7 +17746,7 @@ function taskCompleteCommand(program2) {
|
|
|
17117
17746
|
})
|
|
17118
17747
|
);
|
|
17119
17748
|
} else {
|
|
17120
|
-
console.error(
|
|
17749
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
17121
17750
|
printCliErrorSuggestions(suggestions, lang);
|
|
17122
17751
|
}
|
|
17123
17752
|
process.exitCode = 1;
|
|
@@ -17164,15 +17793,15 @@ function getBanner(opts) {
|
|
|
17164
17793
|
${version}
|
|
17165
17794
|
` : "\n";
|
|
17166
17795
|
if (process.stdout.isTTY) {
|
|
17167
|
-
return `${
|
|
17796
|
+
return `${chalk9.cyan(ascii)}${chalk9.gray(footer)}`;
|
|
17168
17797
|
}
|
|
17169
17798
|
return `${ascii}${footer}`;
|
|
17170
17799
|
}
|
|
17171
|
-
var CACHE_FILE =
|
|
17800
|
+
var CACHE_FILE = path13.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
17172
17801
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
17173
17802
|
function getCurrentVersion() {
|
|
17174
17803
|
try {
|
|
17175
|
-
const packageJsonPath =
|
|
17804
|
+
const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
|
|
17176
17805
|
if (fs.existsSync(packageJsonPath)) {
|
|
17177
17806
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17178
17807
|
return pkg.version;
|
|
@@ -17206,8 +17835,8 @@ function resolveUpdateNoticeLang() {
|
|
|
17206
17835
|
}
|
|
17207
17836
|
function printUpdateNotice(current, latest, lang) {
|
|
17208
17837
|
console.log();
|
|
17209
|
-
console.log(
|
|
17210
|
-
console.log(
|
|
17838
|
+
console.log(chalk9.yellow(tr(lang, "cli", "versionCheck.noticeAvailable", { latest, current })));
|
|
17839
|
+
console.log(chalk9.gray(tr(lang, "cli", "versionCheck.updateCommand")));
|
|
17211
17840
|
console.log();
|
|
17212
17841
|
}
|
|
17213
17842
|
function spawnBackgroundVersionCheck() {
|
|
@@ -17276,7 +17905,7 @@ function shouldCheckForUpdates() {
|
|
|
17276
17905
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
17277
17906
|
function getCliVersion() {
|
|
17278
17907
|
try {
|
|
17279
|
-
const packageJsonPath =
|
|
17908
|
+
const packageJsonPath = path13.join(__dirname$1, "..", "package.json");
|
|
17280
17909
|
if (fs.existsSync(packageJsonPath)) {
|
|
17281
17910
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17282
17911
|
if (pkg?.version) return String(pkg.version);
|
|
@@ -17294,6 +17923,7 @@ if (shouldShowBanner()) {
|
|
|
17294
17923
|
}
|
|
17295
17924
|
initCommand(program);
|
|
17296
17925
|
featureCommand(program);
|
|
17926
|
+
ideaCommand(program);
|
|
17297
17927
|
statusCommand(program);
|
|
17298
17928
|
updateCommand(program);
|
|
17299
17929
|
configCommand(program);
|