lee-spec-kit 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1172 -595
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +13 -0
- package/templates/en/common/agents/agents.md +5 -3
- package/templates/en/common/agents/issue-template.md +1 -5
- package/templates/en/common/agents/pr-template.md +2 -11
- package/templates/en/common/agents/skills/create-feature.md +3 -0
- package/templates/en/common/agents/skills/create-issue.md +4 -7
- package/templates/en/common/agents/skills/create-pr.md +4 -8
- package/templates/en/common/agents/skills/execute-task.md +3 -0
- package/templates/en/common/features/README.md +25 -0
- package/templates/en/common/features/feature-base/decisions.md +1 -0
- package/templates/en/common/features/feature-base/plan.md +1 -0
- package/templates/en/common/features/feature-base/spec.md +1 -0
- package/templates/en/common/features/feature-base/tasks.md +5 -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 +13 -0
- package/templates/ko/common/agents/agents.md +5 -3
- package/templates/ko/common/agents/issue-template.md +1 -5
- package/templates/ko/common/agents/pr-template.md +2 -11
- package/templates/ko/common/agents/skills/create-feature.md +3 -0
- package/templates/ko/common/agents/skills/create-issue.md +4 -7
- package/templates/ko/common/agents/skills/create-pr.md +4 -8
- package/templates/ko/common/agents/skills/execute-task.md +4 -0
- package/templates/ko/common/features/README.md +25 -0
- package/templates/ko/common/features/feature-base/decisions.md +1 -0
- package/templates/ko/common/features/feature-base/plan.md +1 -0
- package/templates/ko/common/features/feature-base/spec.md +1 -0
- package/templates/ko/common/features/feature-base/tasks.md +5 -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 path14 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 fs11 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 = () => path14.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 = path14.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 = path14.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 = path14.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 = path14.dirname(__filename2);
|
|
155
155
|
function getTemplatesDir() {
|
|
156
|
-
const rootDir =
|
|
157
|
-
return
|
|
156
|
+
const rootDir = path14.resolve(__dirname2, "..");
|
|
157
|
+
return path14.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(path14.resolve(value)).digest("hex").slice(0, 16);
|
|
1706
1745
|
}
|
|
1707
1746
|
function getTempRuntimeDir(scopePath) {
|
|
1708
|
-
return
|
|
1747
|
+
return path14.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 path14.isAbsolute(out) ? out : path14.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 = path14.resolve(cwd);
|
|
1729
1768
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1730
1769
|
}
|
|
1731
1770
|
function getDocsLockPath(docsDir) {
|
|
1732
|
-
return
|
|
1771
|
+
return path14.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 path14.join(
|
|
1779
|
+
getRuntimeStateDir(path14.dirname(path14.resolve(targetDir))),
|
|
1741
1780
|
"locks",
|
|
1742
1781
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1743
1782
|
);
|
|
1744
1783
|
}
|
|
1745
1784
|
function getApprovalTicketStorePath(docsDir) {
|
|
1746
|
-
return
|
|
1785
|
+
return path14.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 path14.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(path14.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 = path14.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 = path14.join(docsDir, "agents", file);
|
|
1879
1918
|
if (await fs.pathExists(target)) {
|
|
1880
1919
|
await fs.remove(target);
|
|
1881
|
-
removed.push(
|
|
1920
|
+
removed.push(path14.relative(docsDir, target));
|
|
1882
1921
|
}
|
|
1883
1922
|
}
|
|
1884
1923
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1885
|
-
const target =
|
|
1924
|
+
const target = path14.join(docsDir, "agents", dir);
|
|
1886
1925
|
if (await fs.pathExists(target)) {
|
|
1887
1926
|
await fs.remove(target);
|
|
1888
|
-
removed.push(
|
|
1927
|
+
removed.push(path14.relative(docsDir, target));
|
|
1889
1928
|
}
|
|
1890
1929
|
}
|
|
1891
|
-
const featureBasePath =
|
|
1930
|
+
const featureBasePath = path14.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(path14.relative(docsDir, featureBasePath));
|
|
1895
1934
|
}
|
|
1896
1935
|
return removed;
|
|
1897
1936
|
}
|
|
@@ -1972,7 +2011,7 @@ Auto-run continuity (main/sub-agent orchestration):
|
|
|
1972
2011
|
3. Else run \`npx lee-spec-kit context --json-compact\` (fallback: \`--json\`) and continue from current \`actionOptions\`/\`autoRun\`.
|
|
1973
2012
|
- Pause and report to user only when:
|
|
1974
2013
|
- \`approvalRequest.required === true\`, or
|
|
1975
|
-
- \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\` or \`AUTO_MANUAL_REQUIRED\`, or
|
|
2014
|
+
- \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\`, \`AUTO_DELEGATED_HANDOFF\`, or \`AUTO_MANUAL_REQUIRED\`, or
|
|
1976
2015
|
- command execution fails (non-zero/error), or
|
|
1977
2016
|
- user explicitly asks to pause.
|
|
1978
2017
|
|
|
@@ -2171,7 +2210,7 @@ function initCommand(program2) {
|
|
|
2171
2210
|
} catch (error) {
|
|
2172
2211
|
if (error instanceof Error && error.message === "canceled") {
|
|
2173
2212
|
const lang2 = options.lang ?? DEFAULT_LANG;
|
|
2174
|
-
console.log(
|
|
2213
|
+
console.log(chalk9.yellow(`
|
|
2175
2214
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
2176
2215
|
return;
|
|
2177
2216
|
}
|
|
@@ -2179,8 +2218,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2179
2218
|
const cliError = toCliError(error);
|
|
2180
2219
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
2181
2220
|
console.error(
|
|
2182
|
-
|
|
2183
|
-
|
|
2221
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
2222
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
2184
2223
|
);
|
|
2185
2224
|
printCliErrorSuggestions(suggestions, lang);
|
|
2186
2225
|
process.exitCode = 1;
|
|
@@ -2190,7 +2229,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2190
2229
|
}
|
|
2191
2230
|
async function runInit(options) {
|
|
2192
2231
|
const cwd = process.cwd();
|
|
2193
|
-
const defaultName =
|
|
2232
|
+
const defaultName = path14.basename(cwd);
|
|
2194
2233
|
let projectName = options.name || defaultName;
|
|
2195
2234
|
let projectType = options.type;
|
|
2196
2235
|
let components = parseComponentsOption(options.components);
|
|
@@ -2201,7 +2240,7 @@ async function runInit(options) {
|
|
|
2201
2240
|
let docsRemote = options.docsRemote;
|
|
2202
2241
|
let projectRoot;
|
|
2203
2242
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
2204
|
-
const targetDir =
|
|
2243
|
+
const targetDir = path14.resolve(cwd, options.dir || "./docs");
|
|
2205
2244
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
2206
2245
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
2207
2246
|
throw createCliError(
|
|
@@ -2241,18 +2280,18 @@ async function runInit(options) {
|
|
|
2241
2280
|
}
|
|
2242
2281
|
console.log();
|
|
2243
2282
|
console.log(
|
|
2244
|
-
|
|
2283
|
+
chalk9.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
|
|
2245
2284
|
);
|
|
2246
2285
|
if (isInsideGitRepo) {
|
|
2247
|
-
console.log(
|
|
2286
|
+
console.log(chalk9.green(tr(lang, "cli", "init.gitDetected")));
|
|
2248
2287
|
console.log();
|
|
2249
|
-
console.log(
|
|
2250
|
-
console.log(
|
|
2251
|
-
console.log(
|
|
2252
|
-
console.log(
|
|
2288
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.insideProjectRoot")));
|
|
2289
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeEmbeddedDesc")));
|
|
2290
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeStandaloneDesc")));
|
|
2291
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.modeStandaloneMove")));
|
|
2253
2292
|
} else {
|
|
2254
|
-
console.log(
|
|
2255
|
-
console.log(
|
|
2293
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.gitNotDetected")));
|
|
2294
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
|
|
2256
2295
|
}
|
|
2257
2296
|
console.log();
|
|
2258
2297
|
const response = await prompts(
|
|
@@ -2574,31 +2613,31 @@ async function runInit(options) {
|
|
|
2574
2613
|
initial: false
|
|
2575
2614
|
});
|
|
2576
2615
|
if (!overwrite) {
|
|
2577
|
-
console.log(
|
|
2616
|
+
console.log(chalk9.yellow(tr(lang, "cli", "common.canceled")));
|
|
2578
2617
|
return;
|
|
2579
2618
|
}
|
|
2580
2619
|
}
|
|
2581
2620
|
}
|
|
2582
2621
|
}
|
|
2583
2622
|
console.log();
|
|
2584
|
-
console.log(
|
|
2623
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
2585
2624
|
console.log(
|
|
2586
|
-
|
|
2625
|
+
chalk9.gray(
|
|
2587
2626
|
` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
|
|
2588
2627
|
)
|
|
2589
2628
|
);
|
|
2590
2629
|
console.log(
|
|
2591
|
-
|
|
2630
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
|
|
2592
2631
|
);
|
|
2593
2632
|
console.log(
|
|
2594
|
-
|
|
2633
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`)
|
|
2595
2634
|
);
|
|
2596
2635
|
console.log(
|
|
2597
|
-
|
|
2636
|
+
chalk9.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
2598
2637
|
);
|
|
2599
2638
|
console.log();
|
|
2600
2639
|
const templatesDir = getTemplatesDir();
|
|
2601
|
-
const commonPath =
|
|
2640
|
+
const commonPath = path14.join(templatesDir, lang, "common");
|
|
2602
2641
|
if (!await fs.pathExists(commonPath)) {
|
|
2603
2642
|
throw new Error(
|
|
2604
2643
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2607,11 +2646,11 @@ async function runInit(options) {
|
|
|
2607
2646
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
2608
2647
|
await copyTemplates(fsAdapter, commonPath, targetDir);
|
|
2609
2648
|
if (projectType === "multi") {
|
|
2610
|
-
const featuresRoot =
|
|
2649
|
+
const featuresRoot = path14.join(targetDir, "features");
|
|
2611
2650
|
for (const component of components) {
|
|
2612
|
-
const componentDir =
|
|
2651
|
+
const componentDir = path14.join(featuresRoot, component);
|
|
2613
2652
|
await fs.ensureDir(componentDir);
|
|
2614
|
-
const readmePath =
|
|
2653
|
+
const readmePath = path14.join(componentDir, "README.md");
|
|
2615
2654
|
if (!await fs.pathExists(readmePath)) {
|
|
2616
2655
|
await fs.writeFile(
|
|
2617
2656
|
readmePath,
|
|
@@ -2672,20 +2711,20 @@ async function runInit(options) {
|
|
|
2672
2711
|
config.projectRoot = projectRoot;
|
|
2673
2712
|
}
|
|
2674
2713
|
}
|
|
2675
|
-
const configPath =
|
|
2714
|
+
const configPath = path14.join(targetDir, ".lee-spec-kit.json");
|
|
2676
2715
|
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
2677
2716
|
const extraCommitPathsAbs = [];
|
|
2678
2717
|
try {
|
|
2679
2718
|
if (docsRepo === "embedded") {
|
|
2680
2719
|
const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
|
|
2681
|
-
const agentsMdPath =
|
|
2720
|
+
const agentsMdPath = path14.join(repoRoot, "AGENTS.md");
|
|
2682
2721
|
const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
|
|
2683
2722
|
lang,
|
|
2684
2723
|
docsRepo
|
|
2685
2724
|
});
|
|
2686
2725
|
if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
|
|
2687
2726
|
} else {
|
|
2688
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2727
|
+
await upsertLeeSpecKitAgentsMd(path14.join(targetDir, "AGENTS.md"), {
|
|
2689
2728
|
lang,
|
|
2690
2729
|
docsRepo
|
|
2691
2730
|
});
|
|
@@ -2695,16 +2734,16 @@ async function runInit(options) {
|
|
|
2695
2734
|
} else if (projectRoot && typeof projectRoot === "object") {
|
|
2696
2735
|
roots.push(...Object.values(projectRoot));
|
|
2697
2736
|
}
|
|
2698
|
-
const resolvedCwd =
|
|
2737
|
+
const resolvedCwd = path14.resolve(cwd);
|
|
2699
2738
|
for (const raw of roots) {
|
|
2700
2739
|
const value = String(raw || "").trim();
|
|
2701
2740
|
if (!value) continue;
|
|
2702
|
-
const abs =
|
|
2703
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
2741
|
+
const abs = path14.resolve(cwd, value);
|
|
2742
|
+
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path14.sep}`)) {
|
|
2704
2743
|
if (await fs.pathExists(abs)) {
|
|
2705
2744
|
const stat = await fs.stat(abs);
|
|
2706
2745
|
if (stat.isDirectory()) {
|
|
2707
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2746
|
+
await upsertLeeSpecKitAgentsMd(path14.join(abs, "AGENTS.md"), {
|
|
2708
2747
|
lang,
|
|
2709
2748
|
docsRepo
|
|
2710
2749
|
});
|
|
@@ -2715,7 +2754,7 @@ async function runInit(options) {
|
|
|
2715
2754
|
}
|
|
2716
2755
|
} catch {
|
|
2717
2756
|
}
|
|
2718
|
-
console.log(
|
|
2757
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
2719
2758
|
console.log();
|
|
2720
2759
|
await initGit(
|
|
2721
2760
|
cwd,
|
|
@@ -2726,14 +2765,14 @@ async function runInit(options) {
|
|
|
2726
2765
|
docsRemote,
|
|
2727
2766
|
extraCommitPathsAbs
|
|
2728
2767
|
);
|
|
2729
|
-
console.log(
|
|
2768
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
|
|
2730
2769
|
console.log(
|
|
2731
|
-
|
|
2770
|
+
chalk9.gray(
|
|
2732
2771
|
tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir })
|
|
2733
2772
|
)
|
|
2734
2773
|
);
|
|
2735
|
-
console.log(
|
|
2736
|
-
console.log(
|
|
2774
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
2775
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.log.nextSteps3")));
|
|
2737
2776
|
console.log();
|
|
2738
2777
|
},
|
|
2739
2778
|
{ owner: "init" }
|
|
@@ -2788,31 +2827,31 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2788
2827
|
try {
|
|
2789
2828
|
runGitOrThrow(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
|
|
2790
2829
|
console.log(
|
|
2791
|
-
|
|
2830
|
+
chalk9.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit"))
|
|
2792
2831
|
);
|
|
2793
2832
|
} catch {
|
|
2794
|
-
console.log(
|
|
2833
|
+
console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2795
2834
|
runGitOrThrow(["init"], gitWorkdir);
|
|
2796
2835
|
}
|
|
2797
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2836
|
+
const relativePath = docsRepo === "standalone" ? "." : path14.relative(gitWorkdir, targetDir);
|
|
2798
2837
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2799
2838
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2800
|
-
console.log(
|
|
2801
|
-
console.log(
|
|
2839
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
|
|
2840
|
+
console.log(chalk9.gray(tr(lang, "cli", "init.warn.commitManually")));
|
|
2802
2841
|
console.log();
|
|
2803
2842
|
return;
|
|
2804
2843
|
}
|
|
2805
2844
|
if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
|
|
2806
2845
|
const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
|
|
2807
2846
|
console.log(
|
|
2808
|
-
|
|
2847
|
+
chalk9.yellow(
|
|
2809
2848
|
tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
|
|
2810
2849
|
path: repoRelativePath
|
|
2811
2850
|
})
|
|
2812
2851
|
)
|
|
2813
2852
|
);
|
|
2814
2853
|
console.log(
|
|
2815
|
-
|
|
2854
|
+
chalk9.gray(
|
|
2816
2855
|
tr(lang, "cli", "init.warn.docsPathIgnoredHint", {
|
|
2817
2856
|
path: repoRelativePath
|
|
2818
2857
|
})
|
|
@@ -2821,7 +2860,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2821
2860
|
console.log();
|
|
2822
2861
|
return;
|
|
2823
2862
|
}
|
|
2824
|
-
const extraRelativePaths = extraCommitPathsAbs.map((absPath) =>
|
|
2863
|
+
const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path14.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
|
|
2825
2864
|
const pathsToStage = [relativePath, ...extraRelativePaths];
|
|
2826
2865
|
for (const p of pathsToStage) {
|
|
2827
2866
|
runGitOrThrow(["add", p], gitWorkdir);
|
|
@@ -2840,34 +2879,34 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2840
2879
|
try {
|
|
2841
2880
|
runGitOrThrow(["remote", "add", "origin", docsRemote], gitWorkdir);
|
|
2842
2881
|
console.log(
|
|
2843
|
-
|
|
2882
|
+
chalk9.green(
|
|
2844
2883
|
tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote })
|
|
2845
2884
|
)
|
|
2846
2885
|
);
|
|
2847
2886
|
} catch {
|
|
2848
|
-
console.log(
|
|
2887
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
|
|
2849
2888
|
}
|
|
2850
2889
|
}
|
|
2851
|
-
console.log(
|
|
2890
|
+
console.log(chalk9.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
|
|
2852
2891
|
console.log();
|
|
2853
2892
|
} catch {
|
|
2854
|
-
console.log(
|
|
2893
|
+
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.skipGitInit")));
|
|
2855
2894
|
console.log();
|
|
2856
2895
|
}
|
|
2857
2896
|
}
|
|
2858
2897
|
function getAncestorDirs(startDir) {
|
|
2859
2898
|
const dirs = [];
|
|
2860
|
-
let current =
|
|
2899
|
+
let current = path14.resolve(startDir);
|
|
2861
2900
|
while (true) {
|
|
2862
2901
|
dirs.push(current);
|
|
2863
|
-
const parent =
|
|
2902
|
+
const parent = path14.dirname(current);
|
|
2864
2903
|
if (parent === current) break;
|
|
2865
2904
|
current = parent;
|
|
2866
2905
|
}
|
|
2867
2906
|
return dirs;
|
|
2868
2907
|
}
|
|
2869
2908
|
function hasWorkspaceBoundary(dir) {
|
|
2870
|
-
return fs.existsSync(
|
|
2909
|
+
return fs.existsSync(path14.join(dir, "package.json")) || fs.existsSync(path14.join(dir, ".git"));
|
|
2871
2910
|
}
|
|
2872
2911
|
function getSearchBaseDirs(cwd) {
|
|
2873
2912
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2883,7 +2922,7 @@ function normalizeComponentKeys(value) {
|
|
|
2883
2922
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2884
2923
|
}
|
|
2885
2924
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2886
|
-
const featuresPath =
|
|
2925
|
+
const featuresPath = path14.join(docsDir, "features");
|
|
2887
2926
|
if (!await fs.pathExists(featuresPath)) return [];
|
|
2888
2927
|
const entries = await fs.readdir(featuresPath, { withFileTypes: true });
|
|
2889
2928
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2894,21 +2933,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2894
2933
|
async function getConfig(cwd) {
|
|
2895
2934
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2896
2935
|
const baseDirs = [
|
|
2897
|
-
...explicitDocsDir ? [
|
|
2936
|
+
...explicitDocsDir ? [path14.resolve(explicitDocsDir)] : [],
|
|
2898
2937
|
...getSearchBaseDirs(cwd)
|
|
2899
2938
|
];
|
|
2900
2939
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2901
2940
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2902
2941
|
for (const baseDir of baseDirs) {
|
|
2903
|
-
const resolvedBaseDir =
|
|
2942
|
+
const resolvedBaseDir = path14.resolve(baseDir);
|
|
2904
2943
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2905
2944
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2906
|
-
const possibleDocsDirs = [
|
|
2945
|
+
const possibleDocsDirs = [path14.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2907
2946
|
for (const docsDir of possibleDocsDirs) {
|
|
2908
|
-
const resolvedDocsDir =
|
|
2947
|
+
const resolvedDocsDir = path14.resolve(docsDir);
|
|
2909
2948
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2910
2949
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2911
|
-
const configPath =
|
|
2950
|
+
const configPath = path14.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2912
2951
|
if (await fs.pathExists(configPath)) {
|
|
2913
2952
|
try {
|
|
2914
2953
|
const configFile = await fs.readJson(configPath);
|
|
@@ -2938,16 +2977,16 @@ async function getConfig(cwd) {
|
|
|
2938
2977
|
} catch {
|
|
2939
2978
|
}
|
|
2940
2979
|
}
|
|
2941
|
-
const agentsPath =
|
|
2942
|
-
const featuresPath =
|
|
2980
|
+
const agentsPath = path14.join(resolvedDocsDir, "agents");
|
|
2981
|
+
const featuresPath = path14.join(resolvedDocsDir, "features");
|
|
2943
2982
|
if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
|
|
2944
2983
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2945
2984
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2946
2985
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2947
2986
|
const langProbeCandidates = [
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2987
|
+
path14.join(agentsPath, "custom.md"),
|
|
2988
|
+
path14.join(agentsPath, "constitution.md"),
|
|
2989
|
+
path14.join(agentsPath, "agents.md")
|
|
2951
2990
|
];
|
|
2952
2991
|
let lang = "en";
|
|
2953
2992
|
for (const candidate of langProbeCandidates) {
|
|
@@ -3018,18 +3057,90 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
3018
3057
|
await fs.writeFile(filePath, transform(content), "utf-8");
|
|
3019
3058
|
}
|
|
3020
3059
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
3021
|
-
await patchMarkdownIfExists(
|
|
3060
|
+
await patchMarkdownIfExists(path14.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3022
3061
|
await patchMarkdownIfExists(
|
|
3023
|
-
|
|
3062
|
+
path14.join(featureDir, "tasks.md"),
|
|
3024
3063
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3025
3064
|
);
|
|
3026
|
-
await fs.remove(
|
|
3027
|
-
await fs.remove(
|
|
3065
|
+
await fs.remove(path14.join(featureDir, "issue.md"));
|
|
3066
|
+
await fs.remove(path14.join(featureDir, "pr.md"));
|
|
3067
|
+
}
|
|
3068
|
+
var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
|
|
3069
|
+
var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
|
|
3070
|
+
function extractExplicitIdeaRef(requestText) {
|
|
3071
|
+
const pathMatch = requestText.match(IDEA_PATH_PATTERN);
|
|
3072
|
+
if (pathMatch) return pathMatch[0];
|
|
3073
|
+
const refMatch = requestText.match(IDEA_REF_PATTERN);
|
|
3074
|
+
if (refMatch) return refMatch[1];
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3078
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3079
|
+
const trimmedRef = ref.trim();
|
|
3080
|
+
if (!trimmedRef) {
|
|
3081
|
+
throw createCliError(
|
|
3082
|
+
"INVALID_ARGUMENT",
|
|
3083
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref })
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3087
|
+
const candidate = path14.resolve(process.cwd(), trimmedRef);
|
|
3088
|
+
if (await fs.pathExists(candidate)) {
|
|
3089
|
+
return { path: candidate };
|
|
3090
|
+
}
|
|
3091
|
+
throw createCliError(
|
|
3092
|
+
"INVALID_ARGUMENT",
|
|
3093
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
if (!await fs.pathExists(ideasDir)) {
|
|
3097
|
+
throw createCliError(
|
|
3098
|
+
"INVALID_ARGUMENT",
|
|
3099
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3103
|
+
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3104
|
+
const exactName = `${trimmedRef}.md`;
|
|
3105
|
+
if (files.includes(exactName)) {
|
|
3106
|
+
return { path: path14.join(ideasDir, exactName) };
|
|
3107
|
+
}
|
|
3108
|
+
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3109
|
+
if (byId.length === 1) {
|
|
3110
|
+
return { path: path14.join(ideasDir, byId[0]) };
|
|
3111
|
+
}
|
|
3112
|
+
if (byId.length > 1) {
|
|
3113
|
+
throw createCliError(
|
|
3114
|
+
"INVALID_ARGUMENT",
|
|
3115
|
+
tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
|
|
3116
|
+
);
|
|
3117
|
+
}
|
|
3118
|
+
throw createCliError(
|
|
3119
|
+
"INVALID_ARGUMENT",
|
|
3120
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3121
|
+
);
|
|
3122
|
+
}
|
|
3123
|
+
async function readIdeaMetadataValue(ideaPath, label) {
|
|
3124
|
+
const content = await fs.readFile(ideaPath, "utf-8");
|
|
3125
|
+
const pattern = new RegExp(`^- \\*\\*${escapeRegExp(label)}\\*\\*:\\s*(.+)$`, "m");
|
|
3126
|
+
const match = content.match(pattern);
|
|
3127
|
+
if (!match) return null;
|
|
3128
|
+
const value = match[1].trim();
|
|
3129
|
+
return value.length > 0 ? value : null;
|
|
3130
|
+
}
|
|
3131
|
+
async function deriveFeatureNameFromIdea(ideaPath) {
|
|
3132
|
+
const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
|
|
3133
|
+
if (ideaName && ideaName !== "-") return ideaName;
|
|
3134
|
+
const basename = path14.basename(ideaPath, ".md");
|
|
3135
|
+
return basename.replace(/^I\d{3,}-/, "");
|
|
3136
|
+
}
|
|
3137
|
+
function escapeRegExp(value) {
|
|
3138
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3028
3139
|
}
|
|
3029
3140
|
|
|
3030
3141
|
// src/commands/feature.ts
|
|
3031
3142
|
function featureCommand(program2) {
|
|
3032
|
-
program2.command("feature <name>").description("Create a new feature folder").option("--component <component>", "Component name (multi only)").option("--id <id>", "Feature ID (default: auto)").option("-d, --desc <description>", "Feature description for spec.md").option("--non-interactive", "Fail instead of prompting for input").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3143
|
+
program2.command("feature <name>").description("Create a new feature folder").option("--component <component>", "Component name (multi only)").option("--id <id>", "Feature ID (default: auto)").option("-d, --desc <description>", "Feature description for spec.md").option("--idea <ref>", "Idea reference to promote (I001 | I001-slug | docs/ideas/...)").option("--non-interactive", "Fail instead of prompting for input").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3033
3144
|
try {
|
|
3034
3145
|
const result = await runFeature(name, options);
|
|
3035
3146
|
if (options.json) {
|
|
@@ -3062,7 +3173,7 @@ function featureCommand(program2) {
|
|
|
3062
3173
|
);
|
|
3063
3174
|
return;
|
|
3064
3175
|
}
|
|
3065
|
-
console.log(
|
|
3176
|
+
console.log(chalk9.yellow(`
|
|
3066
3177
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
3067
3178
|
return;
|
|
3068
3179
|
}
|
|
@@ -3083,8 +3194,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
3083
3194
|
return;
|
|
3084
3195
|
}
|
|
3085
3196
|
console.error(
|
|
3086
|
-
|
|
3087
|
-
|
|
3197
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
3198
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
3088
3199
|
);
|
|
3089
3200
|
printCliErrorSuggestions(suggestions, lang);
|
|
3090
3201
|
process.exitCode = 1;
|
|
@@ -3110,6 +3221,7 @@ async function runFeature(name, options) {
|
|
|
3110
3221
|
projectType,
|
|
3111
3222
|
config.components
|
|
3112
3223
|
);
|
|
3224
|
+
const linkedIdea = options.idea ? await resolveIdeaReference(docsDir, options.idea, lang) : null;
|
|
3113
3225
|
assertValid(
|
|
3114
3226
|
validateSafeNameWithLang(name, lang),
|
|
3115
3227
|
tr(lang, "cli", "validation.context.featureName"),
|
|
@@ -3175,19 +3287,19 @@ async function runFeature(name, options) {
|
|
|
3175
3287
|
}
|
|
3176
3288
|
let featuresDir;
|
|
3177
3289
|
if (projectType === "multi") {
|
|
3178
|
-
featuresDir =
|
|
3290
|
+
featuresDir = path14.join(docsDir, "features", component);
|
|
3179
3291
|
} else {
|
|
3180
|
-
featuresDir =
|
|
3292
|
+
featuresDir = path14.join(docsDir, "features");
|
|
3181
3293
|
}
|
|
3182
3294
|
const featureFolderName = `${featureId}-${name}`;
|
|
3183
|
-
const featureDir =
|
|
3295
|
+
const featureDir = path14.join(featuresDir, featureFolderName);
|
|
3184
3296
|
if (await fs.pathExists(featureDir)) {
|
|
3185
3297
|
throw createCliError(
|
|
3186
3298
|
"INVALID_ARGUMENT",
|
|
3187
3299
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3188
3300
|
);
|
|
3189
3301
|
}
|
|
3190
|
-
const featureBasePath =
|
|
3302
|
+
const featureBasePath = path14.join(
|
|
3191
3303
|
getTemplatesDir(),
|
|
3192
3304
|
lang,
|
|
3193
3305
|
"common",
|
|
@@ -3232,23 +3344,30 @@ async function runFeature(name, options) {
|
|
|
3232
3344
|
}
|
|
3233
3345
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
3234
3346
|
await replaceInFiles(fsAdapter, featureDir, replacements);
|
|
3347
|
+
if (linkedIdea) {
|
|
3348
|
+
await stampIdeaReferenceInSpec(
|
|
3349
|
+
path14.join(featureDir, "spec.md"),
|
|
3350
|
+
path14.relative(featureDir, linkedIdea.path)
|
|
3351
|
+
);
|
|
3352
|
+
await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
|
|
3353
|
+
}
|
|
3235
3354
|
if (config.workflow?.mode === "local") {
|
|
3236
3355
|
await applyLocalWorkflowTemplateToFeatureDir(featureDir, lang);
|
|
3237
3356
|
}
|
|
3238
3357
|
if (!options.json) {
|
|
3239
3358
|
console.log();
|
|
3240
3359
|
console.log(
|
|
3241
|
-
|
|
3360
|
+
chalk9.green(tr(lang, "cli", "feature.created", { path: featureDir }))
|
|
3242
3361
|
);
|
|
3243
3362
|
console.log();
|
|
3244
|
-
console.log(
|
|
3363
|
+
console.log(chalk9.blue(tr(lang, "cli", "feature.nextStepsTitle")));
|
|
3245
3364
|
console.log(
|
|
3246
|
-
|
|
3365
|
+
chalk9.gray(
|
|
3247
3366
|
tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
|
|
3248
3367
|
)
|
|
3249
3368
|
);
|
|
3250
|
-
console.log(
|
|
3251
|
-
console.log(
|
|
3369
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps2")));
|
|
3370
|
+
console.log(chalk9.gray(tr(lang, "cli", "feature.nextSteps3")));
|
|
3252
3371
|
console.log();
|
|
3253
3372
|
}
|
|
3254
3373
|
return {
|
|
@@ -3256,18 +3375,69 @@ async function runFeature(name, options) {
|
|
|
3256
3375
|
featureName: name,
|
|
3257
3376
|
component: projectType === "multi" ? component : void 0,
|
|
3258
3377
|
featurePath: featureDir,
|
|
3259
|
-
featurePathFromDocs:
|
|
3378
|
+
featurePathFromDocs: path14.relative(docsDir, featureDir)
|
|
3260
3379
|
};
|
|
3261
3380
|
},
|
|
3262
3381
|
{ owner: "feature" }
|
|
3263
3382
|
);
|
|
3264
3383
|
}
|
|
3384
|
+
async function stampIdeaReferenceInSpec(specPath, relativeIdeaPath) {
|
|
3385
|
+
const normalizedPath = relativeIdeaPath.replace(/\\/g, "/");
|
|
3386
|
+
const ideaLine = `- Idea: \`${normalizedPath}\``;
|
|
3387
|
+
let content = await fs.readFile(specPath, "utf-8");
|
|
3388
|
+
if (content.includes(ideaLine)) {
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
if (content.includes("## Related Documents")) {
|
|
3392
|
+
content = content.replace(
|
|
3393
|
+
"## Related Documents\n\n",
|
|
3394
|
+
`## Related Documents
|
|
3395
|
+
|
|
3396
|
+
${ideaLine}
|
|
3397
|
+
`
|
|
3398
|
+
);
|
|
3399
|
+
} else {
|
|
3400
|
+
content = `${content.trimEnd()}
|
|
3401
|
+
|
|
3402
|
+
${ideaLine}
|
|
3403
|
+
`;
|
|
3404
|
+
}
|
|
3405
|
+
await fs.writeFile(specPath, content, "utf-8");
|
|
3406
|
+
}
|
|
3407
|
+
async function markIdeaAsFeatureized(ideaPath, featureFolderName) {
|
|
3408
|
+
let content = await fs.readFile(ideaPath, "utf-8");
|
|
3409
|
+
content = replaceOrAppendIdeaMetadata(content, "Status", "Featureized");
|
|
3410
|
+
content = replaceOrAppendIdeaMetadata(content, "Feature", featureFolderName);
|
|
3411
|
+
await fs.writeFile(ideaPath, content, "utf-8");
|
|
3412
|
+
}
|
|
3413
|
+
function replaceOrAppendIdeaMetadata(content, label, value) {
|
|
3414
|
+
const pattern = new RegExp(`^- \\*\\*${escapeRegExp2(label)}\\*\\*:.*$`, "m");
|
|
3415
|
+
const line = `- **${label}**: ${value}`;
|
|
3416
|
+
if (pattern.test(content)) {
|
|
3417
|
+
return content.replace(pattern, line);
|
|
3418
|
+
}
|
|
3419
|
+
const heading = "## Promotion Tracking";
|
|
3420
|
+
if (content.includes(heading)) {
|
|
3421
|
+
return content.replace(heading, `${heading}
|
|
3422
|
+
|
|
3423
|
+
${line}`);
|
|
3424
|
+
}
|
|
3425
|
+
return `${content.trimEnd()}
|
|
3426
|
+
|
|
3427
|
+
${heading}
|
|
3428
|
+
|
|
3429
|
+
${line}
|
|
3430
|
+
`;
|
|
3431
|
+
}
|
|
3432
|
+
function escapeRegExp2(value) {
|
|
3433
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3434
|
+
}
|
|
3265
3435
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3266
3436
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3267
3437
|
const candidates = [
|
|
3268
|
-
...explicitDocsDir ? [
|
|
3269
|
-
|
|
3270
|
-
|
|
3438
|
+
...explicitDocsDir ? [path14.resolve(explicitDocsDir)] : [],
|
|
3439
|
+
path14.resolve(cwd, "docs"),
|
|
3440
|
+
path14.resolve(cwd)
|
|
3271
3441
|
];
|
|
3272
3442
|
const endAt = Date.now() + timeoutMs;
|
|
3273
3443
|
while (Date.now() < endAt) {
|
|
@@ -3294,12 +3464,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3294
3464
|
return getConfig(cwd);
|
|
3295
3465
|
}
|
|
3296
3466
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3297
|
-
const featuresDir =
|
|
3467
|
+
const featuresDir = path14.join(docsDir, "features");
|
|
3298
3468
|
let max = 0;
|
|
3299
3469
|
const scanDirs = [];
|
|
3300
3470
|
if (projectType === "multi") {
|
|
3301
3471
|
scanDirs.push(
|
|
3302
|
-
...components.map((component) =>
|
|
3472
|
+
...components.map((component) => path14.join(featuresDir, component))
|
|
3303
3473
|
);
|
|
3304
3474
|
} else {
|
|
3305
3475
|
scanDirs.push(featuresDir);
|
|
@@ -3320,6 +3490,167 @@ async function getNextFeatureId(docsDir, projectType, components) {
|
|
|
3320
3490
|
const width = Math.max(3, String(next).length);
|
|
3321
3491
|
return `F${String(next).padStart(width, "0")}`;
|
|
3322
3492
|
}
|
|
3493
|
+
function ideaCommand(program2) {
|
|
3494
|
+
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) => {
|
|
3495
|
+
try {
|
|
3496
|
+
const result = await runIdea(name, options);
|
|
3497
|
+
if (options.json) {
|
|
3498
|
+
console.log(
|
|
3499
|
+
JSON.stringify(
|
|
3500
|
+
{
|
|
3501
|
+
status: "ok",
|
|
3502
|
+
reasonCode: "IDEA_CREATED",
|
|
3503
|
+
ideaId: result.ideaId,
|
|
3504
|
+
ideaName: result.ideaName,
|
|
3505
|
+
component: result.component,
|
|
3506
|
+
ideaPath: result.ideaPath,
|
|
3507
|
+
ideaPathFromDocs: result.ideaPathFromDocs
|
|
3508
|
+
},
|
|
3509
|
+
null,
|
|
3510
|
+
2
|
|
3511
|
+
)
|
|
3512
|
+
);
|
|
3513
|
+
}
|
|
3514
|
+
} catch (error) {
|
|
3515
|
+
const config = await getConfig(process.cwd());
|
|
3516
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
3517
|
+
const cliError = toCliError(error);
|
|
3518
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
3519
|
+
if (options.json) {
|
|
3520
|
+
console.log(
|
|
3521
|
+
JSON.stringify({
|
|
3522
|
+
status: "error",
|
|
3523
|
+
reasonCode: cliError.code,
|
|
3524
|
+
error: cliError.message,
|
|
3525
|
+
suggestions
|
|
3526
|
+
})
|
|
3527
|
+
);
|
|
3528
|
+
process.exitCode = 1;
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
console.error(
|
|
3532
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
3533
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
3534
|
+
);
|
|
3535
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
3536
|
+
process.exitCode = 1;
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
async function runIdea(name, options) {
|
|
3541
|
+
const cwd = process.cwd();
|
|
3542
|
+
const config = await getConfig(cwd);
|
|
3543
|
+
if (!config) {
|
|
3544
|
+
throw createCliError(
|
|
3545
|
+
"DOCS_NOT_FOUND",
|
|
3546
|
+
tr(DEFAULT_LANG, "cli", "common.docsNotFound")
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
const { docsDir, projectType, lang } = config;
|
|
3550
|
+
const configuredComponents = resolveProjectComponents(
|
|
3551
|
+
projectType,
|
|
3552
|
+
config.components
|
|
3553
|
+
);
|
|
3554
|
+
assertValid(
|
|
3555
|
+
validateSafeNameWithLang(name, lang),
|
|
3556
|
+
tr(lang, "cli", "validation.context.ideaName"),
|
|
3557
|
+
lang
|
|
3558
|
+
);
|
|
3559
|
+
let component = (options.component || "").trim().toLowerCase();
|
|
3560
|
+
if (component && projectType === "single") {
|
|
3561
|
+
throw createCliError(
|
|
3562
|
+
"INVALID_ARGUMENT",
|
|
3563
|
+
"`--component` can only be used in multi mode."
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
if (projectType === "multi" && component) {
|
|
3567
|
+
assertAllowedComponent(component, configuredComponents);
|
|
3568
|
+
}
|
|
3569
|
+
return withFileLock(
|
|
3570
|
+
getDocsLockPath(docsDir),
|
|
3571
|
+
async () => {
|
|
3572
|
+
const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
|
|
3573
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3574
|
+
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3575
|
+
const ideaPath = path14.join(ideasDir, ideaFileName);
|
|
3576
|
+
if (await fs.pathExists(ideaPath)) {
|
|
3577
|
+
throw createCliError(
|
|
3578
|
+
"INVALID_ARGUMENT",
|
|
3579
|
+
tr(lang, "cli", "idea.fileExists", { path: ideaPath })
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
const templatePath = path14.join(
|
|
3583
|
+
getTemplatesDir(),
|
|
3584
|
+
lang,
|
|
3585
|
+
"common",
|
|
3586
|
+
"ideas",
|
|
3587
|
+
"idea.md"
|
|
3588
|
+
);
|
|
3589
|
+
if (!await fs.pathExists(templatePath)) {
|
|
3590
|
+
throw createCliError(
|
|
3591
|
+
"DOCS_NOT_FOUND",
|
|
3592
|
+
tr(lang, "cli", "idea.templateNotFound")
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
await fs.mkdir(ideasDir, { recursive: true });
|
|
3596
|
+
const template = await fs.readFile(templatePath, "utf-8");
|
|
3597
|
+
const content = applyIdeaTemplate(template, {
|
|
3598
|
+
ideaId,
|
|
3599
|
+
name,
|
|
3600
|
+
description: options.desc || "",
|
|
3601
|
+
component: component || "-",
|
|
3602
|
+
created: getLocalDateString()
|
|
3603
|
+
});
|
|
3604
|
+
await fs.writeFile(ideaPath, content, "utf-8");
|
|
3605
|
+
if (!options.json) {
|
|
3606
|
+
console.log();
|
|
3607
|
+
console.log(chalk9.green(tr(lang, "cli", "idea.created", { path: ideaPath })));
|
|
3608
|
+
console.log();
|
|
3609
|
+
console.log(chalk9.blue(tr(lang, "cli", "idea.nextStepsTitle")));
|
|
3610
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps1")));
|
|
3611
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps2", { ideaId })));
|
|
3612
|
+
console.log(chalk9.gray(tr(lang, "cli", "idea.nextSteps3")));
|
|
3613
|
+
console.log();
|
|
3614
|
+
}
|
|
3615
|
+
return {
|
|
3616
|
+
ideaId,
|
|
3617
|
+
ideaName: name,
|
|
3618
|
+
component: component || void 0,
|
|
3619
|
+
ideaPath,
|
|
3620
|
+
ideaPathFromDocs: path14.relative(docsDir, ideaPath)
|
|
3621
|
+
};
|
|
3622
|
+
},
|
|
3623
|
+
{ owner: "idea" }
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
function validateProvidedIdeaId(id, lang) {
|
|
3627
|
+
assertValid(
|
|
3628
|
+
validateIdeaIdWithLang(id, lang),
|
|
3629
|
+
tr(lang, "cli", "validation.context.ideaId"),
|
|
3630
|
+
lang
|
|
3631
|
+
);
|
|
3632
|
+
return id;
|
|
3633
|
+
}
|
|
3634
|
+
function applyIdeaTemplate(template, values) {
|
|
3635
|
+
return template.replaceAll("{idea-id}", values.ideaId).replaceAll("{idea-name}", values.name).replaceAll("{YYYY-MM-DD}", values.created).replaceAll("{{description}}", values.description).replaceAll("{component}", values.component);
|
|
3636
|
+
}
|
|
3637
|
+
async function getNextIdeaId(docsDir) {
|
|
3638
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3639
|
+
let max = 0;
|
|
3640
|
+
if (await fs.pathExists(ideasDir)) {
|
|
3641
|
+
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3642
|
+
for (const entry of entries) {
|
|
3643
|
+
if (!entry.isFile()) continue;
|
|
3644
|
+
const match = entry.name.match(/^I(\d+)-/);
|
|
3645
|
+
if (!match) continue;
|
|
3646
|
+
const num = parseInt(match[1], 10);
|
|
3647
|
+
if (num > max) max = num;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
const next = max + 1;
|
|
3651
|
+
const width = Math.max(3, String(next).length);
|
|
3652
|
+
return `I${String(next).padStart(width, "0")}`;
|
|
3653
|
+
}
|
|
3323
3654
|
var DefaultCommandAdapter = class {
|
|
3324
3655
|
execSync(command, options) {
|
|
3325
3656
|
return execSync(command, options);
|
|
@@ -3579,15 +3910,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3579
3910
|
2. \uB9AC\uBDF0 \uBC94\uC704\uB97C \uBD84\uB9AC\uD574 \uD655\uC778\uD558\uC138\uC694.
|
|
3580
3911
|
- main \uAE30\uC900: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3581
3912
|
- worktree \uAE30\uC900: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3582
|
-
3. \uAD6C\uD604\uC774 feature \uC758\uB3C4\uC5D0 \uB9DE\uB294\uC9C0 \uD3C9\uAC00\uD558\uC138\uC694. \uD2B9\uD788 \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, \`residualRisks\`\uB97C \uAD6C\uCCB4\uC801\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.
|
|
3583
|
-
4. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694.
|
|
3913
|
+
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.
|
|
3914
|
+
4. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uC99D\uAC70\uC5D0\uB294 \uBC18\uB4DC\uC2DC \`baseSha\`, \`headSha\`, \`changedFiles\`, \`reviewedFiles\`, \`riskSummaries\`(blocking/important/minor)\uAC00 \uD3EC\uD568\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4.
|
|
3584
3915
|
5. \uAE30\uBCF8 \uBCA0\uC774\uC2A4\uB77C\uC778\uC740 '${fallbackText}'\uC774\uBA70, 'create-pr' \uBB38\uC11C\uC758 'Pre-PR \uAE30\uBCF8 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8' \uC139\uC158\uC744 \uC218\uD589\uD558\uC138\uC694.
|
|
3585
3916
|
6. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: ${skills.length > 0 ? skills.join(", ") : "\uC5C6\uC74C"} \uB85C \uC2EC\uD654 \uAC80\uD1A0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
|
|
3586
3917
|
7. \uCD94\uAC00 \uAC80\uC99D\uC774 \uAF2D \uD544\uC694\uD560 \uB54C\uB9CC audit/\uD0C0\uAE43 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694. \uBCC4\uB3C4 \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uCD94\uAC00 \uC0DD\uC131\uB3C4 \uAF2D \uD544\uC694\uD560 \uB54C\uB9CC \uD558\uC138\uC694.
|
|
3587
3918
|
8. \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uD55C\uB3C4\uC5D0 \uAC78\uB9AC\uBA74 \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uC5D0\uC11C \uB9AC\uBDF0\uB97C \uC774\uC5B4\uAC00\uACE0, \uC774\uBBF8 \uC218\uC9D1\uD55C \uACB0\uACFC\uB9CC \uC815\uB9AC\uD574\uB3C4 \uB429\uB2C8\uB2E4.
|
|
3588
3919
|
9. \uC2E4\uD589\uD55C \uBA85\uB839\uC774 \uC788\uC73C\uBA74 \`commandsExecuted\`\uC5D0 \uAE30\uB85D\uD558\uC138\uC694.
|
|
3589
|
-
10. \uC9C0\uC801\uC0AC\uD56D\uC774 \uB0A8\uC544 \uC788\uC73C\uBA74 \uBA3C\uC800 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested' \uB85C \uAE30\uB85D\uD558\uC138\uC694. \uB2E8, \`workflow.prePrReview.evidenceMode=any\` \uC774\uACE0 \uC2E4\uD589 \uC99D\uAC70 \uAC15\uC81C\uAC00 \uC5C6\uC73C\uBA74 \`--evidence\` \uC5C6\uC774 \uC9C1\uC811 \uAE30\uB85D\uD574\uB3C4 \uB429\uB2C8\uB2E4.
|
|
3590
|
-
11. \uC218\uC815/\uC7AC\uAC80\uC99D \uD6C4 \uCD5C\uC885 \uC2B9\uC778 \uC2DC\uC810\uC5D0 'npx lee-spec-kit pre-pr-review <feature> --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694.
|
|
3920
|
+
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.
|
|
3921
|
+
11. \uC218\uC815/\uC7AC\uAC80\uC99D \uD6C4 \uCD5C\uC885 \uC2B9\uC778 \uC2DC\uC810\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uAD6C\uC870\uD654\uB41C evidence\uC640 \uD568\uAED8 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694. approve\uB294 evidence \uC5C6\uC774 \uAE30\uB85D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`;
|
|
3591
3922
|
}
|
|
3592
3923
|
return `Conduct a pre-PR code review.
|
|
3593
3924
|
0. Reuse the existing helper/sub-agent for this feature review if one already exists. Default to a single helper agent.
|
|
@@ -3595,15 +3926,15 @@ function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
|
3595
3926
|
2. Split and check the review scope.
|
|
3596
3927
|
- Main scope: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3597
3928
|
- Worktree scope: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3598
|
-
3. Evaluate whether the implementation actually fits the feature intent. Capture concrete \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, and \`
|
|
3599
|
-
4. Generate a 'review-trace.json' file for all changed files, including \`findingCount\`, \`blockingFindings\`,
|
|
3929
|
+
3. Evaluate whether the implementation actually fits the feature intent. Capture concrete \`featureIntentSummary\`, \`implementationFit\`, \`missingCases\`, \`residualRisks\`, and \`approvalRationale\`, and explicitly set \`specAlignmentChecked\`.
|
|
3930
|
+
4. Generate a 'review-trace.json' file for all changed files, including \`baseSha\`, \`headSha\`, \`changedFiles\`, \`reviewedFiles\`, \`riskSummaries\` (blocking/important/minor), \`findingCount\`, \`blockingFindings\`, per-file risk/security/perf/maintainability evaluations, and specific \`fileLine\` locators.
|
|
3600
3931
|
5. The baseline is '${fallbackText}'. Always perform the 'Pre-PR Core Checklist' section of the 'create-pr' document.
|
|
3601
3932
|
6. Priority skills: ${skills.length > 0 ? skills.join(", ") : "None"} for deeper technical review.
|
|
3602
3933
|
7. Run extra audit/targeted verification only when the review truly needs more evidence. Spawn additional helper agents only when necessary.
|
|
3603
3934
|
8. If helper-agent quota is exhausted, continue the review in the main agent and just keep the evidence consistent.
|
|
3604
3935
|
9. Record commands in \`commandsExecuted\` only when you actually ran them.
|
|
3605
|
-
10. If unresolved findings remain, first record them with 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision changes_requested'. When \`workflow.prePrReview.evidenceMode=any\` and execution evidence is not enforced, direct record mode without \`--evidence\` is also valid.
|
|
3606
|
-
11. After fixes and re-validation, run 'npx lee-spec-kit pre-pr-review <feature> --decision approve' for final pre-PR approval.
|
|
3936
|
+
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.
|
|
3937
|
+
11. After fixes and re-validation, run 'npx lee-spec-kit pre-pr-review <feature> --evidence review-trace.json --decision approve' for final pre-PR approval. Approve never records without structured evidence.`;
|
|
3607
3938
|
}
|
|
3608
3939
|
function getCodeReviewPrompt(lang) {
|
|
3609
3940
|
if (lang === "ko") {
|
|
@@ -3743,7 +4074,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
3743
4074
|
}
|
|
3744
4075
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
3745
4076
|
try {
|
|
3746
|
-
const raw =
|
|
4077
|
+
const raw = fs11.readFileSync(evidencePath, "utf-8");
|
|
3747
4078
|
const parsed = JSON.parse(raw);
|
|
3748
4079
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
3749
4080
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -3759,31 +4090,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
3759
4090
|
const candidates = [];
|
|
3760
4091
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
3761
4092
|
if (explicit && explicit !== "-") {
|
|
3762
|
-
if (
|
|
4093
|
+
if (path14.isAbsolute(explicit)) {
|
|
3763
4094
|
candidates.push(explicit);
|
|
3764
4095
|
} else {
|
|
3765
|
-
candidates.push(
|
|
3766
|
-
candidates.push(
|
|
4096
|
+
candidates.push(path14.resolve(feature.path, explicit));
|
|
4097
|
+
candidates.push(path14.resolve(docsRoot, explicit));
|
|
3767
4098
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
3768
4099
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
3769
4100
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
3770
4101
|
if (withoutDocsPrefix) {
|
|
3771
|
-
candidates.push(
|
|
4102
|
+
candidates.push(path14.resolve(docsRoot, withoutDocsPrefix));
|
|
3772
4103
|
}
|
|
3773
4104
|
}
|
|
3774
4105
|
}
|
|
3775
4106
|
}
|
|
3776
|
-
candidates.push(
|
|
3777
|
-
candidates.push(
|
|
4107
|
+
candidates.push(path14.join(feature.path, "review-trace.json"));
|
|
4108
|
+
candidates.push(path14.join(docsRoot, "review-trace.json"));
|
|
3778
4109
|
const seen = /* @__PURE__ */ new Set();
|
|
3779
4110
|
for (const candidate of candidates) {
|
|
3780
|
-
const abs =
|
|
4111
|
+
const abs = path14.resolve(candidate);
|
|
3781
4112
|
if (seen.has(abs)) continue;
|
|
3782
4113
|
seen.add(abs);
|
|
3783
|
-
if (!
|
|
4114
|
+
if (!fs11.existsSync(abs)) continue;
|
|
3784
4115
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
3785
4116
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
3786
|
-
const rel =
|
|
4117
|
+
const rel = path14.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
3787
4118
|
if (rel && !rel.startsWith("../")) {
|
|
3788
4119
|
return rel;
|
|
3789
4120
|
}
|
|
@@ -3824,8 +4155,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
3824
4155
|
}
|
|
3825
4156
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
3826
4157
|
if (!projectGitCwd) return null;
|
|
3827
|
-
const normalized =
|
|
3828
|
-
const marker = `${
|
|
4158
|
+
const normalized = path14.resolve(projectGitCwd);
|
|
4159
|
+
const marker = `${path14.sep}.worktrees${path14.sep}`;
|
|
3829
4160
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
3830
4161
|
if (markerIndex <= 0) return null;
|
|
3831
4162
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -3868,7 +4199,7 @@ function toTaskKey(rawTitle) {
|
|
|
3868
4199
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
3869
4200
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
3870
4201
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
3871
|
-
|
|
4202
|
+
path14.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
3872
4203
|
);
|
|
3873
4204
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
3874
4205
|
"diff",
|
|
@@ -3943,7 +4274,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
3943
4274
|
return { pass: true };
|
|
3944
4275
|
}
|
|
3945
4276
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
3946
|
-
const relativeDocsDir =
|
|
4277
|
+
const relativeDocsDir = path14.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
3947
4278
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
3948
4279
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
3949
4280
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -4325,9 +4656,9 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4325
4656
|
const isPrePrReviewCurrent = (f) => prePrReviewPolicy.enabled && workflowPolicy.requirePr && f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && !f.git.docsHasCommitRequiredChanges && !f.git.projectHasUncommittedChanges && (!isPrMetadataConfigured(f) || !f.pr.link) && !isPrePrReviewSatisfied(f, prePrReviewPolicy);
|
|
4326
4657
|
const isPrePrReviewMetadataMissing = (f) => isPrePrReviewCurrent(f) && !f.docs.prePrReviewFieldExists;
|
|
4327
4658
|
const isPrePrReviewFixRequired = (f) => isPrePrReviewCurrent(f) && !!f.prePrReview.decisionOutcome && f.prePrReview.decisionOutcome !== "approve";
|
|
4328
|
-
const isPrePrReviewRun = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status !== "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4329
|
-
const isPrePrReviewRunning = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status === "Running" && !resolvePrePrReviewEvidencePath(f)
|
|
4330
|
-
const isPrePrReviewRecord = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) &&
|
|
4659
|
+
const isPrePrReviewRun = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status !== "Running" && !resolvePrePrReviewEvidencePath(f);
|
|
4660
|
+
const isPrePrReviewRunning = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && f.prePrReview.status === "Running" && !resolvePrePrReviewEvidencePath(f);
|
|
4661
|
+
const isPrePrReviewRecord = (f) => isPrePrReviewCurrent(f) && f.docs.prePrReviewFieldExists && !isPrePrReviewFixRequired(f) && !!resolvePrePrReviewEvidencePath(f);
|
|
4331
4662
|
const getPrePrReviewMetadataActions = () => [
|
|
4332
4663
|
{
|
|
4333
4664
|
type: "instruction",
|
|
@@ -4371,7 +4702,7 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4371
4702
|
cmd: buildSelfCliCommand(buildPrePrReviewRunCommandArgs(f))
|
|
4372
4703
|
}
|
|
4373
4704
|
];
|
|
4374
|
-
const
|
|
4705
|
+
const getPrePrReviewInProgressActions = () => [
|
|
4375
4706
|
{
|
|
4376
4707
|
type: "instruction",
|
|
4377
4708
|
category: "pre_pr_review_run",
|
|
@@ -4909,15 +5240,15 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
|
4909
5240
|
actions: (f) => getPrePrReviewRunActions(f)
|
|
4910
5241
|
},
|
|
4911
5242
|
{
|
|
4912
|
-
id: "
|
|
5243
|
+
id: "pre_pr_review_in_progress",
|
|
4913
5244
|
phase: "running",
|
|
4914
5245
|
owner: "subagent",
|
|
4915
5246
|
category: "pre_pr_review_run",
|
|
4916
5247
|
when: (f) => isPrePrReviewRunning(f),
|
|
4917
|
-
actions: () =>
|
|
5248
|
+
actions: () => getPrePrReviewInProgressActions()
|
|
4918
5249
|
},
|
|
4919
5250
|
{
|
|
4920
|
-
id: "
|
|
5251
|
+
id: "pre_pr_review_record_pending",
|
|
4921
5252
|
phase: "record",
|
|
4922
5253
|
owner: "main",
|
|
4923
5254
|
category: "pre_pr_review_record",
|
|
@@ -5559,17 +5890,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
5559
5890
|
}
|
|
5560
5891
|
}
|
|
5561
5892
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
5562
|
-
var WORKTREE_MARKER = `${
|
|
5893
|
+
var WORKTREE_MARKER = `${path14.sep}.worktrees${path14.sep}`;
|
|
5563
5894
|
function resetContextGitCaches() {
|
|
5564
5895
|
GIT_WORKTREE_CACHE.clear();
|
|
5565
5896
|
}
|
|
5566
5897
|
function isManagedWorktreePath(cwd) {
|
|
5567
5898
|
if (!cwd) return false;
|
|
5568
|
-
const normalized =
|
|
5899
|
+
const normalized = path14.resolve(cwd);
|
|
5569
5900
|
return normalized.includes(WORKTREE_MARKER);
|
|
5570
5901
|
}
|
|
5571
5902
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
5572
|
-
const normalized =
|
|
5903
|
+
const normalized = path14.resolve(cwd);
|
|
5573
5904
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
5574
5905
|
if (markerIndex <= 0) return normalized;
|
|
5575
5906
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -5588,7 +5919,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
5588
5919
|
}
|
|
5589
5920
|
function listGitWorktrees(ctx, cwd) {
|
|
5590
5921
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
5591
|
-
const cacheKey =
|
|
5922
|
+
const cacheKey = path14.resolve(topLevel);
|
|
5592
5923
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
5593
5924
|
if (cached) return cached;
|
|
5594
5925
|
try {
|
|
@@ -5689,12 +6020,12 @@ function countDocumentLines(content) {
|
|
|
5689
6020
|
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
5690
6021
|
return lines.length;
|
|
5691
6022
|
}
|
|
5692
|
-
function
|
|
6023
|
+
function escapeRegExp3(value) {
|
|
5693
6024
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5694
6025
|
}
|
|
5695
6026
|
function extractSpecValue(content, key) {
|
|
5696
6027
|
const regex = new RegExp(
|
|
5697
|
-
`^\\s*-\\s*\\*\\*${
|
|
6028
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
5698
6029
|
"m"
|
|
5699
6030
|
);
|
|
5700
6031
|
const match = content.match(regex);
|
|
@@ -5702,7 +6033,7 @@ function extractSpecValue(content, key) {
|
|
|
5702
6033
|
}
|
|
5703
6034
|
function hasSpecKey(content, key) {
|
|
5704
6035
|
const regex = new RegExp(
|
|
5705
|
-
`^\\s*-\\s*\\*\\*${
|
|
6036
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:`,
|
|
5706
6037
|
"m"
|
|
5707
6038
|
);
|
|
5708
6039
|
return regex.test(content);
|
|
@@ -5894,7 +6225,7 @@ function splitReviewLogSections(content, headerRegex) {
|
|
|
5894
6225
|
}
|
|
5895
6226
|
function collectStructuredReviewEntries(section, keys) {
|
|
5896
6227
|
const lines = section.split("\n");
|
|
5897
|
-
const escaped = keys.map((key) =>
|
|
6228
|
+
const escaped = keys.map((key) => escapeRegExp3(key));
|
|
5898
6229
|
const fieldRegex = new RegExp(
|
|
5899
6230
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*(.*)$`,
|
|
5900
6231
|
"i"
|
|
@@ -6076,17 +6407,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6076
6407
|
if (!evidencePath) return [];
|
|
6077
6408
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6078
6409
|
const candidates = /* @__PURE__ */ new Set();
|
|
6079
|
-
if (
|
|
6080
|
-
candidates.add(
|
|
6410
|
+
if (path14.isAbsolute(evidencePath)) {
|
|
6411
|
+
candidates.add(path14.resolve(evidencePath));
|
|
6081
6412
|
} else {
|
|
6082
|
-
candidates.add(
|
|
6083
|
-
candidates.add(
|
|
6084
|
-
candidates.add(
|
|
6413
|
+
candidates.add(path14.resolve(context.featurePath, evidencePath));
|
|
6414
|
+
candidates.add(path14.resolve(context.docsDir, evidencePath));
|
|
6415
|
+
candidates.add(path14.resolve(path14.dirname(context.docsDir), evidencePath));
|
|
6085
6416
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6086
6417
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6087
6418
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6088
6419
|
if (withoutDocsPrefix) {
|
|
6089
|
-
candidates.add(
|
|
6420
|
+
candidates.add(path14.resolve(context.docsDir, withoutDocsPrefix));
|
|
6090
6421
|
}
|
|
6091
6422
|
}
|
|
6092
6423
|
}
|
|
@@ -6148,13 +6479,13 @@ function parsePrLink(value) {
|
|
|
6148
6479
|
return trimmed;
|
|
6149
6480
|
}
|
|
6150
6481
|
function normalizeGitPath(value) {
|
|
6151
|
-
return value.split(
|
|
6482
|
+
return value.split(path14.sep).join("/");
|
|
6152
6483
|
}
|
|
6153
6484
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6154
|
-
const relativeDocsDir =
|
|
6485
|
+
const relativeDocsDir = path14.relative(projectGitCwd, docsDir);
|
|
6155
6486
|
if (!relativeDocsDir) return [];
|
|
6156
|
-
if (
|
|
6157
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6487
|
+
if (path14.isAbsolute(relativeDocsDir)) return [];
|
|
6488
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path14.sep}`)) {
|
|
6158
6489
|
return [];
|
|
6159
6490
|
}
|
|
6160
6491
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6192,7 +6523,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6192
6523
|
const seen = /* @__PURE__ */ new Set();
|
|
6193
6524
|
const out = [];
|
|
6194
6525
|
for (const name of names) {
|
|
6195
|
-
const candidate =
|
|
6526
|
+
const candidate = path14.resolve(projectRoot, ".worktrees", name);
|
|
6196
6527
|
if (seen.has(candidate)) continue;
|
|
6197
6528
|
seen.add(candidate);
|
|
6198
6529
|
out.push(candidate);
|
|
@@ -6206,7 +6537,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6206
6537
|
slug,
|
|
6207
6538
|
folderName
|
|
6208
6539
|
)) {
|
|
6209
|
-
if (!
|
|
6540
|
+
if (!fs11.existsSync(candidate)) continue;
|
|
6210
6541
|
return candidate;
|
|
6211
6542
|
}
|
|
6212
6543
|
return void 0;
|
|
@@ -6236,7 +6567,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6236
6567
|
slug,
|
|
6237
6568
|
folderName
|
|
6238
6569
|
)) {
|
|
6239
|
-
if (!
|
|
6570
|
+
if (!fs11.existsSync(candidate)) continue;
|
|
6240
6571
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6241
6572
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6242
6573
|
return {
|
|
@@ -6377,10 +6708,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6377
6708
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6378
6709
|
candidates.map((candidate) => {
|
|
6379
6710
|
if (!candidate) return "";
|
|
6380
|
-
if (!
|
|
6381
|
-
const relative =
|
|
6711
|
+
if (!path14.isAbsolute(candidate)) return candidate;
|
|
6712
|
+
const relative = path14.relative(projectGitCwd, candidate);
|
|
6382
6713
|
if (!relative) return "";
|
|
6383
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
6714
|
+
if (relative === ".." || relative.startsWith(`..${path14.sep}`))
|
|
6384
6715
|
return "";
|
|
6385
6716
|
return relative;
|
|
6386
6717
|
}).filter(Boolean)
|
|
@@ -6394,7 +6725,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6394
6725
|
if (cached) return [...cached];
|
|
6395
6726
|
const existing = [];
|
|
6396
6727
|
for (const candidate of normalizedCandidates) {
|
|
6397
|
-
if (await ctx.fs.pathExists(
|
|
6728
|
+
if (await ctx.fs.pathExists(path14.join(projectGitCwd, candidate))) {
|
|
6398
6729
|
existing.push(candidate);
|
|
6399
6730
|
}
|
|
6400
6731
|
}
|
|
@@ -6482,16 +6813,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6482
6813
|
const lang = options.lang;
|
|
6483
6814
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
6484
6815
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
6485
|
-
const folderName =
|
|
6816
|
+
const folderName = path14.basename(featurePath);
|
|
6486
6817
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6487
6818
|
const id = match?.[1];
|
|
6488
6819
|
const slug = match?.[2] || folderName;
|
|
6489
|
-
const specPath =
|
|
6490
|
-
const planPath =
|
|
6491
|
-
const tasksPath =
|
|
6492
|
-
const decisionsPath =
|
|
6493
|
-
const issueDocPath =
|
|
6494
|
-
const prDocPath =
|
|
6820
|
+
const specPath = path14.join(featurePath, "spec.md");
|
|
6821
|
+
const planPath = path14.join(featurePath, "plan.md");
|
|
6822
|
+
const tasksPath = path14.join(featurePath, "tasks.md");
|
|
6823
|
+
const decisionsPath = path14.join(featurePath, "decisions.md");
|
|
6824
|
+
const issueDocPath = path14.join(featurePath, "issue.md");
|
|
6825
|
+
const prDocPath = path14.join(featurePath, "pr.md");
|
|
6495
6826
|
let specStatus;
|
|
6496
6827
|
let issueNumber;
|
|
6497
6828
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -6753,7 +7084,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6753
7084
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
6754
7085
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
6755
7086
|
}
|
|
6756
|
-
const relativeFeaturePathFromDocs =
|
|
7087
|
+
const relativeFeaturePathFromDocs = path14.relative(
|
|
6757
7088
|
context.docsDir,
|
|
6758
7089
|
featurePath
|
|
6759
7090
|
);
|
|
@@ -7116,7 +7447,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7116
7447
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7117
7448
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7118
7449
|
return dirs.filter(
|
|
7119
|
-
(value) =>
|
|
7450
|
+
(value) => path14.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7120
7451
|
);
|
|
7121
7452
|
}
|
|
7122
7453
|
function normalizeRelPath(value) {
|
|
@@ -7257,7 +7588,7 @@ async function scanFeatures(ctx) {
|
|
|
7257
7588
|
if (config.projectType === "single") {
|
|
7258
7589
|
const featureDirs = await listFeatureDirs(
|
|
7259
7590
|
ctx,
|
|
7260
|
-
|
|
7591
|
+
path14.join(config.docsDir, "features")
|
|
7261
7592
|
);
|
|
7262
7593
|
componentFeatureDirs.set("single", featureDirs);
|
|
7263
7594
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7269,14 +7600,14 @@ async function scanFeatures(ctx) {
|
|
|
7269
7600
|
for (const component of components) {
|
|
7270
7601
|
const componentDirs = await listFeatureDirs(
|
|
7271
7602
|
ctx,
|
|
7272
|
-
|
|
7603
|
+
path14.join(config.docsDir, "features", component)
|
|
7273
7604
|
);
|
|
7274
7605
|
componentFeatureDirs.set(component, componentDirs);
|
|
7275
7606
|
allFeatureDirs.push(...componentDirs);
|
|
7276
7607
|
}
|
|
7277
7608
|
}
|
|
7278
7609
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7279
|
-
(dir) => normalizeRelPath(
|
|
7610
|
+
(dir) => normalizeRelPath(path14.relative(config.docsDir, dir))
|
|
7280
7611
|
);
|
|
7281
7612
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7282
7613
|
ctx,
|
|
@@ -7293,7 +7624,7 @@ async function scanFeatures(ctx) {
|
|
|
7293
7624
|
const parsed = await Promise.all(
|
|
7294
7625
|
target.dirs.map(async (dir) => {
|
|
7295
7626
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7296
|
-
|
|
7627
|
+
path14.relative(config.docsDir, dir)
|
|
7297
7628
|
);
|
|
7298
7629
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7299
7630
|
return parseFeature(
|
|
@@ -7345,8 +7676,8 @@ function statusCommand(program2) {
|
|
|
7345
7676
|
const cliError = toCliError(error);
|
|
7346
7677
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7347
7678
|
console.error(
|
|
7348
|
-
|
|
7349
|
-
|
|
7679
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7680
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7350
7681
|
);
|
|
7351
7682
|
printCliErrorSuggestions(suggestions, lang);
|
|
7352
7683
|
process.exitCode = 1;
|
|
@@ -7363,13 +7694,13 @@ async function runStatus(options) {
|
|
|
7363
7694
|
);
|
|
7364
7695
|
}
|
|
7365
7696
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7366
|
-
const featuresDir =
|
|
7697
|
+
const featuresDir = path14.join(docsDir, "features");
|
|
7367
7698
|
const scan = await scanFeatures(ctx);
|
|
7368
7699
|
const features = [];
|
|
7369
7700
|
const idMap = /* @__PURE__ */ new Map();
|
|
7370
7701
|
for (const f of scan.features) {
|
|
7371
7702
|
const id = f.id || "UNKNOWN";
|
|
7372
|
-
const relPath =
|
|
7703
|
+
const relPath = path14.relative(docsDir, f.path);
|
|
7373
7704
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7374
7705
|
idMap.get(id).push(relPath);
|
|
7375
7706
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7443,7 +7774,7 @@ async function runStatus(options) {
|
|
|
7443
7774
|
return;
|
|
7444
7775
|
}
|
|
7445
7776
|
if (features.length === 0) {
|
|
7446
|
-
console.log(
|
|
7777
|
+
console.log(chalk9.yellow(tr(lang, "cli", "status.noFeatures")));
|
|
7447
7778
|
return;
|
|
7448
7779
|
}
|
|
7449
7780
|
features.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -7453,14 +7784,14 @@ async function runStatus(options) {
|
|
|
7453
7784
|
console.log(header);
|
|
7454
7785
|
console.log(separator);
|
|
7455
7786
|
for (const f of features) {
|
|
7456
|
-
const statusColor = f.status === "WORKFLOW_DONE" ?
|
|
7787
|
+
const statusColor = f.status === "WORKFLOW_DONE" ? chalk9.green : f.status === "DONE" ? chalk9.cyan : f.status === "DOING" ? chalk9.yellow : chalk9.gray;
|
|
7457
7788
|
console.log(
|
|
7458
7789
|
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.step} | ${f.substate} | ${f.path} |`
|
|
7459
7790
|
);
|
|
7460
7791
|
}
|
|
7461
7792
|
console.log();
|
|
7462
7793
|
if (options.write) {
|
|
7463
|
-
const outputPath =
|
|
7794
|
+
const outputPath = path14.join(featuresDir, "status.md");
|
|
7464
7795
|
const date = getLocalDateString();
|
|
7465
7796
|
const content = [
|
|
7466
7797
|
"# Feature Status",
|
|
@@ -7477,22 +7808,22 @@ async function runStatus(options) {
|
|
|
7477
7808
|
].join("\n");
|
|
7478
7809
|
await ctx.fs.writeFile(outputPath, content, "utf-8");
|
|
7479
7810
|
console.log(
|
|
7480
|
-
|
|
7811
|
+
chalk9.green(tr(lang, "cli", "status.wrote", { path: outputPath }))
|
|
7481
7812
|
);
|
|
7482
7813
|
}
|
|
7483
7814
|
}
|
|
7484
|
-
function
|
|
7815
|
+
function escapeRegExp4(value) {
|
|
7485
7816
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7486
7817
|
}
|
|
7487
7818
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7488
7819
|
try {
|
|
7489
|
-
const specPath =
|
|
7820
|
+
const specPath = path14.join(featureDir, "spec.md");
|
|
7490
7821
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7491
7822
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7492
7823
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
7493
7824
|
for (const key of keys) {
|
|
7494
7825
|
const regex = new RegExp(
|
|
7495
|
-
`^\\s*-\\s*\\*\\*${
|
|
7826
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
7496
7827
|
"m"
|
|
7497
7828
|
);
|
|
7498
7829
|
const match = content.match(regex);
|
|
@@ -7514,15 +7845,15 @@ function updateCommand(program2) {
|
|
|
7514
7845
|
const config = await getConfig(process.cwd());
|
|
7515
7846
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
7516
7847
|
if (error instanceof Error && error.message === "canceled") {
|
|
7517
|
-
console.log(
|
|
7848
|
+
console.log(chalk9.yellow(`
|
|
7518
7849
|
${tr(lang, "cli", "common.canceled")}`));
|
|
7519
7850
|
return;
|
|
7520
7851
|
}
|
|
7521
7852
|
const cliError = toCliError(error);
|
|
7522
7853
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7523
7854
|
console.error(
|
|
7524
|
-
|
|
7525
|
-
|
|
7855
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
7856
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7526
7857
|
);
|
|
7527
7858
|
printCliErrorSuggestions(suggestions, lang);
|
|
7528
7859
|
process.exitCode = 1;
|
|
@@ -7552,26 +7883,26 @@ async function runUpdate(options) {
|
|
|
7552
7883
|
const updateAgentsMd = options.agentsMd || !hasExplicitSelection;
|
|
7553
7884
|
const updateTemplates = options.templates || !hasExplicitSelection;
|
|
7554
7885
|
const agentsMode = options.skills && !options.agents ? "skills" : "all";
|
|
7555
|
-
console.log(
|
|
7556
|
-
console.log(
|
|
7886
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.start")));
|
|
7887
|
+
console.log(chalk9.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
|
|
7557
7888
|
console.log(
|
|
7558
|
-
|
|
7889
|
+
chalk9.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
|
|
7559
7890
|
);
|
|
7560
7891
|
console.log();
|
|
7561
7892
|
let updatedCount = 0;
|
|
7562
7893
|
if (updateAgents) {
|
|
7563
7894
|
if (agentsMode === "skills") {
|
|
7564
|
-
console.log(
|
|
7895
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingSkills")));
|
|
7565
7896
|
console.log(
|
|
7566
|
-
|
|
7897
|
+
chalk9.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
|
|
7567
7898
|
);
|
|
7568
|
-
console.log(
|
|
7899
|
+
console.log(chalk9.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
|
|
7569
7900
|
} else {
|
|
7570
|
-
console.log(
|
|
7901
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
7571
7902
|
}
|
|
7572
7903
|
if (agentsMode === "all") {
|
|
7573
|
-
const commonAgentsBase =
|
|
7574
|
-
const targetAgentsBase =
|
|
7904
|
+
const commonAgentsBase = path14.join(templatesDir, lang, "common", "agents");
|
|
7905
|
+
const targetAgentsBase = path14.join(docsDir, "agents");
|
|
7575
7906
|
const commonAgents = commonAgentsBase;
|
|
7576
7907
|
const targetAgents = targetAgentsBase;
|
|
7577
7908
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -7599,7 +7930,7 @@ async function runUpdate(options) {
|
|
|
7599
7930
|
updatedCount += count;
|
|
7600
7931
|
}
|
|
7601
7932
|
console.log(
|
|
7602
|
-
|
|
7933
|
+
chalk9.green(
|
|
7603
7934
|
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
7604
7935
|
)
|
|
7605
7936
|
);
|
|
@@ -7618,13 +7949,13 @@ async function runUpdate(options) {
|
|
|
7618
7949
|
}
|
|
7619
7950
|
}
|
|
7620
7951
|
if (updateTemplates) {
|
|
7621
|
-
console.log(
|
|
7622
|
-
console.log(
|
|
7952
|
+
console.log(chalk9.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
7953
|
+
console.log(chalk9.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
|
|
7623
7954
|
}
|
|
7624
7955
|
const pruned = await pruneEngineManagedDocs(docsDir);
|
|
7625
7956
|
if (pruned.length > 0) {
|
|
7626
7957
|
console.log(
|
|
7627
|
-
|
|
7958
|
+
chalk9.gray(
|
|
7628
7959
|
` - ${tr(lang, "cli", "update.engineManagedPruned", {
|
|
7629
7960
|
count: pruned.length
|
|
7630
7961
|
})}`
|
|
@@ -7634,18 +7965,18 @@ async function runUpdate(options) {
|
|
|
7634
7965
|
console.log();
|
|
7635
7966
|
if (configBackfill.changed) {
|
|
7636
7967
|
console.log(
|
|
7637
|
-
|
|
7968
|
+
chalk9.gray(
|
|
7638
7969
|
` - ${tr(lang, "cli", "update.fileUpdated", { file: ".lee-spec-kit.json" })}`
|
|
7639
7970
|
)
|
|
7640
7971
|
);
|
|
7641
7972
|
console.log(
|
|
7642
|
-
|
|
7973
|
+
chalk9.gray(
|
|
7643
7974
|
` (${configBackfill.changedPaths.join(", ")})`
|
|
7644
7975
|
)
|
|
7645
7976
|
);
|
|
7646
7977
|
}
|
|
7647
7978
|
console.log(
|
|
7648
|
-
|
|
7979
|
+
chalk9.green(`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`)
|
|
7649
7980
|
);
|
|
7650
7981
|
},
|
|
7651
7982
|
{ owner: "update" }
|
|
@@ -7669,21 +8000,21 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
7669
8000
|
const targets = /* @__PURE__ */ new Set();
|
|
7670
8001
|
const docsRepo = config.docsRepo ?? "embedded";
|
|
7671
8002
|
if (docsRepo === "embedded") {
|
|
7672
|
-
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) ||
|
|
7673
|
-
targets.add(
|
|
8003
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path14.resolve(config.docsDir, "..");
|
|
8004
|
+
targets.add(path14.join(repoRoot, "AGENTS.md"));
|
|
7674
8005
|
return [...targets];
|
|
7675
8006
|
}
|
|
7676
|
-
targets.add(
|
|
8007
|
+
targets.add(path14.join(config.docsDir, "AGENTS.md"));
|
|
7677
8008
|
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
7678
8009
|
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
7679
8010
|
for (const rawRoot of rawRoots) {
|
|
7680
8011
|
const value = String(rawRoot || "").trim();
|
|
7681
8012
|
if (!value) continue;
|
|
7682
|
-
const resolved =
|
|
8013
|
+
const resolved = path14.resolve(baseDir, value);
|
|
7683
8014
|
if (!await fs.pathExists(resolved)) continue;
|
|
7684
8015
|
const stat = await fs.stat(resolved);
|
|
7685
8016
|
if (!stat.isDirectory()) continue;
|
|
7686
|
-
targets.add(
|
|
8017
|
+
targets.add(path14.join(resolved, "AGENTS.md"));
|
|
7687
8018
|
}
|
|
7688
8019
|
return [...targets];
|
|
7689
8020
|
}
|
|
@@ -7723,7 +8054,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
7723
8054
|
return [...deduped];
|
|
7724
8055
|
}
|
|
7725
8056
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
7726
|
-
const configPath =
|
|
8057
|
+
const configPath = path14.join(docsDir, ".lee-spec-kit.json");
|
|
7727
8058
|
if (!await fs.pathExists(configPath)) {
|
|
7728
8059
|
return { changed: false, changedPaths: [] };
|
|
7729
8060
|
}
|
|
@@ -7846,8 +8177,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7846
8177
|
const files = await fs.readdir(sourceDir);
|
|
7847
8178
|
let updatedCount = 0;
|
|
7848
8179
|
for (const file of files) {
|
|
7849
|
-
const sourcePath =
|
|
7850
|
-
const targetPath =
|
|
8180
|
+
const sourcePath = path14.join(sourceDir, file);
|
|
8181
|
+
const targetPath = path14.join(targetDir, file);
|
|
7851
8182
|
const stat = await fs.stat(sourcePath);
|
|
7852
8183
|
if (stat.isFile()) {
|
|
7853
8184
|
if (protectedFiles.has(file)) {
|
|
@@ -7865,7 +8196,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7865
8196
|
}
|
|
7866
8197
|
if (!force) {
|
|
7867
8198
|
console.log(
|
|
7868
|
-
|
|
8199
|
+
chalk9.yellow(
|
|
7869
8200
|
` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
|
|
7870
8201
|
)
|
|
7871
8202
|
);
|
|
@@ -7875,7 +8206,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
7875
8206
|
if (shouldUpdate) {
|
|
7876
8207
|
await fs.writeFile(targetPath, sourceContent);
|
|
7877
8208
|
console.log(
|
|
7878
|
-
|
|
8209
|
+
chalk9.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
|
|
7879
8210
|
);
|
|
7880
8211
|
updatedCount++;
|
|
7881
8212
|
}
|
|
@@ -7930,7 +8261,7 @@ function extractPorcelainPaths(line) {
|
|
|
7930
8261
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
7931
8262
|
const top = getGitTopLevel2(docsDir);
|
|
7932
8263
|
if (!top) return null;
|
|
7933
|
-
const rel =
|
|
8264
|
+
const rel = path14.relative(top, docsDir) || ".";
|
|
7934
8265
|
try {
|
|
7935
8266
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
7936
8267
|
cwd: top,
|
|
@@ -7942,7 +8273,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
7942
8273
|
}
|
|
7943
8274
|
const ignoredRelPaths = new Set(
|
|
7944
8275
|
ignoredAbsPaths.map(
|
|
7945
|
-
(absPath) => normalizeGitPath2(
|
|
8276
|
+
(absPath) => normalizeGitPath2(path14.relative(top, absPath) || ".")
|
|
7946
8277
|
)
|
|
7947
8278
|
);
|
|
7948
8279
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -7980,7 +8311,7 @@ function configCommand(program2) {
|
|
|
7980
8311
|
if (error instanceof Error && error.message === "canceled") {
|
|
7981
8312
|
const config2 = await getConfig(process.cwd());
|
|
7982
8313
|
const lang2 = config2?.lang ?? DEFAULT_LANG;
|
|
7983
|
-
console.log(
|
|
8314
|
+
console.log(chalk9.yellow(`
|
|
7984
8315
|
${tr(lang2, "cli", "common.canceled")}`));
|
|
7985
8316
|
return;
|
|
7986
8317
|
}
|
|
@@ -7989,8 +8320,8 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
7989
8320
|
const cliError = toCliError(error);
|
|
7990
8321
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
7991
8322
|
console.error(
|
|
7992
|
-
|
|
7993
|
-
|
|
8323
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
8324
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
7994
8325
|
);
|
|
7995
8326
|
printCliErrorSuggestions(suggestions, lang);
|
|
7996
8327
|
process.exitCode = 1;
|
|
@@ -8000,7 +8331,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
8000
8331
|
}
|
|
8001
8332
|
async function runConfig(options) {
|
|
8002
8333
|
const cwd = process.cwd();
|
|
8003
|
-
const targetCwd = options.dir ?
|
|
8334
|
+
const targetCwd = options.dir ? path14.resolve(cwd, options.dir) : cwd;
|
|
8004
8335
|
const config = await getConfig(targetCwd);
|
|
8005
8336
|
if (!config) {
|
|
8006
8337
|
throw createCliError(
|
|
@@ -8008,13 +8339,13 @@ async function runConfig(options) {
|
|
|
8008
8339
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
8009
8340
|
);
|
|
8010
8341
|
}
|
|
8011
|
-
const configPath =
|
|
8342
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
8012
8343
|
if (!options.projectRoot) {
|
|
8013
8344
|
console.log();
|
|
8014
|
-
console.log(
|
|
8345
|
+
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
8015
8346
|
console.log();
|
|
8016
8347
|
console.log(
|
|
8017
|
-
|
|
8348
|
+
chalk9.gray(
|
|
8018
8349
|
` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
|
|
8019
8350
|
)
|
|
8020
8351
|
);
|
|
@@ -8031,7 +8362,7 @@ async function runConfig(options) {
|
|
|
8031
8362
|
const configFile = await fs.readJson(configPath);
|
|
8032
8363
|
if (configFile.docsRepo !== "standalone") {
|
|
8033
8364
|
console.log(
|
|
8034
|
-
|
|
8365
|
+
chalk9.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
|
|
8035
8366
|
);
|
|
8036
8367
|
return;
|
|
8037
8368
|
}
|
|
@@ -8081,7 +8412,7 @@ async function runConfig(options) {
|
|
|
8081
8412
|
currentRoot[targetComponent] = projectRoot;
|
|
8082
8413
|
configFile.projectRoot = currentRoot;
|
|
8083
8414
|
console.log(
|
|
8084
|
-
|
|
8415
|
+
chalk9.green(
|
|
8085
8416
|
tr(config.lang, "cli", "config.projectRootSet", {
|
|
8086
8417
|
repo: targetComponent.toUpperCase(),
|
|
8087
8418
|
path: projectRoot
|
|
@@ -8097,7 +8428,7 @@ async function runConfig(options) {
|
|
|
8097
8428
|
}
|
|
8098
8429
|
configFile.projectRoot = projectRoot;
|
|
8099
8430
|
console.log(
|
|
8100
|
-
|
|
8431
|
+
chalk9.green(
|
|
8101
8432
|
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
8102
8433
|
path: projectRoot
|
|
8103
8434
|
})
|
|
@@ -8114,47 +8445,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8114
8445
|
{
|
|
8115
8446
|
id: "agents",
|
|
8116
8447
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8117
|
-
relativePath: (_, lang) =>
|
|
8448
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "agents.md")
|
|
8118
8449
|
},
|
|
8119
8450
|
{
|
|
8120
8451
|
id: "git-workflow",
|
|
8121
8452
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8122
|
-
relativePath: (_, lang) =>
|
|
8453
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "git-workflow.md")
|
|
8123
8454
|
},
|
|
8124
8455
|
{
|
|
8125
8456
|
id: "issue-doc",
|
|
8126
8457
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8127
|
-
relativePath: (_, lang) =>
|
|
8458
|
+
relativePath: (_, lang) => path14.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8128
8459
|
},
|
|
8129
8460
|
{
|
|
8130
8461
|
id: "pr-doc",
|
|
8131
8462
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8132
|
-
relativePath: (_, lang) =>
|
|
8463
|
+
relativePath: (_, lang) => path14.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8133
8464
|
},
|
|
8134
8465
|
{
|
|
8135
8466
|
id: "create-feature",
|
|
8136
8467
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8137
|
-
relativePath: (_, lang) =>
|
|
8468
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8138
8469
|
},
|
|
8139
8470
|
{
|
|
8140
8471
|
id: "execute-task",
|
|
8141
8472
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8142
|
-
relativePath: (_, lang) =>
|
|
8473
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8143
8474
|
},
|
|
8144
8475
|
{
|
|
8145
8476
|
id: "create-issue",
|
|
8146
8477
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8147
|
-
relativePath: (_, lang) =>
|
|
8478
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8148
8479
|
},
|
|
8149
8480
|
{
|
|
8150
8481
|
id: "create-pr",
|
|
8151
8482
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8152
|
-
relativePath: (_, lang) =>
|
|
8483
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8153
8484
|
},
|
|
8154
8485
|
{
|
|
8155
8486
|
id: "split-feature",
|
|
8156
8487
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8157
|
-
relativePath: (_, lang) =>
|
|
8488
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8158
8489
|
}
|
|
8159
8490
|
];
|
|
8160
8491
|
var DOC_FOLLOWUPS = {
|
|
@@ -8251,7 +8582,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8251
8582
|
id: doc.id,
|
|
8252
8583
|
title: doc.title[lang],
|
|
8253
8584
|
relativePath,
|
|
8254
|
-
absolutePath:
|
|
8585
|
+
absolutePath: path14.join(templatesDir, relativePath)
|
|
8255
8586
|
};
|
|
8256
8587
|
});
|
|
8257
8588
|
}
|
|
@@ -8661,6 +8992,13 @@ async function resolveContextSelection(ctx, featureName, options) {
|
|
|
8661
8992
|
|
|
8662
8993
|
// src/services/ContextPresenter.ts
|
|
8663
8994
|
function getActionExecutionMetadata(action) {
|
|
8995
|
+
if (action.category === "task_execute" && action.taskExecutePhase === "start") {
|
|
8996
|
+
return {
|
|
8997
|
+
handoffOnly: true,
|
|
8998
|
+
advancesWorkflow: false,
|
|
8999
|
+
nextMainState: "task_complete"
|
|
9000
|
+
};
|
|
9001
|
+
}
|
|
8664
9002
|
if (action.category === "code_review_run") {
|
|
8665
9003
|
return {
|
|
8666
9004
|
handoffOnly: true,
|
|
@@ -8672,7 +9010,7 @@ function getActionExecutionMetadata(action) {
|
|
|
8672
9010
|
return {
|
|
8673
9011
|
handoffOnly: true,
|
|
8674
9012
|
advancesWorkflow: false,
|
|
8675
|
-
nextMainState: "
|
|
9013
|
+
nextMainState: "pre_pr_review_in_progress"
|
|
8676
9014
|
};
|
|
8677
9015
|
}
|
|
8678
9016
|
return null;
|
|
@@ -8745,6 +9083,7 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
|
|
|
8745
9083
|
pauseAndReportWhen: [
|
|
8746
9084
|
"approvalRequest.required=true",
|
|
8747
9085
|
"AUTO_GATE_REACHED",
|
|
9086
|
+
"AUTO_DELEGATED_HANDOFF",
|
|
8748
9087
|
"AUTO_MANUAL_REQUIRED",
|
|
8749
9088
|
"command execution error"
|
|
8750
9089
|
],
|
|
@@ -8830,7 +9169,7 @@ function buildDelegatedActionContract(state) {
|
|
|
8830
9169
|
const feature = state.matchedFeature;
|
|
8831
9170
|
const substateId = feature?.currentSubstateId;
|
|
8832
9171
|
if (!feature || !substateId) return null;
|
|
8833
|
-
if (substateId === "pre_pr_review_run" || substateId === "
|
|
9172
|
+
if (substateId === "pre_pr_review_run" || substateId === "pre_pr_review_in_progress") {
|
|
8834
9173
|
return {
|
|
8835
9174
|
required: true,
|
|
8836
9175
|
mode: "command",
|
|
@@ -8839,8 +9178,8 @@ function buildDelegatedActionContract(state) {
|
|
|
8839
9178
|
delegatedWorkRequired: true,
|
|
8840
9179
|
handoffOnly: true,
|
|
8841
9180
|
advancesWorkflow: false,
|
|
8842
|
-
doNotReapproveSameLabel: substateId === "
|
|
8843
|
-
nextMainState: "
|
|
9181
|
+
doNotReapproveSameLabel: substateId === "pre_pr_review_in_progress",
|
|
9182
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
8844
9183
|
reuseKey: `pre-pr:${feature.folderName}`,
|
|
8845
9184
|
evidenceFile: "review-trace.json",
|
|
8846
9185
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
@@ -8851,7 +9190,7 @@ function buildDelegatedActionContract(state) {
|
|
|
8851
9190
|
),
|
|
8852
9191
|
approve: buildPrePrRecordCommand(feature, "approve")
|
|
8853
9192
|
},
|
|
8854
|
-
guidance: substateId === "
|
|
9193
|
+
guidance: substateId === "pre_pr_review_in_progress" ? "A pre-PR review is already in progress. Reuse or resume the delegated review, generate structured review evidence, then record the result with pre-pr-review. Do not re-approve the same label." : "After approval, spawn_agent first and hand off the delegated pre-PR review. Handoff-only execution only prepares the review session; continue the delegated review immediately after approval."
|
|
8855
9194
|
};
|
|
8856
9195
|
}
|
|
8857
9196
|
if (substateId === "code_review_run" || substateId === "code_review_running") {
|
|
@@ -9083,12 +9422,12 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
9083
9422
|
if (suggestionOptions.length === 0) return;
|
|
9084
9423
|
const finalPrompt = buildSuggestionFinalPrompt(lang, suggestionOptions);
|
|
9085
9424
|
console.log(
|
|
9086
|
-
|
|
9425
|
+
chalk9.green(chalk9.bold(`\u{1F449} ${tr(lang, "cli", "context.suggestionHeader")}`))
|
|
9087
9426
|
);
|
|
9088
9427
|
suggestionOptions.forEach((option) => {
|
|
9089
9428
|
console.log(` ${option.label}: ${option.summary}`);
|
|
9090
9429
|
console.log(
|
|
9091
|
-
|
|
9430
|
+
chalk9.gray(
|
|
9092
9431
|
` \u21B3 ${tr(lang, "cli", "context.suggestionCommandHint", {
|
|
9093
9432
|
command: option.command
|
|
9094
9433
|
})}`
|
|
@@ -9096,7 +9435,7 @@ function printSuggestionOptions(lang, suggestionOptions) {
|
|
|
9096
9435
|
);
|
|
9097
9436
|
});
|
|
9098
9437
|
if (finalPrompt) {
|
|
9099
|
-
console.log(
|
|
9438
|
+
console.log(chalk9.cyan(` \u21B3 ${finalPrompt}`));
|
|
9100
9439
|
}
|
|
9101
9440
|
}
|
|
9102
9441
|
function buildRequiredDocHints(actionOptions) {
|
|
@@ -9288,7 +9627,7 @@ function getApprovalSessionId() {
|
|
|
9288
9627
|
function getApprovalTicketPaths(config) {
|
|
9289
9628
|
return {
|
|
9290
9629
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
9291
|
-
legacyPath:
|
|
9630
|
+
legacyPath: path14.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
9292
9631
|
};
|
|
9293
9632
|
}
|
|
9294
9633
|
async function loadApprovalTicketStore(storePath) {
|
|
@@ -9302,7 +9641,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9302
9641
|
}
|
|
9303
9642
|
}
|
|
9304
9643
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9305
|
-
await fs.ensureDir(
|
|
9644
|
+
await fs.ensureDir(path14.dirname(storePath));
|
|
9306
9645
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9307
9646
|
}
|
|
9308
9647
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -9488,6 +9827,14 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
9488
9827
|
return getProjectExecutionLockPath(action.cwd);
|
|
9489
9828
|
}
|
|
9490
9829
|
function buildApprovedHandoffMetadata(action, featureRef) {
|
|
9830
|
+
if (action.category === "task_execute" && action.taskExecutePhase === "start") {
|
|
9831
|
+
const taskId = action.cmd.match(/\b--task\s+([^\s]+)/)?.[1];
|
|
9832
|
+
return {
|
|
9833
|
+
delegatedWorkRequired: true,
|
|
9834
|
+
doNotReapproveSameLabel: true,
|
|
9835
|
+
reuseKey: taskId ? `task:${featureRef}:${taskId}` : `task:${featureRef}`
|
|
9836
|
+
};
|
|
9837
|
+
}
|
|
9491
9838
|
if (action.category === "pre_pr_review_run") {
|
|
9492
9839
|
return {
|
|
9493
9840
|
delegatedWorkRequired: true,
|
|
@@ -9633,10 +9980,10 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9633
9980
|
return;
|
|
9634
9981
|
}
|
|
9635
9982
|
console.log();
|
|
9636
|
-
console.log(
|
|
9637
|
-
console.log(
|
|
9983
|
+
console.log(chalk9.green(`\u2705 Approved option: ${parsedLabel}`));
|
|
9984
|
+
console.log(chalk9.gray(` - Action: ${freshSelected.detail}`));
|
|
9638
9985
|
if (userRequest) {
|
|
9639
|
-
console.log(
|
|
9986
|
+
console.log(chalk9.gray(` - User request: ${userRequest}`));
|
|
9640
9987
|
}
|
|
9641
9988
|
if (selectedAction.type === "command") {
|
|
9642
9989
|
const selectedComponent = selectionOptions.component || "";
|
|
@@ -9647,24 +9994,24 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9647
9994
|
`--ticket ${ticket.token}`
|
|
9648
9995
|
);
|
|
9649
9996
|
console.log(
|
|
9650
|
-
|
|
9997
|
+
chalk9.gray(
|
|
9651
9998
|
` - Ticket: ${ticket.token} (expires: ${ticket.expiresAt})`
|
|
9652
9999
|
)
|
|
9653
10000
|
);
|
|
9654
10001
|
} else {
|
|
9655
10002
|
executeCommand = executeCommand.replace(" [--ticket <TICKET>]", "");
|
|
9656
10003
|
}
|
|
9657
|
-
console.log(
|
|
10004
|
+
console.log(chalk9.gray(` - Run with: ${executeCommand}`));
|
|
9658
10005
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9659
10006
|
console.log(
|
|
9660
|
-
|
|
10007
|
+
chalk9.gray(
|
|
9661
10008
|
" - This command prepares a handoff only; complete the delegated work and update workflow evidence before re-running context."
|
|
9662
10009
|
)
|
|
9663
10010
|
);
|
|
9664
10011
|
}
|
|
9665
10012
|
} else {
|
|
9666
10013
|
console.log(
|
|
9667
|
-
|
|
10014
|
+
chalk9.gray(" - Instruction-only action (no command execution).")
|
|
9668
10015
|
);
|
|
9669
10016
|
}
|
|
9670
10017
|
console.log();
|
|
@@ -9715,19 +10062,19 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9715
10062
|
}
|
|
9716
10063
|
console.log();
|
|
9717
10064
|
console.log(
|
|
9718
|
-
|
|
10065
|
+
chalk9.yellow(`\u26A0\uFE0F Approved label ${parsedLabel} is instruction-only.`)
|
|
9719
10066
|
);
|
|
9720
10067
|
if (userRequest) {
|
|
9721
|
-
console.log(
|
|
10068
|
+
console.log(chalk9.gray(` User request: ${userRequest}`));
|
|
9722
10069
|
}
|
|
9723
|
-
console.log(
|
|
10070
|
+
console.log(chalk9.gray(` ${selectedAction.message}`));
|
|
9724
10071
|
console.log();
|
|
9725
10072
|
return;
|
|
9726
10073
|
}
|
|
9727
10074
|
if (!jsonMode) {
|
|
9728
10075
|
console.log();
|
|
9729
|
-
console.log(
|
|
9730
|
-
console.log(
|
|
10076
|
+
console.log(chalk9.blue(`\u25B6 Executing option ${parsedLabel}...`));
|
|
10077
|
+
console.log(chalk9.gray(` ${selectedAction.cmd}`));
|
|
9731
10078
|
console.log();
|
|
9732
10079
|
}
|
|
9733
10080
|
try {
|
|
@@ -9789,14 +10136,14 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
9789
10136
|
}
|
|
9790
10137
|
if (executionMetadata?.handoffOnly && !executionMetadata.advancesWorkflow) {
|
|
9791
10138
|
console.log(
|
|
9792
|
-
|
|
10139
|
+
chalk9.yellow(
|
|
9793
10140
|
"Prepared handoff only. Complete the delegated work and update workflow evidence before re-running context."
|
|
9794
10141
|
)
|
|
9795
10142
|
);
|
|
9796
10143
|
console.log();
|
|
9797
10144
|
return;
|
|
9798
10145
|
}
|
|
9799
|
-
console.log(
|
|
10146
|
+
console.log(chalk9.green(`\u2705 Executed option ${parsedLabel}.`));
|
|
9800
10147
|
console.log();
|
|
9801
10148
|
} catch (error) {
|
|
9802
10149
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -9844,8 +10191,8 @@ function contextCommand(program2) {
|
|
|
9844
10191
|
);
|
|
9845
10192
|
} else {
|
|
9846
10193
|
console.error(
|
|
9847
|
-
|
|
9848
|
-
|
|
10194
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
10195
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
9849
10196
|
);
|
|
9850
10197
|
printCliErrorSuggestions(suggestions, lang);
|
|
9851
10198
|
}
|
|
@@ -10155,7 +10502,7 @@ async function runContext(featureName, options) {
|
|
|
10155
10502
|
untilCategories: autoRunPlan.untilCategories,
|
|
10156
10503
|
unknownCategories: autoRunPlan.unknownCategories,
|
|
10157
10504
|
manualBoundary: autoRunPlan.manualBoundary,
|
|
10158
|
-
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
|
|
10505
|
+
guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true`, when auto mode reaches configured gate categories, or when a delegated handoff pause must be resumed.'
|
|
10159
10506
|
},
|
|
10160
10507
|
approvalRequest: {
|
|
10161
10508
|
guidance: approvalGuidance.replace(
|
|
@@ -10214,11 +10561,11 @@ async function runContext(featureName, options) {
|
|
|
10214
10561
|
return;
|
|
10215
10562
|
}
|
|
10216
10563
|
console.log();
|
|
10217
|
-
console.log(
|
|
10564
|
+
console.log(chalk9.bold(tr(lang, "cli", "context.header")));
|
|
10218
10565
|
if (config.projectType === "single") {
|
|
10219
10566
|
if (state.branches.project.single) {
|
|
10220
10567
|
console.log(
|
|
10221
|
-
|
|
10568
|
+
chalk9.gray(
|
|
10222
10569
|
` (Detected from Project Branch: ${state.branches.project.single})`
|
|
10223
10570
|
)
|
|
10224
10571
|
);
|
|
@@ -10227,7 +10574,7 @@ async function runContext(featureName, options) {
|
|
|
10227
10574
|
const branchName = state.branches.project[selectedComponent] || "";
|
|
10228
10575
|
if (branchName) {
|
|
10229
10576
|
console.log(
|
|
10230
|
-
|
|
10577
|
+
chalk9.gray(
|
|
10231
10578
|
` (Detected from Project Branch: ${selectedComponent.toUpperCase()} ${branchName})`
|
|
10232
10579
|
)
|
|
10233
10580
|
);
|
|
@@ -10236,37 +10583,37 @@ async function runContext(featureName, options) {
|
|
|
10236
10583
|
const parts = Object.entries(state.branches.project).filter(([key, value]) => key !== "single" && !!value).map(([key, value]) => `${key.toUpperCase()} ${value}`);
|
|
10237
10584
|
if (parts.length > 0) {
|
|
10238
10585
|
console.log(
|
|
10239
|
-
|
|
10586
|
+
chalk9.gray(` (Detected from Project Branch: ${parts.join(" / ")})`)
|
|
10240
10587
|
);
|
|
10241
10588
|
}
|
|
10242
10589
|
}
|
|
10243
10590
|
if (config.docsRepo === "standalone" && state.branches.docs) {
|
|
10244
|
-
console.log(
|
|
10591
|
+
console.log(chalk9.gray(` (Docs Branch: ${state.branches.docs})`));
|
|
10245
10592
|
}
|
|
10246
|
-
console.log(
|
|
10593
|
+
console.log(chalk9.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
10247
10594
|
console.log();
|
|
10248
10595
|
if (state.features.length === 0) {
|
|
10249
|
-
console.log(
|
|
10596
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10250
10597
|
console.log();
|
|
10251
10598
|
printSuggestionOptions(lang, suggestionOptions);
|
|
10252
10599
|
console.log();
|
|
10253
10600
|
return;
|
|
10254
10601
|
}
|
|
10255
10602
|
if (state.warnings.length > 0) {
|
|
10256
|
-
console.log(
|
|
10257
|
-
state.warnings.forEach((w) => console.log(
|
|
10603
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.envWarnings")));
|
|
10604
|
+
state.warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
10258
10605
|
console.log();
|
|
10259
10606
|
}
|
|
10260
10607
|
if (state.targetFeatures.length === 0) {
|
|
10261
|
-
console.log(
|
|
10608
|
+
console.log(chalk9.yellow(tr(lang, "cli", "context.noActiveFeatures")));
|
|
10262
10609
|
if (state.status === "no_open") {
|
|
10263
10610
|
console.log(
|
|
10264
|
-
|
|
10611
|
+
chalk9.gray(
|
|
10265
10612
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10266
10613
|
)
|
|
10267
10614
|
);
|
|
10268
10615
|
console.log(
|
|
10269
|
-
|
|
10616
|
+
chalk9.gray(
|
|
10270
10617
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10271
10618
|
)
|
|
10272
10619
|
);
|
|
@@ -10279,7 +10626,7 @@ async function runContext(featureName, options) {
|
|
|
10279
10626
|
if (state.targetFeatures.length > 1) {
|
|
10280
10627
|
if (state.selectionMode === "open") {
|
|
10281
10628
|
console.log(
|
|
10282
|
-
|
|
10629
|
+
chalk9.gray(
|
|
10283
10630
|
` ${tr(lang, "cli", "context.openFallbackSummary", {
|
|
10284
10631
|
inProgress: state.inProgressFeatures.length,
|
|
10285
10632
|
readyToClose: state.readyToCloseFeatures.length,
|
|
@@ -10291,7 +10638,7 @@ async function runContext(featureName, options) {
|
|
|
10291
10638
|
}
|
|
10292
10639
|
if (state.selectionMode === "open") {
|
|
10293
10640
|
console.log(
|
|
10294
|
-
|
|
10641
|
+
chalk9.blue(
|
|
10295
10642
|
`\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${state.inProgressFeatures.length})`
|
|
10296
10643
|
)
|
|
10297
10644
|
);
|
|
@@ -10303,14 +10650,14 @@ async function runContext(featureName, options) {
|
|
|
10303
10650
|
workflowPolicy,
|
|
10304
10651
|
prePrReviewPolicy
|
|
10305
10652
|
);
|
|
10306
|
-
const typeStr = config.projectType === "multi" ?
|
|
10653
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10307
10654
|
console.log(
|
|
10308
|
-
` \u2022 ${
|
|
10655
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10309
10656
|
);
|
|
10310
10657
|
});
|
|
10311
10658
|
console.log();
|
|
10312
10659
|
console.log(
|
|
10313
|
-
|
|
10660
|
+
chalk9.blue(
|
|
10314
10661
|
`\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${state.readyToCloseFeatures.length})`
|
|
10315
10662
|
)
|
|
10316
10663
|
);
|
|
@@ -10322,14 +10669,14 @@ async function runContext(featureName, options) {
|
|
|
10322
10669
|
workflowPolicy,
|
|
10323
10670
|
prePrReviewPolicy
|
|
10324
10671
|
);
|
|
10325
|
-
const typeStr = config.projectType === "multi" ?
|
|
10672
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10326
10673
|
console.log(
|
|
10327
|
-
` \u2022 ${
|
|
10674
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10328
10675
|
);
|
|
10329
10676
|
});
|
|
10330
10677
|
} else {
|
|
10331
10678
|
const title = state.selectionMode === "all" ? `\u{1F539} ${state.targetFeatures.length} Features:` : state.selectionMode === "done" ? `\u{1F539} ${state.targetFeatures.length} Done Features:` : `\u{1F539} ${state.targetFeatures.length} Features Detected:`;
|
|
10332
|
-
console.log(
|
|
10679
|
+
console.log(chalk9.blue(title));
|
|
10333
10680
|
console.log();
|
|
10334
10681
|
state.targetFeatures.forEach((f2) => {
|
|
10335
10682
|
const stepName2 = getListLabel(
|
|
@@ -10339,24 +10686,24 @@ async function runContext(featureName, options) {
|
|
|
10339
10686
|
workflowPolicy,
|
|
10340
10687
|
prePrReviewPolicy
|
|
10341
10688
|
);
|
|
10342
|
-
const typeStr = config.projectType === "multi" ?
|
|
10689
|
+
const typeStr = config.projectType === "multi" ? chalk9.cyan(`(${f2.type})`) : "";
|
|
10343
10690
|
console.log(
|
|
10344
|
-
` \u2022 ${
|
|
10691
|
+
` \u2022 ${chalk9.bold(f2.folderName)} ${typeStr} - ${chalk9.yellow(stepName2)}`
|
|
10345
10692
|
);
|
|
10346
10693
|
});
|
|
10347
10694
|
}
|
|
10348
10695
|
console.log();
|
|
10349
|
-
console.log(
|
|
10696
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.tipDetails")));
|
|
10350
10697
|
const selectorTip = config.projectType === "multi" ? selectedComponent ? ` $ npx lee-spec-kit context <slug|F001|F001-slug> --component ${selectedComponent}` : " $ npx lee-spec-kit context <slug|F001|F001-slug> [--component <component>]" : " $ npx lee-spec-kit context <slug|F001|F001-slug>";
|
|
10351
|
-
console.log(
|
|
10698
|
+
console.log(chalk9.gray(selectorTip));
|
|
10352
10699
|
if (state.selectionMode === "open") {
|
|
10353
10700
|
console.log(
|
|
10354
|
-
|
|
10701
|
+
chalk9.gray(
|
|
10355
10702
|
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
10356
10703
|
)
|
|
10357
10704
|
);
|
|
10358
10705
|
console.log(
|
|
10359
|
-
|
|
10706
|
+
chalk9.gray(
|
|
10360
10707
|
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
10361
10708
|
)
|
|
10362
10709
|
);
|
|
@@ -10368,47 +10715,47 @@ async function runContext(featureName, options) {
|
|
|
10368
10715
|
}
|
|
10369
10716
|
const f = state.targetFeatures[0];
|
|
10370
10717
|
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
10371
|
-
const checkTag = (requiresUserCheck) => requiresUserCheck ?
|
|
10718
|
+
const checkTag = (requiresUserCheck) => requiresUserCheck ? chalk9.yellow(tr(lang, "cli", "context.checkRequired")) : "";
|
|
10372
10719
|
const hasCheckAction = approvalRequired;
|
|
10373
10720
|
console.log(
|
|
10374
|
-
`\u{1F539} Feature: ${
|
|
10721
|
+
`\u{1F539} Feature: ${chalk9.bold(f.folderName)} ${config.projectType === "multi" ? chalk9.cyan(`(${f.type})`) : ""}`
|
|
10375
10722
|
);
|
|
10376
10723
|
console.log(
|
|
10377
|
-
` \u2022 Completion: ${f.completion.implementationDone ?
|
|
10724
|
+
` \u2022 Completion: ${f.completion.implementationDone ? chalk9.green("Implementation \u2705") : chalk9.gray("Implementation \u25EF")} / ${f.completion.workflowDone ? chalk9.green("Workflow \u2705") : chalk9.yellow("Workflow \u25EF")}`
|
|
10378
10725
|
);
|
|
10379
10726
|
if (f.issueNumber) {
|
|
10380
10727
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10381
10728
|
}
|
|
10382
|
-
console.log(` \u2022 Path: ${
|
|
10729
|
+
console.log(` \u2022 Path: ${path14.relative(cwd, f.path)}`);
|
|
10383
10730
|
if (f.git.projectBranch) {
|
|
10384
10731
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10385
10732
|
}
|
|
10386
10733
|
console.log();
|
|
10387
10734
|
console.log(
|
|
10388
|
-
`\u{1F539} Progress: ${
|
|
10735
|
+
`\u{1F539} Progress: ${chalk9.yellow(`Step ${f.currentStep}. ${stepName}`)}`
|
|
10389
10736
|
);
|
|
10390
10737
|
if (f.activeTask) {
|
|
10391
10738
|
console.log(
|
|
10392
|
-
` \u2022 Active Task: ${
|
|
10739
|
+
` \u2022 Active Task: ${chalk9.yellow(`[${f.activeTask.status}]`)} ${f.activeTask.title}`
|
|
10393
10740
|
);
|
|
10394
10741
|
} else if (f.nextTodoTask && f.currentStep === 10) {
|
|
10395
10742
|
console.log(
|
|
10396
|
-
` \u2022 Next TODO: ${
|
|
10743
|
+
` \u2022 Next TODO: ${chalk9.gray(`[${f.nextTodoTask.status}]`)} ${f.nextTodoTask.title}`
|
|
10397
10744
|
);
|
|
10398
10745
|
}
|
|
10399
10746
|
printChecklist(f, stepDefinitions);
|
|
10400
10747
|
if (f.warnings.length > 0) {
|
|
10401
10748
|
console.log();
|
|
10402
|
-
console.log(
|
|
10403
|
-
f.warnings.forEach((w) => console.log(
|
|
10749
|
+
console.log(chalk9.yellow("\u26A0\uFE0F Feature Warnings:"));
|
|
10750
|
+
f.warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
10404
10751
|
}
|
|
10405
10752
|
console.log();
|
|
10406
|
-
console.log(
|
|
10753
|
+
console.log(chalk9.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
10407
10754
|
if (hasCheckAction) {
|
|
10408
|
-
console.log(
|
|
10755
|
+
console.log(chalk9.gray(tr(lang, "cli", "context.checkPolicyHint")));
|
|
10409
10756
|
}
|
|
10410
10757
|
if (!f.actions || f.actions.length === 0) {
|
|
10411
|
-
console.log(`\u{1F449} Next Action: ${
|
|
10758
|
+
console.log(`\u{1F449} Next Action: ${chalk9.green(chalk9.bold(f.nextAction))}`);
|
|
10412
10759
|
console.log();
|
|
10413
10760
|
return;
|
|
10414
10761
|
}
|
|
@@ -10421,7 +10768,7 @@ async function runContext(featureName, options) {
|
|
|
10421
10768
|
f.currentSubstateOwner
|
|
10422
10769
|
);
|
|
10423
10770
|
const showOptionLabels = hasCheckAction;
|
|
10424
|
-
console.log(
|
|
10771
|
+
console.log(chalk9.green(chalk9.bold("\u{1F449} Next Options (Atomic):")));
|
|
10425
10772
|
let hasDocsCommand = false;
|
|
10426
10773
|
actionOptions.forEach((option) => {
|
|
10427
10774
|
const requiresCheck = option.action.requiresUserCheck;
|
|
@@ -10434,26 +10781,26 @@ async function runContext(featureName, options) {
|
|
|
10434
10781
|
});
|
|
10435
10782
|
if (hasDocsCommand) {
|
|
10436
10783
|
console.log(
|
|
10437
|
-
|
|
10784
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.tipDocsCommitRules")}`)
|
|
10438
10785
|
);
|
|
10439
10786
|
}
|
|
10440
10787
|
if (hasCommandOption && longRunningDelegation.shouldDelegate) {
|
|
10441
10788
|
console.log(
|
|
10442
|
-
|
|
10789
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.subAgentOrchestrationHint")}`)
|
|
10443
10790
|
);
|
|
10444
10791
|
}
|
|
10445
10792
|
if (hasCheckAction) {
|
|
10446
10793
|
console.log(
|
|
10447
|
-
|
|
10794
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionOptionHint")}`)
|
|
10448
10795
|
);
|
|
10449
10796
|
console.log(
|
|
10450
|
-
|
|
10797
|
+
chalk9.gray(` \u21B3 ${tr(lang, "cli", "context.actionExplainHint")}`)
|
|
10451
10798
|
);
|
|
10452
10799
|
}
|
|
10453
10800
|
if (requiredDocs.length > 0) {
|
|
10454
10801
|
for (const requiredDoc of requiredDocs) {
|
|
10455
10802
|
console.log(
|
|
10456
|
-
|
|
10803
|
+
chalk9.gray(
|
|
10457
10804
|
` \u21B3 ${tr(lang, "cli", "context.readBuiltinDocFirst", {
|
|
10458
10805
|
command: requiredDoc.command
|
|
10459
10806
|
})}`
|
|
@@ -10462,9 +10809,9 @@ async function runContext(featureName, options) {
|
|
|
10462
10809
|
}
|
|
10463
10810
|
}
|
|
10464
10811
|
if (autoRunPlan.available) {
|
|
10465
|
-
console.log(
|
|
10812
|
+
console.log(chalk9.gray(` \u21B3 ${autoRunPlan.summary}`));
|
|
10466
10813
|
console.log(
|
|
10467
|
-
|
|
10814
|
+
chalk9.gray(
|
|
10468
10815
|
` \u21B3 ${tr(lang, "cli", "context.autoRunCommandHint", {
|
|
10469
10816
|
command: autoRunPlan.command
|
|
10470
10817
|
})}`
|
|
@@ -10484,16 +10831,16 @@ async function runContext(featureName, options) {
|
|
|
10484
10831
|
selectedComponent,
|
|
10485
10832
|
true
|
|
10486
10833
|
);
|
|
10487
|
-
console.log(
|
|
10834
|
+
console.log(chalk9.cyan(` \u21B3 ${finalApprovalPrompt}`));
|
|
10488
10835
|
console.log(
|
|
10489
|
-
|
|
10836
|
+
chalk9.gray(
|
|
10490
10837
|
` \u21B3 ${tr(lang, "cli", "context.finalLabelCommandHint", {
|
|
10491
10838
|
command: approveCommand
|
|
10492
10839
|
})}`
|
|
10493
10840
|
)
|
|
10494
10841
|
);
|
|
10495
10842
|
console.log(
|
|
10496
|
-
|
|
10843
|
+
chalk9.gray(
|
|
10497
10844
|
` \u21B3 ${tr(lang, "cli", "context.finalTicketCommandHint", {
|
|
10498
10845
|
command: executeCommand
|
|
10499
10846
|
})}`
|
|
@@ -10507,8 +10854,8 @@ function printChecklist(f, stepDefinitions) {
|
|
|
10507
10854
|
checklistSteps.forEach((definition) => {
|
|
10508
10855
|
const done = definition.checklist.done(f);
|
|
10509
10856
|
const detail = definition.checklist.detail?.(f) ?? "";
|
|
10510
|
-
const mark = done ?
|
|
10511
|
-
const label = definition.step === f.currentStep ?
|
|
10857
|
+
const mark = done ? chalk9.green("\u2705") : chalk9.gray("\u25EF");
|
|
10858
|
+
const label = definition.step === f.currentStep ? chalk9.bold(definition.name) : definition.name;
|
|
10512
10859
|
console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
|
|
10513
10860
|
});
|
|
10514
10861
|
}
|
|
@@ -10531,7 +10878,7 @@ function extractTitleAfterId(line, id) {
|
|
|
10531
10878
|
return cleaned ? cleaned : void 0;
|
|
10532
10879
|
}
|
|
10533
10880
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
10534
|
-
const prdDir =
|
|
10881
|
+
const prdDir = path14.join(docsDir, "prd");
|
|
10535
10882
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
10536
10883
|
extensions: [".md"],
|
|
10537
10884
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -10539,7 +10886,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10539
10886
|
const definitions = /* @__PURE__ */ new Map();
|
|
10540
10887
|
const duplicates = [];
|
|
10541
10888
|
for (const filePath of files) {
|
|
10542
|
-
if (
|
|
10889
|
+
if (path14.basename(filePath).toLowerCase() === "readme.md") {
|
|
10543
10890
|
continue;
|
|
10544
10891
|
}
|
|
10545
10892
|
let content = "";
|
|
@@ -10548,7 +10895,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10548
10895
|
} catch {
|
|
10549
10896
|
continue;
|
|
10550
10897
|
}
|
|
10551
|
-
const relFile = normalizeRelPath2(
|
|
10898
|
+
const relFile = normalizeRelPath2(path14.relative(docsDir, filePath));
|
|
10552
10899
|
const lines = content.split(/\r?\n/);
|
|
10553
10900
|
let inCodeBlock = false;
|
|
10554
10901
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -10623,7 +10970,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
10623
10970
|
]);
|
|
10624
10971
|
function formatPath(cwd, p) {
|
|
10625
10972
|
if (!p) return "";
|
|
10626
|
-
return
|
|
10973
|
+
return path14.isAbsolute(p) ? path14.relative(cwd, p) : p;
|
|
10627
10974
|
}
|
|
10628
10975
|
function detectPlaceholders(content) {
|
|
10629
10976
|
const patterns = [
|
|
@@ -10782,7 +11129,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10782
11129
|
const placeholderContext = {
|
|
10783
11130
|
projectName: config.projectName,
|
|
10784
11131
|
featureName: f.slug,
|
|
10785
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11132
|
+
featurePath: f.docs.featurePathFromDocs || path14.relative(config.docsDir, f.path),
|
|
10786
11133
|
repoType: f.type,
|
|
10787
11134
|
featureNumber
|
|
10788
11135
|
};
|
|
@@ -10792,7 +11139,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
10792
11139
|
"tasks.md"
|
|
10793
11140
|
];
|
|
10794
11141
|
for (const file of files) {
|
|
10795
|
-
const fullPath =
|
|
11142
|
+
const fullPath = path14.join(f.path, file);
|
|
10796
11143
|
if (!await fs.pathExists(fullPath)) continue;
|
|
10797
11144
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
10798
11145
|
let next = original;
|
|
@@ -10845,7 +11192,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10845
11192
|
const issues = [];
|
|
10846
11193
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
10847
11194
|
for (const dir of requiredDirs) {
|
|
10848
|
-
const p =
|
|
11195
|
+
const p = path14.join(config.docsDir, dir);
|
|
10849
11196
|
if (!await fs.pathExists(p)) {
|
|
10850
11197
|
issues.push({
|
|
10851
11198
|
level: "error",
|
|
@@ -10857,7 +11204,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
10857
11204
|
});
|
|
10858
11205
|
}
|
|
10859
11206
|
}
|
|
10860
|
-
const configPath =
|
|
11207
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
10861
11208
|
if (!await fs.pathExists(configPath)) {
|
|
10862
11209
|
issues.push({
|
|
10863
11210
|
level: "warn",
|
|
@@ -10885,7 +11232,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10885
11232
|
}
|
|
10886
11233
|
const idMap = /* @__PURE__ */ new Map();
|
|
10887
11234
|
for (const f of features) {
|
|
10888
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11235
|
+
const rel = f.docs.featurePathFromDocs || path14.relative(config.docsDir, f.path);
|
|
10889
11236
|
const id = f.id || "UNKNOWN";
|
|
10890
11237
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
10891
11238
|
idMap.get(id).push(rel);
|
|
@@ -10893,7 +11240,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10893
11240
|
if (!isInitialTemplateState) {
|
|
10894
11241
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
10895
11242
|
for (const file of featureDocs) {
|
|
10896
|
-
const p =
|
|
11243
|
+
const p = path14.join(f.path, file);
|
|
10897
11244
|
if (!await fs.pathExists(p)) continue;
|
|
10898
11245
|
const content = await fs.readFile(p, "utf-8");
|
|
10899
11246
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10908,7 +11255,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10908
11255
|
});
|
|
10909
11256
|
}
|
|
10910
11257
|
if (decisionsPlaceholderMode !== "off") {
|
|
10911
|
-
const decisionsPath =
|
|
11258
|
+
const decisionsPath = path14.join(f.path, "decisions.md");
|
|
10912
11259
|
if (await fs.pathExists(decisionsPath)) {
|
|
10913
11260
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
10914
11261
|
const placeholders = detectPlaceholders(content);
|
|
@@ -10937,7 +11284,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10937
11284
|
level: "warn",
|
|
10938
11285
|
code: "spec_status_unset",
|
|
10939
11286
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
10940
|
-
path: formatPath(cwd,
|
|
11287
|
+
path: formatPath(cwd, path14.join(f.path, "spec.md"))
|
|
10941
11288
|
});
|
|
10942
11289
|
}
|
|
10943
11290
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -10945,7 +11292,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10945
11292
|
level: "warn",
|
|
10946
11293
|
code: "plan_status_unset",
|
|
10947
11294
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
10948
|
-
path: formatPath(cwd,
|
|
11295
|
+
path: formatPath(cwd, path14.join(f.path, "plan.md"))
|
|
10949
11296
|
});
|
|
10950
11297
|
}
|
|
10951
11298
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -10953,11 +11300,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10953
11300
|
level: "warn",
|
|
10954
11301
|
code: "tasks_empty",
|
|
10955
11302
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
10956
|
-
path: formatPath(cwd,
|
|
11303
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
10957
11304
|
});
|
|
10958
11305
|
}
|
|
10959
11306
|
if (f.docs.tasksExists) {
|
|
10960
|
-
const tasksPath =
|
|
11307
|
+
const tasksPath = path14.join(f.path, "tasks.md");
|
|
10961
11308
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
10962
11309
|
const unknownPrdTags = [...new Set(
|
|
10963
11310
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -10979,7 +11326,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10979
11326
|
level: "warn",
|
|
10980
11327
|
code: "tasks_doc_status_missing",
|
|
10981
11328
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
10982
|
-
path: formatPath(cwd,
|
|
11329
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
10983
11330
|
});
|
|
10984
11331
|
}
|
|
10985
11332
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -10987,7 +11334,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
10987
11334
|
level: "warn",
|
|
10988
11335
|
code: "tasks_doc_status_unset",
|
|
10989
11336
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
10990
|
-
path: formatPath(cwd,
|
|
11337
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
10991
11338
|
});
|
|
10992
11339
|
}
|
|
10993
11340
|
}
|
|
@@ -11011,7 +11358,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11011
11358
|
level: "warn",
|
|
11012
11359
|
code: "missing_feature_id",
|
|
11013
11360
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
11014
|
-
path: formatPath(cwd,
|
|
11361
|
+
path: formatPath(cwd, path14.join(config.docsDir, p))
|
|
11015
11362
|
});
|
|
11016
11363
|
}
|
|
11017
11364
|
return issues;
|
|
@@ -11141,25 +11488,25 @@ function doctorCommand(program2) {
|
|
|
11141
11488
|
return;
|
|
11142
11489
|
}
|
|
11143
11490
|
console.log();
|
|
11144
|
-
console.log(
|
|
11145
|
-
console.log(
|
|
11146
|
-
console.log(
|
|
11147
|
-
console.log(
|
|
11491
|
+
console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
|
|
11492
|
+
console.log(chalk9.gray(`- Docs: ${path14.relative(cwd, docsDir)}`));
|
|
11493
|
+
console.log(chalk9.gray(`- Type: ${projectType}`));
|
|
11494
|
+
console.log(chalk9.gray(`- Lang: ${lang}`));
|
|
11148
11495
|
console.log();
|
|
11149
11496
|
if (warnings.length > 0) {
|
|
11150
|
-
console.log(
|
|
11151
|
-
warnings.forEach((w) => console.log(
|
|
11497
|
+
console.log(chalk9.yellow(tr(lang, "cli", "doctor.envWarnings")));
|
|
11498
|
+
warnings.forEach((w) => console.log(chalk9.yellow(` - ${w}`)));
|
|
11152
11499
|
console.log();
|
|
11153
11500
|
}
|
|
11154
11501
|
if (fixResult) {
|
|
11155
11502
|
const label = fixResult.dryRun ? "\u{1F9EA} Doctor Fix (dry-run)" : "\u{1F6E0}\uFE0F Doctor Fix";
|
|
11156
11503
|
console.log(
|
|
11157
|
-
|
|
11504
|
+
chalk9.blue(`${label}: ${fixResult.changedFiles} file(s)`)
|
|
11158
11505
|
);
|
|
11159
11506
|
if (fixResult.changedFiles > 0) {
|
|
11160
11507
|
fixResult.entries.forEach(
|
|
11161
11508
|
(entry) => console.log(
|
|
11162
|
-
|
|
11509
|
+
chalk9.gray(` - ${entry.path}: ${entry.changes.join(", ")}`)
|
|
11163
11510
|
)
|
|
11164
11511
|
);
|
|
11165
11512
|
}
|
|
@@ -11169,51 +11516,51 @@ function doctorCommand(program2) {
|
|
|
11169
11516
|
const warns = issues.filter((i) => i.level === "warn");
|
|
11170
11517
|
const infos = issues.filter((i) => i.level === "info");
|
|
11171
11518
|
if (!hasIssues && infos.length === 0) {
|
|
11172
|
-
console.log(
|
|
11519
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11173
11520
|
console.log();
|
|
11174
11521
|
return;
|
|
11175
11522
|
}
|
|
11176
11523
|
if (!hasIssues && infos.length > 0) {
|
|
11177
|
-
console.log(
|
|
11524
|
+
console.log(chalk9.green(tr(lang, "cli", "doctor.noIssues")));
|
|
11178
11525
|
console.log();
|
|
11179
11526
|
}
|
|
11180
11527
|
if (errors.length > 0) {
|
|
11181
11528
|
console.log(
|
|
11182
|
-
|
|
11529
|
+
chalk9.red(
|
|
11183
11530
|
`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`
|
|
11184
11531
|
)
|
|
11185
11532
|
);
|
|
11186
11533
|
errors.forEach(
|
|
11187
11534
|
(i) => console.log(
|
|
11188
|
-
|
|
11535
|
+
chalk9.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11189
11536
|
)
|
|
11190
11537
|
);
|
|
11191
11538
|
console.log();
|
|
11192
11539
|
}
|
|
11193
11540
|
if (warns.length > 0) {
|
|
11194
11541
|
console.log(
|
|
11195
|
-
|
|
11542
|
+
chalk9.yellow(
|
|
11196
11543
|
`\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
|
|
11197
11544
|
)
|
|
11198
11545
|
);
|
|
11199
11546
|
warns.forEach(
|
|
11200
11547
|
(i) => console.log(
|
|
11201
|
-
|
|
11548
|
+
chalk9.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11202
11549
|
)
|
|
11203
11550
|
);
|
|
11204
11551
|
console.log();
|
|
11205
11552
|
}
|
|
11206
11553
|
if (infos.length > 0) {
|
|
11207
|
-
console.log(
|
|
11554
|
+
console.log(chalk9.blue(`\u2139\uFE0F Info (${infos.length})`));
|
|
11208
11555
|
infos.forEach(
|
|
11209
11556
|
(i) => console.log(
|
|
11210
|
-
|
|
11557
|
+
chalk9.blue(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
11211
11558
|
)
|
|
11212
11559
|
);
|
|
11213
11560
|
console.log();
|
|
11214
11561
|
}
|
|
11215
11562
|
console.log(
|
|
11216
|
-
|
|
11563
|
+
chalk9.gray(
|
|
11217
11564
|
tr(lang, "cli", "doctor.tipJson", {
|
|
11218
11565
|
strictFlag: options.strict ? " --strict" : ""
|
|
11219
11566
|
})
|
|
@@ -11242,8 +11589,8 @@ function doctorCommand(program2) {
|
|
|
11242
11589
|
);
|
|
11243
11590
|
} else {
|
|
11244
11591
|
console.error(
|
|
11245
|
-
|
|
11246
|
-
|
|
11592
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11593
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11247
11594
|
);
|
|
11248
11595
|
printCliErrorSuggestions(suggestions, lang);
|
|
11249
11596
|
}
|
|
@@ -11272,8 +11619,8 @@ function viewCommand(program2) {
|
|
|
11272
11619
|
);
|
|
11273
11620
|
} else {
|
|
11274
11621
|
console.error(
|
|
11275
|
-
|
|
11276
|
-
|
|
11622
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
11623
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
11277
11624
|
);
|
|
11278
11625
|
printCliErrorSuggestions(suggestions, lang);
|
|
11279
11626
|
}
|
|
@@ -11322,49 +11669,49 @@ async function runView(featureName, options) {
|
|
|
11322
11669
|
return;
|
|
11323
11670
|
}
|
|
11324
11671
|
console.log();
|
|
11325
|
-
console.log(
|
|
11326
|
-
console.log(
|
|
11672
|
+
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11673
|
+
console.log(chalk9.gray(`- Docs: ${path14.relative(cwd, config.docsDir)}`));
|
|
11327
11674
|
console.log(
|
|
11328
|
-
|
|
11675
|
+
chalk9.gray(
|
|
11329
11676
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
11330
11677
|
)
|
|
11331
11678
|
);
|
|
11332
11679
|
console.log(
|
|
11333
|
-
|
|
11680
|
+
chalk9.gray(
|
|
11334
11681
|
`- In Progress: ${state.inProgressFeatures.length}, Ready To Close: ${state.readyToCloseFeatures.length}`
|
|
11335
11682
|
)
|
|
11336
11683
|
);
|
|
11337
11684
|
if (state.warnings.length > 0) {
|
|
11338
11685
|
console.log();
|
|
11339
|
-
console.log(
|
|
11686
|
+
console.log(chalk9.yellow("\u26A0\uFE0F Environment warnings:"));
|
|
11340
11687
|
for (const warning of state.warnings) {
|
|
11341
|
-
console.log(
|
|
11688
|
+
console.log(chalk9.yellow(` - ${warning}`));
|
|
11342
11689
|
}
|
|
11343
11690
|
}
|
|
11344
11691
|
if (state.features.length === 0) {
|
|
11345
11692
|
console.log();
|
|
11346
|
-
console.log(
|
|
11347
|
-
console.log(
|
|
11693
|
+
console.log(chalk9.yellow("No features found."));
|
|
11694
|
+
console.log(chalk9.gray("Try: npx lee-spec-kit feature <name>"));
|
|
11348
11695
|
console.log();
|
|
11349
11696
|
return;
|
|
11350
11697
|
}
|
|
11351
11698
|
if (!state.matchedFeature) {
|
|
11352
11699
|
console.log();
|
|
11353
11700
|
console.log(
|
|
11354
|
-
|
|
11701
|
+
chalk9.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`)
|
|
11355
11702
|
);
|
|
11356
11703
|
const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
|
|
11357
11704
|
for (const f2 of rows) {
|
|
11358
|
-
const statusText = f2.completion.workflowDone ?
|
|
11705
|
+
const statusText = f2.completion.workflowDone ? chalk9.green("WORKFLOW_DONE") : f2.completion.implementationDone ? chalk9.cyan("DONE") : chalk9.yellow("IN_PROGRESS");
|
|
11359
11706
|
const substateText = f2.currentSubstateId ? `${f2.currentSubstateId} (${f2.currentSubstateOwner ?? "-"} / ${f2.currentSubstatePhase ?? "-"})` : "-";
|
|
11360
11707
|
console.log(
|
|
11361
11708
|
`- ${f2.folderName} (${f2.tasks.done}/${f2.tasks.total}) ${statusText} step:${f2.currentStep} substate:${substateText}`
|
|
11362
11709
|
);
|
|
11363
|
-
console.log(
|
|
11710
|
+
console.log(chalk9.gray(` next: ${f2.nextAction}`));
|
|
11364
11711
|
}
|
|
11365
11712
|
console.log();
|
|
11366
11713
|
const selectorTip = config.projectType === "multi" ? selectedComponent ? `Tip: npx lee-spec-kit view <slug|F001|F001-slug> --component ${selectedComponent}` : "Tip: npx lee-spec-kit view <slug|F001|F001-slug> [--component <component>]" : "Tip: npx lee-spec-kit view <slug|F001|F001-slug>";
|
|
11367
|
-
console.log(
|
|
11714
|
+
console.log(chalk9.gray(selectorTip));
|
|
11368
11715
|
console.log();
|
|
11369
11716
|
return;
|
|
11370
11717
|
}
|
|
@@ -11372,7 +11719,7 @@ async function runView(featureName, options) {
|
|
|
11372
11719
|
const completion = `${f.tasks.done}/${f.tasks.total}`;
|
|
11373
11720
|
const stateLabel = f.completion.workflowDone ? "WORKFLOW_DONE" : f.completion.implementationDone ? "DONE" : "IN_PROGRESS";
|
|
11374
11721
|
console.log();
|
|
11375
|
-
console.log(
|
|
11722
|
+
console.log(chalk9.blue(`Feature: ${chalk9.bold(f.folderName)}`));
|
|
11376
11723
|
console.log(`- State: ${stateLabel}`);
|
|
11377
11724
|
console.log(`- Progress: ${completion}`);
|
|
11378
11725
|
console.log(`- Step: ${f.currentStep}`);
|
|
@@ -11383,14 +11730,14 @@ async function runView(featureName, options) {
|
|
|
11383
11730
|
console.log(`- Next: ${nextSummary}`);
|
|
11384
11731
|
if (state.actionOptions.length > 0) {
|
|
11385
11732
|
console.log();
|
|
11386
|
-
console.log(
|
|
11733
|
+
console.log(chalk9.green("Atomic options:"));
|
|
11387
11734
|
for (const option of state.actionOptions) {
|
|
11388
11735
|
const lang = config.lang ?? DEFAULT_LANG;
|
|
11389
11736
|
const requiresCheck = option.action.requiresUserCheck ? tr(lang, "cli", "context.checkRequired") : "";
|
|
11390
11737
|
console.log(` ${option.label}. ${requiresCheck}${option.detail}`);
|
|
11391
11738
|
}
|
|
11392
11739
|
console.log(
|
|
11393
|
-
|
|
11740
|
+
chalk9.gray(
|
|
11394
11741
|
`Approve with: npx lee-spec-kit context ${f.folderName} --approve <LABEL>`
|
|
11395
11742
|
)
|
|
11396
11743
|
);
|
|
@@ -11408,13 +11755,13 @@ function normalizeRunId(raw) {
|
|
|
11408
11755
|
return value;
|
|
11409
11756
|
}
|
|
11410
11757
|
function getFlowRunBaseDir(cwd) {
|
|
11411
|
-
return
|
|
11758
|
+
return path14.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11412
11759
|
}
|
|
11413
11760
|
function getFlowRunPath(cwd, runId) {
|
|
11414
|
-
return
|
|
11761
|
+
return path14.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11415
11762
|
}
|
|
11416
11763
|
function getFlowRunLockPath(cwd, runId) {
|
|
11417
|
-
return
|
|
11764
|
+
return path14.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11418
11765
|
}
|
|
11419
11766
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11420
11767
|
const normalized = normalizeRunId(runId);
|
|
@@ -11440,7 +11787,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11440
11787
|
}
|
|
11441
11788
|
async function writeFlowRunRecord(cwd, record) {
|
|
11442
11789
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
11443
|
-
await fs.ensureDir(
|
|
11790
|
+
await fs.ensureDir(path14.dirname(filePath));
|
|
11444
11791
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
11445
11792
|
}
|
|
11446
11793
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -11590,6 +11937,7 @@ function toCompactAutoRun(autoRun) {
|
|
|
11590
11937
|
iterations: autoRun.iterations,
|
|
11591
11938
|
executionCount: autoRun.executions.length,
|
|
11592
11939
|
lastExecution,
|
|
11940
|
+
delegated: autoRun.delegated ?? null,
|
|
11593
11941
|
gate: autoRun.gate ?? null,
|
|
11594
11942
|
manual: autoRun.manual ?? null,
|
|
11595
11943
|
resume: autoRun.resume,
|
|
@@ -11634,6 +11982,7 @@ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
|
|
|
11634
11982
|
pauseAndReportWhen: [
|
|
11635
11983
|
"approvalRequest.required=true",
|
|
11636
11984
|
"AUTO_GATE_REACHED",
|
|
11985
|
+
"AUTO_DELEGATED_HANDOFF",
|
|
11637
11986
|
"AUTO_MANUAL_REQUIRED",
|
|
11638
11987
|
"command execution error"
|
|
11639
11988
|
],
|
|
@@ -11859,6 +12208,7 @@ function resolveAutoMode(config, options, requestText) {
|
|
|
11859
12208
|
}
|
|
11860
12209
|
function toAutoReasonCode(status) {
|
|
11861
12210
|
const map = {
|
|
12211
|
+
delegated_handoff: "AUTO_DELEGATED_HANDOFF",
|
|
11862
12212
|
gate_reached: "AUTO_GATE_REACHED",
|
|
11863
12213
|
manual_required: "AUTO_MANUAL_REQUIRED",
|
|
11864
12214
|
no_action_options: "AUTO_NO_ACTION_OPTIONS",
|
|
@@ -11882,6 +12232,7 @@ function isAutoRunFailureStatus(status) {
|
|
|
11882
12232
|
}
|
|
11883
12233
|
function toFlowRunStatus(status) {
|
|
11884
12234
|
switch (status) {
|
|
12235
|
+
case "delegated_handoff":
|
|
11885
12236
|
case "gate_reached":
|
|
11886
12237
|
case "manual_required":
|
|
11887
12238
|
return "paused";
|
|
@@ -11896,6 +12247,17 @@ function toFlowRunStatus(status) {
|
|
|
11896
12247
|
return "failed";
|
|
11897
12248
|
}
|
|
11898
12249
|
}
|
|
12250
|
+
function isTaskCommitCheckpointOption(option, state) {
|
|
12251
|
+
if (!option || state.status !== "single_matched" || !state.matchedFeature) {
|
|
12252
|
+
return false;
|
|
12253
|
+
}
|
|
12254
|
+
if (state.matchedFeature.currentSubstateId !== "task_commit_pending") {
|
|
12255
|
+
return false;
|
|
12256
|
+
}
|
|
12257
|
+
if (option.action.type !== "command") return false;
|
|
12258
|
+
if (option.action.category === "docs_commit") return true;
|
|
12259
|
+
return option.action.category === "task_execute" && option.action.scope === "project" && /\bgit\s+commit\b/i.test(option.action.cmd);
|
|
12260
|
+
}
|
|
11899
12261
|
async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
|
|
11900
12262
|
const contextArgs = [
|
|
11901
12263
|
"context",
|
|
@@ -12073,6 +12435,29 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
12073
12435
|
manual: null
|
|
12074
12436
|
};
|
|
12075
12437
|
}
|
|
12438
|
+
const taskCommitCheckpoint = actionOptions.find(
|
|
12439
|
+
(option) => isTaskCommitCheckpointOption(option, state)
|
|
12440
|
+
);
|
|
12441
|
+
if (taskCommitCheckpoint) {
|
|
12442
|
+
return {
|
|
12443
|
+
enabled: true,
|
|
12444
|
+
untilCategories,
|
|
12445
|
+
request: requestText,
|
|
12446
|
+
preset: metadata?.preset ?? null,
|
|
12447
|
+
source: metadata?.source ?? null,
|
|
12448
|
+
resume,
|
|
12449
|
+
status: "manual_required",
|
|
12450
|
+
reasonCode: toAutoReasonCode("manual_required"),
|
|
12451
|
+
iterations,
|
|
12452
|
+
executions,
|
|
12453
|
+
gate: null,
|
|
12454
|
+
manual: {
|
|
12455
|
+
label: taskCommitCheckpoint.label,
|
|
12456
|
+
category: taskCommitCheckpoint.action.category,
|
|
12457
|
+
detail: taskCommitCheckpoint.detail
|
|
12458
|
+
}
|
|
12459
|
+
};
|
|
12460
|
+
}
|
|
12076
12461
|
const executable = actionOptions.find(
|
|
12077
12462
|
(option) => option.action.type === "command"
|
|
12078
12463
|
);
|
|
@@ -12158,16 +12543,18 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
12158
12543
|
preset: metadata?.preset ?? null,
|
|
12159
12544
|
source: metadata?.source ?? null,
|
|
12160
12545
|
resume,
|
|
12161
|
-
status: "
|
|
12162
|
-
reasonCode: toAutoReasonCode("
|
|
12546
|
+
status: "delegated_handoff",
|
|
12547
|
+
reasonCode: toAutoReasonCode("delegated_handoff"),
|
|
12163
12548
|
iterations,
|
|
12164
12549
|
executions,
|
|
12165
|
-
|
|
12166
|
-
manual: {
|
|
12550
|
+
delegated: {
|
|
12167
12551
|
label: executable.label,
|
|
12168
12552
|
category: executable.action.category,
|
|
12169
|
-
detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail
|
|
12170
|
-
|
|
12553
|
+
detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail,
|
|
12554
|
+
nextMainState: executeResult?.nextMainState
|
|
12555
|
+
},
|
|
12556
|
+
gate: null,
|
|
12557
|
+
manual: null
|
|
12171
12558
|
};
|
|
12172
12559
|
}
|
|
12173
12560
|
if (executeResult?.status !== "approved_executed") {
|
|
@@ -12235,8 +12622,8 @@ function flowCommand(program2) {
|
|
|
12235
12622
|
);
|
|
12236
12623
|
} else {
|
|
12237
12624
|
console.error(
|
|
12238
|
-
|
|
12239
|
-
|
|
12625
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
12626
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
12240
12627
|
);
|
|
12241
12628
|
printCliErrorSuggestions(suggestions, lang);
|
|
12242
12629
|
}
|
|
@@ -12358,6 +12745,20 @@ async function runFlow(featureName, options) {
|
|
|
12358
12745
|
"`--request` requires auto mode. Use `--auto-until-category`, `--auto-preset`, or configure `workflow.auto.defaultPreset`."
|
|
12359
12746
|
);
|
|
12360
12747
|
}
|
|
12748
|
+
if (autoMode && !resolvedFeatureName && requestText) {
|
|
12749
|
+
const bootstrapped = await bootstrapFeatureFromIdeaRequest(
|
|
12750
|
+
config,
|
|
12751
|
+
requestText,
|
|
12752
|
+
selectedComponent
|
|
12753
|
+
);
|
|
12754
|
+
if (bootstrapped) {
|
|
12755
|
+
resolvedFeatureName = bootstrapped.featureRef;
|
|
12756
|
+
if (!selectedComponent && bootstrapped.component) {
|
|
12757
|
+
selectedComponent = bootstrapped.component;
|
|
12758
|
+
selectionOptions.component = bootstrapped.component;
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12361
12762
|
if (autoMode && !featureName) {
|
|
12362
12763
|
if (!resolvedFeatureName) {
|
|
12363
12764
|
throw createCliError(
|
|
@@ -12452,7 +12853,8 @@ async function runFlow(featureName, options) {
|
|
|
12452
12853
|
},
|
|
12453
12854
|
lastAutoStatus: autoRun.status,
|
|
12454
12855
|
lastReasonCode: autoRun.reasonCode,
|
|
12455
|
-
lastError: autoRun.error
|
|
12856
|
+
lastError: autoRun.error,
|
|
12857
|
+
lastDelegatedHandoff: autoRun.delegated ?? null
|
|
12456
12858
|
})
|
|
12457
12859
|
);
|
|
12458
12860
|
autoRun.run = {
|
|
@@ -12555,16 +12957,16 @@ async function runFlow(featureName, options) {
|
|
|
12555
12957
|
return;
|
|
12556
12958
|
}
|
|
12557
12959
|
console.log();
|
|
12558
|
-
console.log(
|
|
12960
|
+
console.log(chalk9.bold("\u{1F501} Flow Summary"));
|
|
12559
12961
|
console.log(
|
|
12560
|
-
|
|
12962
|
+
chalk9.gray(
|
|
12561
12963
|
`- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
|
|
12562
12964
|
)
|
|
12563
12965
|
);
|
|
12564
12966
|
if (approvalResult && typeof approvalResult === "object") {
|
|
12565
12967
|
const result = approvalResult;
|
|
12566
12968
|
console.log(
|
|
12567
|
-
|
|
12969
|
+
chalk9.gray(
|
|
12568
12970
|
`- Approval: ${result.status ?? "unknown"} (${result.reasonCode ?? "-"})`
|
|
12569
12971
|
)
|
|
12570
12972
|
);
|
|
@@ -12572,18 +12974,18 @@ async function runFlow(featureName, options) {
|
|
|
12572
12974
|
if (autoRun) {
|
|
12573
12975
|
const presetSuffix = autoRun.preset ? `, preset ${autoRun.preset}` : "";
|
|
12574
12976
|
console.log(
|
|
12575
|
-
|
|
12977
|
+
chalk9.gray(
|
|
12576
12978
|
`- Auto: ${autoRun.status} (${autoRun.reasonCode}), iterations ${autoRun.iterations}, executions ${autoRun.executions.length}${presetSuffix}`
|
|
12577
12979
|
)
|
|
12578
12980
|
);
|
|
12579
|
-
console.log(
|
|
12981
|
+
console.log(chalk9.gray(`- Auto resume: ${autoRun.resume.flowCommand}`));
|
|
12580
12982
|
if (autoRun.run) {
|
|
12581
12983
|
console.log(
|
|
12582
|
-
|
|
12984
|
+
chalk9.gray(
|
|
12583
12985
|
`- Auto run: ${autoRun.run.mode} ${autoRun.run.runId} (${autoRun.run.status})`
|
|
12584
12986
|
)
|
|
12585
12987
|
);
|
|
12586
|
-
console.log(
|
|
12988
|
+
console.log(chalk9.gray(`- Resume with: ${autoRun.run.resumeCommand}`));
|
|
12587
12989
|
}
|
|
12588
12990
|
}
|
|
12589
12991
|
const agentOrchestration = buildAgentOrchestrationPolicy2(
|
|
@@ -12591,21 +12993,21 @@ async function runFlow(featureName, options) {
|
|
|
12591
12993
|
resolvedFeatureName || null
|
|
12592
12994
|
);
|
|
12593
12995
|
console.log(
|
|
12594
|
-
|
|
12996
|
+
chalk9.gray(
|
|
12595
12997
|
`- Orchestration: ${agentOrchestration.mode}, prefer substate-owner delegation and fall back to legacy long-running categories`
|
|
12596
12998
|
)
|
|
12597
12999
|
);
|
|
12598
13000
|
const statusCounts = statusReport.counts;
|
|
12599
13001
|
const doctorCounts = doctorReport.counts;
|
|
12600
|
-
console.log(
|
|
13002
|
+
console.log(chalk9.gray(`- Status features: ${statusCounts?.features ?? 0}`));
|
|
12601
13003
|
console.log(
|
|
12602
|
-
|
|
13004
|
+
chalk9.gray(
|
|
12603
13005
|
`- Doctor issues: ${doctorCounts?.issues ?? 0} (errors ${doctorCounts?.errors ?? 0}, warnings ${doctorCounts?.warnings ?? 0})`
|
|
12604
13006
|
)
|
|
12605
13007
|
);
|
|
12606
13008
|
if (strictChecks) {
|
|
12607
|
-
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ?
|
|
12608
|
-
console.log(
|
|
13009
|
+
const strictLabel = strictChecks.statusStrictOk && strictChecks.doctorStrictOk ? chalk9.green("PASS") : chalk9.red("FAIL");
|
|
13010
|
+
console.log(chalk9.gray(`- Strict checks: ${strictLabel}`));
|
|
12609
13011
|
}
|
|
12610
13012
|
console.log();
|
|
12611
13013
|
if (autoRun?.status === "gate_reached" && autoRun.gate?.userFacingLines?.length) {
|
|
@@ -12613,7 +13015,7 @@ async function runFlow(featureName, options) {
|
|
|
12613
13015
|
console.log(line);
|
|
12614
13016
|
}
|
|
12615
13017
|
console.log(
|
|
12616
|
-
|
|
13018
|
+
chalk9.gray(
|
|
12617
13019
|
"Auto gate reached. Reply with one of the labels shown above (example: A OK)."
|
|
12618
13020
|
)
|
|
12619
13021
|
);
|
|
@@ -12624,20 +13026,59 @@ async function runFlow(featureName, options) {
|
|
|
12624
13026
|
}
|
|
12625
13027
|
if (after.matchedFeature) {
|
|
12626
13028
|
console.log(
|
|
12627
|
-
|
|
13029
|
+
chalk9.blue(
|
|
12628
13030
|
`Next: npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}`
|
|
12629
13031
|
)
|
|
12630
13032
|
);
|
|
12631
13033
|
} else {
|
|
12632
|
-
console.log(
|
|
13034
|
+
console.log(chalk9.blue(`Next: npx lee-spec-kit context${componentHint}`));
|
|
12633
13035
|
}
|
|
12634
13036
|
console.log(
|
|
12635
|
-
|
|
13037
|
+
chalk9.gray(
|
|
12636
13038
|
"Tip: add --approve <LABEL> [--execute] to run the selected atomic action."
|
|
12637
13039
|
)
|
|
12638
13040
|
);
|
|
12639
13041
|
console.log();
|
|
12640
13042
|
}
|
|
13043
|
+
async function bootstrapFeatureFromIdeaRequest(config, requestText, selectedComponent) {
|
|
13044
|
+
const ideaRef = extractExplicitIdeaRef(requestText);
|
|
13045
|
+
if (!ideaRef) return null;
|
|
13046
|
+
const resolvedIdea = await resolveIdeaReference(
|
|
13047
|
+
config.docsDir,
|
|
13048
|
+
ideaRef,
|
|
13049
|
+
config.lang
|
|
13050
|
+
);
|
|
13051
|
+
const existingFeatureRef = await readIdeaMetadataValue(
|
|
13052
|
+
resolvedIdea.path,
|
|
13053
|
+
"Feature"
|
|
13054
|
+
);
|
|
13055
|
+
if (existingFeatureRef && existingFeatureRef !== "-") {
|
|
13056
|
+
return { featureRef: existingFeatureRef, component: selectedComponent };
|
|
13057
|
+
}
|
|
13058
|
+
let component = selectedComponent;
|
|
13059
|
+
if (config.projectType === "multi" && !component) {
|
|
13060
|
+
const ideaComponent = await readIdeaMetadataValue(
|
|
13061
|
+
resolvedIdea.path,
|
|
13062
|
+
"Component"
|
|
13063
|
+
);
|
|
13064
|
+
if (ideaComponent && ideaComponent !== "-" && ideaComponent !== "all") {
|
|
13065
|
+
component = ideaComponent.toLowerCase();
|
|
13066
|
+
} else {
|
|
13067
|
+
return null;
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13070
|
+
const featureName = await deriveFeatureNameFromIdea(resolvedIdea.path);
|
|
13071
|
+
const result = await runFeature(featureName, {
|
|
13072
|
+
component,
|
|
13073
|
+
idea: ideaRef,
|
|
13074
|
+
nonInteractive: true,
|
|
13075
|
+
json: true
|
|
13076
|
+
});
|
|
13077
|
+
return {
|
|
13078
|
+
featureRef: `${result.featureId}-${result.featureName}`,
|
|
13079
|
+
component
|
|
13080
|
+
};
|
|
13081
|
+
}
|
|
12641
13082
|
function runProcess(bin, args, cwd) {
|
|
12642
13083
|
const result = spawnSync(bin, args, {
|
|
12643
13084
|
cwd,
|
|
@@ -12751,27 +13192,27 @@ function tg(lang, key, vars = {}) {
|
|
|
12751
13192
|
function detectGithubCliLangSync(cwd) {
|
|
12752
13193
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
12753
13194
|
const startDirs = [
|
|
12754
|
-
explicitDocsDir ?
|
|
12755
|
-
|
|
13195
|
+
explicitDocsDir ? path14.resolve(explicitDocsDir) : "",
|
|
13196
|
+
path14.resolve(cwd)
|
|
12756
13197
|
].filter(Boolean);
|
|
12757
13198
|
const scanOrder = [];
|
|
12758
13199
|
const seen = /* @__PURE__ */ new Set();
|
|
12759
13200
|
for (const start of startDirs) {
|
|
12760
13201
|
let current = start;
|
|
12761
13202
|
while (true) {
|
|
12762
|
-
const abs =
|
|
13203
|
+
const abs = path14.resolve(current);
|
|
12763
13204
|
if (!seen.has(abs)) {
|
|
12764
13205
|
scanOrder.push(abs);
|
|
12765
13206
|
seen.add(abs);
|
|
12766
13207
|
}
|
|
12767
|
-
const parent =
|
|
13208
|
+
const parent = path14.dirname(abs);
|
|
12768
13209
|
if (parent === abs) break;
|
|
12769
13210
|
current = parent;
|
|
12770
13211
|
}
|
|
12771
13212
|
}
|
|
12772
13213
|
for (const base of scanOrder) {
|
|
12773
|
-
for (const docsDir of [
|
|
12774
|
-
const configPath =
|
|
13214
|
+
for (const docsDir of [path14.join(base, "docs"), base]) {
|
|
13215
|
+
const configPath = path14.join(docsDir, ".lee-spec-kit.json");
|
|
12775
13216
|
if (fs.existsSync(configPath)) {
|
|
12776
13217
|
try {
|
|
12777
13218
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -12780,11 +13221,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
12780
13221
|
} catch {
|
|
12781
13222
|
}
|
|
12782
13223
|
}
|
|
12783
|
-
const agentsPath =
|
|
12784
|
-
const featuresPath =
|
|
13224
|
+
const agentsPath = path14.join(docsDir, "agents");
|
|
13225
|
+
const featuresPath = path14.join(docsDir, "features");
|
|
12785
13226
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
12786
13227
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
12787
|
-
const file =
|
|
13228
|
+
const file = path14.join(agentsPath, probe);
|
|
12788
13229
|
if (!fs.existsSync(file)) continue;
|
|
12789
13230
|
try {
|
|
12790
13231
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -12804,13 +13245,13 @@ function parseLabels(raw, lang) {
|
|
|
12804
13245
|
}
|
|
12805
13246
|
return [...new Set(labels)];
|
|
12806
13247
|
}
|
|
12807
|
-
function
|
|
13248
|
+
function escapeRegExp5(value) {
|
|
12808
13249
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12809
13250
|
}
|
|
12810
13251
|
function extractDraftMetadataValue(content, keys) {
|
|
12811
13252
|
for (const key of keys) {
|
|
12812
13253
|
const re = new RegExp(
|
|
12813
|
-
`^\\s*-\\s*\\*\\*${
|
|
13254
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp5(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
|
|
12814
13255
|
"mi"
|
|
12815
13256
|
);
|
|
12816
13257
|
const match = content.match(re);
|
|
@@ -12889,7 +13330,7 @@ async function prepareGithubBody(params) {
|
|
|
12889
13330
|
};
|
|
12890
13331
|
}
|
|
12891
13332
|
}
|
|
12892
|
-
await fs.ensureDir(
|
|
13333
|
+
await fs.ensureDir(path14.dirname(defaultBodyFile));
|
|
12893
13334
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
12894
13335
|
return {
|
|
12895
13336
|
body: generatedBody,
|
|
@@ -12929,7 +13370,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12929
13370
|
};
|
|
12930
13371
|
const hasMetadataField = (field) => {
|
|
12931
13372
|
const re = new RegExp(
|
|
12932
|
-
`^\\s*-\\s*\\*\\*${
|
|
13373
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp5(field)}\\*\\*\\s*:`,
|
|
12933
13374
|
"m"
|
|
12934
13375
|
);
|
|
12935
13376
|
return re.test(body);
|
|
@@ -12959,7 +13400,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
12959
13400
|
}
|
|
12960
13401
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
12961
13402
|
const missing = relativePaths.filter(
|
|
12962
|
-
(relativePath) => !fs.existsSync(
|
|
13403
|
+
(relativePath) => !fs.existsSync(path14.join(docsDir, relativePath))
|
|
12963
13404
|
);
|
|
12964
13405
|
if (missing.length > 0) {
|
|
12965
13406
|
throw createCliError(
|
|
@@ -12969,18 +13410,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
12969
13410
|
}
|
|
12970
13411
|
}
|
|
12971
13412
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
12972
|
-
const key = `${
|
|
13413
|
+
const key = `${path14.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
12973
13414
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
12974
13415
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
12975
13416
|
}
|
|
12976
13417
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
12977
|
-
const selected = raw?.trim() ||
|
|
13418
|
+
const selected = raw?.trim() || path14.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
12978
13419
|
assertValid(
|
|
12979
13420
|
validatePathWithLang(selected, lang),
|
|
12980
13421
|
`github.${kind}.bodyFile`,
|
|
12981
13422
|
lang
|
|
12982
13423
|
);
|
|
12983
|
-
return
|
|
13424
|
+
return path14.resolve(selected);
|
|
12984
13425
|
}
|
|
12985
13426
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
12986
13427
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -13817,7 +14258,7 @@ function stripMarkdownCodeContexts(body) {
|
|
|
13817
14258
|
function hasIssueClosingKeyword(body, issueNumber) {
|
|
13818
14259
|
if (!issueNumber) return false;
|
|
13819
14260
|
const cleaned = stripMarkdownCodeContexts(body);
|
|
13820
|
-
const issue =
|
|
14261
|
+
const issue = escapeRegExp5(issueNumber);
|
|
13821
14262
|
const closeKeywordRegex = new RegExp(
|
|
13822
14263
|
`\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
|
|
13823
14264
|
"i"
|
|
@@ -14035,7 +14476,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
14035
14476
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
14036
14477
|
const uniqueRelativePaths = [
|
|
14037
14478
|
...new Set(
|
|
14038
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14479
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path14.relative(cwd, absPath) || absPath)
|
|
14039
14480
|
)
|
|
14040
14481
|
];
|
|
14041
14482
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14231,15 +14672,15 @@ function githubCommand(program2) {
|
|
|
14231
14672
|
config.lang
|
|
14232
14673
|
);
|
|
14233
14674
|
const specContent = await fs.readFile(
|
|
14234
|
-
|
|
14675
|
+
path14.join(config.docsDir, paths.specPath),
|
|
14235
14676
|
"utf-8"
|
|
14236
14677
|
);
|
|
14237
14678
|
const planContent = await fs.readFile(
|
|
14238
|
-
|
|
14679
|
+
path14.join(config.docsDir, paths.planPath),
|
|
14239
14680
|
"utf-8"
|
|
14240
14681
|
);
|
|
14241
14682
|
const tasksContent = await fs.readFile(
|
|
14242
|
-
|
|
14683
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14243
14684
|
"utf-8"
|
|
14244
14685
|
);
|
|
14245
14686
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14282,7 +14723,7 @@ function githubCommand(program2) {
|
|
|
14282
14723
|
create: options.create,
|
|
14283
14724
|
explicitBodyFile,
|
|
14284
14725
|
defaultBodyFile,
|
|
14285
|
-
workflowDraftPath:
|
|
14726
|
+
workflowDraftPath: path14.join(config.docsDir, paths.issuePath),
|
|
14286
14727
|
generatedBody,
|
|
14287
14728
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
14288
14729
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -14298,7 +14739,7 @@ function githubCommand(program2) {
|
|
|
14298
14739
|
`${feature.type}-issue-sanitized`,
|
|
14299
14740
|
config.lang
|
|
14300
14741
|
);
|
|
14301
|
-
await fs.ensureDir(
|
|
14742
|
+
await fs.ensureDir(path14.dirname(sanitizedBodyFile));
|
|
14302
14743
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14303
14744
|
bodyFile = sanitizedBodyFile;
|
|
14304
14745
|
}
|
|
@@ -14347,12 +14788,12 @@ function githubCommand(program2) {
|
|
|
14347
14788
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
14348
14789
|
if (syncedIssueNumber) {
|
|
14349
14790
|
const synced = syncTasksIssueMetadata(
|
|
14350
|
-
|
|
14791
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14351
14792
|
syncedIssueNumber,
|
|
14352
14793
|
config.lang
|
|
14353
14794
|
);
|
|
14354
14795
|
const draftSynced = syncIssueDraftMetadata(
|
|
14355
|
-
|
|
14796
|
+
path14.join(config.docsDir, paths.issuePath),
|
|
14356
14797
|
syncedIssueNumber
|
|
14357
14798
|
);
|
|
14358
14799
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -14380,31 +14821,31 @@ function githubCommand(program2) {
|
|
|
14380
14821
|
return;
|
|
14381
14822
|
}
|
|
14382
14823
|
console.log();
|
|
14383
|
-
console.log(
|
|
14824
|
+
console.log(chalk9.bold(tg(config.lang, "issueHeader")));
|
|
14384
14825
|
console.log(
|
|
14385
|
-
|
|
14826
|
+
chalk9.gray(
|
|
14386
14827
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14387
14828
|
)
|
|
14388
14829
|
);
|
|
14389
14830
|
console.log(
|
|
14390
|
-
|
|
14831
|
+
chalk9.gray(
|
|
14391
14832
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14392
14833
|
)
|
|
14393
14834
|
);
|
|
14394
14835
|
console.log(
|
|
14395
|
-
|
|
14836
|
+
chalk9.gray(
|
|
14396
14837
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14397
14838
|
)
|
|
14398
14839
|
);
|
|
14399
14840
|
if (issueUrl) {
|
|
14400
14841
|
console.log(
|
|
14401
|
-
|
|
14842
|
+
chalk9.green(
|
|
14402
14843
|
tg(config.lang, "issueCreated", { url: issueUrl })
|
|
14403
14844
|
)
|
|
14404
14845
|
);
|
|
14405
14846
|
} else {
|
|
14406
14847
|
console.log(
|
|
14407
|
-
|
|
14848
|
+
chalk9.blue(tg(config.lang, "issueTemplateGenerated"))
|
|
14408
14849
|
);
|
|
14409
14850
|
}
|
|
14410
14851
|
console.log();
|
|
@@ -14423,8 +14864,8 @@ function githubCommand(program2) {
|
|
|
14423
14864
|
);
|
|
14424
14865
|
} else {
|
|
14425
14866
|
console.error(
|
|
14426
|
-
|
|
14427
|
-
|
|
14867
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
14868
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14428
14869
|
);
|
|
14429
14870
|
printCliErrorSuggestions(suggestions, lang);
|
|
14430
14871
|
}
|
|
@@ -14463,13 +14904,13 @@ function githubCommand(program2) {
|
|
|
14463
14904
|
config.lang
|
|
14464
14905
|
);
|
|
14465
14906
|
const specContent = await fs.readFile(
|
|
14466
|
-
|
|
14907
|
+
path14.join(config.docsDir, paths.specPath),
|
|
14467
14908
|
"utf-8"
|
|
14468
14909
|
);
|
|
14469
|
-
const planPath =
|
|
14910
|
+
const planPath = path14.join(config.docsDir, paths.planPath);
|
|
14470
14911
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
14471
14912
|
const tasksContent = await fs.readFile(
|
|
14472
|
-
|
|
14913
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14473
14914
|
"utf-8"
|
|
14474
14915
|
);
|
|
14475
14916
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14517,7 +14958,7 @@ function githubCommand(program2) {
|
|
|
14517
14958
|
create: options.create,
|
|
14518
14959
|
explicitBodyFile,
|
|
14519
14960
|
defaultBodyFile,
|
|
14520
|
-
workflowDraftPath:
|
|
14961
|
+
workflowDraftPath: path14.join(config.docsDir, paths.prPath),
|
|
14521
14962
|
generatedBody,
|
|
14522
14963
|
requiredSections: getRequiredPrSections(config.lang),
|
|
14523
14964
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -14535,7 +14976,7 @@ function githubCommand(program2) {
|
|
|
14535
14976
|
`${feature.type}-pr-sanitized`,
|
|
14536
14977
|
config.lang
|
|
14537
14978
|
);
|
|
14538
|
-
await fs.ensureDir(
|
|
14979
|
+
await fs.ensureDir(path14.dirname(sanitizedBodyFile));
|
|
14539
14980
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14540
14981
|
bodyFile = sanitizedBodyFile;
|
|
14541
14982
|
}
|
|
@@ -14563,7 +15004,7 @@ function githubCommand(program2) {
|
|
|
14563
15004
|
if (preparedBody.source === "generated") {
|
|
14564
15005
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
14565
15006
|
} else {
|
|
14566
|
-
await fs.ensureDir(
|
|
15007
|
+
await fs.ensureDir(path14.dirname(fallbackBodyFile));
|
|
14567
15008
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
14568
15009
|
bodyFile = fallbackBodyFile;
|
|
14569
15010
|
}
|
|
@@ -14624,13 +15065,13 @@ function githubCommand(program2) {
|
|
|
14624
15065
|
}
|
|
14625
15066
|
if (prUrl && options.syncTasks !== false) {
|
|
14626
15067
|
const syncedTasks = syncTasksPrMetadata(
|
|
14627
|
-
|
|
15068
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14628
15069
|
prUrl,
|
|
14629
15070
|
"Review",
|
|
14630
15071
|
config.lang
|
|
14631
15072
|
);
|
|
14632
15073
|
const syncedDraft = syncPrDraftMetadata(
|
|
14633
|
-
|
|
15074
|
+
path14.join(config.docsDir, paths.prPath),
|
|
14634
15075
|
prUrl,
|
|
14635
15076
|
"Review"
|
|
14636
15077
|
);
|
|
@@ -14671,13 +15112,13 @@ function githubCommand(program2) {
|
|
|
14671
15112
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
14672
15113
|
if (prUrl && options.syncTasks !== false) {
|
|
14673
15114
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
14674
|
-
|
|
15115
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14675
15116
|
prUrl,
|
|
14676
15117
|
"Approved",
|
|
14677
15118
|
config.lang
|
|
14678
15119
|
);
|
|
14679
15120
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
14680
|
-
|
|
15121
|
+
path14.join(config.docsDir, paths.prPath),
|
|
14681
15122
|
prUrl,
|
|
14682
15123
|
"Approved"
|
|
14683
15124
|
);
|
|
@@ -14761,35 +15202,35 @@ function githubCommand(program2) {
|
|
|
14761
15202
|
return;
|
|
14762
15203
|
}
|
|
14763
15204
|
console.log();
|
|
14764
|
-
console.log(
|
|
15205
|
+
console.log(chalk9.bold(tg(config.lang, "prHeader")));
|
|
14765
15206
|
console.log(
|
|
14766
|
-
|
|
15207
|
+
chalk9.gray(
|
|
14767
15208
|
`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`
|
|
14768
15209
|
)
|
|
14769
15210
|
);
|
|
14770
15211
|
console.log(
|
|
14771
|
-
|
|
15212
|
+
chalk9.gray(
|
|
14772
15213
|
`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`
|
|
14773
15214
|
)
|
|
14774
15215
|
);
|
|
14775
15216
|
console.log(
|
|
14776
|
-
|
|
15217
|
+
chalk9.gray(
|
|
14777
15218
|
`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`
|
|
14778
15219
|
)
|
|
14779
15220
|
);
|
|
14780
15221
|
if (prUrl) {
|
|
14781
15222
|
console.log(
|
|
14782
|
-
|
|
15223
|
+
chalk9.gray(`- ${tg(config.lang, "labelPr")}: ${prUrl}`)
|
|
14783
15224
|
);
|
|
14784
15225
|
}
|
|
14785
15226
|
if (syncChanged) {
|
|
14786
15227
|
console.log(
|
|
14787
|
-
|
|
15228
|
+
chalk9.green(tg(config.lang, "prTasksSynced"))
|
|
14788
15229
|
);
|
|
14789
15230
|
}
|
|
14790
15231
|
if (options.merge) {
|
|
14791
15232
|
console.log(
|
|
14792
|
-
|
|
15233
|
+
chalk9.green(
|
|
14793
15234
|
tg(config.lang, "prMerged", {
|
|
14794
15235
|
attempts: mergedAttempts ?? 1
|
|
14795
15236
|
})
|
|
@@ -14797,15 +15238,15 @@ function githubCommand(program2) {
|
|
|
14797
15238
|
);
|
|
14798
15239
|
if (mergeAlreadyMerged) {
|
|
14799
15240
|
console.log(
|
|
14800
|
-
|
|
15241
|
+
chalk9.yellow(tg(config.lang, "prAlreadyMergedNotice"))
|
|
14801
15242
|
);
|
|
14802
15243
|
}
|
|
14803
15244
|
for (const warning of postMergeWarnings) {
|
|
14804
|
-
console.log(
|
|
15245
|
+
console.log(chalk9.yellow(`\u26A0\uFE0F ${warning}`));
|
|
14805
15246
|
}
|
|
14806
15247
|
} else if (!options.create) {
|
|
14807
15248
|
console.log(
|
|
14808
|
-
|
|
15249
|
+
chalk9.blue(tg(config.lang, "prTemplateGenerated"))
|
|
14809
15250
|
);
|
|
14810
15251
|
}
|
|
14811
15252
|
console.log();
|
|
@@ -14824,8 +15265,8 @@ function githubCommand(program2) {
|
|
|
14824
15265
|
);
|
|
14825
15266
|
} else {
|
|
14826
15267
|
console.error(
|
|
14827
|
-
|
|
14828
|
-
|
|
15268
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15269
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14829
15270
|
);
|
|
14830
15271
|
printCliErrorSuggestions(suggestions, lang);
|
|
14831
15272
|
}
|
|
@@ -14883,10 +15324,10 @@ function docsCommand(program2) {
|
|
|
14883
15324
|
return;
|
|
14884
15325
|
}
|
|
14885
15326
|
console.log();
|
|
14886
|
-
console.log(
|
|
15327
|
+
console.log(chalk9.bold(tr(config.lang, "cli", "docs.listHeader")));
|
|
14887
15328
|
for (const doc of docsList) {
|
|
14888
15329
|
console.log(`- ${doc.id}: ${doc.title}`);
|
|
14889
|
-
console.log(
|
|
15330
|
+
console.log(chalk9.gray(` ${doc.command}`));
|
|
14890
15331
|
}
|
|
14891
15332
|
console.log();
|
|
14892
15333
|
} catch (error) {
|
|
@@ -14905,8 +15346,8 @@ function docsCommand(program2) {
|
|
|
14905
15346
|
);
|
|
14906
15347
|
} else {
|
|
14907
15348
|
console.error(
|
|
14908
|
-
|
|
14909
|
-
|
|
15349
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15350
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14910
15351
|
);
|
|
14911
15352
|
printCliErrorSuggestions(suggestions, lang);
|
|
14912
15353
|
}
|
|
@@ -14948,25 +15389,25 @@ function docsCommand(program2) {
|
|
|
14948
15389
|
);
|
|
14949
15390
|
return;
|
|
14950
15391
|
}
|
|
14951
|
-
const relativeFromCwd =
|
|
15392
|
+
const relativeFromCwd = path14.relative(process.cwd(), loaded.entry.absolutePath);
|
|
14952
15393
|
console.log();
|
|
14953
|
-
console.log(
|
|
15394
|
+
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
14954
15395
|
console.log(
|
|
14955
|
-
|
|
15396
|
+
chalk9.gray(
|
|
14956
15397
|
`${tr(config.lang, "cli", "docs.sourceLabel")}: ${relativeFromCwd || loaded.entry.absolutePath}`
|
|
14957
15398
|
)
|
|
14958
15399
|
);
|
|
14959
15400
|
console.log(
|
|
14960
|
-
|
|
15401
|
+
chalk9.gray(`${tr(config.lang, "cli", "docs.hashLabel")}: ${loaded.hash}`)
|
|
14961
15402
|
);
|
|
14962
15403
|
console.log();
|
|
14963
15404
|
process.stdout.write(loaded.content.endsWith("\n") ? loaded.content : `${loaded.content}
|
|
14964
15405
|
`);
|
|
14965
15406
|
if (followups.length > 0) {
|
|
14966
15407
|
console.log();
|
|
14967
|
-
console.log(
|
|
15408
|
+
console.log(chalk9.blue(`${tr(config.lang, "cli", "docs.nextDocs")}:`));
|
|
14968
15409
|
for (const followup of followups) {
|
|
14969
|
-
console.log(
|
|
15410
|
+
console.log(chalk9.gray(`- ${followup.command}`));
|
|
14970
15411
|
}
|
|
14971
15412
|
console.log();
|
|
14972
15413
|
}
|
|
@@ -14986,8 +15427,8 @@ function docsCommand(program2) {
|
|
|
14986
15427
|
);
|
|
14987
15428
|
} else {
|
|
14988
15429
|
console.error(
|
|
14989
|
-
|
|
14990
|
-
|
|
15430
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15431
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
14991
15432
|
);
|
|
14992
15433
|
printCliErrorSuggestions(suggestions, lang);
|
|
14993
15434
|
}
|
|
@@ -15016,8 +15457,8 @@ function detectCommand(program2) {
|
|
|
15016
15457
|
);
|
|
15017
15458
|
} else {
|
|
15018
15459
|
console.error(
|
|
15019
|
-
|
|
15020
|
-
|
|
15460
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
15461
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
15021
15462
|
);
|
|
15022
15463
|
printCliErrorSuggestions(suggestions, lang);
|
|
15023
15464
|
}
|
|
@@ -15028,7 +15469,7 @@ function detectCommand(program2) {
|
|
|
15028
15469
|
}
|
|
15029
15470
|
async function runDetect(options) {
|
|
15030
15471
|
const cwd = process.cwd();
|
|
15031
|
-
const targetCwd = options.dir ?
|
|
15472
|
+
const targetCwd = options.dir ? path14.resolve(cwd, options.dir) : cwd;
|
|
15032
15473
|
const config = await getConfig(targetCwd);
|
|
15033
15474
|
const detected = !!config;
|
|
15034
15475
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -15055,7 +15496,7 @@ async function runDetect(options) {
|
|
|
15055
15496
|
);
|
|
15056
15497
|
return;
|
|
15057
15498
|
}
|
|
15058
|
-
const configPath2 =
|
|
15499
|
+
const configPath2 = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
15059
15500
|
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
15060
15501
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
15061
15502
|
console.log(
|
|
@@ -15081,26 +15522,26 @@ async function runDetect(options) {
|
|
|
15081
15522
|
}
|
|
15082
15523
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
15083
15524
|
console.log();
|
|
15084
|
-
console.log(
|
|
15085
|
-
console.log(
|
|
15525
|
+
console.log(chalk9.blue(tr(lang, "cli", "detect.header")));
|
|
15526
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelTarget")}: ${targetCwd}`));
|
|
15086
15527
|
if (!config) {
|
|
15087
|
-
console.log(
|
|
15088
|
-
console.log(
|
|
15528
|
+
console.log(chalk9.yellow(`- ${tr(lang, "cli", "detect.resultNotDetected")}`));
|
|
15529
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.notDetectedHint")}`));
|
|
15089
15530
|
console.log();
|
|
15090
15531
|
return;
|
|
15091
15532
|
}
|
|
15092
|
-
const configPath =
|
|
15533
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
15093
15534
|
const configFilePresent = await fs.pathExists(configPath);
|
|
15094
15535
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
15095
|
-
console.log(
|
|
15096
|
-
console.log(
|
|
15536
|
+
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
15537
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${config.docsDir}`));
|
|
15097
15538
|
console.log(
|
|
15098
|
-
|
|
15539
|
+
chalk9.gray(
|
|
15099
15540
|
`- ${tr(lang, "cli", "detect.labelConfigPath")}: ${configFilePresent ? configPath : "-"}`
|
|
15100
15541
|
)
|
|
15101
15542
|
);
|
|
15102
15543
|
console.log(
|
|
15103
|
-
|
|
15544
|
+
chalk9.gray(
|
|
15104
15545
|
`- ${tr(lang, "cli", "detect.labelSource")}: ${tr(
|
|
15105
15546
|
lang,
|
|
15106
15547
|
"cli",
|
|
@@ -15109,14 +15550,14 @@ async function runDetect(options) {
|
|
|
15109
15550
|
)
|
|
15110
15551
|
);
|
|
15111
15552
|
console.log(
|
|
15112
|
-
|
|
15553
|
+
chalk9.gray(
|
|
15113
15554
|
`- ${tr(lang, "cli", "detect.labelProjectType")}: ${config.projectType}`
|
|
15114
15555
|
)
|
|
15115
15556
|
);
|
|
15116
|
-
console.log(
|
|
15557
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelLang")}: ${config.lang}`));
|
|
15117
15558
|
if (config.projectName) {
|
|
15118
15559
|
console.log(
|
|
15119
|
-
|
|
15560
|
+
chalk9.gray(
|
|
15120
15561
|
`- ${tr(lang, "cli", "detect.labelProjectName")}: ${config.projectName}`
|
|
15121
15562
|
)
|
|
15122
15563
|
);
|
|
@@ -15166,19 +15607,19 @@ function hasTemplateMarkers(content) {
|
|
|
15166
15607
|
return patterns.some((pattern) => pattern.test(content));
|
|
15167
15608
|
}
|
|
15168
15609
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15169
|
-
const featuresRoot =
|
|
15610
|
+
const featuresRoot = path14.join(docsDir, "features");
|
|
15170
15611
|
if (projectType === "single") {
|
|
15171
15612
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15172
|
-
return dirs.filter((value) =>
|
|
15613
|
+
return dirs.filter((value) => path14.basename(value) !== "feature-base").length;
|
|
15173
15614
|
}
|
|
15174
15615
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15175
15616
|
let total = 0;
|
|
15176
15617
|
for (const componentDir of components) {
|
|
15177
|
-
const componentName =
|
|
15618
|
+
const componentName = path14.basename(componentDir).trim().toLowerCase();
|
|
15178
15619
|
if (!componentName || componentName === "feature-base") continue;
|
|
15179
15620
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15180
15621
|
total += dirs.filter(
|
|
15181
|
-
(value) =>
|
|
15622
|
+
(value) => path14.basename(value) !== "feature-base"
|
|
15182
15623
|
).length;
|
|
15183
15624
|
}
|
|
15184
15625
|
return total;
|
|
@@ -15190,7 +15631,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15190
15631
|
ignoreDirs: ["node_modules"]
|
|
15191
15632
|
});
|
|
15192
15633
|
return files.some(
|
|
15193
|
-
(absolutePath) =>
|
|
15634
|
+
(absolutePath) => path14.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15194
15635
|
);
|
|
15195
15636
|
}
|
|
15196
15637
|
function finalizeChecks(checks) {
|
|
@@ -15207,17 +15648,17 @@ function finalizeChecks(checks) {
|
|
|
15207
15648
|
function printOnboardResult(lang, result) {
|
|
15208
15649
|
console.log();
|
|
15209
15650
|
console.log(
|
|
15210
|
-
|
|
15651
|
+
chalk9.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks"))
|
|
15211
15652
|
);
|
|
15212
15653
|
for (const check of result.checks) {
|
|
15213
|
-
const mark = check.status === "ok" ?
|
|
15214
|
-
const level = check.status === "ok" ?
|
|
15654
|
+
const mark = check.status === "ok" ? chalk9.green("\u2705") : check.status === "warn" ? chalk9.yellow("\u26A0\uFE0F") : chalk9.red("\u274C");
|
|
15655
|
+
const level = check.status === "ok" ? chalk9.green("OK") : check.status === "warn" ? chalk9.yellow("WARN") : chalk9.red("BLOCK");
|
|
15215
15656
|
console.log(`${mark} [${level}] ${check.title}`);
|
|
15216
15657
|
console.log(` ${check.message}`);
|
|
15217
|
-
if (check.path) console.log(
|
|
15658
|
+
if (check.path) console.log(chalk9.gray(` path: ${check.path}`));
|
|
15218
15659
|
if (check.suggestedCommand) {
|
|
15219
15660
|
console.log(
|
|
15220
|
-
|
|
15661
|
+
chalk9.gray(
|
|
15221
15662
|
` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`
|
|
15222
15663
|
)
|
|
15223
15664
|
);
|
|
@@ -15225,7 +15666,7 @@ function printOnboardResult(lang, result) {
|
|
|
15225
15666
|
}
|
|
15226
15667
|
console.log();
|
|
15227
15668
|
console.log(
|
|
15228
|
-
|
|
15669
|
+
chalk9.bold(
|
|
15229
15670
|
t(
|
|
15230
15671
|
lang,
|
|
15231
15672
|
`\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
|
|
@@ -15235,13 +15676,13 @@ function printOnboardResult(lang, result) {
|
|
|
15235
15676
|
);
|
|
15236
15677
|
if (result.status === "ready") {
|
|
15237
15678
|
console.log(
|
|
15238
|
-
|
|
15679
|
+
chalk9.green(
|
|
15239
15680
|
t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")
|
|
15240
15681
|
)
|
|
15241
15682
|
);
|
|
15242
15683
|
} else if (result.status === "needs_action") {
|
|
15243
15684
|
console.log(
|
|
15244
|
-
|
|
15685
|
+
chalk9.yellow(
|
|
15245
15686
|
t(
|
|
15246
15687
|
lang,
|
|
15247
15688
|
"\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15251,7 +15692,7 @@ function printOnboardResult(lang, result) {
|
|
|
15251
15692
|
);
|
|
15252
15693
|
} else {
|
|
15253
15694
|
console.log(
|
|
15254
|
-
|
|
15695
|
+
chalk9.red(
|
|
15255
15696
|
t(
|
|
15256
15697
|
lang,
|
|
15257
15698
|
"\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
@@ -15359,7 +15800,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15359
15800
|
});
|
|
15360
15801
|
}
|
|
15361
15802
|
}
|
|
15362
|
-
const constitutionPath =
|
|
15803
|
+
const constitutionPath = path14.join(docsDir, "agents", "constitution.md");
|
|
15363
15804
|
if (!await fs.pathExists(constitutionPath)) {
|
|
15364
15805
|
checks.push({
|
|
15365
15806
|
id: "constitution_exists",
|
|
@@ -15401,7 +15842,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15401
15842
|
});
|
|
15402
15843
|
}
|
|
15403
15844
|
}
|
|
15404
|
-
const customPath =
|
|
15845
|
+
const customPath = path14.join(docsDir, "agents", "custom.md");
|
|
15405
15846
|
if (await fs.pathExists(customPath)) {
|
|
15406
15847
|
const content = await fs.readFile(customPath, "utf-8");
|
|
15407
15848
|
if (hasTemplateMarkers(content)) {
|
|
@@ -15430,7 +15871,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15430
15871
|
});
|
|
15431
15872
|
}
|
|
15432
15873
|
}
|
|
15433
|
-
const prdDir =
|
|
15874
|
+
const prdDir = path14.join(docsDir, "prd");
|
|
15434
15875
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
15435
15876
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
15436
15877
|
if (!prdReady) {
|
|
@@ -15448,7 +15889,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15448
15889
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
15449
15890
|
),
|
|
15450
15891
|
path: prdDir,
|
|
15451
|
-
suggestedCommand: `touch ${quotePath(
|
|
15892
|
+
suggestedCommand: `touch ${quotePath(path14.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
15452
15893
|
});
|
|
15453
15894
|
} else {
|
|
15454
15895
|
checks.push({
|
|
@@ -15589,8 +16030,8 @@ function onboardCommand(program2) {
|
|
|
15589
16030
|
);
|
|
15590
16031
|
} else {
|
|
15591
16032
|
console.error(
|
|
15592
|
-
|
|
15593
|
-
|
|
16033
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
16034
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
15594
16035
|
);
|
|
15595
16036
|
printCliErrorSuggestions(suggestions, lang);
|
|
15596
16037
|
}
|
|
@@ -15633,6 +16074,14 @@ function asNonEmptyString(value, fallback) {
|
|
|
15633
16074
|
const trimmed = value.trim();
|
|
15634
16075
|
return trimmed || fallback;
|
|
15635
16076
|
}
|
|
16077
|
+
function asRequiredNonEmptyString(value, field) {
|
|
16078
|
+
const normalized = asNonEmptyString(value, "");
|
|
16079
|
+
if (normalized) return normalized;
|
|
16080
|
+
throw createCliError(
|
|
16081
|
+
"VALIDATION_FAILED",
|
|
16082
|
+
`Evidence JSON ${field} is required.`
|
|
16083
|
+
);
|
|
16084
|
+
}
|
|
15636
16085
|
function asRequiredBoolean(value, field) {
|
|
15637
16086
|
if (typeof value === "boolean") return value;
|
|
15638
16087
|
throw createCliError(
|
|
@@ -15711,6 +16160,61 @@ function normalizeCommandsExecuted(value) {
|
|
|
15711
16160
|
return entry.trim();
|
|
15712
16161
|
});
|
|
15713
16162
|
}
|
|
16163
|
+
function normalizeRequiredPathList(value, field) {
|
|
16164
|
+
if (!Array.isArray(value)) {
|
|
16165
|
+
throw createCliError(
|
|
16166
|
+
"VALIDATION_FAILED",
|
|
16167
|
+
`Evidence JSON ${field} must be a non-empty array of repo-relative paths.`
|
|
16168
|
+
);
|
|
16169
|
+
}
|
|
16170
|
+
const out = value.map((entry, index) => {
|
|
16171
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
16172
|
+
throw createCliError(
|
|
16173
|
+
"VALIDATION_FAILED",
|
|
16174
|
+
`Evidence JSON ${field}[${index}] must be a non-empty repo-relative path string.`
|
|
16175
|
+
);
|
|
16176
|
+
}
|
|
16177
|
+
return normalizeGitPath3(entry);
|
|
16178
|
+
}).filter(Boolean);
|
|
16179
|
+
if (out.length === 0) {
|
|
16180
|
+
throw createCliError(
|
|
16181
|
+
"VALIDATION_FAILED",
|
|
16182
|
+
`Evidence JSON ${field} must include at least one repo-relative path.`
|
|
16183
|
+
);
|
|
16184
|
+
}
|
|
16185
|
+
return uniquePaths(out);
|
|
16186
|
+
}
|
|
16187
|
+
function normalizeRiskSummaries(value) {
|
|
16188
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16189
|
+
throw createCliError(
|
|
16190
|
+
"VALIDATION_FAILED",
|
|
16191
|
+
'Evidence JSON "riskSummaries" is required and must include blocking, important, and minor fields.'
|
|
16192
|
+
);
|
|
16193
|
+
}
|
|
16194
|
+
const riskSummaries = value;
|
|
16195
|
+
return {
|
|
16196
|
+
blocking: asRequiredReviewField(
|
|
16197
|
+
riskSummaries.blocking,
|
|
16198
|
+
'"riskSummaries.blocking"',
|
|
16199
|
+
{ allowExplicitNone: true }
|
|
16200
|
+
),
|
|
16201
|
+
important: asRequiredReviewField(
|
|
16202
|
+
riskSummaries.important,
|
|
16203
|
+
'"riskSummaries.important"',
|
|
16204
|
+
{ allowExplicitNone: true }
|
|
16205
|
+
),
|
|
16206
|
+
minor: asRequiredReviewField(
|
|
16207
|
+
riskSummaries.minor,
|
|
16208
|
+
'"riskSummaries.minor"',
|
|
16209
|
+
{ allowExplicitNone: true }
|
|
16210
|
+
)
|
|
16211
|
+
};
|
|
16212
|
+
}
|
|
16213
|
+
function formatMissingPathGuidance(field, missingFiles) {
|
|
16214
|
+
return `Evidence JSON ${field} is missing these repo-relative paths:
|
|
16215
|
+
${missingFiles.map((file) => `- ${file}`).join("\n")}
|
|
16216
|
+
Use paths relative to the project git root, matching files.path entries.`;
|
|
16217
|
+
}
|
|
15714
16218
|
function normalizeGitPath3(value) {
|
|
15715
16219
|
return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
15716
16220
|
}
|
|
@@ -15823,7 +16327,7 @@ var PrePrReviewValidator = class {
|
|
|
15823
16327
|
return result.evidence;
|
|
15824
16328
|
}
|
|
15825
16329
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
15826
|
-
const fullPath =
|
|
16330
|
+
const fullPath = path14.resolve(evidencePath);
|
|
15827
16331
|
if (!await fs.pathExists(fullPath)) {
|
|
15828
16332
|
throw createCliError(
|
|
15829
16333
|
"INVALID_ARGUMENT",
|
|
@@ -15866,6 +16370,27 @@ var PrePrReviewValidator = class {
|
|
|
15866
16370
|
evidence.blockingFindings,
|
|
15867
16371
|
'"blockingFindings"'
|
|
15868
16372
|
),
|
|
16373
|
+
baseSha: asRequiredNonEmptyString(evidence.baseSha, '"baseSha"'),
|
|
16374
|
+
headSha: asRequiredNonEmptyString(evidence.headSha, '"headSha"'),
|
|
16375
|
+
changedFiles: normalizeRequiredPathList(
|
|
16376
|
+
evidence.changedFiles,
|
|
16377
|
+
'"changedFiles"'
|
|
16378
|
+
),
|
|
16379
|
+
reviewedFiles: normalizeRequiredPathList(
|
|
16380
|
+
evidence.reviewedFiles,
|
|
16381
|
+
'"reviewedFiles"'
|
|
16382
|
+
),
|
|
16383
|
+
riskSummaries: normalizeRiskSummaries(evidence.riskSummaries),
|
|
16384
|
+
approvalRationale: asRequiredReviewField(
|
|
16385
|
+
evidence.approvalRationale,
|
|
16386
|
+
'"approvalRationale"'
|
|
16387
|
+
),
|
|
16388
|
+
coverage: {
|
|
16389
|
+
changedFileCount: 0,
|
|
16390
|
+
reviewedFileCount: 0,
|
|
16391
|
+
reviewCoverageRatio: 0,
|
|
16392
|
+
missingFiles: []
|
|
16393
|
+
},
|
|
15869
16394
|
files: normalizeEvidenceFiles(evidence.files),
|
|
15870
16395
|
residualRisks: normalizeResidualRisks(evidence.residualRisks),
|
|
15871
16396
|
commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
|
|
@@ -15900,9 +16425,9 @@ var PrePrReviewValidator = class {
|
|
|
15900
16425
|
]);
|
|
15901
16426
|
const reviewedFiles = new Set(
|
|
15902
16427
|
normalizedEvidence.files.map(
|
|
15903
|
-
(f) =>
|
|
16428
|
+
(f) => path14.relative(
|
|
15904
16429
|
projectRoot,
|
|
15905
|
-
|
|
16430
|
+
path14.resolve(projectRoot, f.path)
|
|
15906
16431
|
)
|
|
15907
16432
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
15908
16433
|
);
|
|
@@ -15914,6 +16439,36 @@ var PrePrReviewValidator = class {
|
|
|
15914
16439
|
${missingFiles.map((f) => `- ${f}`).join("\n")}`
|
|
15915
16440
|
);
|
|
15916
16441
|
}
|
|
16442
|
+
const changedFilesInEvidence = new Set(
|
|
16443
|
+
normalizedEvidence.changedFiles.map((entry) => normalizeGitPath3(entry))
|
|
16444
|
+
);
|
|
16445
|
+
const missingChangedFiles = changedFiles.filter(
|
|
16446
|
+
(file) => !changedFilesInEvidence.has(file)
|
|
16447
|
+
);
|
|
16448
|
+
if (missingChangedFiles.length > 0) {
|
|
16449
|
+
throw createCliError(
|
|
16450
|
+
"VALIDATION_FAILED",
|
|
16451
|
+
formatMissingPathGuidance('"changedFiles"', missingChangedFiles)
|
|
16452
|
+
);
|
|
16453
|
+
}
|
|
16454
|
+
const reviewedFilesInEvidence = new Set(
|
|
16455
|
+
normalizedEvidence.reviewedFiles.map((entry) => normalizeGitPath3(entry))
|
|
16456
|
+
);
|
|
16457
|
+
const missingReviewedFiles = changedFiles.filter(
|
|
16458
|
+
(file) => !reviewedFilesInEvidence.has(file)
|
|
16459
|
+
);
|
|
16460
|
+
if (missingReviewedFiles.length > 0) {
|
|
16461
|
+
throw createCliError(
|
|
16462
|
+
"VALIDATION_FAILED",
|
|
16463
|
+
formatMissingPathGuidance('"reviewedFiles"', missingReviewedFiles)
|
|
16464
|
+
);
|
|
16465
|
+
}
|
|
16466
|
+
normalizedEvidence.coverage = {
|
|
16467
|
+
changedFileCount: changedFiles.length,
|
|
16468
|
+
reviewedFileCount: normalizedEvidence.reviewedFiles.length,
|
|
16469
|
+
reviewCoverageRatio: changedFiles.length === 0 ? 1 : normalizedEvidence.reviewedFiles.length / changedFiles.length,
|
|
16470
|
+
missingFiles: missingReviewedFiles
|
|
16471
|
+
};
|
|
15917
16472
|
return {
|
|
15918
16473
|
evidence: normalizedEvidence,
|
|
15919
16474
|
scope
|
|
@@ -16048,11 +16603,27 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
|
|
|
16048
16603
|
specAlignmentChecked: true,
|
|
16049
16604
|
findingCount: 0,
|
|
16050
16605
|
blockingFindings: 0,
|
|
16606
|
+
baseSha: "unrecorded",
|
|
16607
|
+
headSha: "unrecorded",
|
|
16608
|
+
changedFiles: [],
|
|
16609
|
+
reviewedFiles: [],
|
|
16610
|
+
riskSummaries: {
|
|
16611
|
+
blocking: "none",
|
|
16612
|
+
important: "manual direct record without structured evidence",
|
|
16613
|
+
minor: "manual direct record without structured evidence"
|
|
16614
|
+
},
|
|
16615
|
+
approvalRationale: "manual direct record without structured evidence; not valid for approve",
|
|
16616
|
+
coverage: {
|
|
16617
|
+
changedFileCount: 0,
|
|
16618
|
+
reviewedFileCount: 0,
|
|
16619
|
+
reviewCoverageRatio: 0,
|
|
16620
|
+
missingFiles: []
|
|
16621
|
+
},
|
|
16051
16622
|
files: [],
|
|
16052
16623
|
residualRisks: ["none"],
|
|
16053
16624
|
commandsExecuted: []
|
|
16054
16625
|
};
|
|
16055
|
-
function
|
|
16626
|
+
function escapeRegExp6(value) {
|
|
16056
16627
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16057
16628
|
}
|
|
16058
16629
|
function normalizeDecision(raw) {
|
|
@@ -16066,14 +16637,14 @@ function normalizeDecision(raw) {
|
|
|
16066
16637
|
return null;
|
|
16067
16638
|
}
|
|
16068
16639
|
function findSpecLineIndex(lines, keys) {
|
|
16069
|
-
const escaped = keys.map((key) =>
|
|
16640
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16070
16641
|
const re = new RegExp(
|
|
16071
16642
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16072
16643
|
);
|
|
16073
16644
|
return lines.findIndex((line) => re.test(line));
|
|
16074
16645
|
}
|
|
16075
16646
|
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
16076
|
-
const escaped = keys.map((key) =>
|
|
16647
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16077
16648
|
const re = new RegExp(
|
|
16078
16649
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16079
16650
|
);
|
|
@@ -16163,11 +16734,28 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
|
|
|
16163
16734
|
- **Spec Alignment Checked**: ${input.evidence.specAlignmentChecked ? "yes" : "no"}
|
|
16164
16735
|
- **Finding Count**: ${input.evidence.findingCount}
|
|
16165
16736
|
- **Blocking Findings**: ${input.evidence.blockingFindings}
|
|
16737
|
+
- **Base SHA**: ${input.evidence.baseSha}
|
|
16738
|
+
- **Head SHA**: ${input.evidence.headSha}
|
|
16739
|
+
- **Approval Rationale**: ${input.evidence.approvalRationale}
|
|
16166
16740
|
${commandsRun}
|
|
16167
16741
|
|
|
16168
16742
|
- **Residual Risks**:
|
|
16169
16743
|
${residualRisksSection}
|
|
16170
16744
|
|
|
16745
|
+
- **Risk Summaries**:
|
|
16746
|
+
- Blocking: ${input.evidence.riskSummaries.blocking}
|
|
16747
|
+
- Important: ${input.evidence.riskSummaries.important}
|
|
16748
|
+
- Minor: ${input.evidence.riskSummaries.minor}
|
|
16749
|
+
|
|
16750
|
+
- **Evidence Coverage**:
|
|
16751
|
+
- Changed File Count: ${input.evidence.coverage.changedFileCount}
|
|
16752
|
+
- Reviewed File Count: ${input.evidence.coverage.reviewedFileCount}
|
|
16753
|
+
- Review Coverage Ratio: ${input.evidence.coverage.reviewCoverageRatio.toFixed(2)}
|
|
16754
|
+
- Reviewed Files:
|
|
16755
|
+
${input.evidence.reviewedFiles.length > 0 ? input.evidence.reviewedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)"}
|
|
16756
|
+
- Evidence Changed Files:
|
|
16757
|
+
${input.evidence.changedFiles.length > 0 ? input.evidence.changedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)"}
|
|
16758
|
+
|
|
16171
16759
|
- **Review Scope**:
|
|
16172
16760
|
- **Main Base Ref**: ${input.scope.baseRef}
|
|
16173
16761
|
- **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
|
|
@@ -16221,7 +16809,7 @@ function prePrReviewCommand(program2) {
|
|
|
16221
16809
|
})
|
|
16222
16810
|
);
|
|
16223
16811
|
} else {
|
|
16224
|
-
console.error(
|
|
16812
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16225
16813
|
printCliErrorSuggestions(suggestions, lang);
|
|
16226
16814
|
}
|
|
16227
16815
|
process.exitCode = 1;
|
|
@@ -16233,7 +16821,7 @@ function prePrReviewCommand(program2) {
|
|
|
16233
16821
|
"Decision outcome: approve | changes_requested | blocked"
|
|
16234
16822
|
).option("--note <text>", "Decision note text").option(
|
|
16235
16823
|
"--evidence <path>",
|
|
16236
|
-
"
|
|
16824
|
+
"Structured review evidence path (for example review-trace.json); required for approve and whenever policy demands it"
|
|
16237
16825
|
).option("--json", "Output JSON").action(
|
|
16238
16826
|
async (featureName, options) => {
|
|
16239
16827
|
try {
|
|
@@ -16253,7 +16841,7 @@ function prePrReviewCommand(program2) {
|
|
|
16253
16841
|
})
|
|
16254
16842
|
);
|
|
16255
16843
|
} else {
|
|
16256
|
-
console.error(
|
|
16844
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16257
16845
|
printCliErrorSuggestions(suggestions, lang);
|
|
16258
16846
|
}
|
|
16259
16847
|
process.exitCode = 1;
|
|
@@ -16300,7 +16888,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16300
16888
|
);
|
|
16301
16889
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16302
16890
|
const preferred = getPreferredKeys(config.lang);
|
|
16303
|
-
const tasksPath =
|
|
16891
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16304
16892
|
let tasksUpdated = false;
|
|
16305
16893
|
if (await fs.pathExists(tasksPath)) {
|
|
16306
16894
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16347,11 +16935,9 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16347
16935
|
handoffOnly: true,
|
|
16348
16936
|
advancesWorkflow: false,
|
|
16349
16937
|
reuseKey: `pre-pr:${featureRef}`,
|
|
16350
|
-
suggestedParallelism: 1,
|
|
16351
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
16352
16938
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
16353
16939
|
delegatedWorkRequired: true,
|
|
16354
|
-
nextMainState: "
|
|
16940
|
+
nextMainState: "pre_pr_review_in_progress",
|
|
16355
16941
|
evidenceFile: "review-trace.json",
|
|
16356
16942
|
tasksUpdated,
|
|
16357
16943
|
tasksPath,
|
|
@@ -16370,26 +16956,26 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16370
16956
|
console.log(prompt);
|
|
16371
16957
|
console.log();
|
|
16372
16958
|
console.log(
|
|
16373
|
-
|
|
16374
|
-
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. review-trace.json\uC744 \uC9C1\uC811 \uC0DD\uC131\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the review handoff. It does not
|
|
16959
|
+
chalk9.yellow(
|
|
16960
|
+
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. review-trace.json\uC744 \uC9C1\uC811 \uC0DD\uC131\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the review handoff. It does not complete the review or advance workflow state by itself."
|
|
16375
16961
|
)
|
|
16376
16962
|
);
|
|
16377
16963
|
console.log(
|
|
16378
|
-
|
|
16964
|
+
chalk9.gray(
|
|
16379
16965
|
config.lang === "ko" ? "- \uAE30\uC874 pre-PR \uB9AC\uBDF0 \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC788\uC73C\uBA74 \uC7AC\uC0AC\uC6A9\uD558\uACE0, \uAE30\uBCF8\uC740 1\uAC1C\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694." : "- Reuse the existing pre-PR helper agent if one already exists; default to a single helper agent."
|
|
16380
16966
|
)
|
|
16381
16967
|
);
|
|
16382
16968
|
console.log(
|
|
16383
|
-
|
|
16969
|
+
chalk9.gray(
|
|
16384
16970
|
config.lang === "ko" ? "- \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8 \uD55C\uB3C4\uC5D0 \uAC78\uB9AC\uBA74 \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uC5D0\uC11C \uB9AC\uBDF0\uB97C \uC774\uC5B4\uAC00\uC138\uC694." : "- If helper-agent quota is exhausted, continue the review in the main agent."
|
|
16385
16971
|
)
|
|
16386
16972
|
);
|
|
16387
16973
|
console.log(`Reuse key: pre-pr:${featureRef}`);
|
|
16388
16974
|
console.log(`Suggested parallelism: 1`);
|
|
16389
|
-
console.log(`Next main state:
|
|
16975
|
+
console.log(`Next main state: pre_pr_review_in_progress`);
|
|
16390
16976
|
console.log(`Evidence file: review-trace.json`);
|
|
16391
16977
|
console.log(
|
|
16392
|
-
config.lang === "ko" ? "Next required: delegated review\uB97C \uC774\uC5B4\uC11C \uC218\uD589\uD558\uACE0 review-trace.json\uC744 \uB9CC\uB4E0 \uB4A4 pre-pr-review\uB85C \uAE30\uB85D" : "Next required: continue the delegated review, generate review-trace.json, then record the result with pre-pr-review"
|
|
16978
|
+
config.lang === "ko" ? "Next required: delegated review\uB97C \uC774\uC5B4\uC11C \uC218\uD589\uD558\uACE0 \uAD6C\uC870\uD654\uB41C review-trace.json\uC744 \uB9CC\uB4E0 \uB4A4 pre-pr-review\uB85C \uAE30\uB85D" : "Next required: continue the delegated review, generate structured review-trace.json, then record the result with pre-pr-review"
|
|
16393
16979
|
);
|
|
16394
16980
|
if (tasksUpdated) {
|
|
16395
16981
|
console.log(`tasks.md updated: ${tasksPath}`);
|
|
@@ -16411,7 +16997,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16411
16997
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
16412
16998
|
);
|
|
16413
16999
|
}
|
|
16414
|
-
const tasksPath =
|
|
17000
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16415
17001
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
16416
17002
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16417
17003
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -16444,6 +17030,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
16444
17030
|
"`--evidence <path>` is required when workflow.prePrReview.enforceExecutionEvidence=true."
|
|
16445
17031
|
);
|
|
16446
17032
|
}
|
|
17033
|
+
if (decision === "approve" && !options.evidence) {
|
|
17034
|
+
throw createCliError(
|
|
17035
|
+
"INVALID_ARGUMENT",
|
|
17036
|
+
"`--evidence <path>` is required for approve decisions. Generate structured review evidence first, for example `--evidence review-trace.json`."
|
|
17037
|
+
);
|
|
17038
|
+
}
|
|
16447
17039
|
if (options.evidence) {
|
|
16448
17040
|
const validator = new PrePrReviewValidator(ctx);
|
|
16449
17041
|
const validationResult = await validator.validateEvidenceWithScope(
|
|
@@ -16503,7 +17095,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16503
17095
|
}
|
|
16504
17096
|
}
|
|
16505
17097
|
}
|
|
16506
|
-
const decisionsPath =
|
|
17098
|
+
const decisionsPath = path14.join(feature.path, "decisions.md");
|
|
16507
17099
|
const decisionLogEntry = buildReportContent({
|
|
16508
17100
|
folderName: feature.folderName,
|
|
16509
17101
|
date,
|
|
@@ -16519,9 +17111,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
16519
17111
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
16520
17112
|
}
|
|
16521
17113
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
16522
|
-
|
|
17114
|
+
path14.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
16523
17115
|
);
|
|
16524
|
-
const evidencePath =
|
|
17116
|
+
const evidencePath = path14.basename(config.docsDir) === "docs" ? normalizePathForDoc(path14.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
16525
17117
|
let nextTasks = tasksContent;
|
|
16526
17118
|
nextTasks = upsertSpecLine(
|
|
16527
17119
|
nextTasks,
|
|
@@ -16568,26 +17160,26 @@ async function runPrePrReview(featureName, options) {
|
|
|
16568
17160
|
return;
|
|
16569
17161
|
}
|
|
16570
17162
|
console.log();
|
|
16571
|
-
console.log(
|
|
16572
|
-
console.log(
|
|
16573
|
-
console.log(
|
|
17163
|
+
console.log(chalk9.green(`\u2705 pre-pr-review completed: ${feature.folderName}`));
|
|
17164
|
+
console.log(chalk9.gray(`- Decision: ${decision}`));
|
|
17165
|
+
console.log(chalk9.gray(`- Decisions log: ${decisionsPath}`));
|
|
16574
17166
|
if (nextTasks !== tasksContent) {
|
|
16575
|
-
console.log(
|
|
17167
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
16576
17168
|
}
|
|
16577
17169
|
console.log();
|
|
16578
17170
|
}
|
|
16579
|
-
function
|
|
17171
|
+
function escapeRegExp7(value) {
|
|
16580
17172
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16581
17173
|
}
|
|
16582
17174
|
function findSpecLineIndex2(lines, keys) {
|
|
16583
|
-
const escaped = keys.map((key) =>
|
|
17175
|
+
const escaped = keys.map((key) => escapeRegExp7(key));
|
|
16584
17176
|
const re = new RegExp(
|
|
16585
17177
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16586
17178
|
);
|
|
16587
17179
|
return lines.findIndex((line) => re.test(line));
|
|
16588
17180
|
}
|
|
16589
17181
|
function replaceSpecLine2(line, keys, preferredKey, value) {
|
|
16590
|
-
const escaped = keys.map((key) =>
|
|
17182
|
+
const escaped = keys.map((key) => escapeRegExp7(key));
|
|
16591
17183
|
const re = new RegExp(
|
|
16592
17184
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16593
17185
|
);
|
|
@@ -16662,7 +17254,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16662
17254
|
);
|
|
16663
17255
|
}
|
|
16664
17256
|
const feature = state.matchedFeature;
|
|
16665
|
-
const tasksPath =
|
|
17257
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16666
17258
|
let tasksUpdated = false;
|
|
16667
17259
|
if (await fs.pathExists(tasksPath)) {
|
|
16668
17260
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16692,12 +17284,10 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16692
17284
|
handoffOnly: true,
|
|
16693
17285
|
advancesWorkflow: false,
|
|
16694
17286
|
reuseKey: `code-review:${feature.folderName}`,
|
|
16695
|
-
suggestedParallelism: 1,
|
|
16696
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
16697
17287
|
nextMainState: "code_review_running",
|
|
16698
17288
|
tasksUpdated,
|
|
16699
17289
|
tasksPath,
|
|
16700
|
-
decisionsPath:
|
|
17290
|
+
decisionsPath: path14.join(feature.path, "decisions.md"),
|
|
16701
17291
|
prompt,
|
|
16702
17292
|
recordedAt: getLocalDateString()
|
|
16703
17293
|
};
|
|
@@ -16708,30 +17298,24 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
16708
17298
|
console.log(prompt);
|
|
16709
17299
|
console.log();
|
|
16710
17300
|
console.log(
|
|
16711
|
-
|
|
17301
|
+
chalk9.yellow(
|
|
16712
17302
|
config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 PR \uB9AC\uBDF0 \uCF54\uBA58\uD2B8 \uB300\uC751\uC6A9 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. \uCF54\uBA58\uD2B8\uB97C \uC9C1\uC811 \uC77D\uC5B4\uC624\uAC70\uB098 evidence/decision\uC744 \uC790\uB3D9 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC73C\uBA70, \uC0C1\uD0DC\uB3C4 \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares a handoff for addressing PR review comments. It does not fetch comments automatically, record review evidence/decision, or advance workflow state by itself."
|
|
16713
17303
|
)
|
|
16714
17304
|
);
|
|
16715
|
-
console.log(
|
|
16716
|
-
console.log(
|
|
16717
|
-
console.log(
|
|
16718
|
-
console.log(
|
|
16719
|
-
console.log(
|
|
16720
|
-
chalk8.gray(
|
|
16721
|
-
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
16722
|
-
)
|
|
16723
|
-
);
|
|
16724
|
-
console.log(chalk8.gray(`- next main state: ${payload.nextMainState}`));
|
|
17305
|
+
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17306
|
+
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17307
|
+
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17308
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
16725
17309
|
if (tasksUpdated) {
|
|
16726
|
-
console.log(
|
|
17310
|
+
console.log(chalk9.gray(`- tasks.md updated: ${payload.tasksPath}`));
|
|
16727
17311
|
console.log(
|
|
16728
|
-
|
|
17312
|
+
chalk9.gray(
|
|
16729
17313
|
config.lang === "ko" ? "- PR \uB9AC\uBDF0 \uC0C1\uD0DC: Running" : "- PR Review status: Running"
|
|
16730
17314
|
)
|
|
16731
17315
|
);
|
|
16732
17316
|
}
|
|
16733
|
-
console.log(
|
|
16734
|
-
console.log(
|
|
17317
|
+
console.log(chalk9.gray(`- tasks.md: ${payload.tasksPath}`));
|
|
17318
|
+
console.log(chalk9.gray(`- decisions.md: ${payload.decisionsPath}`));
|
|
16735
17319
|
}
|
|
16736
17320
|
function codeReviewRunCommand(program2) {
|
|
16737
17321
|
program2.command("code-review-run [feature-name]").description("Prepare a PR review execution handoff for sub-agent work").option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(
|
|
@@ -16753,7 +17337,7 @@ function codeReviewRunCommand(program2) {
|
|
|
16753
17337
|
})
|
|
16754
17338
|
);
|
|
16755
17339
|
} else {
|
|
16756
|
-
console.error(
|
|
17340
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
16757
17341
|
printCliErrorSuggestions(suggestions, lang);
|
|
16758
17342
|
}
|
|
16759
17343
|
process.exitCode = 1;
|
|
@@ -16788,8 +17372,8 @@ function requirementsCommand(program2) {
|
|
|
16788
17372
|
const lang = ctx?.config?.lang ?? DEFAULT_LANG;
|
|
16789
17373
|
const cliError = toCliError(error);
|
|
16790
17374
|
console.error(
|
|
16791
|
-
|
|
16792
|
-
|
|
17375
|
+
chalk9.red(tr(lang, "cli", "common.errorLabel")),
|
|
17376
|
+
chalk9.red(`[${cliError.code}] ${cliError.message}`)
|
|
16793
17377
|
);
|
|
16794
17378
|
process.exitCode = 1;
|
|
16795
17379
|
}
|
|
@@ -16822,7 +17406,7 @@ async function runRequirements(options) {
|
|
|
16822
17406
|
}
|
|
16823
17407
|
for (const feature of scan.features) {
|
|
16824
17408
|
if (!feature.docs.tasksExists) continue;
|
|
16825
|
-
const tasksPath =
|
|
17409
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16826
17410
|
let tasksContent = "";
|
|
16827
17411
|
try {
|
|
16828
17412
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -16956,10 +17540,10 @@ async function runRequirements(options) {
|
|
|
16956
17540
|
process.stdout.write(`${lines.join("\n")}
|
|
16957
17541
|
`);
|
|
16958
17542
|
if (options.write) {
|
|
16959
|
-
const outputPath =
|
|
17543
|
+
const outputPath = path14.join(docsDir, "prd", "status.md");
|
|
16960
17544
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
16961
17545
|
`, "utf-8");
|
|
16962
|
-
console.log(
|
|
17546
|
+
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
16963
17547
|
}
|
|
16964
17548
|
if (options.strict && issuesFound) process.exitCode = 1;
|
|
16965
17549
|
}
|
|
@@ -17041,7 +17625,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
17041
17625
|
}
|
|
17042
17626
|
async function runTaskRun(featureName, options) {
|
|
17043
17627
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
17044
|
-
const tasksPath =
|
|
17628
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17045
17629
|
if (!await fs.pathExists(tasksPath)) {
|
|
17046
17630
|
throw createCliError(
|
|
17047
17631
|
"PRECONDITION_FAILED",
|
|
@@ -17098,8 +17682,6 @@ async function runTaskRun(featureName, options) {
|
|
|
17098
17682
|
owner: "subagent",
|
|
17099
17683
|
handoffOnly: true,
|
|
17100
17684
|
reuseKey: `task:${feature.folderName}:${resolvedTask.taskId}`,
|
|
17101
|
-
suggestedParallelism: 1,
|
|
17102
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
17103
17685
|
nextMainState: "task_complete",
|
|
17104
17686
|
tasksUpdated,
|
|
17105
17687
|
tasksPath,
|
|
@@ -17112,20 +17694,14 @@ async function runTaskRun(featureName, options) {
|
|
|
17112
17694
|
}
|
|
17113
17695
|
console.log(prompt);
|
|
17114
17696
|
console.log();
|
|
17115
|
-
console.log(
|
|
17116
|
-
console.log(
|
|
17117
|
-
console.log(
|
|
17118
|
-
console.log(
|
|
17119
|
-
console.log(
|
|
17120
|
-
chalk8.gray(
|
|
17121
|
-
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
17122
|
-
)
|
|
17123
|
-
);
|
|
17124
|
-
console.log(chalk8.gray(`- next main state: ${payload.nextMainState}`));
|
|
17697
|
+
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17698
|
+
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17699
|
+
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17700
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17125
17701
|
if (tasksUpdated) {
|
|
17126
17702
|
console.log();
|
|
17127
|
-
console.log(
|
|
17128
|
-
console.log(
|
|
17703
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
17704
|
+
console.log(chalk9.gray(`- task status: TODO -> DOING`));
|
|
17129
17705
|
}
|
|
17130
17706
|
}
|
|
17131
17707
|
function taskRunCommand(program2) {
|
|
@@ -17149,7 +17725,7 @@ function taskRunCommand(program2) {
|
|
|
17149
17725
|
})
|
|
17150
17726
|
);
|
|
17151
17727
|
} else {
|
|
17152
|
-
console.error(
|
|
17728
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
17153
17729
|
printCliErrorSuggestions(suggestions, lang);
|
|
17154
17730
|
}
|
|
17155
17731
|
process.exitCode = 1;
|
|
@@ -17200,7 +17776,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
17200
17776
|
}
|
|
17201
17777
|
async function runTaskComplete(featureName, options) {
|
|
17202
17778
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
17203
|
-
const tasksPath =
|
|
17779
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17204
17780
|
if (!await fs.pathExists(tasksPath)) {
|
|
17205
17781
|
throw createCliError(
|
|
17206
17782
|
"PRECONDITION_FAILED",
|
|
@@ -17260,13 +17836,13 @@ async function runTaskComplete(featureName, options) {
|
|
|
17260
17836
|
return;
|
|
17261
17837
|
}
|
|
17262
17838
|
console.log(
|
|
17263
|
-
|
|
17839
|
+
chalk9.green(
|
|
17264
17840
|
`Marked task ${resolvedTask.taskId} as DONE for ${feature.folderName}.`
|
|
17265
17841
|
)
|
|
17266
17842
|
);
|
|
17267
|
-
console.log(
|
|
17268
|
-
console.log(
|
|
17269
|
-
console.log(
|
|
17843
|
+
console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
|
|
17844
|
+
console.log(chalk9.gray(`- status: ${resolvedTask.status} -> DONE`));
|
|
17845
|
+
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17270
17846
|
}
|
|
17271
17847
|
function taskCompleteCommand(program2) {
|
|
17272
17848
|
program2.command("task-complete [feature-name]").description("Mark the active DOING/REVIEW task as DONE").option("--component <component>", "Component name for multi projects").option("--task <task-id>", "Explicit task id to mark DONE").option("--json", "Output JSON").action(
|
|
@@ -17288,7 +17864,7 @@ function taskCompleteCommand(program2) {
|
|
|
17288
17864
|
})
|
|
17289
17865
|
);
|
|
17290
17866
|
} else {
|
|
17291
|
-
console.error(
|
|
17867
|
+
console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
|
|
17292
17868
|
printCliErrorSuggestions(suggestions, lang);
|
|
17293
17869
|
}
|
|
17294
17870
|
process.exitCode = 1;
|
|
@@ -17335,15 +17911,15 @@ function getBanner(opts) {
|
|
|
17335
17911
|
${version}
|
|
17336
17912
|
` : "\n";
|
|
17337
17913
|
if (process.stdout.isTTY) {
|
|
17338
|
-
return `${
|
|
17914
|
+
return `${chalk9.cyan(ascii)}${chalk9.gray(footer)}`;
|
|
17339
17915
|
}
|
|
17340
17916
|
return `${ascii}${footer}`;
|
|
17341
17917
|
}
|
|
17342
|
-
var CACHE_FILE =
|
|
17918
|
+
var CACHE_FILE = path14.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
17343
17919
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
17344
17920
|
function getCurrentVersion() {
|
|
17345
17921
|
try {
|
|
17346
|
-
const packageJsonPath =
|
|
17922
|
+
const packageJsonPath = path14.join(__dirname$1, "..", "package.json");
|
|
17347
17923
|
if (fs.existsSync(packageJsonPath)) {
|
|
17348
17924
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17349
17925
|
return pkg.version;
|
|
@@ -17377,8 +17953,8 @@ function resolveUpdateNoticeLang() {
|
|
|
17377
17953
|
}
|
|
17378
17954
|
function printUpdateNotice(current, latest, lang) {
|
|
17379
17955
|
console.log();
|
|
17380
|
-
console.log(
|
|
17381
|
-
console.log(
|
|
17956
|
+
console.log(chalk9.yellow(tr(lang, "cli", "versionCheck.noticeAvailable", { latest, current })));
|
|
17957
|
+
console.log(chalk9.gray(tr(lang, "cli", "versionCheck.updateCommand")));
|
|
17382
17958
|
console.log();
|
|
17383
17959
|
}
|
|
17384
17960
|
function spawnBackgroundVersionCheck() {
|
|
@@ -17447,7 +18023,7 @@ function shouldCheckForUpdates() {
|
|
|
17447
18023
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
17448
18024
|
function getCliVersion() {
|
|
17449
18025
|
try {
|
|
17450
|
-
const packageJsonPath =
|
|
18026
|
+
const packageJsonPath = path14.join(__dirname$1, "..", "package.json");
|
|
17451
18027
|
if (fs.existsSync(packageJsonPath)) {
|
|
17452
18028
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17453
18029
|
if (pkg?.version) return String(pkg.version);
|
|
@@ -17465,6 +18041,7 @@ if (shouldShowBanner()) {
|
|
|
17465
18041
|
}
|
|
17466
18042
|
initCommand(program);
|
|
17467
18043
|
featureCommand(program);
|
|
18044
|
+
ideaCommand(program);
|
|
17468
18045
|
statusCommand(program);
|
|
17469
18046
|
updateCommand(program);
|
|
17470
18047
|
configCommand(program);
|