lee-spec-kit 0.6.25 → 0.6.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +21 -3
- package/README.md +21 -3
- package/dist/index.js +975 -455
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/agents/agents.md +6 -1
- package/templates/en/common/agents/skills/create-pr.md +1 -1
- package/templates/en/common/agents/skills/execute-task.md +8 -2
- package/templates/en/common/features/README.md +0 -1
- package/templates/en/common/features/feature-base/tasks.md +1 -3
- package/templates/ko/common/agents/agents.md +6 -1
- package/templates/ko/common/agents/skills/create-pr.md +1 -1
- package/templates/ko/common/agents/skills/execute-task.md +8 -2
- package/templates/ko/common/features/README.md +0 -1
- package/templates/ko/common/features/feature-base/tasks.md +1 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path23 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
|
-
import
|
|
5
|
+
import fs17 from 'fs-extra';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
7
|
import chalk6 from 'chalk';
|
|
8
8
|
import { spawn, spawnSync, execFileSync, execSync } from 'child_process';
|
|
@@ -10,7 +10,7 @@ import os from 'os';
|
|
|
10
10
|
import { createHash, randomUUID } from 'crypto';
|
|
11
11
|
|
|
12
12
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
13
|
-
var getDirname = () =>
|
|
13
|
+
var getDirname = () => path23.dirname(getFilename());
|
|
14
14
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
15
15
|
async function walkFiles(rootDir, options = {}) {
|
|
16
16
|
const out = [];
|
|
@@ -21,9 +21,9 @@ async function walkFiles(rootDir, options = {}) {
|
|
|
21
21
|
(options.ignoreDirs || []).map((value) => value.trim().toLowerCase()).filter(Boolean)
|
|
22
22
|
);
|
|
23
23
|
async function visit(current) {
|
|
24
|
-
const entries = await
|
|
24
|
+
const entries = await fs17.readdir(current, { withFileTypes: true });
|
|
25
25
|
for (const entry of entries) {
|
|
26
|
-
const absolute =
|
|
26
|
+
const absolute = path23.join(current, entry.name);
|
|
27
27
|
if (entry.isDirectory()) {
|
|
28
28
|
if (ignored.has(entry.name.trim().toLowerCase())) continue;
|
|
29
29
|
await visit(absolute);
|
|
@@ -31,26 +31,26 @@ async function walkFiles(rootDir, options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
if (!entry.isFile()) continue;
|
|
33
33
|
if (normalizedExtensions.size > 0) {
|
|
34
|
-
const ext =
|
|
34
|
+
const ext = path23.extname(entry.name).toLowerCase();
|
|
35
35
|
if (!normalizedExtensions.has(ext)) continue;
|
|
36
36
|
}
|
|
37
37
|
out.push(absolute);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
if (await
|
|
40
|
+
if (await fs17.pathExists(rootDir)) {
|
|
41
41
|
await visit(rootDir);
|
|
42
42
|
}
|
|
43
43
|
return out;
|
|
44
44
|
}
|
|
45
45
|
async function listSubdirectories(rootDir) {
|
|
46
|
-
if (!await
|
|
47
|
-
const entries = await
|
|
48
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) =>
|
|
46
|
+
if (!await fs17.pathExists(rootDir)) return [];
|
|
47
|
+
const entries = await fs17.readdir(rootDir, { withFileTypes: true });
|
|
48
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => path23.join(rootDir, entry.name));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// src/utils/template.ts
|
|
52
52
|
async function copyTemplates(src, dest) {
|
|
53
|
-
await
|
|
53
|
+
await fs17.copy(src, dest, {
|
|
54
54
|
overwrite: true,
|
|
55
55
|
errorOnExist: false
|
|
56
56
|
});
|
|
@@ -66,22 +66,22 @@ function applyReplacements(content, replacements) {
|
|
|
66
66
|
async function replaceInFiles(dir, replacements) {
|
|
67
67
|
const files = await walkFiles(dir, { extensions: [".md"] });
|
|
68
68
|
for (const file of files) {
|
|
69
|
-
let content = await
|
|
69
|
+
let content = await fs17.readFile(file, "utf-8");
|
|
70
70
|
content = applyReplacements(content, replacements);
|
|
71
|
-
await
|
|
71
|
+
await fs17.writeFile(file, content, "utf-8");
|
|
72
72
|
}
|
|
73
73
|
const shFiles = await walkFiles(dir, { extensions: [".sh"] });
|
|
74
74
|
for (const file of shFiles) {
|
|
75
|
-
let content = await
|
|
75
|
+
let content = await fs17.readFile(file, "utf-8");
|
|
76
76
|
content = applyReplacements(content, replacements);
|
|
77
|
-
await
|
|
77
|
+
await fs17.writeFile(file, content, "utf-8");
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
81
|
-
var __dirname2 =
|
|
81
|
+
var __dirname2 = path23.dirname(__filename2);
|
|
82
82
|
function getTemplatesDir() {
|
|
83
|
-
const rootDir =
|
|
84
|
-
return
|
|
83
|
+
const rootDir = path23.resolve(__dirname2, "..");
|
|
84
|
+
return path23.join(rootDir, "templates");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// src/utils/locales/ko.ts
|
|
@@ -166,11 +166,13 @@ var ko = {
|
|
|
166
166
|
"context.autoRunUnavailable": "\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C\uB294 \uC790\uB3D9 \uC2E4\uD589\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
167
167
|
"context.autoRunSummary": "config \uAE30\uC900\uC73C\uB85C \uC2B9\uC778 \uD544\uC694 \uCE74\uD14C\uACE0\uB9AC \uC804\uAE4C\uC9C0 \uC5F0\uC18D \uC2E4\uD589\uD558\uC138\uC694: {categories}",
|
|
168
168
|
"context.autoRunCommandHint": "\uC790\uB3D9 \uC2E4\uD589 \uBA85\uB839(config \uAC8C\uC774\uD2B8): {command}",
|
|
169
|
+
"context.subAgentOrchestrationHint": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158: \uC9E7\uC740 \uB2E8\uACC4\uB294 \uBA54\uC778\uC774 \uC9C1\uC811 \uC218\uD589\uD558\uACE0, \uC7A5\uC2DC\uAC04 \uB8E8\uD504(task_execute/code_review/review_fix_commit/pre_pr_review \uB610\uB294 auto)\uB294 \uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC704\uC784\uD558\uC138\uC694.",
|
|
169
170
|
"context.commandDetail.branchCreateWithWorktree": "({scope}) worktree {worktree}\uB97C \uC0AC\uC6A9\uD574 \uBE0C\uB79C\uCE58 {branch}\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
170
171
|
"context.commandDetail.branchCreateWithBranch": "({scope}) \uBE0C\uB79C\uCE58 {branch}\uC6A9 worktree\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
171
172
|
"context.commandDetail.branchCreateGeneric": "({scope}) feature \uBE0C\uB79C\uCE58\uC6A9 worktree\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
172
173
|
"context.commandDetail.codeReviewMergeAfterOk": "({scope}) \uBA85\uC2DC\uC801 \uC2B9\uC778 \uD6C4 PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
|
|
173
174
|
"context.commandDetail.codeReviewPushFix": "({scope}) \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 push\uD558\uC138\uC694",
|
|
175
|
+
"context.commandDetail.prePrReviewRun": "({scope}) Pre-PR \uB9AC\uBDF0\uB97C \uC2E4\uD589\uD558\uACE0 decisions.md\uC640 tasks.md\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
|
|
174
176
|
"context.actionSummary.runDocsCommand": "\uBB38\uC11C \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
|
|
175
177
|
"context.actionSummary.runProjectCommand": "\uD504\uB85C\uC81D\uD2B8 \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
|
|
176
178
|
"context.actionDetail.featureFolder": "Feature \uD3F4\uB354\uC640 \uAE30\uBCF8 \uBB38\uC11C \uACE8\uACA9\uC744 \uC900\uBE44\uD558\uC138\uC694",
|
|
@@ -179,16 +181,38 @@ var ko = {
|
|
|
179
181
|
"context.actionDetail.planWrite": "plan.md\uB97C \uC791\uC131/\uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C \uB9DE\uCD94\uC138\uC694",
|
|
180
182
|
"context.actionDetail.planApprove": "plan.md\uB97C \uC2B9\uC778\uD569\uB2C8\uB2E4",
|
|
181
183
|
"context.actionDetail.tasksWrite": "tasks.md\uB97C \uC791\uC131/\uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C \uC815\uB82C\uD558\uC138\uC694",
|
|
184
|
+
"context.actionDetail.tasksWriteCreate": "tasks.md\uB97C \uC0DD\uC131\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
185
|
+
"context.actionDetail.tasksWriteNeedAtLeastOne": "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
186
|
+
"context.actionDetail.tasksWriteImprove": "tasks.md\uB97C \uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C \uC815\uB82C\uD558\uC138\uC694",
|
|
182
187
|
"context.actionDetail.tasksApprove": "tasks.md\uB97C \uC2B9\uC778\uD569\uB2C8\uB2E4",
|
|
183
188
|
"context.actionDetail.issueCreate": "\uC774\uC288\uB97C \uC0DD\uC131\uD558\uACE0 tasks.md\uC758 \uC774\uC288 \uC815\uBCF4\uB97C \uB9DE\uCD94\uC138\uC694",
|
|
189
|
+
"context.actionDetail.issueCreateAndWrite": "\uC774\uC288 \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC2B9\uC778(OK) \uD6C4 \uC774\uC288\uB97C \uC0DD\uC131\uD574 \uBC88\uD638\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
|
|
190
|
+
"context.actionDetail.issueCreatePrepareFromDoc": "issue.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
191
|
+
"context.actionDetail.issueCreateFromDoc": "Ready \uC0C1\uD0DC issue.md\uB85C \uC774\uC288\uB97C \uC0DD\uC131\uD558\uACE0 \uBC88\uD638\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
|
|
184
192
|
"context.actionDetail.taskExecute": "\uD604\uC7AC \uD0DC\uC2A4\uD06C\uB97C \uC9C4\uD589\uD558\uC138\uC694",
|
|
185
193
|
"context.actionDetail.reviewFixCommit": "\uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9 \uC694\uC57D\uC73C\uB85C \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uB9CC\uB4DC\uC138\uC694",
|
|
186
194
|
"context.actionDetail.prePrReview": "PR \uC804 \uB9AC\uBDF0\uB97C \uC218\uD589\uD558\uACE0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694",
|
|
187
195
|
"context.actionDetail.prCreate": "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC758 PR \uC815\uBCF4\uB97C \uB9DE\uCD94\uC138\uC694",
|
|
196
|
+
"context.actionDetail.prCreateRequiredSequence": "PR 2\uB2E8\uACC4(\uCD08\uC548/\uC2B9\uC778 \uD6C4 \uC0DD\uC131/\uB3D9\uAE30\uD654)\uB97C \uC21C\uC11C\uB300\uB85C \uC644\uB8CC\uD558\uC138\uC694",
|
|
197
|
+
"context.actionDetail.prCreatePrepareFromDoc": "pr.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
198
|
+
"context.actionDetail.prCreateExecuteFromDoc": "Ready \uC0C1\uD0DC pr.md\uB85C PR\uC744 \uC0DD\uC131\uD558\uACE0 \uB9C1\uD06C/\uC0C1\uD0DC\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
|
|
188
199
|
"context.actionDetail.prStatusUpdate": "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C \uCD5C\uC2E0\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
|
|
200
|
+
"context.actionDetail.prStatusUpdateSetReview": "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
201
|
+
"context.actionDetail.prStatusUpdateSyncApproved": "\uC6D0\uACA9 \uBA38\uC9C0 \uC0C1\uD0DC\uB97C \uBC18\uC601\uD574 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
|
|
189
202
|
"context.actionDetail.codeReview": "\uCF54\uB4DC \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D\uC744 \uBC18\uC601\uD558\uACE0 PR \uB9AC\uBDF0 \uC815\uBCF4\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
|
|
203
|
+
"context.actionDetail.codeReviewNeedEvidenceField": "tasks.md\uC5D0 PR \uB9AC\uBDF0 Evidence \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
204
|
+
"context.actionDetail.codeReviewNeedEvidence": "PR \uB9AC\uBDF0 Evidence \uC694\uC57D\uC744 \uAE30\uB85D\uD558\uC138\uC694",
|
|
205
|
+
"context.actionDetail.codeReviewNeedDecisionField": "tasks.md\uC5D0 PR \uB9AC\uBDF0 Decision \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
206
|
+
"context.actionDetail.codeReviewNeedDecision": "PR \uB9AC\uBDF0 Decision\uC744 \uAE30\uB85D\uD558\uC138\uC694",
|
|
207
|
+
"context.actionDetail.codeReviewResolve": "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uBC18\uC601\uD558\uACE0 PR \uB9AC\uBDF0 \uBB38\uC11C\uB97C \uCD5C\uC2E0\uD654\uD558\uC138\uC694",
|
|
208
|
+
"context.actionDetail.codeReviewNeedProjectRoot": "\uB9AC\uBDF0 \uC791\uC5C5\uC744 \uACC4\uC18D\uD558\uB824\uBA74 projectRoot\uB97C \uC124\uC815\uD558\uC138\uC694",
|
|
209
|
+
"context.actionDetail.codeReviewRemoteBlocked": "\uC6D0\uACA9 PR \uCC28\uB2E8 \uC0AC\uC720\uB97C \uD574\uC18C\uD55C \uB4A4 \uBA38\uC9C0\uB97C \uC9C4\uD589\uD558\uC138\uC694",
|
|
210
|
+
"context.actionDetail.codeReviewMergeAfterOk": "\uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
|
|
211
|
+
"context.actionDetail.codeReviewRequestReview": "\uB9AC\uBDF0 \uC694\uCCAD\uC744 \uC9C4\uD589\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC720\uC9C0\uD558\uC138\uC694",
|
|
190
212
|
"context.actionDetail.worktreeCleanup": "\uC644\uB8CC\uB41C feature worktree\uB97C \uC815\uB9AC\uD558\uC138\uC694",
|
|
191
213
|
"context.actionDetail.prMetadataMigrate": "tasks.md\uC758 PR \uD56D\uBAA9 \uD615\uC2DD\uC744 \uCD5C\uC2E0 \uD15C\uD50C\uB9BF\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
|
|
214
|
+
"context.actionDetail.prMetadataMigratePrFields": "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
215
|
+
"context.actionDetail.prMetadataMigratePrePrReviewField": "tasks.md\uC5D0 PR \uC804 \uB9AC\uBDF0 \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
192
216
|
"context.actionDetail.userRequestReplan": "\uC0C8 \uC0AC\uC6A9\uC790 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694",
|
|
193
217
|
"context.actionDetail.featureDone": "\uC774 Feature\uC758 \uC644\uB8CC \uC870\uAC74\uC774 \uBAA8\uB450 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
194
218
|
"context.actionDetail.fallback": "\uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694",
|
|
@@ -200,7 +224,7 @@ var ko = {
|
|
|
200
224
|
"context.suggestion.showOpen": "\uC9C4\uD589 \uC911 Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
201
225
|
"context.finalLabelCommandHint": "\uB77C\uBCA8\uC744 \uBC1B\uC73C\uBA74 \uC2B9\uC778 \uC120\uD0DD \uC2E4\uD589: {command}",
|
|
202
226
|
"context.finalTicketCommandHint": "\uBA85\uB839 \uC2E4\uD589\uC740 \uC2B9\uC778 \uACB0\uACFC\uC758 \uD2F0\uCF13\uC73C\uB85C \uC2E4\uD589: {command}",
|
|
203
|
-
"context.readBuiltinDocFirst": "\uC774\uBC88 \uC138\uC158\uC5D0 \uC544\uC9C1 \uC77D\uC9C0 \uC54A\uC558\uAC70\uB098 \uBCC0\uACBD \uAC00\uB2A5\uC131\uC774 \uC788\uC744 \uB54C\uB9CC \uD544\uC694\uD55C \uB0B4\uC7A5 \uBB38\uC11C\uB97C \uD655\uC778\uD558\uC138\uC694
|
|
227
|
+
"context.readBuiltinDocFirst": "\uC774\uBC88 \uC138\uC158\uC5D0 \uC544\uC9C1 \uC77D\uC9C0 \uC54A\uC558\uAC70\uB098 \uBCC0\uACBD \uAC00\uB2A5\uC131\uC774 \uC788\uC744 \uB54C\uB9CC \uD544\uC694\uD55C \uB0B4\uC7A5 \uBB38\uC11C\uB97C \uD655\uC778\uD558\uC138\uC694: {command}",
|
|
204
228
|
"context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 git-workflow \uAC00\uC774\uB4DC\uB97C \uAE30\uC900\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
|
|
205
229
|
"context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
|
|
206
230
|
"context.list.projectCommitNeeded": "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694",
|
|
@@ -209,7 +233,6 @@ var ko = {
|
|
|
209
233
|
"context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
|
|
210
234
|
"context.list.addPrePrReviewField": "Pre-PR Review \uD544\uB4DC \uCD94\uAC00",
|
|
211
235
|
"context.list.completePrePrReview": "Pre-PR \uB9AC\uBDF0 \uC644\uB8CC \uCC98\uB9AC",
|
|
212
|
-
"context.list.addPrePrFindings": "Pre-PR Findings \uAE30\uB85D",
|
|
213
236
|
"context.list.addPrePrEvidence": "Pre-PR Evidence \uADFC\uAC70 \uCD94\uAC00",
|
|
214
237
|
"context.list.addPrePrDecision": "Pre-PR Decision \uAE30\uB85D",
|
|
215
238
|
"context.list.resolvePrePrDecision": "Pre-PR Decision\uC744 approve\uB85C \uC815\uB9AC",
|
|
@@ -547,7 +570,6 @@ var ko = {
|
|
|
547
570
|
legacyTasksDocStatusField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. `\uBB38\uC11C \uC0C1\uD0DC` \uD544\uB4DC(Draft/Review/Approved)\uB97C \uCD94\uAC00\uD574 \uD0DC\uC2A4\uD06C \uC2B9\uC778 \uB2E8\uACC4\uB97C \uD65C\uC131\uD654\uD558\uC138\uC694.",
|
|
548
571
|
legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
549
572
|
legacyTasksPrePrReviewField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`)",
|
|
550
|
-
legacyTasksPrePrFindingsField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Findings` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0 Findings**: major=0, minor=0`)",
|
|
551
573
|
legacyTasksPrePrEvidenceField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
552
574
|
legacyTasksPrePrDecisionField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Decision` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0 Decision**: \uACB0\uC815: ...`)",
|
|
553
575
|
legacyTasksPrReviewEvidenceField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uB9AC\uBDF0 \uB2E8\uACC4 \uC804\uC5D0 `PR \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
@@ -566,10 +588,9 @@ var ko = {
|
|
|
566
588
|
workflowPrRemoteChecksPending: "\uC6D0\uACA9 PR \uCCB4\uD06C \uB300\uAE30\uAC00 {count}\uAC74 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCCB4\uD06C \uC644\uB8CC \uD6C4 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.",
|
|
567
589
|
workflowPrePrReviewMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC5D0 `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`\uC744 \uCD94\uAC00\uD558\uC138\uC694.)",
|
|
568
590
|
workflowPrePrReviewNotDone: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0`\uAC00 Done\uC774 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0 \uD6C4 Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
|
|
569
|
-
workflowPrePrFindingsMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Findings`\uAC00 \uC5C6\uAC70\uB098 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (`major=<n>, minor=<n>` \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D)",
|
|
570
591
|
workflowPrePrEvidenceMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (path_required \uC815\uCC45\uC774\uBA74 \uC2E4\uC81C \uC874\uC7AC\uD558\uB294 \uACBD\uB85C\uB97C \uAE30\uB85D\uD558\uC138\uC694.)",
|
|
571
592
|
workflowPrePrDecisionMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (`decision: approve|changes_requested|blocked ...` \uD615\uC2DD)",
|
|
572
|
-
workflowPrePrDecisionNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{outcome}`\uC785\uB2C8\uB2E4.
|
|
593
|
+
workflowPrePrDecisionNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{outcome}`\uC785\uB2C8\uB2E4. \uB9AC\uBDF0 \uB9AC\uC2A4\uD06C\uB97C \uD574\uC18C\uD55C \uB4A4 pre-pr-review\uB97C \uC7AC\uC2E4\uD589\uD574 `approve`\uB85C \uB9DE\uCD94\uC138\uC694."
|
|
573
594
|
}
|
|
574
595
|
};
|
|
575
596
|
var ko_default = ko;
|
|
@@ -656,11 +677,13 @@ var en = {
|
|
|
656
677
|
"context.autoRunUnavailable": "Auto-run is not available in the current context.",
|
|
657
678
|
"context.autoRunSummary": "Run continuously by config until approval-required categories appear: {categories}",
|
|
658
679
|
"context.autoRunCommandHint": "Auto-run command (config-based gate): {command}",
|
|
680
|
+
"context.subAgentOrchestrationHint": "Main-agent orchestration: keep short steps in the main agent, and delegate only long-running loops (task_execute/code_review/review_fix_commit/pre_pr_review or auto mode) to a sub-agent.",
|
|
659
681
|
"context.commandDetail.branchCreateWithWorktree": "({scope}) create or reuse worktree {worktree} for branch {branch}",
|
|
660
682
|
"context.commandDetail.branchCreateWithBranch": "({scope}) create or reuse worktree for branch {branch}",
|
|
661
683
|
"context.commandDetail.branchCreateGeneric": "({scope}) create or reuse feature branch worktree",
|
|
662
684
|
"context.commandDetail.codeReviewMergeAfterOk": "({scope}) merge PR after explicit OK",
|
|
663
685
|
"context.commandDetail.codeReviewPushFix": "({scope}) push review-fix commits",
|
|
686
|
+
"context.commandDetail.prePrReviewRun": "({scope}) run pre-PR review and sync decisions.md + tasks.md",
|
|
664
687
|
"context.actionSummary.runDocsCommand": "Run docs command",
|
|
665
688
|
"context.actionSummary.runProjectCommand": "Run project command",
|
|
666
689
|
"context.actionDetail.featureFolder": "Prepare feature folder and baseline docs",
|
|
@@ -669,16 +692,38 @@ var en = {
|
|
|
669
692
|
"context.actionDetail.planWrite": "Write or refine plan.md and set status",
|
|
670
693
|
"context.actionDetail.planApprove": "Approve plan.md",
|
|
671
694
|
"context.actionDetail.tasksWrite": "Write or refine tasks.md and align document status",
|
|
695
|
+
"context.actionDetail.tasksWriteCreate": "Create tasks.md and set Doc Status to Review",
|
|
696
|
+
"context.actionDetail.tasksWriteNeedAtLeastOne": "Add at least one task to tasks.md",
|
|
697
|
+
"context.actionDetail.tasksWriteImprove": "Refine tasks.md and align Doc Status",
|
|
672
698
|
"context.actionDetail.tasksApprove": "Approve tasks.md",
|
|
673
699
|
"context.actionDetail.issueCreate": "Create the issue and sync issue fields in tasks.md",
|
|
700
|
+
"context.actionDetail.issueCreateAndWrite": "Draft issue content, get explicit OK, then create and sync Issue",
|
|
701
|
+
"context.actionDetail.issueCreatePrepareFromDoc": "Refine issue.md draft and set Status to Ready",
|
|
702
|
+
"context.actionDetail.issueCreateFromDoc": "Create GitHub Issue from ready issue.md and sync Issue",
|
|
674
703
|
"context.actionDetail.taskExecute": "Proceed with the current task",
|
|
675
704
|
"context.actionDetail.reviewFixCommit": "Create a review-fix commit with resolved feedback summary",
|
|
676
705
|
"context.actionDetail.prePrReview": "Run pre-PR review and record results",
|
|
677
706
|
"context.actionDetail.prCreate": "Create PR and sync PR fields in tasks.md",
|
|
707
|
+
"context.actionDetail.prCreateRequiredSequence": "Complete PR 2-step flow: prepare draft + OK, then create and sync",
|
|
708
|
+
"context.actionDetail.prCreatePrepareFromDoc": "Refine pr.md draft and set Status to Ready",
|
|
709
|
+
"context.actionDetail.prCreateExecuteFromDoc": "Create PR from ready pr.md and sync PR link/status",
|
|
678
710
|
"context.actionDetail.prStatusUpdate": "Sync PR status in tasks.md with remote status",
|
|
711
|
+
"context.actionDetail.prStatusUpdateSetReview": "Set PR Status to Review",
|
|
712
|
+
"context.actionDetail.prStatusUpdateSyncApproved": "PR merged remotely; sync PR Status to Approved",
|
|
679
713
|
"context.actionDetail.codeReview": "Address review feedback and update PR review fields",
|
|
714
|
+
"context.actionDetail.codeReviewNeedEvidenceField": "Add PR Review Evidence field in tasks.md",
|
|
715
|
+
"context.actionDetail.codeReviewNeedEvidence": "Record PR Review Evidence summary",
|
|
716
|
+
"context.actionDetail.codeReviewNeedDecisionField": "Add PR Review Decision field in tasks.md",
|
|
717
|
+
"context.actionDetail.codeReviewNeedDecision": "Record PR Review Decision",
|
|
718
|
+
"context.actionDetail.codeReviewResolve": "Address review feedback and keep PR review docs updated",
|
|
719
|
+
"context.actionDetail.codeReviewNeedProjectRoot": "Set projectRoot to continue review actions",
|
|
720
|
+
"context.actionDetail.codeReviewRemoteBlocked": "Resolve remote PR blockers before merge",
|
|
721
|
+
"context.actionDetail.codeReviewMergeAfterOk": "Merge PR after explicit OK",
|
|
722
|
+
"context.actionDetail.codeReviewRequestReview": "Request review and keep PR Status as Review",
|
|
680
723
|
"context.actionDetail.worktreeCleanup": "Clean up the completed feature worktree",
|
|
681
724
|
"context.actionDetail.prMetadataMigrate": "Update tasks.md PR fields to the latest template format",
|
|
725
|
+
"context.actionDetail.prMetadataMigratePrFields": "Update tasks.md with PR/PR Status fields",
|
|
726
|
+
"context.actionDetail.prMetadataMigratePrePrReviewField": "Add Pre-PR Review field in tasks.md",
|
|
682
727
|
"context.actionDetail.userRequestReplan": "Handle the new user request first and re-run context",
|
|
683
728
|
"context.actionDetail.featureDone": "All completion checks are satisfied for this feature",
|
|
684
729
|
"context.actionDetail.fallback": "Verify current status and re-run context",
|
|
@@ -690,7 +735,7 @@ var en = {
|
|
|
690
735
|
"context.suggestion.showOpen": "Show open features",
|
|
691
736
|
"context.finalLabelCommandHint": "When a label is provided, run approval selection: {command}",
|
|
692
737
|
"context.finalTicketCommandHint": "Execute commands using the ticket from approval result: {command}",
|
|
693
|
-
"context.readBuiltinDocFirst": "Read required built-in docs only if not read in this session yet or likely changed
|
|
738
|
+
"context.readBuiltinDocFirst": "Read required built-in docs only if not read in this session yet or likely changed: {command}",
|
|
694
739
|
"context.tipDocsCommitRules": "Check commit message rules against the git-workflow guide.",
|
|
695
740
|
"context.list.docsCommitNeeded": "Commit docs changes",
|
|
696
741
|
"context.list.projectCommitNeeded": "Commit project code changes",
|
|
@@ -699,7 +744,6 @@ var en = {
|
|
|
699
744
|
"context.list.recordPrLink": "Record PR link",
|
|
700
745
|
"context.list.addPrePrReviewField": "Add Pre-PR Review field",
|
|
701
746
|
"context.list.completePrePrReview": "Complete Pre-PR review",
|
|
702
|
-
"context.list.addPrePrFindings": "Record Pre-PR Findings",
|
|
703
747
|
"context.list.addPrePrEvidence": "Add Pre-PR Evidence",
|
|
704
748
|
"context.list.addPrePrDecision": "Add Pre-PR Decision",
|
|
705
749
|
"context.list.resolvePrePrDecision": "Resolve Pre-PR decision to approve",
|
|
@@ -1037,7 +1081,6 @@ var en = {
|
|
|
1037
1081
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
|
|
1038
1082
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
1039
1083
|
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
1040
|
-
legacyTasksPrePrFindingsField: "Legacy tasks.md format detected. Add `Pre-PR Findings` before PR steps. (`- **Pre-PR Findings**: major=0, minor=0`)",
|
|
1041
1084
|
legacyTasksPrePrEvidenceField: "Legacy tasks.md format detected. Add `Pre-PR Evidence` before PR steps.",
|
|
1042
1085
|
legacyTasksPrePrDecisionField: "Legacy tasks.md format detected. Add `Pre-PR Decision` before PR steps. (`- **Pre-PR Decision**: decision: ...`)",
|
|
1043
1086
|
legacyTasksPrReviewEvidenceField: "Legacy tasks.md format detected. Add `PR Review Evidence` before review iteration.",
|
|
@@ -1056,10 +1099,9 @@ var en = {
|
|
|
1056
1099
|
workflowPrRemoteChecksPending: "Remote PR has {count} pending check(s). Wait for checks to complete, then re-check.",
|
|
1057
1100
|
workflowPrePrReviewMissing: "Implementation is done but `Pre-PR Review` is missing. (Add `- **Pre-PR Review**: Pending | Done` in tasks.md.)",
|
|
1058
1101
|
workflowPrePrReviewNotDone: "Implementation is done but `Pre-PR Review` is not Done. (Run pre-PR review, then update it to Done.)",
|
|
1059
|
-
workflowPrePrFindingsMissing: "Implementation is done but `Pre-PR Findings` is missing/invalid. (Use `major=<n>, minor=<n>`.)",
|
|
1060
1102
|
workflowPrePrEvidenceMissing: "Implementation is done but `Pre-PR Evidence` is empty/invalid. (Record a real existing path when path_required policy is enabled.)",
|
|
1061
1103
|
workflowPrePrDecisionMissing: "Implementation is done but `Pre-PR Decision` is empty/invalid. (Use `decision: approve|changes_requested|blocked ...`.)",
|
|
1062
|
-
workflowPrePrDecisionNotApproved: "Implementation is done but `Pre-PR Decision` is `{outcome}`. Resolve
|
|
1104
|
+
workflowPrePrDecisionNotApproved: "Implementation is done but `Pre-PR Decision` is `{outcome}`. Resolve review risks and re-run pre-PR review until decision becomes `approve`."
|
|
1063
1105
|
}
|
|
1064
1106
|
};
|
|
1065
1107
|
var en_default = en;
|
|
@@ -1485,10 +1527,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
|
|
|
1485
1527
|
var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
|
|
1486
1528
|
var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
|
|
1487
1529
|
function toScopeKey(value) {
|
|
1488
|
-
return createHash("sha1").update(
|
|
1530
|
+
return createHash("sha1").update(path23.resolve(value)).digest("hex").slice(0, 16);
|
|
1489
1531
|
}
|
|
1490
1532
|
function getTempRuntimeDir(scopePath) {
|
|
1491
|
-
return
|
|
1533
|
+
return path23.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
|
|
1492
1534
|
}
|
|
1493
1535
|
function resolveGitRuntimeDir(cwd) {
|
|
1494
1536
|
try {
|
|
@@ -1502,42 +1544,42 @@ function resolveGitRuntimeDir(cwd) {
|
|
|
1502
1544
|
}
|
|
1503
1545
|
).trim();
|
|
1504
1546
|
if (!out) return null;
|
|
1505
|
-
return
|
|
1547
|
+
return path23.isAbsolute(out) ? out : path23.resolve(cwd, out);
|
|
1506
1548
|
} catch {
|
|
1507
1549
|
return null;
|
|
1508
1550
|
}
|
|
1509
1551
|
}
|
|
1510
1552
|
function getRuntimeStateDir(cwd) {
|
|
1511
|
-
const resolved =
|
|
1553
|
+
const resolved = path23.resolve(cwd);
|
|
1512
1554
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1513
1555
|
}
|
|
1514
1556
|
function getDocsLockPath(docsDir) {
|
|
1515
|
-
return
|
|
1557
|
+
return path23.join(
|
|
1516
1558
|
getRuntimeStateDir(docsDir),
|
|
1517
1559
|
"locks",
|
|
1518
1560
|
`docs-${toScopeKey(docsDir)}.lock`
|
|
1519
1561
|
);
|
|
1520
1562
|
}
|
|
1521
1563
|
function getInitLockPath(targetDir) {
|
|
1522
|
-
return
|
|
1523
|
-
getRuntimeStateDir(
|
|
1564
|
+
return path23.join(
|
|
1565
|
+
getRuntimeStateDir(path23.dirname(path23.resolve(targetDir))),
|
|
1524
1566
|
"locks",
|
|
1525
1567
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1526
1568
|
);
|
|
1527
1569
|
}
|
|
1528
1570
|
function getApprovalTicketStorePath(docsDir) {
|
|
1529
|
-
return
|
|
1571
|
+
return path23.join(
|
|
1530
1572
|
getRuntimeStateDir(docsDir),
|
|
1531
1573
|
"tickets",
|
|
1532
1574
|
`approval-${toScopeKey(docsDir)}.json`
|
|
1533
1575
|
);
|
|
1534
1576
|
}
|
|
1535
1577
|
function getProjectExecutionLockPath(cwd) {
|
|
1536
|
-
return
|
|
1578
|
+
return path23.join(getRuntimeStateDir(cwd), "locks", "project.lock");
|
|
1537
1579
|
}
|
|
1538
1580
|
async function isStaleLock(lockPath, staleMs) {
|
|
1539
1581
|
try {
|
|
1540
|
-
const stat = await
|
|
1582
|
+
const stat = await fs17.stat(lockPath);
|
|
1541
1583
|
if (Date.now() - stat.mtimeMs <= staleMs) {
|
|
1542
1584
|
return false;
|
|
1543
1585
|
}
|
|
@@ -1552,7 +1594,7 @@ async function isStaleLock(lockPath, staleMs) {
|
|
|
1552
1594
|
}
|
|
1553
1595
|
async function readLockPayload(lockPath) {
|
|
1554
1596
|
try {
|
|
1555
|
-
const raw = await
|
|
1597
|
+
const raw = await fs17.readFile(lockPath, "utf8");
|
|
1556
1598
|
const parsed = JSON.parse(raw);
|
|
1557
1599
|
if (!parsed || typeof parsed !== "object") return null;
|
|
1558
1600
|
return parsed;
|
|
@@ -1574,17 +1616,17 @@ function isProcessAlive(pid) {
|
|
|
1574
1616
|
}
|
|
1575
1617
|
}
|
|
1576
1618
|
async function tryAcquire(lockPath, owner) {
|
|
1577
|
-
await
|
|
1619
|
+
await fs17.ensureDir(path23.dirname(lockPath));
|
|
1578
1620
|
try {
|
|
1579
|
-
const fd = await
|
|
1621
|
+
const fd = await fs17.open(lockPath, "wx");
|
|
1580
1622
|
const payload = JSON.stringify(
|
|
1581
1623
|
{ pid: process.pid, owner: owner ?? "unknown", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1582
1624
|
null,
|
|
1583
1625
|
2
|
|
1584
1626
|
);
|
|
1585
|
-
await
|
|
1627
|
+
await fs17.writeFile(fd, `${payload}
|
|
1586
1628
|
`, { encoding: "utf8" });
|
|
1587
|
-
await
|
|
1629
|
+
await fs17.close(fd);
|
|
1588
1630
|
return true;
|
|
1589
1631
|
} catch (error) {
|
|
1590
1632
|
if (error.code === "EEXIST") {
|
|
@@ -1598,9 +1640,9 @@ async function waitForLockRelease(lockPath, options = {}) {
|
|
|
1598
1640
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
1599
1641
|
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
1600
1642
|
const startedAt = Date.now();
|
|
1601
|
-
while (await
|
|
1643
|
+
while (await fs17.pathExists(lockPath)) {
|
|
1602
1644
|
if (await isStaleLock(lockPath, staleMs)) {
|
|
1603
|
-
await
|
|
1645
|
+
await fs17.remove(lockPath);
|
|
1604
1646
|
break;
|
|
1605
1647
|
}
|
|
1606
1648
|
if (Date.now() - startedAt > timeoutMs) {
|
|
@@ -1618,7 +1660,7 @@ async function withFileLock(lockPath, task, options = {}) {
|
|
|
1618
1660
|
const acquired = await tryAcquire(lockPath, options.owner);
|
|
1619
1661
|
if (acquired) break;
|
|
1620
1662
|
if (await isStaleLock(lockPath, staleMs)) {
|
|
1621
|
-
await
|
|
1663
|
+
await fs17.remove(lockPath);
|
|
1622
1664
|
continue;
|
|
1623
1665
|
}
|
|
1624
1666
|
if (Date.now() - startedAt > timeoutMs) {
|
|
@@ -1632,7 +1674,7 @@ async function withFileLock(lockPath, task, options = {}) {
|
|
|
1632
1674
|
try {
|
|
1633
1675
|
return await task();
|
|
1634
1676
|
} finally {
|
|
1635
|
-
await
|
|
1677
|
+
await fs17.remove(lockPath).catch(() => {
|
|
1636
1678
|
});
|
|
1637
1679
|
}
|
|
1638
1680
|
}
|
|
@@ -1651,30 +1693,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
|
|
|
1651
1693
|
"pr-template.md"
|
|
1652
1694
|
];
|
|
1653
1695
|
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1654
|
-
var ENGINE_MANAGED_FEATURE_PATH =
|
|
1696
|
+
var ENGINE_MANAGED_FEATURE_PATH = path23.join(
|
|
1655
1697
|
"features",
|
|
1656
1698
|
"feature-base"
|
|
1657
1699
|
);
|
|
1658
1700
|
async function pruneEngineManagedDocs(docsDir) {
|
|
1659
1701
|
const removed = [];
|
|
1660
1702
|
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1661
|
-
const target =
|
|
1662
|
-
if (await
|
|
1663
|
-
await
|
|
1664
|
-
removed.push(
|
|
1703
|
+
const target = path23.join(docsDir, "agents", file);
|
|
1704
|
+
if (await fs17.pathExists(target)) {
|
|
1705
|
+
await fs17.remove(target);
|
|
1706
|
+
removed.push(path23.relative(docsDir, target));
|
|
1665
1707
|
}
|
|
1666
1708
|
}
|
|
1667
1709
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1668
|
-
const target =
|
|
1669
|
-
if (await
|
|
1670
|
-
await
|
|
1671
|
-
removed.push(
|
|
1710
|
+
const target = path23.join(docsDir, "agents", dir);
|
|
1711
|
+
if (await fs17.pathExists(target)) {
|
|
1712
|
+
await fs17.remove(target);
|
|
1713
|
+
removed.push(path23.relative(docsDir, target));
|
|
1672
1714
|
}
|
|
1673
1715
|
}
|
|
1674
|
-
const featureBasePath =
|
|
1675
|
-
if (await
|
|
1676
|
-
await
|
|
1677
|
-
removed.push(
|
|
1716
|
+
const featureBasePath = path23.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1717
|
+
if (await fs17.pathExists(featureBasePath)) {
|
|
1718
|
+
await fs17.remove(featureBasePath);
|
|
1719
|
+
removed.push(path23.relative(docsDir, featureBasePath));
|
|
1678
1720
|
}
|
|
1679
1721
|
return removed;
|
|
1680
1722
|
}
|
|
@@ -1831,7 +1873,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
|
|
|
1831
1873
|
}
|
|
1832
1874
|
async function runInit(options) {
|
|
1833
1875
|
const cwd = process.cwd();
|
|
1834
|
-
const defaultName =
|
|
1876
|
+
const defaultName = path23.basename(cwd);
|
|
1835
1877
|
let projectName = options.name || defaultName;
|
|
1836
1878
|
let projectType = options.type;
|
|
1837
1879
|
let components = parseComponentsOption(options.components);
|
|
@@ -1842,7 +1884,7 @@ async function runInit(options) {
|
|
|
1842
1884
|
let docsRemote = options.docsRemote;
|
|
1843
1885
|
let projectRoot;
|
|
1844
1886
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
1845
|
-
const targetDir =
|
|
1887
|
+
const targetDir = path23.resolve(cwd, options.dir || "./docs");
|
|
1846
1888
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
1847
1889
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
1848
1890
|
throw createCliError(
|
|
@@ -2194,8 +2236,8 @@ async function runInit(options) {
|
|
|
2194
2236
|
await withFileLock(
|
|
2195
2237
|
initLockPath,
|
|
2196
2238
|
async () => {
|
|
2197
|
-
if (await
|
|
2198
|
-
const files = await
|
|
2239
|
+
if (await fs17.pathExists(targetDir)) {
|
|
2240
|
+
const files = await fs17.readdir(targetDir);
|
|
2199
2241
|
if (files.length > 0) {
|
|
2200
2242
|
if (options.force) {
|
|
2201
2243
|
} else if (options.nonInteractive) {
|
|
@@ -2231,21 +2273,21 @@ async function runInit(options) {
|
|
|
2231
2273
|
);
|
|
2232
2274
|
console.log();
|
|
2233
2275
|
const templatesDir = getTemplatesDir();
|
|
2234
|
-
const commonPath =
|
|
2235
|
-
if (!await
|
|
2276
|
+
const commonPath = path23.join(templatesDir, lang, "common");
|
|
2277
|
+
if (!await fs17.pathExists(commonPath)) {
|
|
2236
2278
|
throw new Error(
|
|
2237
2279
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
2238
2280
|
);
|
|
2239
2281
|
}
|
|
2240
2282
|
await copyTemplates(commonPath, targetDir);
|
|
2241
2283
|
if (projectType === "multi") {
|
|
2242
|
-
const featuresRoot =
|
|
2284
|
+
const featuresRoot = path23.join(targetDir, "features");
|
|
2243
2285
|
for (const component of components) {
|
|
2244
|
-
const componentDir =
|
|
2245
|
-
await
|
|
2246
|
-
const readmePath =
|
|
2247
|
-
if (!await
|
|
2248
|
-
await
|
|
2286
|
+
const componentDir = path23.join(featuresRoot, component);
|
|
2287
|
+
await fs17.ensureDir(componentDir);
|
|
2288
|
+
const readmePath = path23.join(componentDir, "README.md");
|
|
2289
|
+
if (!await fs17.pathExists(readmePath)) {
|
|
2290
|
+
await fs17.writeFile(
|
|
2249
2291
|
readmePath,
|
|
2250
2292
|
getComponentFeaturesReadme(lang, component),
|
|
2251
2293
|
"utf-8"
|
|
@@ -2280,7 +2322,6 @@ async function runInit(options) {
|
|
|
2280
2322
|
skills: ["code-review-excellence"],
|
|
2281
2323
|
fallback: "builtin-checklist",
|
|
2282
2324
|
evidenceMode: "path_required",
|
|
2283
|
-
findings: "required",
|
|
2284
2325
|
decisionEnum: ["approve", "changes_requested", "blocked"]
|
|
2285
2326
|
}
|
|
2286
2327
|
},
|
|
@@ -2302,8 +2343,8 @@ async function runInit(options) {
|
|
|
2302
2343
|
config.projectRoot = projectRoot;
|
|
2303
2344
|
}
|
|
2304
2345
|
}
|
|
2305
|
-
const configPath =
|
|
2306
|
-
await
|
|
2346
|
+
const configPath = path23.join(targetDir, ".lee-spec-kit.json");
|
|
2347
|
+
await fs17.writeJson(configPath, config, { spaces: 2 });
|
|
2307
2348
|
console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
2308
2349
|
console.log();
|
|
2309
2350
|
await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
|
|
@@ -2366,7 +2407,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2366
2407
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2367
2408
|
runGitOrThrow(["init"], gitWorkdir);
|
|
2368
2409
|
}
|
|
2369
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2410
|
+
const relativePath = docsRepo === "standalone" ? "." : path23.relative(cwd, targetDir);
|
|
2370
2411
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2371
2412
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2372
2413
|
console.log(
|
|
@@ -2423,17 +2464,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2423
2464
|
}
|
|
2424
2465
|
function getAncestorDirs(startDir) {
|
|
2425
2466
|
const dirs = [];
|
|
2426
|
-
let current =
|
|
2467
|
+
let current = path23.resolve(startDir);
|
|
2427
2468
|
while (true) {
|
|
2428
2469
|
dirs.push(current);
|
|
2429
|
-
const parent =
|
|
2470
|
+
const parent = path23.dirname(current);
|
|
2430
2471
|
if (parent === current) break;
|
|
2431
2472
|
current = parent;
|
|
2432
2473
|
}
|
|
2433
2474
|
return dirs;
|
|
2434
2475
|
}
|
|
2435
2476
|
function hasWorkspaceBoundary(dir) {
|
|
2436
|
-
return
|
|
2477
|
+
return fs17.existsSync(path23.join(dir, "package.json")) || fs17.existsSync(path23.join(dir, ".git"));
|
|
2437
2478
|
}
|
|
2438
2479
|
function getSearchBaseDirs(cwd) {
|
|
2439
2480
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2449,9 +2490,9 @@ function normalizeComponentKeys(value) {
|
|
|
2449
2490
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2450
2491
|
}
|
|
2451
2492
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2452
|
-
const featuresPath =
|
|
2453
|
-
if (!await
|
|
2454
|
-
const entries = await
|
|
2493
|
+
const featuresPath = path23.join(docsDir, "features");
|
|
2494
|
+
if (!await fs17.pathExists(featuresPath)) return [];
|
|
2495
|
+
const entries = await fs17.readdir(featuresPath, { withFileTypes: true });
|
|
2455
2496
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
2456
2497
|
(name) => !!name && name !== "feature-base" && !FEATURE_FOLDER_PATTERN.test(name)
|
|
2457
2498
|
);
|
|
@@ -2460,24 +2501,24 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2460
2501
|
async function getConfig(cwd) {
|
|
2461
2502
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2462
2503
|
const baseDirs = [
|
|
2463
|
-
...explicitDocsDir ? [
|
|
2504
|
+
...explicitDocsDir ? [path23.resolve(explicitDocsDir)] : [],
|
|
2464
2505
|
...getSearchBaseDirs(cwd)
|
|
2465
2506
|
];
|
|
2466
2507
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2467
2508
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2468
2509
|
for (const baseDir of baseDirs) {
|
|
2469
|
-
const resolvedBaseDir =
|
|
2510
|
+
const resolvedBaseDir = path23.resolve(baseDir);
|
|
2470
2511
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2471
2512
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2472
|
-
const possibleDocsDirs = [
|
|
2513
|
+
const possibleDocsDirs = [path23.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2473
2514
|
for (const docsDir of possibleDocsDirs) {
|
|
2474
|
-
const resolvedDocsDir =
|
|
2515
|
+
const resolvedDocsDir = path23.resolve(docsDir);
|
|
2475
2516
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2476
2517
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2477
|
-
const configPath =
|
|
2478
|
-
if (await
|
|
2518
|
+
const configPath = path23.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2519
|
+
if (await fs17.pathExists(configPath)) {
|
|
2479
2520
|
try {
|
|
2480
|
-
const configFile = await
|
|
2521
|
+
const configFile = await fs17.readJson(configPath);
|
|
2481
2522
|
const projectType = normalizeProjectType(configFile.projectType);
|
|
2482
2523
|
const inferredComponents = [
|
|
2483
2524
|
...normalizeComponentKeys(configFile.projectRoot),
|
|
@@ -2504,21 +2545,21 @@ async function getConfig(cwd) {
|
|
|
2504
2545
|
} catch {
|
|
2505
2546
|
}
|
|
2506
2547
|
}
|
|
2507
|
-
const agentsPath =
|
|
2508
|
-
const featuresPath =
|
|
2509
|
-
if (await
|
|
2548
|
+
const agentsPath = path23.join(resolvedDocsDir, "agents");
|
|
2549
|
+
const featuresPath = path23.join(resolvedDocsDir, "features");
|
|
2550
|
+
if (await fs17.pathExists(agentsPath) && await fs17.pathExists(featuresPath)) {
|
|
2510
2551
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2511
2552
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2512
2553
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2513
2554
|
const langProbeCandidates = [
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2555
|
+
path23.join(agentsPath, "custom.md"),
|
|
2556
|
+
path23.join(agentsPath, "constitution.md"),
|
|
2557
|
+
path23.join(agentsPath, "agents.md")
|
|
2517
2558
|
];
|
|
2518
2559
|
let lang = "en";
|
|
2519
2560
|
for (const candidate of langProbeCandidates) {
|
|
2520
|
-
if (!await
|
|
2521
|
-
const content = await
|
|
2561
|
+
if (!await fs17.pathExists(candidate)) continue;
|
|
2562
|
+
const content = await fs17.readFile(candidate, "utf-8");
|
|
2522
2563
|
if (/[가-힣]/.test(content)) {
|
|
2523
2564
|
lang = "ko";
|
|
2524
2565
|
break;
|
|
@@ -2553,7 +2594,7 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2553
2594
|
)) {
|
|
2554
2595
|
continue;
|
|
2555
2596
|
}
|
|
2556
|
-
if (/^\s*-\s*\*\*(Pre-PR
|
|
2597
|
+
if (/^\s*-\s*\*\*(Pre-PR Evidence|PR 전 리뷰 Evidence)\*\*\s*:/.test(
|
|
2557
2598
|
line
|
|
2558
2599
|
)) {
|
|
2559
2600
|
continue;
|
|
@@ -2567,8 +2608,6 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2567
2608
|
if (/^\s*-\s*(예|값)\s*:/.test(line)) continue;
|
|
2568
2609
|
if (/^\s*-\s*Mark\s+`?Done`?/i.test(line)) continue;
|
|
2569
2610
|
if (/^\s*-\s*사전 코드리뷰 완료 후/.test(line)) continue;
|
|
2570
|
-
if (/^\s*-\s*Update with final findings counts from pre-PR review/i.test(line))
|
|
2571
|
-
continue;
|
|
2572
2611
|
if (/^\s*-\s*Record your key review decision/i.test(line)) continue;
|
|
2573
2612
|
if (/^\s*-\s*사전 리뷰 주요 판단 근거를/.test(line)) continue;
|
|
2574
2613
|
if (/^\s*-\s*Example:\s*review note link/i.test(line)) continue;
|
|
@@ -2581,18 +2620,18 @@ function sanitizeTasksForLocal(content, lang) {
|
|
|
2581
2620
|
return normalizeTrailingBlankLines(next);
|
|
2582
2621
|
}
|
|
2583
2622
|
async function patchMarkdownIfExists(filePath, transform) {
|
|
2584
|
-
if (!await
|
|
2585
|
-
const content = await
|
|
2586
|
-
await
|
|
2623
|
+
if (!await fs17.pathExists(filePath)) return;
|
|
2624
|
+
const content = await fs17.readFile(filePath, "utf-8");
|
|
2625
|
+
await fs17.writeFile(filePath, transform(content), "utf-8");
|
|
2587
2626
|
}
|
|
2588
2627
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
2589
|
-
await patchMarkdownIfExists(
|
|
2628
|
+
await patchMarkdownIfExists(path23.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
2590
2629
|
await patchMarkdownIfExists(
|
|
2591
|
-
|
|
2630
|
+
path23.join(featureDir, "tasks.md"),
|
|
2592
2631
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
2593
2632
|
);
|
|
2594
|
-
await
|
|
2595
|
-
await
|
|
2633
|
+
await fs17.remove(path23.join(featureDir, "issue.md"));
|
|
2634
|
+
await fs17.remove(path23.join(featureDir, "pr.md"));
|
|
2596
2635
|
}
|
|
2597
2636
|
|
|
2598
2637
|
// src/commands/feature.ts
|
|
@@ -2739,29 +2778,29 @@ async function runFeature(name, options) {
|
|
|
2739
2778
|
}
|
|
2740
2779
|
let featuresDir;
|
|
2741
2780
|
if (projectType === "multi") {
|
|
2742
|
-
featuresDir =
|
|
2781
|
+
featuresDir = path23.join(docsDir, "features", component);
|
|
2743
2782
|
} else {
|
|
2744
|
-
featuresDir =
|
|
2783
|
+
featuresDir = path23.join(docsDir, "features");
|
|
2745
2784
|
}
|
|
2746
2785
|
const featureFolderName = `${featureId}-${name}`;
|
|
2747
|
-
const featureDir =
|
|
2748
|
-
if (await
|
|
2786
|
+
const featureDir = path23.join(featuresDir, featureFolderName);
|
|
2787
|
+
if (await fs17.pathExists(featureDir)) {
|
|
2749
2788
|
throw createCliError(
|
|
2750
2789
|
"INVALID_ARGUMENT",
|
|
2751
2790
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
2752
2791
|
);
|
|
2753
2792
|
}
|
|
2754
|
-
const featureBasePath =
|
|
2793
|
+
const featureBasePath = path23.join(
|
|
2755
2794
|
getTemplatesDir(),
|
|
2756
2795
|
lang,
|
|
2757
2796
|
"common",
|
|
2758
2797
|
"features",
|
|
2759
2798
|
"feature-base"
|
|
2760
2799
|
);
|
|
2761
|
-
if (!await
|
|
2800
|
+
if (!await fs17.pathExists(featureBasePath)) {
|
|
2762
2801
|
throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
|
|
2763
2802
|
}
|
|
2764
|
-
await
|
|
2803
|
+
await fs17.copy(featureBasePath, featureDir);
|
|
2765
2804
|
const idNumber = featureId.replace("F", "");
|
|
2766
2805
|
const repoName = projectType === "multi" ? `{{projectName}}-${component}` : "{{projectName}}";
|
|
2767
2806
|
const replacements = {
|
|
@@ -2810,7 +2849,7 @@ async function runFeature(name, options) {
|
|
|
2810
2849
|
featureName: name,
|
|
2811
2850
|
component: projectType === "multi" ? component : void 0,
|
|
2812
2851
|
featurePath: featureDir,
|
|
2813
|
-
featurePathFromDocs:
|
|
2852
|
+
featurePathFromDocs: path23.relative(docsDir, featureDir)
|
|
2814
2853
|
};
|
|
2815
2854
|
},
|
|
2816
2855
|
{ owner: "feature" }
|
|
@@ -2819,9 +2858,9 @@ async function runFeature(name, options) {
|
|
|
2819
2858
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
2820
2859
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2821
2860
|
const candidates = [
|
|
2822
|
-
...explicitDocsDir ? [
|
|
2823
|
-
|
|
2824
|
-
|
|
2861
|
+
...explicitDocsDir ? [path23.resolve(explicitDocsDir)] : [],
|
|
2862
|
+
path23.resolve(cwd, "docs"),
|
|
2863
|
+
path23.resolve(cwd)
|
|
2825
2864
|
];
|
|
2826
2865
|
const endAt = Date.now() + timeoutMs;
|
|
2827
2866
|
while (Date.now() < endAt) {
|
|
@@ -2832,7 +2871,7 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
2832
2871
|
const initLockPath = getInitLockPath(dir);
|
|
2833
2872
|
const docsLockPath = getDocsLockPath(dir);
|
|
2834
2873
|
for (const lockPath of [initLockPath, docsLockPath]) {
|
|
2835
|
-
if (await
|
|
2874
|
+
if (await fs17.pathExists(lockPath)) {
|
|
2836
2875
|
sawLock = true;
|
|
2837
2876
|
await waitForLockRelease(lockPath, {
|
|
2838
2877
|
timeoutMs: Math.max(200, endAt - Date.now()),
|
|
@@ -2848,17 +2887,17 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
2848
2887
|
return getConfig(cwd);
|
|
2849
2888
|
}
|
|
2850
2889
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
2851
|
-
const featuresDir =
|
|
2890
|
+
const featuresDir = path23.join(docsDir, "features");
|
|
2852
2891
|
let max = 0;
|
|
2853
2892
|
const scanDirs = [];
|
|
2854
2893
|
if (projectType === "multi") {
|
|
2855
|
-
scanDirs.push(...components.map((component) =>
|
|
2894
|
+
scanDirs.push(...components.map((component) => path23.join(featuresDir, component)));
|
|
2856
2895
|
} else {
|
|
2857
2896
|
scanDirs.push(featuresDir);
|
|
2858
2897
|
}
|
|
2859
2898
|
for (const dir of scanDirs) {
|
|
2860
|
-
if (!await
|
|
2861
|
-
const entries = await
|
|
2899
|
+
if (!await fs17.pathExists(dir)) continue;
|
|
2900
|
+
const entries = await fs17.readdir(dir, { withFileTypes: true });
|
|
2862
2901
|
for (const entry of entries) {
|
|
2863
2902
|
if (!entry.isDirectory()) continue;
|
|
2864
2903
|
const match = entry.name.match(/^F(\d+)-/);
|
|
@@ -3000,7 +3039,6 @@ function resolvePrePrReviewPolicy(workflow) {
|
|
|
3000
3039
|
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
3001
3040
|
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
3002
3041
|
evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
|
|
3003
|
-
findings: configured?.findings === "optional" ? "optional" : "required",
|
|
3004
3042
|
decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM
|
|
3005
3043
|
};
|
|
3006
3044
|
}
|
|
@@ -3029,9 +3067,6 @@ function isPrePrReviewSatisfied(feature, prePrReviewPolicy) {
|
|
|
3029
3067
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
3030
3068
|
return false;
|
|
3031
3069
|
}
|
|
3032
|
-
if (prePrReviewPolicy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
3033
|
-
return false;
|
|
3034
|
-
}
|
|
3035
3070
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
3036
3071
|
return false;
|
|
3037
3072
|
}
|
|
@@ -3101,8 +3136,8 @@ function resolveProjectCommitTopic(feature) {
|
|
|
3101
3136
|
}
|
|
3102
3137
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
3103
3138
|
if (!projectGitCwd) return null;
|
|
3104
|
-
const normalized =
|
|
3105
|
-
const marker = `${
|
|
3139
|
+
const normalized = path23.resolve(projectGitCwd);
|
|
3140
|
+
const marker = `${path23.sep}.worktrees${path23.sep}`;
|
|
3106
3141
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
3107
3142
|
if (markerIndex <= 0) return null;
|
|
3108
3143
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -3146,7 +3181,7 @@ function toTaskKey(rawTitle) {
|
|
|
3146
3181
|
function countDoneTransitionsInLatestTasksCommit(feature) {
|
|
3147
3182
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
3148
3183
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
3149
|
-
|
|
3184
|
+
path23.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
3150
3185
|
);
|
|
3151
3186
|
const diff = readGitText(docsGitCwd, [
|
|
3152
3187
|
"diff",
|
|
@@ -3219,7 +3254,7 @@ function checkTaskCommitGate(feature) {
|
|
|
3219
3254
|
return { pass: true };
|
|
3220
3255
|
}
|
|
3221
3256
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
3222
|
-
const relativeDocsDir =
|
|
3257
|
+
const relativeDocsDir = path23.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
3223
3258
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
3224
3259
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
3225
3260
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -3345,6 +3380,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3345
3380
|
{
|
|
3346
3381
|
type: "instruction",
|
|
3347
3382
|
category: "tasks_write",
|
|
3383
|
+
uiDetailKey: "context.actionDetail.tasksWriteCreate",
|
|
3348
3384
|
message: tr(lang, "messages", "tasksCreate")
|
|
3349
3385
|
}
|
|
3350
3386
|
];
|
|
@@ -3354,6 +3390,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3354
3390
|
{
|
|
3355
3391
|
type: "instruction",
|
|
3356
3392
|
category: "tasks_write",
|
|
3393
|
+
uiDetailKey: "context.actionDetail.tasksWriteNeedAtLeastOne",
|
|
3357
3394
|
message: tr(lang, "messages", "tasksNeedAtLeastOne")
|
|
3358
3395
|
}
|
|
3359
3396
|
];
|
|
@@ -3363,6 +3400,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3363
3400
|
{
|
|
3364
3401
|
type: "instruction",
|
|
3365
3402
|
category: "tasks_write",
|
|
3403
|
+
uiDetailKey: "context.actionDetail.tasksWriteImprove",
|
|
3366
3404
|
message: tr(lang, "messages", "tasksImprove")
|
|
3367
3405
|
}
|
|
3368
3406
|
];
|
|
@@ -3381,6 +3419,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3381
3419
|
{
|
|
3382
3420
|
type: "instruction",
|
|
3383
3421
|
category: "tasks_write",
|
|
3422
|
+
uiDetailKey: "context.actionDetail.tasksWriteImprove",
|
|
3384
3423
|
message: tr(lang, "messages", "tasksImprove")
|
|
3385
3424
|
}
|
|
3386
3425
|
];
|
|
@@ -3449,6 +3488,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3449
3488
|
type: "instruction",
|
|
3450
3489
|
category: "issue_create",
|
|
3451
3490
|
requiresUserCheck: true,
|
|
3491
|
+
uiDetailKey: "context.actionDetail.issueCreateAndWrite",
|
|
3452
3492
|
message: tr(lang, "messages", "issueCreateAndWrite", {
|
|
3453
3493
|
featureRef: f.id || f.folderName
|
|
3454
3494
|
})
|
|
@@ -3461,6 +3501,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3461
3501
|
type: "instruction",
|
|
3462
3502
|
category: "issue_create",
|
|
3463
3503
|
requiresUserCheck: true,
|
|
3504
|
+
uiDetailKey: "context.actionDetail.issueCreateFromDoc",
|
|
3464
3505
|
message: tr(lang, "messages", "issueCreateFromDoc", {
|
|
3465
3506
|
featureRef: f.id || f.folderName
|
|
3466
3507
|
})
|
|
@@ -3472,6 +3513,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3472
3513
|
type: "instruction",
|
|
3473
3514
|
category: "issue_create",
|
|
3474
3515
|
requiresUserCheck: true,
|
|
3516
|
+
uiDetailKey: "context.actionDetail.issueCreatePrepareFromDoc",
|
|
3475
3517
|
message: tr(lang, "messages", "issuePrepareFromDoc", {
|
|
3476
3518
|
featureRef: f.id || f.folderName
|
|
3477
3519
|
})
|
|
@@ -3616,6 +3658,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
3616
3658
|
type: "instruction",
|
|
3617
3659
|
category: "pr_metadata_migrate",
|
|
3618
3660
|
requiresUserCheck: true,
|
|
3661
|
+
uiDetailKey: "context.actionDetail.prMetadataMigratePrFields",
|
|
3619
3662
|
message: tr(lang, "messages", "prLegacyAsk")
|
|
3620
3663
|
});
|
|
3621
3664
|
}
|
|
@@ -3851,6 +3894,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3851
3894
|
type: "instruction",
|
|
3852
3895
|
category: "pr_metadata_migrate",
|
|
3853
3896
|
requiresUserCheck: true,
|
|
3897
|
+
uiDetailKey: "context.actionDetail.prMetadataMigratePrePrReviewField",
|
|
3854
3898
|
message: tr(lang, "messages", "prePrReviewFieldMissing")
|
|
3855
3899
|
}
|
|
3856
3900
|
];
|
|
@@ -3888,6 +3932,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3888
3932
|
type: "instruction",
|
|
3889
3933
|
category: "pr_metadata_migrate",
|
|
3890
3934
|
requiresUserCheck: true,
|
|
3935
|
+
uiDetailKey: "context.actionDetail.prMetadataMigratePrFields",
|
|
3891
3936
|
message: tr(lang, "messages", "prLegacyAsk")
|
|
3892
3937
|
}
|
|
3893
3938
|
];
|
|
@@ -3898,6 +3943,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3898
3943
|
type: "instruction",
|
|
3899
3944
|
category: "pr_create",
|
|
3900
3945
|
requiresUserCheck: true,
|
|
3946
|
+
uiDetailKey: "context.actionDetail.prCreateRequiredSequence",
|
|
3901
3947
|
message: tr(lang, "messages", "prCreateRequiredSequence", {
|
|
3902
3948
|
featureRef: f.id || f.folderName
|
|
3903
3949
|
})
|
|
@@ -3910,6 +3956,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3910
3956
|
type: "instruction",
|
|
3911
3957
|
category: "pr_create",
|
|
3912
3958
|
requiresUserCheck: true,
|
|
3959
|
+
uiDetailKey: "context.actionDetail.prCreateExecuteFromDoc",
|
|
3913
3960
|
message: tr(lang, "messages", "prCreateExecuteFromDoc", {
|
|
3914
3961
|
featureRef: f.id || f.folderName
|
|
3915
3962
|
})
|
|
@@ -3921,6 +3968,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3921
3968
|
type: "instruction",
|
|
3922
3969
|
category: "pr_create",
|
|
3923
3970
|
requiresUserCheck: true,
|
|
3971
|
+
uiDetailKey: "context.actionDetail.prCreatePrepareFromDoc",
|
|
3924
3972
|
message: tr(lang, "messages", "prCreatePrepareFromDoc", {
|
|
3925
3973
|
featureRef: f.id || f.folderName
|
|
3926
3974
|
})
|
|
@@ -3944,6 +3992,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3944
3992
|
type: "instruction",
|
|
3945
3993
|
category: "pr_status_update",
|
|
3946
3994
|
requiresUserCheck: true,
|
|
3995
|
+
uiDetailKey: "context.actionDetail.prStatusUpdateSetReview",
|
|
3947
3996
|
message: tr(lang, "messages", "prFillStatus")
|
|
3948
3997
|
}
|
|
3949
3998
|
];
|
|
@@ -3955,6 +4004,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3955
4004
|
type: "instruction",
|
|
3956
4005
|
category: "pr_status_update",
|
|
3957
4006
|
requiresUserCheck: true,
|
|
4007
|
+
uiDetailKey: "context.actionDetail.prStatusUpdateSyncApproved",
|
|
3958
4008
|
message: tr(lang, "messages", "prReviewMergedSyncStatus")
|
|
3959
4009
|
}
|
|
3960
4010
|
];
|
|
@@ -3965,6 +4015,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3965
4015
|
type: "instruction",
|
|
3966
4016
|
category: "code_review",
|
|
3967
4017
|
requiresUserCheck: true,
|
|
4018
|
+
uiDetailKey: "context.actionDetail.codeReviewNeedEvidenceField",
|
|
3968
4019
|
message: tr(lang, "messages", "prReviewEvidenceFieldMissing")
|
|
3969
4020
|
}
|
|
3970
4021
|
];
|
|
@@ -3975,6 +4026,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3975
4026
|
type: "instruction",
|
|
3976
4027
|
category: "code_review",
|
|
3977
4028
|
requiresUserCheck: true,
|
|
4029
|
+
uiDetailKey: "context.actionDetail.codeReviewNeedEvidence",
|
|
3978
4030
|
message: tr(lang, "messages", "prReviewEvidenceMissing")
|
|
3979
4031
|
}
|
|
3980
4032
|
];
|
|
@@ -3985,6 +4037,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3985
4037
|
type: "instruction",
|
|
3986
4038
|
category: "code_review",
|
|
3987
4039
|
requiresUserCheck: true,
|
|
4040
|
+
uiDetailKey: "context.actionDetail.codeReviewNeedDecisionField",
|
|
3988
4041
|
message: tr(lang, "messages", "prReviewDecisionFieldMissing")
|
|
3989
4042
|
}
|
|
3990
4043
|
];
|
|
@@ -3995,6 +4048,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3995
4048
|
type: "instruction",
|
|
3996
4049
|
category: "code_review",
|
|
3997
4050
|
requiresUserCheck: true,
|
|
4051
|
+
uiDetailKey: "context.actionDetail.codeReviewNeedDecision",
|
|
3998
4052
|
message: tr(lang, "messages", "prReviewDecisionMissing")
|
|
3999
4053
|
}
|
|
4000
4054
|
];
|
|
@@ -4006,6 +4060,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4006
4060
|
type: "instruction",
|
|
4007
4061
|
category: "code_review",
|
|
4008
4062
|
requiresUserCheck: true,
|
|
4063
|
+
uiDetailKey: "context.actionDetail.codeReviewResolve",
|
|
4009
4064
|
message: tr(lang, "messages", "prReviewResolve")
|
|
4010
4065
|
}
|
|
4011
4066
|
];
|
|
@@ -4014,6 +4069,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4014
4069
|
type: "instruction",
|
|
4015
4070
|
category: "code_review",
|
|
4016
4071
|
requiresUserCheck: true,
|
|
4072
|
+
uiDetailKey: "context.actionDetail.codeReviewNeedProjectRoot",
|
|
4017
4073
|
message: tr(lang, "messages", "standaloneNeedsProjectRoot")
|
|
4018
4074
|
});
|
|
4019
4075
|
} else if ((f.git.projectBranchAhead || 0) > 0) {
|
|
@@ -4037,6 +4093,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4037
4093
|
type: "instruction",
|
|
4038
4094
|
category: "code_review",
|
|
4039
4095
|
requiresUserCheck: true,
|
|
4096
|
+
uiDetailKey: "context.actionDetail.codeReviewRemoteBlocked",
|
|
4040
4097
|
message: tr(lang, "messages", "prReviewRemoteBlocked", {
|
|
4041
4098
|
reasons: reasons.join("; ")
|
|
4042
4099
|
})
|
|
@@ -4058,6 +4115,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4058
4115
|
type: "instruction",
|
|
4059
4116
|
category: "code_review",
|
|
4060
4117
|
requiresUserCheck: true,
|
|
4118
|
+
uiDetailKey: "context.actionDetail.codeReviewMergeAfterOk",
|
|
4061
4119
|
message: tr(lang, "messages", "prReviewMerge", {
|
|
4062
4120
|
featureRef: f.id || f.folderName
|
|
4063
4121
|
})
|
|
@@ -4069,6 +4127,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4069
4127
|
{
|
|
4070
4128
|
type: "instruction",
|
|
4071
4129
|
category: "code_review",
|
|
4130
|
+
uiDetailKey: "context.actionDetail.codeReviewRequestReview",
|
|
4072
4131
|
message: tr(lang, "messages", "prRequestReview")
|
|
4073
4132
|
}
|
|
4074
4133
|
];
|
|
@@ -4372,7 +4431,7 @@ function getGitTopLevel(cwd) {
|
|
|
4372
4431
|
}
|
|
4373
4432
|
function listGitWorktrees(cwd) {
|
|
4374
4433
|
const topLevel = getGitTopLevel(cwd) || cwd;
|
|
4375
|
-
const cacheKey =
|
|
4434
|
+
const cacheKey = path23.resolve(topLevel);
|
|
4376
4435
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
4377
4436
|
if (cached) return cached;
|
|
4378
4437
|
try {
|
|
@@ -4531,19 +4590,6 @@ function parsePrePrReviewStatus(value) {
|
|
|
4531
4590
|
if (/^pending$/i.test(trimmed)) return "Pending";
|
|
4532
4591
|
return void 0;
|
|
4533
4592
|
}
|
|
4534
|
-
function parseReviewFindings(value) {
|
|
4535
|
-
if (!value) return void 0;
|
|
4536
|
-
const trimmed = value.trim();
|
|
4537
|
-
if (!trimmed || trimmed.includes("|")) return void 0;
|
|
4538
|
-
const majorMatch = trimmed.match(/\bmajor\s*[:=]\s*(\d+)\b/i);
|
|
4539
|
-
const minorMatch = trimmed.match(/\bminor\s*[:=]\s*(\d+)\b/i);
|
|
4540
|
-
if (!majorMatch || !minorMatch) return void 0;
|
|
4541
|
-
const major = Number(majorMatch[1]);
|
|
4542
|
-
const minor = Number(minorMatch[1]);
|
|
4543
|
-
if (!Number.isInteger(major) || !Number.isInteger(minor)) return void 0;
|
|
4544
|
-
if (major < 0 || minor < 0) return void 0;
|
|
4545
|
-
return { major, minor };
|
|
4546
|
-
}
|
|
4547
4593
|
function isPlaceholderReviewEvidence(value) {
|
|
4548
4594
|
if (!value) return true;
|
|
4549
4595
|
const trimmed = value.trim();
|
|
@@ -4598,15 +4644,15 @@ async function isPrePrEvidenceProvided(rawValue, policy, context) {
|
|
|
4598
4644
|
if (!evidencePath) return false;
|
|
4599
4645
|
if (/^https?:\/\//i.test(evidencePath)) return false;
|
|
4600
4646
|
const candidates = /* @__PURE__ */ new Set();
|
|
4601
|
-
if (
|
|
4602
|
-
candidates.add(
|
|
4647
|
+
if (path23.isAbsolute(evidencePath)) {
|
|
4648
|
+
candidates.add(path23.resolve(evidencePath));
|
|
4603
4649
|
} else {
|
|
4604
|
-
candidates.add(
|
|
4605
|
-
candidates.add(
|
|
4606
|
-
candidates.add(
|
|
4650
|
+
candidates.add(path23.resolve(context.featurePath, evidencePath));
|
|
4651
|
+
candidates.add(path23.resolve(context.docsDir, evidencePath));
|
|
4652
|
+
candidates.add(path23.resolve(path23.dirname(context.docsDir), evidencePath));
|
|
4607
4653
|
}
|
|
4608
4654
|
for (const candidate of candidates) {
|
|
4609
|
-
if (await
|
|
4655
|
+
if (await fs17.pathExists(candidate)) return true;
|
|
4610
4656
|
}
|
|
4611
4657
|
return false;
|
|
4612
4658
|
}
|
|
@@ -4627,13 +4673,13 @@ function parsePrLink(value) {
|
|
|
4627
4673
|
return trimmed;
|
|
4628
4674
|
}
|
|
4629
4675
|
function normalizeGitPath(value) {
|
|
4630
|
-
return value.split(
|
|
4676
|
+
return value.split(path23.sep).join("/");
|
|
4631
4677
|
}
|
|
4632
4678
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
4633
|
-
const relativeDocsDir =
|
|
4679
|
+
const relativeDocsDir = path23.relative(projectGitCwd, docsDir);
|
|
4634
4680
|
if (!relativeDocsDir) return [];
|
|
4635
|
-
if (
|
|
4636
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
4681
|
+
if (path23.isAbsolute(relativeDocsDir)) return [];
|
|
4682
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path23.sep}`)) {
|
|
4637
4683
|
return [];
|
|
4638
4684
|
}
|
|
4639
4685
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
|
|
@@ -4812,10 +4858,10 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4812
4858
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
4813
4859
|
candidates.map((candidate) => {
|
|
4814
4860
|
if (!candidate) return "";
|
|
4815
|
-
if (!
|
|
4816
|
-
const relative =
|
|
4861
|
+
if (!path23.isAbsolute(candidate)) return candidate;
|
|
4862
|
+
const relative = path23.relative(projectGitCwd, candidate);
|
|
4817
4863
|
if (!relative) return "";
|
|
4818
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
4864
|
+
if (relative === ".." || relative.startsWith(`..${path23.sep}`)) return "";
|
|
4819
4865
|
return relative;
|
|
4820
4866
|
}).filter(Boolean)
|
|
4821
4867
|
);
|
|
@@ -4828,7 +4874,7 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4828
4874
|
if (cached) return [...cached];
|
|
4829
4875
|
const existing = [];
|
|
4830
4876
|
for (const candidate of normalizedCandidates) {
|
|
4831
|
-
if (await
|
|
4877
|
+
if (await fs17.pathExists(path23.join(projectGitCwd, candidate))) {
|
|
4832
4878
|
existing.push(candidate);
|
|
4833
4879
|
}
|
|
4834
4880
|
}
|
|
@@ -4902,9 +4948,6 @@ function isPrePrReviewSatisfied2(feature, policy) {
|
|
|
4902
4948
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
4903
4949
|
return false;
|
|
4904
4950
|
}
|
|
4905
|
-
if (policy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
4906
|
-
return false;
|
|
4907
|
-
}
|
|
4908
4951
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
4909
4952
|
return false;
|
|
4910
4953
|
}
|
|
@@ -4917,20 +4960,20 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4917
4960
|
const lang = options.lang;
|
|
4918
4961
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
4919
4962
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
4920
|
-
const folderName =
|
|
4963
|
+
const folderName = path23.basename(featurePath);
|
|
4921
4964
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
4922
4965
|
const id = match?.[1];
|
|
4923
4966
|
const slug = match?.[2] || folderName;
|
|
4924
|
-
const specPath =
|
|
4925
|
-
const planPath =
|
|
4926
|
-
const tasksPath =
|
|
4927
|
-
const issueDocPath =
|
|
4928
|
-
const prDocPath =
|
|
4967
|
+
const specPath = path23.join(featurePath, "spec.md");
|
|
4968
|
+
const planPath = path23.join(featurePath, "plan.md");
|
|
4969
|
+
const tasksPath = path23.join(featurePath, "tasks.md");
|
|
4970
|
+
const issueDocPath = path23.join(featurePath, "issue.md");
|
|
4971
|
+
const prDocPath = path23.join(featurePath, "pr.md");
|
|
4929
4972
|
let specStatus;
|
|
4930
4973
|
let issueNumber;
|
|
4931
|
-
const specExists = await
|
|
4974
|
+
const specExists = await fs17.pathExists(specPath);
|
|
4932
4975
|
if (specExists) {
|
|
4933
|
-
const content = await
|
|
4976
|
+
const content = await fs17.readFile(specPath, "utf-8");
|
|
4934
4977
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
4935
4978
|
specStatus = parseDocStatus(statusValue);
|
|
4936
4979
|
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
@@ -4961,13 +5004,13 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4961
5004
|
}
|
|
4962
5005
|
}
|
|
4963
5006
|
let planStatus;
|
|
4964
|
-
const planExists = await
|
|
5007
|
+
const planExists = await fs17.pathExists(planPath);
|
|
4965
5008
|
if (planExists) {
|
|
4966
|
-
const content = await
|
|
5009
|
+
const content = await fs17.readFile(planPath, "utf-8");
|
|
4967
5010
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
4968
5011
|
planStatus = parseDocStatus(statusValue);
|
|
4969
5012
|
}
|
|
4970
|
-
const tasksExists = await
|
|
5013
|
+
const tasksExists = await fs17.pathExists(tasksPath);
|
|
4971
5014
|
const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
4972
5015
|
let activeTask;
|
|
4973
5016
|
let lastDoneTask;
|
|
@@ -4976,14 +5019,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4976
5019
|
let tasksDocStatusFieldExists = false;
|
|
4977
5020
|
let completionChecklist;
|
|
4978
5021
|
let prePrReviewStatus;
|
|
4979
|
-
let prePrFindings;
|
|
4980
|
-
let prePrFindingsProvided = false;
|
|
4981
5022
|
let prePrEvidence;
|
|
4982
5023
|
let prePrEvidenceProvided = false;
|
|
4983
5024
|
let prePrDecision;
|
|
4984
5025
|
let prePrDecisionOutcome;
|
|
4985
5026
|
let prePrDecisionProvided = false;
|
|
4986
|
-
let prReviewFindings;
|
|
4987
5027
|
let prReviewEvidence;
|
|
4988
5028
|
let prReviewEvidenceProvided = false;
|
|
4989
5029
|
let prReviewDecision;
|
|
@@ -5001,14 +5041,12 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5001
5041
|
let prDocPrFieldExists = false;
|
|
5002
5042
|
let prDocReviewStatusFieldExists = false;
|
|
5003
5043
|
let prePrReviewFieldExists = false;
|
|
5004
|
-
let prePrFindingsFieldExists = false;
|
|
5005
5044
|
let prePrEvidenceFieldExists = false;
|
|
5006
5045
|
let prePrDecisionFieldExists = false;
|
|
5007
|
-
let prReviewFindingsFieldExists = false;
|
|
5008
5046
|
let prReviewEvidenceFieldExists = false;
|
|
5009
5047
|
let prReviewDecisionFieldExists = false;
|
|
5010
5048
|
if (tasksExists) {
|
|
5011
|
-
const content = await
|
|
5049
|
+
const content = await fs17.readFile(tasksPath, "utf-8");
|
|
5012
5050
|
const {
|
|
5013
5051
|
summary,
|
|
5014
5052
|
activeTask: active,
|
|
@@ -5047,16 +5085,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5047
5085
|
"Pre-PR Review"
|
|
5048
5086
|
]);
|
|
5049
5087
|
prePrReviewStatus = parsePrePrReviewStatus(prePrReviewValue);
|
|
5050
|
-
const prePrFindingsValue = extractFirstSpecValue(content, [
|
|
5051
|
-
"PR \uC804 \uB9AC\uBDF0 Findings",
|
|
5052
|
-
"Pre-PR Findings"
|
|
5053
|
-
]);
|
|
5054
|
-
prePrFindingsFieldExists = hasAnySpecKey(content, [
|
|
5055
|
-
"PR \uC804 \uB9AC\uBDF0 Findings",
|
|
5056
|
-
"Pre-PR Findings"
|
|
5057
|
-
]);
|
|
5058
|
-
prePrFindings = parseReviewFindings(prePrFindingsValue);
|
|
5059
|
-
prePrFindingsProvided = !!prePrFindings;
|
|
5060
5088
|
const prePrEvidenceValue = extractFirstSpecValue(content, [
|
|
5061
5089
|
"PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
5062
5090
|
"Pre-PR Evidence"
|
|
@@ -5082,15 +5110,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5082
5110
|
prePrDecision = prePrDecisionValue?.trim();
|
|
5083
5111
|
prePrDecisionOutcome = parsePrePrDecisionOutcome(prePrDecisionValue);
|
|
5084
5112
|
prePrDecisionProvided = !isPlaceholderReviewEvidence(prePrDecisionValue) && hasStructuredReviewDecision(prePrDecisionValue) && !!prePrDecisionOutcome && prePrReviewPolicy.decisionEnum.includes(prePrDecisionOutcome);
|
|
5085
|
-
const prReviewFindingsValue = extractFirstSpecValue(content, [
|
|
5086
|
-
"PR \uB9AC\uBDF0 Findings",
|
|
5087
|
-
"PR Review Findings"
|
|
5088
|
-
]);
|
|
5089
|
-
prReviewFindingsFieldExists = hasAnySpecKey(content, [
|
|
5090
|
-
"PR \uB9AC\uBDF0 Findings",
|
|
5091
|
-
"PR Review Findings"
|
|
5092
|
-
]);
|
|
5093
|
-
prReviewFindings = parseReviewFindings(prReviewFindingsValue);
|
|
5094
5113
|
const prReviewEvidenceValue = extractFirstSpecValue(content, [
|
|
5095
5114
|
"PR \uB9AC\uBDF0 Evidence",
|
|
5096
5115
|
"PR Review Evidence"
|
|
@@ -5133,9 +5152,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5133
5152
|
}
|
|
5134
5153
|
}
|
|
5135
5154
|
}
|
|
5136
|
-
const issueDocExists = await
|
|
5155
|
+
const issueDocExists = await fs17.pathExists(issueDocPath);
|
|
5137
5156
|
if (issueDocExists) {
|
|
5138
|
-
const content = await
|
|
5157
|
+
const content = await fs17.readFile(issueDocPath, "utf-8");
|
|
5139
5158
|
const issueDocStatusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5140
5159
|
issueDocStatusFieldExists = hasAnySpecKey(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5141
5160
|
issueDocStatus = parseWorkflowDocStatus(issueDocStatusValue);
|
|
@@ -5145,9 +5164,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5145
5164
|
"Issue"
|
|
5146
5165
|
]);
|
|
5147
5166
|
}
|
|
5148
|
-
const prDocExists = await
|
|
5167
|
+
const prDocExists = await fs17.pathExists(prDocPath);
|
|
5149
5168
|
if (prDocExists) {
|
|
5150
|
-
const content = await
|
|
5169
|
+
const content = await fs17.readFile(prDocPath, "utf-8");
|
|
5151
5170
|
const prDocStatusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5152
5171
|
prDocStatusFieldExists = hasAnySpecKey(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5153
5172
|
prDocStatus = parseWorkflowDocStatus(prDocStatusValue);
|
|
@@ -5167,7 +5186,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5167
5186
|
slug,
|
|
5168
5187
|
folderName
|
|
5169
5188
|
);
|
|
5170
|
-
const relativeFeaturePathFromDocs =
|
|
5189
|
+
const relativeFeaturePathFromDocs = path23.relative(context.docsDir, featurePath);
|
|
5171
5190
|
const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
|
|
5172
5191
|
const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
|
|
5173
5192
|
let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
|
|
@@ -5256,9 +5275,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5256
5275
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrReviewFieldExists) {
|
|
5257
5276
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrReviewField"));
|
|
5258
5277
|
}
|
|
5259
|
-
if (tasksExists && prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && !prePrFindingsFieldExists) {
|
|
5260
|
-
warnings.push(tr(lang, "warnings", "legacyTasksPrePrFindingsField"));
|
|
5261
|
-
}
|
|
5262
5278
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrEvidenceFieldExists) {
|
|
5263
5279
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrEvidenceField"));
|
|
5264
5280
|
}
|
|
@@ -5303,13 +5319,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5303
5319
|
{
|
|
5304
5320
|
docs: {
|
|
5305
5321
|
prePrReviewFieldExists,
|
|
5306
|
-
prePrFindingsFieldExists,
|
|
5307
5322
|
prePrEvidenceFieldExists,
|
|
5308
5323
|
prePrDecisionFieldExists
|
|
5309
5324
|
},
|
|
5310
5325
|
prePrReview: {
|
|
5311
5326
|
status: prePrReviewStatus,
|
|
5312
|
-
findingsProvided: prePrFindingsProvided,
|
|
5313
5327
|
evidenceProvided: prePrEvidenceProvided,
|
|
5314
5328
|
decisionOutcome: prePrDecisionOutcome,
|
|
5315
5329
|
decisionProvided: prePrDecisionProvided
|
|
@@ -5352,8 +5366,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5352
5366
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewMissing"));
|
|
5353
5367
|
} else if (prePrReviewStatus !== "Done") {
|
|
5354
5368
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewNotDone"));
|
|
5355
|
-
} else if (prePrReviewPolicy.findings === "required" && (!prePrFindingsFieldExists || !prePrFindingsProvided)) {
|
|
5356
|
-
warnings.push(tr(lang, "warnings", "workflowPrePrFindingsMissing"));
|
|
5357
5369
|
} else if (!prePrEvidenceFieldExists || !prePrEvidenceProvided) {
|
|
5358
5370
|
warnings.push(tr(lang, "warnings", "workflowPrePrEvidenceMissing"));
|
|
5359
5371
|
} else if (!prePrDecisionFieldExists || !prePrDecisionProvided) {
|
|
@@ -5388,8 +5400,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5388
5400
|
completionChecklist,
|
|
5389
5401
|
prePrReview: {
|
|
5390
5402
|
status: prePrReviewStatus,
|
|
5391
|
-
findings: prePrFindings,
|
|
5392
|
-
findingsProvided: prePrFindingsProvided,
|
|
5393
5403
|
evidence: prePrEvidence,
|
|
5394
5404
|
evidenceProvided: prePrEvidenceProvided,
|
|
5395
5405
|
decision: prePrDecision,
|
|
@@ -5397,7 +5407,6 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5397
5407
|
decisionProvided: prePrDecisionProvided
|
|
5398
5408
|
},
|
|
5399
5409
|
prReview: {
|
|
5400
|
-
findings: prReviewFindings,
|
|
5401
5410
|
evidence: prReviewEvidence,
|
|
5402
5411
|
evidenceProvided: prReviewEvidenceProvided,
|
|
5403
5412
|
decision: prReviewDecision,
|
|
@@ -5437,10 +5446,8 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5437
5446
|
prFieldExists,
|
|
5438
5447
|
prStatusFieldExists,
|
|
5439
5448
|
prePrReviewFieldExists,
|
|
5440
|
-
prePrFindingsFieldExists,
|
|
5441
5449
|
prePrEvidenceFieldExists,
|
|
5442
5450
|
prePrDecisionFieldExists,
|
|
5443
|
-
prReviewFindingsFieldExists,
|
|
5444
5451
|
prReviewEvidenceFieldExists,
|
|
5445
5452
|
prReviewDecisionFieldExists
|
|
5446
5453
|
}
|
|
@@ -5456,7 +5463,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5456
5463
|
async function listFeatureDirs(rootDir) {
|
|
5457
5464
|
const dirs = await listSubdirectories(rootDir);
|
|
5458
5465
|
return dirs.filter(
|
|
5459
|
-
(value) =>
|
|
5466
|
+
(value) => path23.basename(value).trim().toLowerCase() !== "feature-base"
|
|
5460
5467
|
);
|
|
5461
5468
|
}
|
|
5462
5469
|
function normalizeRelPath(value) {
|
|
@@ -5576,21 +5583,21 @@ async function scanFeatures(config) {
|
|
|
5576
5583
|
const allFeatureDirs = [];
|
|
5577
5584
|
const componentFeatureDirs = /* @__PURE__ */ new Map();
|
|
5578
5585
|
if (config.projectType === "single") {
|
|
5579
|
-
const featureDirs = await listFeatureDirs(
|
|
5586
|
+
const featureDirs = await listFeatureDirs(path23.join(config.docsDir, "features"));
|
|
5580
5587
|
componentFeatureDirs.set("single", featureDirs);
|
|
5581
5588
|
allFeatureDirs.push(...featureDirs);
|
|
5582
5589
|
} else {
|
|
5583
5590
|
const components = resolveProjectComponents(config.projectType, config.components);
|
|
5584
5591
|
for (const component of components) {
|
|
5585
5592
|
const componentDirs = await listFeatureDirs(
|
|
5586
|
-
|
|
5593
|
+
path23.join(config.docsDir, "features", component)
|
|
5587
5594
|
);
|
|
5588
5595
|
componentFeatureDirs.set(component, componentDirs);
|
|
5589
5596
|
allFeatureDirs.push(...componentDirs);
|
|
5590
5597
|
}
|
|
5591
5598
|
}
|
|
5592
5599
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
5593
|
-
(dir) => normalizeRelPath(
|
|
5600
|
+
(dir) => normalizeRelPath(path23.relative(config.docsDir, dir))
|
|
5594
5601
|
);
|
|
5595
5602
|
const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
|
|
5596
5603
|
const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
|
|
@@ -5601,7 +5608,7 @@ async function scanFeatures(config) {
|
|
|
5601
5608
|
const parsed = await Promise.all(
|
|
5602
5609
|
target.dirs.map(async (dir) => {
|
|
5603
5610
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
5604
|
-
|
|
5611
|
+
path23.relative(config.docsDir, dir)
|
|
5605
5612
|
);
|
|
5606
5613
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
5607
5614
|
return parseFeature(
|
|
@@ -5671,13 +5678,13 @@ async function runStatus(options) {
|
|
|
5671
5678
|
);
|
|
5672
5679
|
}
|
|
5673
5680
|
const { docsDir, projectType, projectName, lang } = config;
|
|
5674
|
-
const featuresDir =
|
|
5681
|
+
const featuresDir = path23.join(docsDir, "features");
|
|
5675
5682
|
const scan = await scanFeatures(config);
|
|
5676
5683
|
const features = [];
|
|
5677
5684
|
const idMap = /* @__PURE__ */ new Map();
|
|
5678
5685
|
for (const f of scan.features) {
|
|
5679
5686
|
const id = f.id || "UNKNOWN";
|
|
5680
|
-
const relPath =
|
|
5687
|
+
const relPath = path23.relative(docsDir, f.path);
|
|
5681
5688
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
5682
5689
|
idMap.get(id).push(relPath);
|
|
5683
5690
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -5758,7 +5765,7 @@ async function runStatus(options) {
|
|
|
5758
5765
|
}
|
|
5759
5766
|
console.log();
|
|
5760
5767
|
if (options.write) {
|
|
5761
|
-
const outputPath =
|
|
5768
|
+
const outputPath = path23.join(featuresDir, "status.md");
|
|
5762
5769
|
const date = getLocalDateString();
|
|
5763
5770
|
const content = [
|
|
5764
5771
|
"# Feature Status",
|
|
@@ -5773,7 +5780,7 @@ async function runStatus(options) {
|
|
|
5773
5780
|
),
|
|
5774
5781
|
""
|
|
5775
5782
|
].join("\n");
|
|
5776
|
-
await
|
|
5783
|
+
await fs17.writeFile(outputPath, content, "utf-8");
|
|
5777
5784
|
console.log(
|
|
5778
5785
|
chalk6.green(
|
|
5779
5786
|
tr(lang, "cli", "status.wrote", { path: outputPath })
|
|
@@ -5786,9 +5793,9 @@ function escapeRegExp2(value) {
|
|
|
5786
5793
|
}
|
|
5787
5794
|
async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
|
|
5788
5795
|
try {
|
|
5789
|
-
const specPath =
|
|
5790
|
-
if (!await
|
|
5791
|
-
const content = await
|
|
5796
|
+
const specPath = path23.join(featureDir, "spec.md");
|
|
5797
|
+
if (!await fs17.pathExists(specPath)) return fallbackSlug;
|
|
5798
|
+
const content = await fs17.readFile(specPath, "utf-8");
|
|
5792
5799
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
5793
5800
|
for (const key of keys) {
|
|
5794
5801
|
const regex = new RegExp(
|
|
@@ -5869,8 +5876,8 @@ async function runUpdate(options) {
|
|
|
5869
5876
|
console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
5870
5877
|
}
|
|
5871
5878
|
if (agentsMode === "all") {
|
|
5872
|
-
const commonAgentsBase =
|
|
5873
|
-
const targetAgentsBase =
|
|
5879
|
+
const commonAgentsBase = path23.join(templatesDir, lang, "common", "agents");
|
|
5880
|
+
const targetAgentsBase = path23.join(docsDir, "agents");
|
|
5874
5881
|
const commonAgents = commonAgentsBase;
|
|
5875
5882
|
const targetAgents = targetAgentsBase;
|
|
5876
5883
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -5879,7 +5886,7 @@ async function runUpdate(options) {
|
|
|
5879
5886
|
"{{projectName}}": projectName,
|
|
5880
5887
|
"{{featurePath}}": featurePath
|
|
5881
5888
|
};
|
|
5882
|
-
if (await
|
|
5889
|
+
if (await fs17.pathExists(commonAgents)) {
|
|
5883
5890
|
const count = await updateFolder(
|
|
5884
5891
|
commonAgents,
|
|
5885
5892
|
targetAgents,
|
|
@@ -5964,11 +5971,11 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
5964
5971
|
return [...deduped];
|
|
5965
5972
|
}
|
|
5966
5973
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
5967
|
-
const configPath =
|
|
5968
|
-
if (!await
|
|
5974
|
+
const configPath = path23.join(docsDir, ".lee-spec-kit.json");
|
|
5975
|
+
if (!await fs17.pathExists(configPath)) {
|
|
5969
5976
|
return { changed: false, changedPaths: [] };
|
|
5970
5977
|
}
|
|
5971
|
-
const raw = await
|
|
5978
|
+
const raw = await fs17.readJson(configPath);
|
|
5972
5979
|
if (!isPlainObject(raw)) {
|
|
5973
5980
|
return { changed: false, changedPaths: [] };
|
|
5974
5981
|
}
|
|
@@ -6021,14 +6028,8 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6021
6028
|
prePrReview.evidenceMode = "path_required";
|
|
6022
6029
|
changedPaths.push("workflow.prePrReview.evidenceMode");
|
|
6023
6030
|
}
|
|
6024
|
-
|
|
6025
|
-
prePrReview
|
|
6026
|
-
"findings",
|
|
6027
|
-
"required",
|
|
6028
|
-
"workflow.prePrReview.findings"
|
|
6029
|
-
);
|
|
6030
|
-
if (prePrReview.findings !== void 0 && prePrReview.findings !== "required" && prePrReview.findings !== "optional") {
|
|
6031
|
-
prePrReview.findings = "required";
|
|
6031
|
+
if ("findings" in prePrReview) {
|
|
6032
|
+
delete prePrReview.findings;
|
|
6032
6033
|
changedPaths.push("workflow.prePrReview.findings");
|
|
6033
6034
|
}
|
|
6034
6035
|
if (prePrReview.decisionEnum === void 0) {
|
|
@@ -6064,30 +6065,30 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6064
6065
|
if (changedPaths.length === 0) {
|
|
6065
6066
|
return { changed: false, changedPaths: [] };
|
|
6066
6067
|
}
|
|
6067
|
-
await
|
|
6068
|
+
await fs17.writeJson(configPath, raw, { spaces: 2 });
|
|
6068
6069
|
return { changed: true, changedPaths };
|
|
6069
6070
|
}
|
|
6070
6071
|
async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
|
|
6071
6072
|
const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
6072
6073
|
const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
|
|
6073
|
-
await
|
|
6074
|
-
const files = await
|
|
6074
|
+
await fs17.ensureDir(targetDir);
|
|
6075
|
+
const files = await fs17.readdir(sourceDir);
|
|
6075
6076
|
let updatedCount = 0;
|
|
6076
6077
|
for (const file of files) {
|
|
6077
|
-
const sourcePath =
|
|
6078
|
-
const targetPath =
|
|
6079
|
-
const stat = await
|
|
6078
|
+
const sourcePath = path23.join(sourceDir, file);
|
|
6079
|
+
const targetPath = path23.join(targetDir, file);
|
|
6080
|
+
const stat = await fs17.stat(sourcePath);
|
|
6080
6081
|
if (stat.isFile()) {
|
|
6081
6082
|
if (protectedFiles.has(file)) {
|
|
6082
6083
|
continue;
|
|
6083
6084
|
}
|
|
6084
|
-
let sourceContent = await
|
|
6085
|
+
let sourceContent = await fs17.readFile(sourcePath, "utf-8");
|
|
6085
6086
|
if (replacements) {
|
|
6086
6087
|
sourceContent = applyReplacements(sourceContent, replacements);
|
|
6087
6088
|
}
|
|
6088
6089
|
let shouldUpdate = true;
|
|
6089
|
-
if (await
|
|
6090
|
-
const targetContent = await
|
|
6090
|
+
if (await fs17.pathExists(targetPath)) {
|
|
6091
|
+
const targetContent = await fs17.readFile(targetPath, "utf-8");
|
|
6091
6092
|
if (sourceContent === targetContent) {
|
|
6092
6093
|
continue;
|
|
6093
6094
|
}
|
|
@@ -6101,7 +6102,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
6101
6102
|
}
|
|
6102
6103
|
}
|
|
6103
6104
|
if (shouldUpdate) {
|
|
6104
|
-
await
|
|
6105
|
+
await fs17.writeFile(targetPath, sourceContent);
|
|
6105
6106
|
console.log(
|
|
6106
6107
|
chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
|
|
6107
6108
|
);
|
|
@@ -6158,7 +6159,7 @@ function extractPorcelainPaths(line) {
|
|
|
6158
6159
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
6159
6160
|
const top = getGitTopLevel2(docsDir);
|
|
6160
6161
|
if (!top) return null;
|
|
6161
|
-
const rel =
|
|
6162
|
+
const rel = path23.relative(top, docsDir) || ".";
|
|
6162
6163
|
try {
|
|
6163
6164
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
6164
6165
|
cwd: top,
|
|
@@ -6170,7 +6171,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
6170
6171
|
}
|
|
6171
6172
|
const ignoredRelPaths = new Set(
|
|
6172
6173
|
ignoredAbsPaths.map(
|
|
6173
|
-
(absPath) => normalizeGitPath2(
|
|
6174
|
+
(absPath) => normalizeGitPath2(path23.relative(top, absPath) || ".")
|
|
6174
6175
|
)
|
|
6175
6176
|
);
|
|
6176
6177
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -6228,7 +6229,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
6228
6229
|
}
|
|
6229
6230
|
async function runConfig(options) {
|
|
6230
6231
|
const cwd = process.cwd();
|
|
6231
|
-
const targetCwd = options.dir ?
|
|
6232
|
+
const targetCwd = options.dir ? path23.resolve(cwd, options.dir) : cwd;
|
|
6232
6233
|
const config = await getConfig(targetCwd);
|
|
6233
6234
|
if (!config) {
|
|
6234
6235
|
throw createCliError(
|
|
@@ -6236,7 +6237,7 @@ async function runConfig(options) {
|
|
|
6236
6237
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
6237
6238
|
);
|
|
6238
6239
|
}
|
|
6239
|
-
const configPath =
|
|
6240
|
+
const configPath = path23.join(config.docsDir, ".lee-spec-kit.json");
|
|
6240
6241
|
if (!options.projectRoot) {
|
|
6241
6242
|
console.log();
|
|
6242
6243
|
console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -6247,7 +6248,7 @@ async function runConfig(options) {
|
|
|
6247
6248
|
)
|
|
6248
6249
|
);
|
|
6249
6250
|
console.log();
|
|
6250
|
-
const configFile = await
|
|
6251
|
+
const configFile = await fs17.readJson(configPath);
|
|
6251
6252
|
console.log(JSON.stringify(configFile, null, 2));
|
|
6252
6253
|
console.log();
|
|
6253
6254
|
return;
|
|
@@ -6256,7 +6257,7 @@ async function runConfig(options) {
|
|
|
6256
6257
|
await withFileLock(
|
|
6257
6258
|
getDocsLockPath(config.docsDir),
|
|
6258
6259
|
async () => {
|
|
6259
|
-
const configFile = await
|
|
6260
|
+
const configFile = await fs17.readJson(configPath);
|
|
6260
6261
|
if (configFile.docsRepo !== "standalone") {
|
|
6261
6262
|
console.log(
|
|
6262
6263
|
chalk6.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
|
|
@@ -6332,7 +6333,7 @@ async function runConfig(options) {
|
|
|
6332
6333
|
)
|
|
6333
6334
|
);
|
|
6334
6335
|
}
|
|
6335
|
-
await
|
|
6336
|
+
await fs17.writeJson(configPath, configFile, { spaces: 2 });
|
|
6336
6337
|
console.log();
|
|
6337
6338
|
},
|
|
6338
6339
|
{ owner: "config" }
|
|
@@ -6413,6 +6414,14 @@ function annotateActions(actions) {
|
|
|
6413
6414
|
return actions.map((action) => annotateActionOperationType(action));
|
|
6414
6415
|
}
|
|
6415
6416
|
function getActionSummary(action, lang) {
|
|
6417
|
+
if (action.uiSummaryKey) {
|
|
6418
|
+
const localized = tr(lang, "cli", action.uiSummaryKey);
|
|
6419
|
+
if (localized !== `cli.${action.uiSummaryKey}`) return localized;
|
|
6420
|
+
}
|
|
6421
|
+
if (action.uiDetailKey) {
|
|
6422
|
+
const localized = tr(lang, "cli", action.uiDetailKey);
|
|
6423
|
+
if (localized !== `cli.${action.uiDetailKey}`) return localized;
|
|
6424
|
+
}
|
|
6416
6425
|
const detailKey = action.category ? ACTION_DETAIL_KEY_BY_CATEGORY[action.category] : void 0;
|
|
6417
6426
|
if (detailKey) {
|
|
6418
6427
|
const localized = tr(lang, "cli", detailKey);
|
|
@@ -6434,6 +6443,10 @@ function toOneLine(text) {
|
|
|
6434
6443
|
return `${normalized.slice(0, 157).trimEnd()}...`;
|
|
6435
6444
|
}
|
|
6436
6445
|
function buildActionDetail(action, lang) {
|
|
6446
|
+
if (action.uiDetailKey) {
|
|
6447
|
+
const localized = tr(lang, "cli", action.uiDetailKey);
|
|
6448
|
+
if (localized !== `cli.${action.uiDetailKey}`) return localized;
|
|
6449
|
+
}
|
|
6437
6450
|
const formatBranchCreateDetail = (command) => {
|
|
6438
6451
|
const worktreeMatch = command.match(/\.worktrees\/([A-Za-z0-9._-]+)/);
|
|
6439
6452
|
const branchMatch = command.match(/\bfeat\/([A-Za-z0-9._-]+)/);
|
|
@@ -6489,6 +6502,11 @@ function buildActionDetail(action, lang) {
|
|
|
6489
6502
|
});
|
|
6490
6503
|
}
|
|
6491
6504
|
}
|
|
6505
|
+
if (action.category === "pre_pr_review") {
|
|
6506
|
+
return tr(lang, "cli", "context.commandDetail.prePrReviewRun", {
|
|
6507
|
+
scope: action.scope
|
|
6508
|
+
});
|
|
6509
|
+
}
|
|
6492
6510
|
if (action.category === "worktree_cleanup") {
|
|
6493
6511
|
return `(${action.scope}) ${tr(lang, "cli", "context.actionDetail.worktreeCleanup")}`;
|
|
6494
6512
|
}
|
|
@@ -6537,7 +6555,9 @@ function buildActionSnapshot(actionOptions) {
|
|
|
6537
6555
|
cmd: action.cmd,
|
|
6538
6556
|
category: action.category,
|
|
6539
6557
|
operationType: action.operationType,
|
|
6540
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
6558
|
+
requiresUserCheck: !!action.requiresUserCheck,
|
|
6559
|
+
uiSummaryKey: action.uiSummaryKey,
|
|
6560
|
+
uiDetailKey: action.uiDetailKey
|
|
6541
6561
|
};
|
|
6542
6562
|
}
|
|
6543
6563
|
return {
|
|
@@ -6546,7 +6566,9 @@ function buildActionSnapshot(actionOptions) {
|
|
|
6546
6566
|
message: action.message,
|
|
6547
6567
|
category: action.category,
|
|
6548
6568
|
operationType: action.operationType,
|
|
6549
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
6569
|
+
requiresUserCheck: !!action.requiresUserCheck,
|
|
6570
|
+
uiSummaryKey: action.uiSummaryKey,
|
|
6571
|
+
uiDetailKey: action.uiDetailKey
|
|
6550
6572
|
};
|
|
6551
6573
|
});
|
|
6552
6574
|
}
|
|
@@ -6732,13 +6754,13 @@ function getApprovalSessionId() {
|
|
|
6732
6754
|
function getApprovalTicketPaths(config) {
|
|
6733
6755
|
return {
|
|
6734
6756
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
6735
|
-
legacyPath:
|
|
6757
|
+
legacyPath: path23.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
6736
6758
|
};
|
|
6737
6759
|
}
|
|
6738
6760
|
async function loadApprovalTicketStore(storePath) {
|
|
6739
|
-
if (!await
|
|
6761
|
+
if (!await fs17.pathExists(storePath)) return { tickets: [] };
|
|
6740
6762
|
try {
|
|
6741
|
-
const parsed = await
|
|
6763
|
+
const parsed = await fs17.readJson(storePath);
|
|
6742
6764
|
if (!parsed || !Array.isArray(parsed.tickets)) return { tickets: [] };
|
|
6743
6765
|
return { tickets: parsed.tickets };
|
|
6744
6766
|
} catch {
|
|
@@ -6746,8 +6768,8 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
6746
6768
|
}
|
|
6747
6769
|
}
|
|
6748
6770
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
6749
|
-
await
|
|
6750
|
-
await
|
|
6771
|
+
await fs17.ensureDir(path23.dirname(storePath));
|
|
6772
|
+
await fs17.writeJson(storePath, payload, { spaces: 2 });
|
|
6751
6773
|
}
|
|
6752
6774
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
6753
6775
|
return tickets.filter((ticket) => {
|
|
@@ -6759,13 +6781,13 @@ function pruneApprovalTickets(tickets, nowMs) {
|
|
|
6759
6781
|
}
|
|
6760
6782
|
async function resolveApprovalTicketStoreAndPath(config, nowMs) {
|
|
6761
6783
|
const { runtimePath, legacyPath } = getApprovalTicketPaths(config);
|
|
6762
|
-
if (await
|
|
6784
|
+
if (await fs17.pathExists(runtimePath)) {
|
|
6763
6785
|
return {
|
|
6764
6786
|
storePath: runtimePath,
|
|
6765
6787
|
store: await loadApprovalTicketStore(runtimePath)
|
|
6766
6788
|
};
|
|
6767
6789
|
}
|
|
6768
|
-
if (!await
|
|
6790
|
+
if (!await fs17.pathExists(legacyPath)) {
|
|
6769
6791
|
return {
|
|
6770
6792
|
storePath: runtimePath,
|
|
6771
6793
|
store: { tickets: [] }
|
|
@@ -6781,7 +6803,7 @@ async function resolveApprovalTicketStoreAndPath(config, nowMs) {
|
|
|
6781
6803
|
migratedFrom: legacyPath
|
|
6782
6804
|
}
|
|
6783
6805
|
);
|
|
6784
|
-
await
|
|
6806
|
+
await fs17.remove(legacyPath).catch(() => {
|
|
6785
6807
|
});
|
|
6786
6808
|
return {
|
|
6787
6809
|
storePath: runtimePath,
|
|
@@ -6913,42 +6935,42 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
6913
6935
|
{
|
|
6914
6936
|
id: "agents",
|
|
6915
6937
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
6916
|
-
relativePath: (_, lang) =>
|
|
6938
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "agents.md")
|
|
6917
6939
|
},
|
|
6918
6940
|
{
|
|
6919
6941
|
id: "git-workflow",
|
|
6920
6942
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
6921
|
-
relativePath: (_, lang) =>
|
|
6943
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "git-workflow.md")
|
|
6922
6944
|
},
|
|
6923
6945
|
{
|
|
6924
6946
|
id: "issue-doc",
|
|
6925
6947
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
6926
|
-
relativePath: (_, lang) =>
|
|
6948
|
+
relativePath: (_, lang) => path23.join(lang, "common", "features", "feature-base", "issue.md")
|
|
6927
6949
|
},
|
|
6928
6950
|
{
|
|
6929
6951
|
id: "pr-doc",
|
|
6930
6952
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
6931
|
-
relativePath: (_, lang) =>
|
|
6953
|
+
relativePath: (_, lang) => path23.join(lang, "common", "features", "feature-base", "pr.md")
|
|
6932
6954
|
},
|
|
6933
6955
|
{
|
|
6934
6956
|
id: "create-feature",
|
|
6935
6957
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
6936
|
-
relativePath: (_, lang) =>
|
|
6958
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
6937
6959
|
},
|
|
6938
6960
|
{
|
|
6939
6961
|
id: "execute-task",
|
|
6940
6962
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
6941
|
-
relativePath: (_, lang) =>
|
|
6963
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
6942
6964
|
},
|
|
6943
6965
|
{
|
|
6944
6966
|
id: "create-issue",
|
|
6945
6967
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
6946
|
-
relativePath: (_, lang) =>
|
|
6968
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
6947
6969
|
},
|
|
6948
6970
|
{
|
|
6949
6971
|
id: "create-pr",
|
|
6950
6972
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
6951
|
-
relativePath: (_, lang) =>
|
|
6973
|
+
relativePath: (_, lang) => path23.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
6952
6974
|
}
|
|
6953
6975
|
];
|
|
6954
6976
|
var DOC_FOLLOWUPS = {
|
|
@@ -7037,7 +7059,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
7037
7059
|
id: doc.id,
|
|
7038
7060
|
title: doc.title[lang],
|
|
7039
7061
|
relativePath,
|
|
7040
|
-
absolutePath:
|
|
7062
|
+
absolutePath: path23.join(templatesDir, relativePath)
|
|
7041
7063
|
};
|
|
7042
7064
|
});
|
|
7043
7065
|
}
|
|
@@ -7046,7 +7068,7 @@ async function getBuiltinDoc(docId, projectType, lang) {
|
|
|
7046
7068
|
if (!entry) {
|
|
7047
7069
|
throw new Error(`Unknown builtin doc: ${docId}`);
|
|
7048
7070
|
}
|
|
7049
|
-
const content = await
|
|
7071
|
+
const content = await fs17.readFile(entry.absolutePath, "utf-8");
|
|
7050
7072
|
const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
7051
7073
|
return {
|
|
7052
7074
|
entry,
|
|
@@ -7057,6 +7079,56 @@ async function getBuiltinDoc(docId, projectType, lang) {
|
|
|
7057
7079
|
}
|
|
7058
7080
|
|
|
7059
7081
|
// src/commands/context.ts
|
|
7082
|
+
var LONG_RUNNING_DELEGATION_CATEGORIES = [
|
|
7083
|
+
"task_execute",
|
|
7084
|
+
"code_review",
|
|
7085
|
+
"review_fix_commit",
|
|
7086
|
+
"pre_pr_review"
|
|
7087
|
+
];
|
|
7088
|
+
function shouldDelegateCurrentAction(actionOptions, autoRunAvailable) {
|
|
7089
|
+
const primaryCategory = actionOptions[0]?.action?.category || null;
|
|
7090
|
+
const longRunningSet = new Set(LONG_RUNNING_DELEGATION_CATEGORIES);
|
|
7091
|
+
const shouldDelegate = autoRunAvailable || !!primaryCategory && longRunningSet.has(primaryCategory);
|
|
7092
|
+
return {
|
|
7093
|
+
shouldDelegate,
|
|
7094
|
+
category: primaryCategory
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
7097
|
+
function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable) {
|
|
7098
|
+
const delegation = shouldDelegateCurrentAction(actionOptions, autoRunAvailable);
|
|
7099
|
+
return {
|
|
7100
|
+
mode: "main_orchestrates_subagent_execution",
|
|
7101
|
+
delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
|
|
7102
|
+
delegateCommandExecution: "long_running_only",
|
|
7103
|
+
delegateAutoRunExecution: true,
|
|
7104
|
+
fallbackToMainAgentWhenSubAgentUnavailable: true,
|
|
7105
|
+
longRunningCategories: [...LONG_RUNNING_DELEGATION_CATEGORIES],
|
|
7106
|
+
currentActionShouldDelegate: delegation.shouldDelegate,
|
|
7107
|
+
currentActionCategory: delegation.category,
|
|
7108
|
+
mainAgentResponsibilities: [
|
|
7109
|
+
"Keep user conversation state and approval boundaries",
|
|
7110
|
+
"Run the same execution loop directly when sub-agent is unavailable",
|
|
7111
|
+
"Delegate only long-running command/auto loops to sub-agents",
|
|
7112
|
+
"Report only on approval/manual/error boundaries"
|
|
7113
|
+
],
|
|
7114
|
+
subAgentResponsibilities: [
|
|
7115
|
+
"Run flow/context command loops",
|
|
7116
|
+
"Execute only currently selected atomic command actions",
|
|
7117
|
+
"Return structured status to main agent"
|
|
7118
|
+
],
|
|
7119
|
+
pauseAndReportWhen: [
|
|
7120
|
+
"approvalRequest.required=true",
|
|
7121
|
+
"AUTO_GATE_REACHED",
|
|
7122
|
+
"AUTO_MANUAL_REQUIRED",
|
|
7123
|
+
"command execution error"
|
|
7124
|
+
],
|
|
7125
|
+
resumePriority: [
|
|
7126
|
+
"flow --resume <RUN_ID>",
|
|
7127
|
+
"autoRun.resume.flowCommand",
|
|
7128
|
+
"context --json-compact"
|
|
7129
|
+
]
|
|
7130
|
+
};
|
|
7131
|
+
}
|
|
7060
7132
|
async function resolveContextState(config, featureName, options) {
|
|
7061
7133
|
if (!config) {
|
|
7062
7134
|
throw createCliError(
|
|
@@ -7381,9 +7453,6 @@ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
|
|
|
7381
7453
|
if (prePrReviewPolicy.enabled && f.prePrReview.status !== "Done") {
|
|
7382
7454
|
return tr(lang, "cli", "context.list.completePrePrReview");
|
|
7383
7455
|
}
|
|
7384
|
-
if (prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && (!f.docs.prePrFindingsFieldExists || !f.prePrReview.findingsProvided)) {
|
|
7385
|
-
return tr(lang, "cli", "context.list.addPrePrFindings");
|
|
7386
|
-
}
|
|
7387
7456
|
if (prePrReviewPolicy.enabled && (!f.docs.prePrEvidenceFieldExists || !f.prePrReview.evidenceProvided)) {
|
|
7388
7457
|
return tr(lang, "cli", "context.list.addPrePrEvidence");
|
|
7389
7458
|
}
|
|
@@ -7448,14 +7517,11 @@ function toCompactFeature(feature) {
|
|
|
7448
7517
|
tasks: feature.tasks,
|
|
7449
7518
|
prePrReview: {
|
|
7450
7519
|
status: feature.prePrReview.status,
|
|
7451
|
-
findings: feature.prePrReview.findings,
|
|
7452
|
-
findingsProvided: feature.prePrReview.findingsProvided,
|
|
7453
7520
|
evidenceProvided: feature.prePrReview.evidenceProvided,
|
|
7454
7521
|
decisionOutcome: feature.prePrReview.decisionOutcome,
|
|
7455
7522
|
decisionProvided: feature.prePrReview.decisionProvided
|
|
7456
7523
|
},
|
|
7457
7524
|
prReview: {
|
|
7458
|
-
findings: feature.prReview.findings,
|
|
7459
7525
|
evidenceProvided: feature.prReview.evidenceProvided,
|
|
7460
7526
|
decisionProvided: feature.prReview.decisionProvided
|
|
7461
7527
|
},
|
|
@@ -7487,10 +7553,8 @@ function toCompactFeature(feature) {
|
|
|
7487
7553
|
prFieldExists: feature.docs.prFieldExists,
|
|
7488
7554
|
prStatusFieldExists: feature.docs.prStatusFieldExists,
|
|
7489
7555
|
prePrReviewFieldExists: feature.docs.prePrReviewFieldExists,
|
|
7490
|
-
prePrFindingsFieldExists: feature.docs.prePrFindingsFieldExists,
|
|
7491
7556
|
prePrEvidenceFieldExists: feature.docs.prePrEvidenceFieldExists,
|
|
7492
7557
|
prePrDecisionFieldExists: feature.docs.prePrDecisionFieldExists,
|
|
7493
|
-
prReviewFindingsFieldExists: feature.docs.prReviewFindingsFieldExists,
|
|
7494
7558
|
prReviewEvidenceFieldExists: feature.docs.prReviewEvidenceFieldExists,
|
|
7495
7559
|
prReviewDecisionFieldExists: feature.docs.prReviewDecisionFieldExists
|
|
7496
7560
|
},
|
|
@@ -7612,6 +7676,10 @@ async function runContext(featureName, options) {
|
|
|
7612
7676
|
config.approval,
|
|
7613
7677
|
approvalRequired
|
|
7614
7678
|
);
|
|
7679
|
+
const agentOrchestration = buildAgentOrchestrationPolicy(
|
|
7680
|
+
state.actionOptions,
|
|
7681
|
+
autoRunPlan.available
|
|
7682
|
+
);
|
|
7615
7683
|
if (options.approve || options.execute) {
|
|
7616
7684
|
await runApprovedOption(
|
|
7617
7685
|
state,
|
|
@@ -7687,6 +7755,7 @@ async function runContext(featureName, options) {
|
|
|
7687
7755
|
contextVersion: state.contextVersion,
|
|
7688
7756
|
config: config.approval ?? { mode: "builtin" }
|
|
7689
7757
|
},
|
|
7758
|
+
agentOrchestration,
|
|
7690
7759
|
autoRun: {
|
|
7691
7760
|
available: autoRunPlan.available,
|
|
7692
7761
|
reasonCode: autoRunPlan.reasonCode,
|
|
@@ -7768,12 +7837,13 @@ async function runContext(featureName, options) {
|
|
|
7768
7837
|
"actionOptions[].detail",
|
|
7769
7838
|
"actionOptions[].approvalPrompt"
|
|
7770
7839
|
] : [],
|
|
7771
|
-
recommendation: "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`). For command execution, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed.",
|
|
7840
|
+
recommendation: "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`). For command execution, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed. Use main-agent orchestration: keep short steps in main agent, and delegate only long-running command/auto loops to sub-agents.",
|
|
7772
7841
|
oneApprovalPerAction: approvalRequired,
|
|
7773
7842
|
requireFreshContext: true,
|
|
7774
7843
|
contextVersion: state.contextVersion,
|
|
7775
7844
|
config: config.approval ?? { mode: "builtin" }
|
|
7776
7845
|
},
|
|
7846
|
+
agentOrchestration,
|
|
7777
7847
|
autoRun: {
|
|
7778
7848
|
available: autoRunPlan.available,
|
|
7779
7849
|
reasonCode: autoRunPlan.reasonCode,
|
|
@@ -7784,7 +7854,7 @@ async function runContext(featureName, options) {
|
|
|
7784
7854
|
guidance: "Use auto-run only when `autoRun.available=true`. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories."
|
|
7785
7855
|
},
|
|
7786
7856
|
approvalRequest: {
|
|
7787
|
-
guidance: "User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`. Do not expose `requiredDocs`, `checkPolicy`, or raw `cmd` unless explicitly requested. For approved command actions, prefer one-shot `flow --approve <LABEL> --execute`.",
|
|
7857
|
+
guidance: "User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`. Do not expose `requiredDocs`, `checkPolicy`, or raw `cmd` unless explicitly requested. For approved command actions, prefer one-shot `flow --approve <LABEL> --execute`. Keep short steps in main agent and delegate only long-running command/auto loops to sub-agents.",
|
|
7788
7858
|
required: approvalRequired,
|
|
7789
7859
|
finalPrompt: finalApprovalPrompt,
|
|
7790
7860
|
userFacingLines: approvalUserFacingLines,
|
|
@@ -8006,7 +8076,7 @@ async function runContext(featureName, options) {
|
|
|
8006
8076
|
if (f.issueNumber) {
|
|
8007
8077
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
8008
8078
|
}
|
|
8009
|
-
console.log(` \u2022 Path: ${
|
|
8079
|
+
console.log(` \u2022 Path: ${path23.relative(cwd, f.path)}`);
|
|
8010
8080
|
if (f.git.projectBranch) {
|
|
8011
8081
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
8012
8082
|
}
|
|
@@ -8040,6 +8110,11 @@ async function runContext(featureName, options) {
|
|
|
8040
8110
|
return;
|
|
8041
8111
|
}
|
|
8042
8112
|
const actionOptions = state.actionOptions;
|
|
8113
|
+
const hasCommandOption = actionOptions.some((option) => option.action.type === "command");
|
|
8114
|
+
const longRunningDelegation = shouldDelegateCurrentAction(
|
|
8115
|
+
actionOptions,
|
|
8116
|
+
autoRunPlan.available
|
|
8117
|
+
);
|
|
8043
8118
|
console.log(chalk6.green(chalk6.bold("\u{1F449} Next Options (Atomic):")));
|
|
8044
8119
|
let hasDocsCommand = false;
|
|
8045
8120
|
actionOptions.forEach((option) => {
|
|
@@ -8053,6 +8128,9 @@ async function runContext(featureName, options) {
|
|
|
8053
8128
|
if (hasDocsCommand) {
|
|
8054
8129
|
console.log(chalk6.gray(` \u21B3 ${tr(lang, "cli", "context.tipDocsCommitRules")}`));
|
|
8055
8130
|
}
|
|
8131
|
+
if (hasCommandOption && longRunningDelegation.shouldDelegate) {
|
|
8132
|
+
console.log(chalk6.gray(` \u21B3 ${tr(lang, "cli", "context.subAgentOrchestrationHint")}`));
|
|
8133
|
+
}
|
|
8056
8134
|
if (hasCheckAction) {
|
|
8057
8135
|
console.log(chalk6.gray(` \u21B3 ${tr(lang, "cli", "context.actionOptionHint")}`));
|
|
8058
8136
|
console.log(chalk6.gray(` \u21B3 ${tr(lang, "cli", "context.actionExplainHint")}`));
|
|
@@ -8370,7 +8448,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
8370
8448
|
]);
|
|
8371
8449
|
function formatPath(cwd, p) {
|
|
8372
8450
|
if (!p) return "";
|
|
8373
|
-
return
|
|
8451
|
+
return path23.isAbsolute(p) ? path23.relative(cwd, p) : p;
|
|
8374
8452
|
}
|
|
8375
8453
|
function detectPlaceholders(content) {
|
|
8376
8454
|
const patterns = [
|
|
@@ -8519,7 +8597,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
8519
8597
|
const placeholderContext = {
|
|
8520
8598
|
projectName: config.projectName,
|
|
8521
8599
|
featureName: f.slug,
|
|
8522
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
8600
|
+
featurePath: f.docs.featurePathFromDocs || path23.relative(config.docsDir, f.path),
|
|
8523
8601
|
repoType: f.type,
|
|
8524
8602
|
featureNumber
|
|
8525
8603
|
};
|
|
@@ -8529,9 +8607,9 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
8529
8607
|
"tasks.md"
|
|
8530
8608
|
];
|
|
8531
8609
|
for (const file of files) {
|
|
8532
|
-
const fullPath =
|
|
8533
|
-
if (!await
|
|
8534
|
-
const original = await
|
|
8610
|
+
const fullPath = path23.join(f.path, file);
|
|
8611
|
+
if (!await fs17.pathExists(fullPath)) continue;
|
|
8612
|
+
const original = await fs17.readFile(fullPath, "utf-8");
|
|
8535
8613
|
let next = original;
|
|
8536
8614
|
const changes = [];
|
|
8537
8615
|
const placeholderFix = applyPlaceholderFixes(next, placeholderContext, config.lang);
|
|
@@ -8555,7 +8633,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
8555
8633
|
}
|
|
8556
8634
|
if (next === original) continue;
|
|
8557
8635
|
if (!dryRun) {
|
|
8558
|
-
await
|
|
8636
|
+
await fs17.writeFile(fullPath, next, "utf-8");
|
|
8559
8637
|
}
|
|
8560
8638
|
entries.push({
|
|
8561
8639
|
path: formatPath(cwd, fullPath),
|
|
@@ -8574,8 +8652,8 @@ async function checkDocsStructure(config, cwd) {
|
|
|
8574
8652
|
const issues = [];
|
|
8575
8653
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
8576
8654
|
for (const dir of requiredDirs) {
|
|
8577
|
-
const p =
|
|
8578
|
-
if (!await
|
|
8655
|
+
const p = path23.join(config.docsDir, dir);
|
|
8656
|
+
if (!await fs17.pathExists(p)) {
|
|
8579
8657
|
issues.push({
|
|
8580
8658
|
level: "error",
|
|
8581
8659
|
code: "missing_dir",
|
|
@@ -8584,8 +8662,8 @@ async function checkDocsStructure(config, cwd) {
|
|
|
8584
8662
|
});
|
|
8585
8663
|
}
|
|
8586
8664
|
}
|
|
8587
|
-
const configPath =
|
|
8588
|
-
if (!await
|
|
8665
|
+
const configPath = path23.join(config.docsDir, ".lee-spec-kit.json");
|
|
8666
|
+
if (!await fs17.pathExists(configPath)) {
|
|
8589
8667
|
issues.push({
|
|
8590
8668
|
level: "warn",
|
|
8591
8669
|
code: "missing_config",
|
|
@@ -8607,7 +8685,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8607
8685
|
}
|
|
8608
8686
|
const idMap = /* @__PURE__ */ new Map();
|
|
8609
8687
|
for (const f of features) {
|
|
8610
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
8688
|
+
const rel = f.docs.featurePathFromDocs || path23.relative(config.docsDir, f.path);
|
|
8611
8689
|
const id = f.id || "UNKNOWN";
|
|
8612
8690
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
8613
8691
|
idMap.get(id).push(rel);
|
|
@@ -8615,9 +8693,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8615
8693
|
if (!isInitialTemplateState) {
|
|
8616
8694
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
8617
8695
|
for (const file of featureDocs) {
|
|
8618
|
-
const p =
|
|
8619
|
-
if (!await
|
|
8620
|
-
const content = await
|
|
8696
|
+
const p = path23.join(f.path, file);
|
|
8697
|
+
if (!await fs17.pathExists(p)) continue;
|
|
8698
|
+
const content = await fs17.readFile(p, "utf-8");
|
|
8621
8699
|
const placeholders = detectPlaceholders(content);
|
|
8622
8700
|
if (placeholders.length === 0) continue;
|
|
8623
8701
|
issues.push({
|
|
@@ -8630,9 +8708,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8630
8708
|
});
|
|
8631
8709
|
}
|
|
8632
8710
|
if (decisionsPlaceholderMode !== "off") {
|
|
8633
|
-
const decisionsPath =
|
|
8634
|
-
if (await
|
|
8635
|
-
const content = await
|
|
8711
|
+
const decisionsPath = path23.join(f.path, "decisions.md");
|
|
8712
|
+
if (await fs17.pathExists(decisionsPath)) {
|
|
8713
|
+
const content = await fs17.readFile(decisionsPath, "utf-8");
|
|
8636
8714
|
const placeholders = detectPlaceholders(content);
|
|
8637
8715
|
if (placeholders.length > 0) {
|
|
8638
8716
|
issues.push({
|
|
@@ -8659,7 +8737,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8659
8737
|
level: "warn",
|
|
8660
8738
|
code: "spec_status_unset",
|
|
8661
8739
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
8662
|
-
path: formatPath(cwd,
|
|
8740
|
+
path: formatPath(cwd, path23.join(f.path, "spec.md"))
|
|
8663
8741
|
});
|
|
8664
8742
|
}
|
|
8665
8743
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -8667,7 +8745,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8667
8745
|
level: "warn",
|
|
8668
8746
|
code: "plan_status_unset",
|
|
8669
8747
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
8670
|
-
path: formatPath(cwd,
|
|
8748
|
+
path: formatPath(cwd, path23.join(f.path, "plan.md"))
|
|
8671
8749
|
});
|
|
8672
8750
|
}
|
|
8673
8751
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -8675,7 +8753,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8675
8753
|
level: "warn",
|
|
8676
8754
|
code: "tasks_empty",
|
|
8677
8755
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
8678
|
-
path: formatPath(cwd,
|
|
8756
|
+
path: formatPath(cwd, path23.join(f.path, "tasks.md"))
|
|
8679
8757
|
});
|
|
8680
8758
|
}
|
|
8681
8759
|
if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
|
|
@@ -8683,7 +8761,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8683
8761
|
level: "warn",
|
|
8684
8762
|
code: "tasks_doc_status_missing",
|
|
8685
8763
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
8686
|
-
path: formatPath(cwd,
|
|
8764
|
+
path: formatPath(cwd, path23.join(f.path, "tasks.md"))
|
|
8687
8765
|
});
|
|
8688
8766
|
}
|
|
8689
8767
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -8691,7 +8769,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8691
8769
|
level: "warn",
|
|
8692
8770
|
code: "tasks_doc_status_unset",
|
|
8693
8771
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
8694
|
-
path: formatPath(cwd,
|
|
8772
|
+
path: formatPath(cwd, path23.join(f.path, "tasks.md"))
|
|
8695
8773
|
});
|
|
8696
8774
|
}
|
|
8697
8775
|
}
|
|
@@ -8715,7 +8793,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
8715
8793
|
level: "warn",
|
|
8716
8794
|
code: "missing_feature_id",
|
|
8717
8795
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
8718
|
-
path: formatPath(cwd,
|
|
8796
|
+
path: formatPath(cwd, path23.join(config.docsDir, p))
|
|
8719
8797
|
});
|
|
8720
8798
|
}
|
|
8721
8799
|
return issues;
|
|
@@ -8838,7 +8916,7 @@ function doctorCommand(program2) {
|
|
|
8838
8916
|
}
|
|
8839
8917
|
console.log();
|
|
8840
8918
|
console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
|
|
8841
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
8919
|
+
console.log(chalk6.gray(`- Docs: ${path23.relative(cwd, docsDir)}`));
|
|
8842
8920
|
console.log(chalk6.gray(`- Type: ${projectType}`));
|
|
8843
8921
|
console.log(chalk6.gray(`- Lang: ${lang}`));
|
|
8844
8922
|
console.log();
|
|
@@ -9010,7 +9088,7 @@ async function runView(featureName, options) {
|
|
|
9010
9088
|
}
|
|
9011
9089
|
console.log();
|
|
9012
9090
|
console.log(chalk6.bold("\u{1F4CA} Workflow View"));
|
|
9013
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
9091
|
+
console.log(chalk6.gray(`- Docs: ${path23.relative(cwd, config.docsDir)}`));
|
|
9014
9092
|
console.log(
|
|
9015
9093
|
chalk6.gray(
|
|
9016
9094
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -9080,9 +9158,145 @@ async function runView(featureName, options) {
|
|
|
9080
9158
|
}
|
|
9081
9159
|
console.log();
|
|
9082
9160
|
}
|
|
9161
|
+
function normalizeRunId(raw) {
|
|
9162
|
+
const value = raw.trim();
|
|
9163
|
+
if (!/^[A-Za-z0-9_-]{6,64}$/.test(value)) {
|
|
9164
|
+
throw createCliError(
|
|
9165
|
+
"INVALID_ARGUMENT",
|
|
9166
|
+
"Invalid flow run id format. Use an id returned by `flow --start-auto --json`."
|
|
9167
|
+
);
|
|
9168
|
+
}
|
|
9169
|
+
return value;
|
|
9170
|
+
}
|
|
9171
|
+
function getFlowRunBaseDir(cwd) {
|
|
9172
|
+
return path23.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
9173
|
+
}
|
|
9174
|
+
function getFlowRunPath(cwd, runId) {
|
|
9175
|
+
return path23.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
9176
|
+
}
|
|
9177
|
+
function getFlowRunLockPath(cwd, runId) {
|
|
9178
|
+
return path23.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
9179
|
+
}
|
|
9180
|
+
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
9181
|
+
const normalized = normalizeRunId(runId);
|
|
9182
|
+
const filePath = getFlowRunPath(cwd, normalized);
|
|
9183
|
+
if (!await fs17.pathExists(filePath)) {
|
|
9184
|
+
throw createCliError(
|
|
9185
|
+
"INVALID_ARGUMENT",
|
|
9186
|
+
`Unknown flow run id: ${normalized}. Start with \`flow <feature> --auto-... --start-auto\`.`
|
|
9187
|
+
);
|
|
9188
|
+
}
|
|
9189
|
+
try {
|
|
9190
|
+
const parsed = await fs17.readJson(filePath);
|
|
9191
|
+
if (!parsed || typeof parsed !== "object" || parsed.runId !== normalized) {
|
|
9192
|
+
throw new Error("invalid payload");
|
|
9193
|
+
}
|
|
9194
|
+
return parsed;
|
|
9195
|
+
} catch {
|
|
9196
|
+
throw createCliError(
|
|
9197
|
+
"INVALID_ARGUMENT",
|
|
9198
|
+
`Cannot load flow run record: ${normalized}`
|
|
9199
|
+
);
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
async function writeFlowRunRecord(cwd, record) {
|
|
9203
|
+
const filePath = getFlowRunPath(cwd, record.runId);
|
|
9204
|
+
await fs17.ensureDir(path23.dirname(filePath));
|
|
9205
|
+
await fs17.writeJson(filePath, record, { spaces: 2 });
|
|
9206
|
+
}
|
|
9207
|
+
async function createFlowRunRecord(cwd, input) {
|
|
9208
|
+
const runId = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
9209
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
9210
|
+
const record = {
|
|
9211
|
+
runId,
|
|
9212
|
+
featureName: input.featureName,
|
|
9213
|
+
selection: {
|
|
9214
|
+
component: input.selection.component || void 0,
|
|
9215
|
+
all: !!input.selection.all,
|
|
9216
|
+
done: !!input.selection.done
|
|
9217
|
+
},
|
|
9218
|
+
auto: {
|
|
9219
|
+
untilCategories: [...input.auto.untilCategories],
|
|
9220
|
+
requestText: input.auto.requestText?.trim() || void 0,
|
|
9221
|
+
requestPending: input.auto.requestPending,
|
|
9222
|
+
preset: input.auto.preset ?? null,
|
|
9223
|
+
source: input.auto.source ?? null
|
|
9224
|
+
},
|
|
9225
|
+
status: "running",
|
|
9226
|
+
createdAt: nowIso,
|
|
9227
|
+
updatedAt: nowIso
|
|
9228
|
+
};
|
|
9229
|
+
const lockPath = getFlowRunLockPath(cwd, runId);
|
|
9230
|
+
return withFileLock(
|
|
9231
|
+
lockPath,
|
|
9232
|
+
async () => {
|
|
9233
|
+
await writeFlowRunRecord(cwd, record);
|
|
9234
|
+
return record;
|
|
9235
|
+
},
|
|
9236
|
+
{ owner: "flow-run:create" }
|
|
9237
|
+
);
|
|
9238
|
+
}
|
|
9239
|
+
async function getFlowRunRecord(cwd, runId) {
|
|
9240
|
+
const normalized = normalizeRunId(runId);
|
|
9241
|
+
return readFlowRunRecordUnsafe(cwd, normalized);
|
|
9242
|
+
}
|
|
9243
|
+
async function updateFlowRunRecord(cwd, runId, updater) {
|
|
9244
|
+
const normalized = normalizeRunId(runId);
|
|
9245
|
+
const lockPath = getFlowRunLockPath(cwd, normalized);
|
|
9246
|
+
return withFileLock(
|
|
9247
|
+
lockPath,
|
|
9248
|
+
async () => {
|
|
9249
|
+
const current = await readFlowRunRecordUnsafe(cwd, normalized);
|
|
9250
|
+
const next = updater(current);
|
|
9251
|
+
const updated = {
|
|
9252
|
+
...next,
|
|
9253
|
+
runId: normalized,
|
|
9254
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9255
|
+
};
|
|
9256
|
+
await writeFlowRunRecord(cwd, updated);
|
|
9257
|
+
return updated;
|
|
9258
|
+
},
|
|
9259
|
+
{ owner: "flow-run:update" }
|
|
9260
|
+
);
|
|
9261
|
+
}
|
|
9262
|
+
|
|
9263
|
+
// src/commands/flow.ts
|
|
9264
|
+
var LONG_RUNNING_DELEGATION_CATEGORIES2 = [
|
|
9265
|
+
"task_execute",
|
|
9266
|
+
"code_review",
|
|
9267
|
+
"review_fix_commit",
|
|
9268
|
+
"pre_pr_review"
|
|
9269
|
+
];
|
|
9083
9270
|
var BUILTIN_AUTO_PRESETS = {
|
|
9084
9271
|
"pr-handoff": ["pr_create", "code_review", "pr_status_update"]
|
|
9085
9272
|
};
|
|
9273
|
+
function shellEscape(arg) {
|
|
9274
|
+
if (/^[A-Za-z0-9_./:@%-]+$/.test(arg)) return arg;
|
|
9275
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
9276
|
+
}
|
|
9277
|
+
function toLeeSpecKitCommand(args) {
|
|
9278
|
+
return ["npx", "lee-spec-kit", ...args].map((arg) => shellEscape(arg)).join(" ");
|
|
9279
|
+
}
|
|
9280
|
+
function buildResumeRunCommand(runId) {
|
|
9281
|
+
return toLeeSpecKitCommand(["flow", "--resume", runId]);
|
|
9282
|
+
}
|
|
9283
|
+
function buildAutoResume(featureName, selectionOptions, untilCategories, requestText, requestPending) {
|
|
9284
|
+
const selectionArgs = buildSelectionArgs(featureName, selectionOptions);
|
|
9285
|
+
const flowArgs = ["flow", ...selectionArgs];
|
|
9286
|
+
if (requestPending && requestText) {
|
|
9287
|
+
flowArgs.push("--request", requestText);
|
|
9288
|
+
}
|
|
9289
|
+
flowArgs.push("--auto-until-category", untilCategories.join(","));
|
|
9290
|
+
const contextArgs = ["context", ...selectionArgs];
|
|
9291
|
+
return {
|
|
9292
|
+
flowArgs,
|
|
9293
|
+
flowCommand: toLeeSpecKitCommand(flowArgs),
|
|
9294
|
+
contextArgs,
|
|
9295
|
+
contextCommand: toLeeSpecKitCommand(contextArgs),
|
|
9296
|
+
requiresFreshContext: true,
|
|
9297
|
+
requestPending
|
|
9298
|
+
};
|
|
9299
|
+
}
|
|
9086
9300
|
function runSelfCli(args) {
|
|
9087
9301
|
const entry = process.argv[1];
|
|
9088
9302
|
const result = spawnSync(process.execPath, [entry, "--no-banner", ...args], {
|
|
@@ -9271,6 +9485,153 @@ function toAutoReasonCode(status) {
|
|
|
9271
9485
|
function isAutoRunFailureStatus(status) {
|
|
9272
9486
|
return status === "manual_required" || status === "selection_required" || status === "no_progress" || status === "request_label_missing" || status === "request_failed" || status === "execution_failed";
|
|
9273
9487
|
}
|
|
9488
|
+
function toFlowRunStatus(status) {
|
|
9489
|
+
switch (status) {
|
|
9490
|
+
case "gate_reached":
|
|
9491
|
+
case "manual_required":
|
|
9492
|
+
return "paused";
|
|
9493
|
+
case "no_action_options":
|
|
9494
|
+
return "completed";
|
|
9495
|
+
case "selection_required":
|
|
9496
|
+
case "no_progress":
|
|
9497
|
+
case "request_label_missing":
|
|
9498
|
+
case "request_failed":
|
|
9499
|
+
case "execution_failed":
|
|
9500
|
+
default:
|
|
9501
|
+
return "failed";
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
function buildAgentOrchestrationPolicy2(autoRun) {
|
|
9505
|
+
const preferredResumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
|
|
9506
|
+
return {
|
|
9507
|
+
mode: "main_orchestrates_subagent_execution",
|
|
9508
|
+
delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
|
|
9509
|
+
delegateCommandExecution: "long_running_only",
|
|
9510
|
+
delegateAutoRunExecution: true,
|
|
9511
|
+
fallbackToMainAgentWhenSubAgentUnavailable: true,
|
|
9512
|
+
longRunningCategories: [...LONG_RUNNING_DELEGATION_CATEGORIES2],
|
|
9513
|
+
mainAgentResponsibilities: [
|
|
9514
|
+
"Keep user conversation state and approval boundaries",
|
|
9515
|
+
"Run the same execution loop directly when sub-agent is unavailable",
|
|
9516
|
+
"Delegate only long-running command/auto loops to sub-agents",
|
|
9517
|
+
"Report only on approval/manual/error boundaries"
|
|
9518
|
+
],
|
|
9519
|
+
subAgentResponsibilities: [
|
|
9520
|
+
"Run flow/context command loops",
|
|
9521
|
+
"Execute selected atomic command actions",
|
|
9522
|
+
"Return structured status and errors to main agent"
|
|
9523
|
+
],
|
|
9524
|
+
pauseAndReportWhen: [
|
|
9525
|
+
"approvalRequest.required=true",
|
|
9526
|
+
"AUTO_GATE_REACHED",
|
|
9527
|
+
"AUTO_MANUAL_REQUIRED",
|
|
9528
|
+
"command execution error"
|
|
9529
|
+
],
|
|
9530
|
+
preferredResumeCommand
|
|
9531
|
+
};
|
|
9532
|
+
}
|
|
9533
|
+
function getFeatureRef2(feature) {
|
|
9534
|
+
return feature.folderName;
|
|
9535
|
+
}
|
|
9536
|
+
function toCompactFlowFeature(feature) {
|
|
9537
|
+
if (!feature) return null;
|
|
9538
|
+
return {
|
|
9539
|
+
ref: getFeatureRef2(feature),
|
|
9540
|
+
id: feature.id,
|
|
9541
|
+
slug: feature.slug,
|
|
9542
|
+
type: feature.type,
|
|
9543
|
+
issueNumber: feature.issueNumber,
|
|
9544
|
+
specStatus: feature.specStatus,
|
|
9545
|
+
planStatus: feature.planStatus,
|
|
9546
|
+
tasksDocStatus: feature.tasksDocStatus,
|
|
9547
|
+
currentStep: feature.currentStep,
|
|
9548
|
+
completion: {
|
|
9549
|
+
implementationDone: feature.completion.implementationDone,
|
|
9550
|
+
workflowDone: feature.completion.workflowDone
|
|
9551
|
+
},
|
|
9552
|
+
tasks: feature.tasks,
|
|
9553
|
+
completionChecklist: feature.completionChecklist,
|
|
9554
|
+
warnings: feature.warnings
|
|
9555
|
+
};
|
|
9556
|
+
}
|
|
9557
|
+
function toCompactFlowActionOption(option) {
|
|
9558
|
+
const base = {
|
|
9559
|
+
label: option.label,
|
|
9560
|
+
summary: option.summary,
|
|
9561
|
+
detail: option.detail,
|
|
9562
|
+
approvalPrompt: option.approvalPrompt,
|
|
9563
|
+
requiresRequestText: option.requiresRequestText,
|
|
9564
|
+
replyExample: option.replyExample,
|
|
9565
|
+
actionType: option.action.type,
|
|
9566
|
+
category: option.action.category,
|
|
9567
|
+
operationType: option.action.operationType,
|
|
9568
|
+
requiresUserCheck: !!option.action.requiresUserCheck
|
|
9569
|
+
};
|
|
9570
|
+
if (option.action.type === "command") {
|
|
9571
|
+
base.scope = option.action.scope;
|
|
9572
|
+
base.cwd = option.action.cwd;
|
|
9573
|
+
base.cmd = option.action.cmd;
|
|
9574
|
+
return base;
|
|
9575
|
+
}
|
|
9576
|
+
base.message = option.action.message;
|
|
9577
|
+
return base;
|
|
9578
|
+
}
|
|
9579
|
+
function toCompactFlowContextSnapshot(state) {
|
|
9580
|
+
const primaryAction = state.actionOptions[0] ?? null;
|
|
9581
|
+
return {
|
|
9582
|
+
status: state.status,
|
|
9583
|
+
reasonCode: toReasonCode(state.status),
|
|
9584
|
+
selectionMode: state.selectionMode,
|
|
9585
|
+
selectionFallback: state.selectionFallback,
|
|
9586
|
+
branches: state.branches,
|
|
9587
|
+
warnings: state.warnings,
|
|
9588
|
+
contextVersion: state.contextVersion,
|
|
9589
|
+
matchedFeature: toCompactFlowFeature(state.matchedFeature),
|
|
9590
|
+
candidateRefs: state.targetFeatures.length > 1 ? state.targetFeatures.map((feature) => getFeatureRef2(feature)) : [],
|
|
9591
|
+
completedCandidateRefs: state.selectionMode === "open" ? state.doneFeatures.map((feature) => getFeatureRef2(feature)) : [],
|
|
9592
|
+
openCandidateRefs: state.selectionMode === "open" ? state.openFeatures.map((feature) => getFeatureRef2(feature)) : [],
|
|
9593
|
+
inProgressCandidateRefs: state.selectionMode === "open" ? state.inProgressFeatures.map((feature) => getFeatureRef2(feature)) : [],
|
|
9594
|
+
readyToCloseCandidateRefs: state.selectionMode === "open" ? state.readyToCloseFeatures.map((feature) => getFeatureRef2(feature)) : [],
|
|
9595
|
+
actionOptions: state.actionOptions.map(
|
|
9596
|
+
(option) => toCompactFlowActionOption(option)
|
|
9597
|
+
),
|
|
9598
|
+
primaryActionLabel: primaryAction?.label ?? null,
|
|
9599
|
+
primaryActionType: primaryAction?.action.type ?? null,
|
|
9600
|
+
primaryActionCategory: primaryAction?.action.category ?? null,
|
|
9601
|
+
primaryActionOperationType: primaryAction?.action.operationType ?? null
|
|
9602
|
+
};
|
|
9603
|
+
}
|
|
9604
|
+
function toCompactAutoRun(autoRun) {
|
|
9605
|
+
if (!autoRun) return null;
|
|
9606
|
+
const lastExecution = autoRun.executions.length > 0 ? autoRun.executions[autoRun.executions.length - 1] : null;
|
|
9607
|
+
return {
|
|
9608
|
+
enabled: autoRun.enabled,
|
|
9609
|
+
status: autoRun.status,
|
|
9610
|
+
reasonCode: autoRun.reasonCode,
|
|
9611
|
+
untilCategories: autoRun.untilCategories,
|
|
9612
|
+
request: autoRun.request,
|
|
9613
|
+
preset: autoRun.preset ?? null,
|
|
9614
|
+
source: autoRun.source ?? null,
|
|
9615
|
+
iterations: autoRun.iterations,
|
|
9616
|
+
executionCount: autoRun.executions.length,
|
|
9617
|
+
lastExecution,
|
|
9618
|
+
gate: autoRun.gate ?? null,
|
|
9619
|
+
manual: autoRun.manual ?? null,
|
|
9620
|
+
resume: autoRun.resume,
|
|
9621
|
+
run: autoRun.run ?? null,
|
|
9622
|
+
error: autoRun.error ?? null
|
|
9623
|
+
};
|
|
9624
|
+
}
|
|
9625
|
+
function toCompactStatusReport(report) {
|
|
9626
|
+
if (!report || typeof report !== "object") return null;
|
|
9627
|
+
const payload = report;
|
|
9628
|
+
return {
|
|
9629
|
+
status: payload.status ?? null,
|
|
9630
|
+
reasonCode: payload.reasonCode ?? null,
|
|
9631
|
+
counts: payload.counts ?? null,
|
|
9632
|
+
recommendation: payload.recommendation ?? null
|
|
9633
|
+
};
|
|
9634
|
+
}
|
|
9274
9635
|
async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
|
|
9275
9636
|
const contextArgs = ["context", ...buildSelectionArgs(featureName, selectionOptions)];
|
|
9276
9637
|
const gateSet = new Set(untilCategories);
|
|
@@ -9281,6 +9642,13 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9281
9642
|
let requestHandled = !requestText;
|
|
9282
9643
|
let iterations = 0;
|
|
9283
9644
|
while (true) {
|
|
9645
|
+
const resume = buildAutoResume(
|
|
9646
|
+
featureName,
|
|
9647
|
+
selectionOptions,
|
|
9648
|
+
untilCategories,
|
|
9649
|
+
requestText,
|
|
9650
|
+
!requestHandled
|
|
9651
|
+
);
|
|
9284
9652
|
iterations += 1;
|
|
9285
9653
|
const state = await resolveContextSelection(config, featureName, selectionOptions);
|
|
9286
9654
|
const actionOptions = state.actionOptions;
|
|
@@ -9306,6 +9674,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9306
9674
|
request: requestText,
|
|
9307
9675
|
preset: metadata?.preset ?? null,
|
|
9308
9676
|
source: metadata?.source ?? null,
|
|
9677
|
+
resume,
|
|
9309
9678
|
status: "no_progress",
|
|
9310
9679
|
reasonCode: toAutoReasonCode("no_progress"),
|
|
9311
9680
|
iterations,
|
|
@@ -9322,6 +9691,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9322
9691
|
request: requestText,
|
|
9323
9692
|
preset: metadata?.preset ?? null,
|
|
9324
9693
|
source: metadata?.source ?? null,
|
|
9694
|
+
resume,
|
|
9325
9695
|
status: "selection_required",
|
|
9326
9696
|
reasonCode: toAutoReasonCode("selection_required"),
|
|
9327
9697
|
iterations,
|
|
@@ -9338,6 +9708,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9338
9708
|
request: requestText,
|
|
9339
9709
|
preset: metadata?.preset ?? null,
|
|
9340
9710
|
source: metadata?.source ?? null,
|
|
9711
|
+
resume,
|
|
9341
9712
|
status: "no_action_options",
|
|
9342
9713
|
reasonCode: toAutoReasonCode("no_action_options"),
|
|
9343
9714
|
iterations,
|
|
@@ -9357,6 +9728,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9357
9728
|
request: requestText,
|
|
9358
9729
|
preset: metadata?.preset ?? null,
|
|
9359
9730
|
source: metadata?.source ?? null,
|
|
9731
|
+
resume,
|
|
9360
9732
|
status: "request_label_missing",
|
|
9361
9733
|
reasonCode: toAutoReasonCode("request_label_missing"),
|
|
9362
9734
|
iterations,
|
|
@@ -9390,6 +9762,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9390
9762
|
request: requestText,
|
|
9391
9763
|
preset: metadata?.preset ?? null,
|
|
9392
9764
|
source: metadata?.source ?? null,
|
|
9765
|
+
resume,
|
|
9393
9766
|
status: "request_failed",
|
|
9394
9767
|
reasonCode: toAutoReasonCode("request_failed"),
|
|
9395
9768
|
iterations,
|
|
@@ -9413,6 +9786,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9413
9786
|
request: requestText,
|
|
9414
9787
|
preset: metadata?.preset ?? null,
|
|
9415
9788
|
source: metadata?.source ?? null,
|
|
9789
|
+
resume,
|
|
9416
9790
|
status: "gate_reached",
|
|
9417
9791
|
reasonCode: toAutoReasonCode("gate_reached"),
|
|
9418
9792
|
iterations,
|
|
@@ -9435,6 +9809,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9435
9809
|
request: requestText,
|
|
9436
9810
|
preset: metadata?.preset ?? null,
|
|
9437
9811
|
source: metadata?.source ?? null,
|
|
9812
|
+
resume,
|
|
9438
9813
|
status: "manual_required",
|
|
9439
9814
|
reasonCode: toAutoReasonCode("manual_required"),
|
|
9440
9815
|
iterations,
|
|
@@ -9470,6 +9845,7 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9470
9845
|
request: requestText,
|
|
9471
9846
|
preset: metadata?.preset ?? null,
|
|
9472
9847
|
source: metadata?.source ?? null,
|
|
9848
|
+
resume,
|
|
9473
9849
|
status: "execution_failed",
|
|
9474
9850
|
reasonCode: toAutoReasonCode("execution_failed"),
|
|
9475
9851
|
iterations,
|
|
@@ -9505,6 +9881,9 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9505
9881
|
enabled: true,
|
|
9506
9882
|
untilCategories,
|
|
9507
9883
|
request: requestText,
|
|
9884
|
+
preset: metadata?.preset ?? null,
|
|
9885
|
+
source: metadata?.source ?? null,
|
|
9886
|
+
resume,
|
|
9508
9887
|
status: "execution_failed",
|
|
9509
9888
|
reasonCode: toAutoReasonCode("execution_failed"),
|
|
9510
9889
|
iterations,
|
|
@@ -9517,7 +9896,10 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
9517
9896
|
}
|
|
9518
9897
|
}
|
|
9519
9898
|
function flowCommand(program2) {
|
|
9520
|
-
program2.command("flow [feature-name]").description("Run combined workflow checks (context + status + doctor)").option("--json", "Output in JSON format for agents").option(
|
|
9899
|
+
program2.command("flow [feature-name]").description("Run combined workflow checks (context + status + doctor)").option("--json", "Output in JSON format for agents").option(
|
|
9900
|
+
"--json-compact",
|
|
9901
|
+
"Output compact JSON for agents (implies --json, reduced duplication)"
|
|
9902
|
+
).option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
|
|
9521
9903
|
"--request <text>",
|
|
9522
9904
|
"Apply a new user request first via user_request_replan when auto mode is enabled"
|
|
9523
9905
|
).option(
|
|
@@ -9526,6 +9908,12 @@ function flowCommand(program2) {
|
|
|
9526
9908
|
).option(
|
|
9527
9909
|
"--auto-until-category <categories>",
|
|
9528
9910
|
"Auto-run command actions until one of categories appears (comma-separated)"
|
|
9911
|
+
).option(
|
|
9912
|
+
"--start-auto",
|
|
9913
|
+
"Persist auto-run checkpoint and emit resumable run id in JSON output"
|
|
9914
|
+
).option(
|
|
9915
|
+
"--resume <run-id>",
|
|
9916
|
+
"Resume previously started auto-run checkpoint by run id"
|
|
9529
9917
|
).option(
|
|
9530
9918
|
"--approve <reply>",
|
|
9531
9919
|
"Approve one labeled context option (examples: A, A OK, A proceed, A \uC9C4\uD589\uD574)"
|
|
@@ -9540,7 +9928,7 @@ function flowCommand(program2) {
|
|
|
9540
9928
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
9541
9929
|
const cliError = toCliError(error);
|
|
9542
9930
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
9543
|
-
if (options.json) {
|
|
9931
|
+
if (options.json || options.jsonCompact) {
|
|
9544
9932
|
console.log(
|
|
9545
9933
|
JSON.stringify({
|
|
9546
9934
|
status: "error",
|
|
@@ -9581,14 +9969,79 @@ async function runFlow(featureName, options) {
|
|
|
9581
9969
|
"`--execute` requires `--approve <reply>`."
|
|
9582
9970
|
);
|
|
9583
9971
|
}
|
|
9584
|
-
const
|
|
9972
|
+
const resumeRunId = (options.resume || "").trim() || void 0;
|
|
9973
|
+
if (options.startAuto && resumeRunId) {
|
|
9974
|
+
throw createCliError(
|
|
9975
|
+
"INVALID_ARGUMENT",
|
|
9976
|
+
"`--start-auto` cannot be combined with `--resume`."
|
|
9977
|
+
);
|
|
9978
|
+
}
|
|
9979
|
+
if (resumeRunId) {
|
|
9980
|
+
if (featureName) {
|
|
9981
|
+
throw createCliError(
|
|
9982
|
+
"INVALID_ARGUMENT",
|
|
9983
|
+
"`--resume` cannot be combined with <feature-name>."
|
|
9984
|
+
);
|
|
9985
|
+
}
|
|
9986
|
+
if (options.autoPreset || options.autoUntilCategory || options.request) {
|
|
9987
|
+
throw createCliError(
|
|
9988
|
+
"INVALID_ARGUMENT",
|
|
9989
|
+
"`--resume` cannot be combined with `--auto-*` or `--request`."
|
|
9990
|
+
);
|
|
9991
|
+
}
|
|
9992
|
+
if (options.component || options.all || options.done) {
|
|
9993
|
+
throw createCliError(
|
|
9994
|
+
"INVALID_ARGUMENT",
|
|
9995
|
+
"`--resume` cannot be combined with `--component`, `--all`, or `--done`."
|
|
9996
|
+
);
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
let requestText = options.request?.trim() || void 0;
|
|
9585
10000
|
if (options.autoPreset && options.autoUntilCategory) {
|
|
9586
10001
|
throw createCliError(
|
|
9587
10002
|
"INVALID_ARGUMENT",
|
|
9588
10003
|
"`--auto-preset` cannot be combined with `--auto-until-category`."
|
|
9589
10004
|
);
|
|
9590
10005
|
}
|
|
9591
|
-
|
|
10006
|
+
let resolvedFeatureName = featureName;
|
|
10007
|
+
let selectedComponent = resolveComponentOption(options.component);
|
|
10008
|
+
const selectionOptions = {
|
|
10009
|
+
component: selectedComponent,
|
|
10010
|
+
all: options.all,
|
|
10011
|
+
done: options.done
|
|
10012
|
+
};
|
|
10013
|
+
let autoMode = resolveAutoMode(config, options, requestText);
|
|
10014
|
+
let flowRunRecord = null;
|
|
10015
|
+
let flowRunMode = null;
|
|
10016
|
+
if (resumeRunId) {
|
|
10017
|
+
flowRunRecord = await getFlowRunRecord(process.cwd(), resumeRunId);
|
|
10018
|
+
if (flowRunRecord.status === "completed") {
|
|
10019
|
+
throw createCliError(
|
|
10020
|
+
"INVALID_ARGUMENT",
|
|
10021
|
+
`Flow run ${resumeRunId} is already completed.`
|
|
10022
|
+
);
|
|
10023
|
+
}
|
|
10024
|
+
resolvedFeatureName = flowRunRecord.featureName;
|
|
10025
|
+
selectedComponent = resolveComponentOption(flowRunRecord.selection.component);
|
|
10026
|
+
selectionOptions.component = selectedComponent;
|
|
10027
|
+
selectionOptions.all = !!flowRunRecord.selection.all;
|
|
10028
|
+
selectionOptions.done = !!flowRunRecord.selection.done;
|
|
10029
|
+
requestText = flowRunRecord.auto.requestPending ? flowRunRecord.auto.requestText?.trim() || void 0 : void 0;
|
|
10030
|
+
autoMode = {
|
|
10031
|
+
untilCategories: [...flowRunRecord.auto.untilCategories],
|
|
10032
|
+
preset: flowRunRecord.auto.preset ?? null,
|
|
10033
|
+
source: `resume:${resumeRunId}`
|
|
10034
|
+
};
|
|
10035
|
+
flowRunMode = "resumed";
|
|
10036
|
+
flowRunRecord = await updateFlowRunRecord(
|
|
10037
|
+
process.cwd(),
|
|
10038
|
+
resumeRunId,
|
|
10039
|
+
(current) => ({
|
|
10040
|
+
...current,
|
|
10041
|
+
status: "running"
|
|
10042
|
+
})
|
|
10043
|
+
);
|
|
10044
|
+
}
|
|
9592
10045
|
if (autoMode && options.approve) {
|
|
9593
10046
|
throw createCliError(
|
|
9594
10047
|
"INVALID_ARGUMENT",
|
|
@@ -9608,26 +10061,46 @@ async function runFlow(featureName, options) {
|
|
|
9608
10061
|
);
|
|
9609
10062
|
}
|
|
9610
10063
|
if (autoMode && !featureName) {
|
|
10064
|
+
if (!resolvedFeatureName) {
|
|
10065
|
+
throw createCliError(
|
|
10066
|
+
"CONTEXT_SELECTION_REQUIRED",
|
|
10067
|
+
"Auto mode requires explicit <feature-name> (e.g. F004)."
|
|
10068
|
+
);
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
if (options.startAuto && !autoMode) {
|
|
9611
10072
|
throw createCliError(
|
|
9612
|
-
"
|
|
9613
|
-
"
|
|
10073
|
+
"INVALID_ARGUMENT",
|
|
10074
|
+
"`--start-auto` requires auto mode (`--auto-until-category` or `--auto-preset`)."
|
|
9614
10075
|
);
|
|
9615
10076
|
}
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
10077
|
+
if (options.startAuto && autoMode && !flowRunRecord && resolvedFeatureName) {
|
|
10078
|
+
flowRunRecord = await createFlowRunRecord(process.cwd(), {
|
|
10079
|
+
featureName: resolvedFeatureName,
|
|
10080
|
+
selection: {
|
|
10081
|
+
component: selectedComponent || void 0,
|
|
10082
|
+
all: !!selectionOptions.all,
|
|
10083
|
+
done: !!selectionOptions.done
|
|
10084
|
+
},
|
|
10085
|
+
auto: {
|
|
10086
|
+
untilCategories: [...autoMode.untilCategories],
|
|
10087
|
+
requestText,
|
|
10088
|
+
requestPending: !!requestText,
|
|
10089
|
+
preset: autoMode.preset ?? null,
|
|
10090
|
+
source: autoMode.source
|
|
10091
|
+
}
|
|
10092
|
+
});
|
|
10093
|
+
flowRunMode = "started";
|
|
10094
|
+
}
|
|
9622
10095
|
const componentHint = selectedComponent ? ` --component ${selectedComponent}` : "";
|
|
9623
|
-
const before = await resolveContextSelection(config,
|
|
10096
|
+
const before = await resolveContextSelection(config, resolvedFeatureName, selectionOptions);
|
|
9624
10097
|
let approvalResult = null;
|
|
9625
10098
|
let autoRun = null;
|
|
9626
|
-
const contextArgs = ["context", ...buildSelectionArgs(
|
|
10099
|
+
const contextArgs = ["context", ...buildSelectionArgs(resolvedFeatureName, selectionOptions)];
|
|
9627
10100
|
if (autoMode) {
|
|
9628
10101
|
autoRun = await runAutoUntilCategory(
|
|
9629
10102
|
config,
|
|
9630
|
-
|
|
10103
|
+
resolvedFeatureName,
|
|
9631
10104
|
selectionOptions,
|
|
9632
10105
|
autoMode.untilCategories,
|
|
9633
10106
|
requestText,
|
|
@@ -9659,7 +10132,31 @@ async function runFlow(featureName, options) {
|
|
|
9659
10132
|
approvalResult = selected;
|
|
9660
10133
|
}
|
|
9661
10134
|
}
|
|
9662
|
-
|
|
10135
|
+
if (autoRun && flowRunRecord && flowRunMode) {
|
|
10136
|
+
const runStatus2 = toFlowRunStatus(autoRun.status);
|
|
10137
|
+
flowRunRecord = await updateFlowRunRecord(
|
|
10138
|
+
process.cwd(),
|
|
10139
|
+
flowRunRecord.runId,
|
|
10140
|
+
(current) => ({
|
|
10141
|
+
...current,
|
|
10142
|
+
status: runStatus2,
|
|
10143
|
+
auto: {
|
|
10144
|
+
...current.auto,
|
|
10145
|
+
requestPending: autoRun.resume.requestPending
|
|
10146
|
+
},
|
|
10147
|
+
lastAutoStatus: autoRun.status,
|
|
10148
|
+
lastReasonCode: autoRun.reasonCode,
|
|
10149
|
+
lastError: autoRun.error
|
|
10150
|
+
})
|
|
10151
|
+
);
|
|
10152
|
+
autoRun.run = {
|
|
10153
|
+
runId: flowRunRecord.runId,
|
|
10154
|
+
mode: flowRunMode,
|
|
10155
|
+
status: flowRunRecord.status,
|
|
10156
|
+
resumeCommand: buildResumeRunCommand(flowRunRecord.runId)
|
|
10157
|
+
};
|
|
10158
|
+
}
|
|
10159
|
+
const after = await resolveContextSelection(config, resolvedFeatureName, selectionOptions);
|
|
9663
10160
|
const statusReport = runSelfCliJson(["status"]);
|
|
9664
10161
|
const doctorReport = runSelfCliJson(["doctor"]);
|
|
9665
10162
|
let strictChecks = null;
|
|
@@ -9678,11 +10175,38 @@ async function runFlow(featureName, options) {
|
|
|
9678
10175
|
);
|
|
9679
10176
|
}
|
|
9680
10177
|
}
|
|
9681
|
-
|
|
10178
|
+
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
10179
|
+
if (jsonMode) {
|
|
9682
10180
|
const autoRunFailed = !!(autoRun && isAutoRunFailureStatus(autoRun.status));
|
|
10181
|
+
const agentOrchestration2 = buildAgentOrchestrationPolicy2(autoRun);
|
|
10182
|
+
const status = autoRunFailed ? "error" : "ok";
|
|
10183
|
+
const reasonCode = autoRunFailed ? autoRun?.reasonCode || "AUTO_EXECUTION_FAILED" : "FLOW_SUMMARY";
|
|
10184
|
+
if (options.jsonCompact) {
|
|
10185
|
+
const compactPayload = {
|
|
10186
|
+
schema: "flow.v2.compact",
|
|
10187
|
+
status,
|
|
10188
|
+
reasonCode,
|
|
10189
|
+
context: {
|
|
10190
|
+
before: toCompactFlowContextSnapshot(before),
|
|
10191
|
+
after: toCompactFlowContextSnapshot(after)
|
|
10192
|
+
},
|
|
10193
|
+
approval: approvalResult,
|
|
10194
|
+
autoRun: toCompactAutoRun(autoRun),
|
|
10195
|
+
agentOrchestration: agentOrchestration2,
|
|
10196
|
+
statusReport: toCompactStatusReport(statusReport),
|
|
10197
|
+
doctorReport: toCompactStatusReport(doctorReport),
|
|
10198
|
+
strictChecks,
|
|
10199
|
+
suggestion: after.matchedFeature ? `npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}` : `npx lee-spec-kit context${componentHint}`
|
|
10200
|
+
};
|
|
10201
|
+
console.log(JSON.stringify(compactPayload, null, 2));
|
|
10202
|
+
if (autoRunFailed) {
|
|
10203
|
+
process.exitCode = 1;
|
|
10204
|
+
}
|
|
10205
|
+
return;
|
|
10206
|
+
}
|
|
9683
10207
|
const payload = {
|
|
9684
|
-
status
|
|
9685
|
-
reasonCode
|
|
10208
|
+
status,
|
|
10209
|
+
reasonCode,
|
|
9686
10210
|
context: {
|
|
9687
10211
|
before: {
|
|
9688
10212
|
status: before.status,
|
|
@@ -9705,6 +10229,7 @@ async function runFlow(featureName, options) {
|
|
|
9705
10229
|
},
|
|
9706
10230
|
approval: approvalResult,
|
|
9707
10231
|
autoRun,
|
|
10232
|
+
agentOrchestration: agentOrchestration2,
|
|
9708
10233
|
statusReport,
|
|
9709
10234
|
doctorReport,
|
|
9710
10235
|
strictChecks,
|
|
@@ -9738,7 +10263,22 @@ async function runFlow(featureName, options) {
|
|
|
9738
10263
|
`- Auto: ${autoRun.status} (${autoRun.reasonCode}), iterations ${autoRun.iterations}, executions ${autoRun.executions.length}${presetSuffix}`
|
|
9739
10264
|
)
|
|
9740
10265
|
);
|
|
10266
|
+
console.log(chalk6.gray(`- Auto resume: ${autoRun.resume.flowCommand}`));
|
|
10267
|
+
if (autoRun.run) {
|
|
10268
|
+
console.log(
|
|
10269
|
+
chalk6.gray(
|
|
10270
|
+
`- Auto run: ${autoRun.run.mode} ${autoRun.run.runId} (${autoRun.run.status})`
|
|
10271
|
+
)
|
|
10272
|
+
);
|
|
10273
|
+
console.log(chalk6.gray(`- Resume with: ${autoRun.run.resumeCommand}`));
|
|
10274
|
+
}
|
|
9741
10275
|
}
|
|
10276
|
+
const agentOrchestration = buildAgentOrchestrationPolicy2(autoRun);
|
|
10277
|
+
console.log(
|
|
10278
|
+
chalk6.gray(
|
|
10279
|
+
`- Orchestration: ${agentOrchestration.mode}, delegate long-running loops to sub-agent`
|
|
10280
|
+
)
|
|
10281
|
+
);
|
|
9742
10282
|
const statusCounts = statusReport.counts;
|
|
9743
10283
|
const doctorCounts = doctorReport.counts;
|
|
9744
10284
|
console.log(chalk6.gray(`- Status features: ${statusCounts?.features ?? 0}`));
|
|
@@ -9886,40 +10426,40 @@ function tg(lang, key, vars = {}) {
|
|
|
9886
10426
|
}
|
|
9887
10427
|
function detectGithubCliLangSync(cwd) {
|
|
9888
10428
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
9889
|
-
const startDirs = [explicitDocsDir ?
|
|
10429
|
+
const startDirs = [explicitDocsDir ? path23.resolve(explicitDocsDir) : "", path23.resolve(cwd)].filter(Boolean);
|
|
9890
10430
|
const scanOrder = [];
|
|
9891
10431
|
const seen = /* @__PURE__ */ new Set();
|
|
9892
10432
|
for (const start of startDirs) {
|
|
9893
10433
|
let current = start;
|
|
9894
10434
|
while (true) {
|
|
9895
|
-
const abs =
|
|
10435
|
+
const abs = path23.resolve(current);
|
|
9896
10436
|
if (!seen.has(abs)) {
|
|
9897
10437
|
scanOrder.push(abs);
|
|
9898
10438
|
seen.add(abs);
|
|
9899
10439
|
}
|
|
9900
|
-
const parent =
|
|
10440
|
+
const parent = path23.dirname(abs);
|
|
9901
10441
|
if (parent === abs) break;
|
|
9902
10442
|
current = parent;
|
|
9903
10443
|
}
|
|
9904
10444
|
}
|
|
9905
10445
|
for (const base of scanOrder) {
|
|
9906
|
-
for (const docsDir of [
|
|
9907
|
-
const configPath =
|
|
9908
|
-
if (
|
|
10446
|
+
for (const docsDir of [path23.join(base, "docs"), base]) {
|
|
10447
|
+
const configPath = path23.join(docsDir, ".lee-spec-kit.json");
|
|
10448
|
+
if (fs17.existsSync(configPath)) {
|
|
9909
10449
|
try {
|
|
9910
|
-
const parsed =
|
|
10450
|
+
const parsed = fs17.readJsonSync(configPath);
|
|
9911
10451
|
if (parsed?.lang === "ko" || parsed?.lang === "en") return parsed.lang;
|
|
9912
10452
|
} catch {
|
|
9913
10453
|
}
|
|
9914
10454
|
}
|
|
9915
|
-
const agentsPath =
|
|
9916
|
-
const featuresPath =
|
|
9917
|
-
if (!
|
|
10455
|
+
const agentsPath = path23.join(docsDir, "agents");
|
|
10456
|
+
const featuresPath = path23.join(docsDir, "features");
|
|
10457
|
+
if (!fs17.existsSync(agentsPath) || !fs17.existsSync(featuresPath)) continue;
|
|
9918
10458
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
9919
|
-
const file =
|
|
9920
|
-
if (!
|
|
10459
|
+
const file = path23.join(agentsPath, probe);
|
|
10460
|
+
if (!fs17.existsSync(file)) continue;
|
|
9921
10461
|
try {
|
|
9922
|
-
const content =
|
|
10462
|
+
const content = fs17.readFileSync(file, "utf-8");
|
|
9923
10463
|
if (/[가-힣]/.test(content)) return "ko";
|
|
9924
10464
|
} catch {
|
|
9925
10465
|
}
|
|
@@ -9996,8 +10536,8 @@ async function prepareGithubBody(params) {
|
|
|
9996
10536
|
kindLabel,
|
|
9997
10537
|
lang
|
|
9998
10538
|
} = params;
|
|
9999
|
-
if (create && explicitBodyFile && await
|
|
10000
|
-
const body = await
|
|
10539
|
+
if (create && explicitBodyFile && await fs17.pathExists(defaultBodyFile)) {
|
|
10540
|
+
const body = await fs17.readFile(defaultBodyFile, "utf-8");
|
|
10001
10541
|
ensureSections(body, requiredSections, kindLabel, lang);
|
|
10002
10542
|
return {
|
|
10003
10543
|
body,
|
|
@@ -10005,8 +10545,8 @@ async function prepareGithubBody(params) {
|
|
|
10005
10545
|
source: "explicit"
|
|
10006
10546
|
};
|
|
10007
10547
|
}
|
|
10008
|
-
if (create && !explicitBodyFile && await
|
|
10009
|
-
const body = await
|
|
10548
|
+
if (create && !explicitBodyFile && await fs17.pathExists(workflowDraftPath)) {
|
|
10549
|
+
const body = await fs17.readFile(workflowDraftPath, "utf-8");
|
|
10010
10550
|
const draftMetadata = parseWorkflowDraftMetadata(body);
|
|
10011
10551
|
if (draftMetadata.status === "ready") {
|
|
10012
10552
|
ensureSections(body, requiredSections, kindLabel, lang);
|
|
@@ -10018,8 +10558,8 @@ async function prepareGithubBody(params) {
|
|
|
10018
10558
|
};
|
|
10019
10559
|
}
|
|
10020
10560
|
}
|
|
10021
|
-
await
|
|
10022
|
-
await
|
|
10561
|
+
await fs17.ensureDir(path23.dirname(defaultBodyFile));
|
|
10562
|
+
await fs17.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
10023
10563
|
return {
|
|
10024
10564
|
body: generatedBody,
|
|
10025
10565
|
bodyFile: defaultBodyFile,
|
|
@@ -10079,7 +10619,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
10079
10619
|
}
|
|
10080
10620
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
10081
10621
|
const missing = relativePaths.filter(
|
|
10082
|
-
(relativePath) => !
|
|
10622
|
+
(relativePath) => !fs17.existsSync(path23.join(docsDir, relativePath))
|
|
10083
10623
|
);
|
|
10084
10624
|
if (missing.length > 0) {
|
|
10085
10625
|
throw createCliError(
|
|
@@ -10089,18 +10629,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
10089
10629
|
}
|
|
10090
10630
|
}
|
|
10091
10631
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
10092
|
-
const key = `${
|
|
10632
|
+
const key = `${path23.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
10093
10633
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
10094
10634
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
10095
10635
|
}
|
|
10096
10636
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
10097
|
-
const selected = raw?.trim() ||
|
|
10637
|
+
const selected = raw?.trim() || path23.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
10098
10638
|
assertValid(
|
|
10099
10639
|
validatePathWithLang(selected, lang),
|
|
10100
10640
|
`github.${kind}.bodyFile`,
|
|
10101
10641
|
lang
|
|
10102
10642
|
);
|
|
10103
|
-
return
|
|
10643
|
+
return path23.resolve(selected);
|
|
10104
10644
|
}
|
|
10105
10645
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
10106
10646
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -10867,10 +11407,10 @@ function insertFieldInGithubIssueSection(content, key, value) {
|
|
|
10867
11407
|
return { content: lines.join("\n"), changed: true };
|
|
10868
11408
|
}
|
|
10869
11409
|
function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
|
|
10870
|
-
if (!
|
|
11410
|
+
if (!fs17.existsSync(tasksPath)) {
|
|
10871
11411
|
throw createCliError("DOCS_NOT_FOUND", tg(lang, "tasksNotFound", { path: tasksPath }));
|
|
10872
11412
|
}
|
|
10873
|
-
const original =
|
|
11413
|
+
const original = fs17.readFileSync(tasksPath, "utf-8");
|
|
10874
11414
|
let next = original;
|
|
10875
11415
|
let changed = false;
|
|
10876
11416
|
const prReplaced = replaceListField(next, ["PR", "Pull Request"], prUrl);
|
|
@@ -10894,7 +11434,7 @@ function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
|
|
|
10894
11434
|
changed = changed || inserted.changed;
|
|
10895
11435
|
}
|
|
10896
11436
|
if (changed) {
|
|
10897
|
-
|
|
11437
|
+
fs17.writeFileSync(tasksPath, next, "utf-8");
|
|
10898
11438
|
}
|
|
10899
11439
|
return { changed, path: tasksPath };
|
|
10900
11440
|
}
|
|
@@ -10922,7 +11462,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
10922
11462
|
}
|
|
10923
11463
|
}
|
|
10924
11464
|
function commitAndPushPath(cwd, absPath, message, lang, options) {
|
|
10925
|
-
const relativePath =
|
|
11465
|
+
const relativePath = path23.relative(cwd, absPath) || absPath;
|
|
10926
11466
|
const status = runProcessOrThrow(
|
|
10927
11467
|
"git",
|
|
10928
11468
|
["status", "--porcelain=v1", "--", relativePath],
|
|
@@ -11091,9 +11631,9 @@ function githubCommand(program2) {
|
|
|
11091
11631
|
[paths.specPath, paths.planPath, paths.tasksPath],
|
|
11092
11632
|
config.lang
|
|
11093
11633
|
);
|
|
11094
|
-
const specContent = await
|
|
11095
|
-
const planContent = await
|
|
11096
|
-
const tasksContent = await
|
|
11634
|
+
const specContent = await fs17.readFile(path23.join(config.docsDir, paths.specPath), "utf-8");
|
|
11635
|
+
const planContent = await fs17.readFile(path23.join(config.docsDir, paths.planPath), "utf-8");
|
|
11636
|
+
const tasksContent = await fs17.readFile(path23.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
11097
11637
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
11098
11638
|
const defaultTitle = tg(config.lang, "issueDefaultTitle", {
|
|
11099
11639
|
slug: feature.slug,
|
|
@@ -11126,7 +11666,7 @@ function githubCommand(program2) {
|
|
|
11126
11666
|
create: options.create,
|
|
11127
11667
|
explicitBodyFile,
|
|
11128
11668
|
defaultBodyFile,
|
|
11129
|
-
workflowDraftPath:
|
|
11669
|
+
workflowDraftPath: path23.join(config.docsDir, paths.issuePath),
|
|
11130
11670
|
generatedBody,
|
|
11131
11671
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
11132
11672
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -11237,10 +11777,10 @@ function githubCommand(program2) {
|
|
|
11237
11777
|
const generatedLabels = parseLabels(optionLabels || void 0, config.lang);
|
|
11238
11778
|
const paths = getFeatureDocPaths(feature);
|
|
11239
11779
|
ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
|
|
11240
|
-
const specContent = await
|
|
11241
|
-
const planPath =
|
|
11242
|
-
const planContent = await
|
|
11243
|
-
const tasksContent = await
|
|
11780
|
+
const specContent = await fs17.readFile(path23.join(config.docsDir, paths.specPath), "utf-8");
|
|
11781
|
+
const planPath = path23.join(config.docsDir, paths.planPath);
|
|
11782
|
+
const planContent = await fs17.pathExists(planPath) ? await fs17.readFile(planPath, "utf-8") : "";
|
|
11783
|
+
const tasksContent = await fs17.readFile(path23.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
11244
11784
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
11245
11785
|
const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
|
|
11246
11786
|
issue: feature.issueNumber,
|
|
@@ -11279,7 +11819,7 @@ function githubCommand(program2) {
|
|
|
11279
11819
|
create: options.create,
|
|
11280
11820
|
explicitBodyFile,
|
|
11281
11821
|
defaultBodyFile,
|
|
11282
|
-
workflowDraftPath:
|
|
11822
|
+
workflowDraftPath: path23.join(config.docsDir, paths.prPath),
|
|
11283
11823
|
generatedBody,
|
|
11284
11824
|
requiredSections: getRequiredPrSections(config.lang),
|
|
11285
11825
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -11350,7 +11890,7 @@ function githubCommand(program2) {
|
|
|
11350
11890
|
}
|
|
11351
11891
|
if (prUrl && options.syncTasks !== false) {
|
|
11352
11892
|
const synced = syncTasksPrMetadata(
|
|
11353
|
-
|
|
11893
|
+
path23.join(config.docsDir, paths.tasksPath),
|
|
11354
11894
|
prUrl,
|
|
11355
11895
|
"Review",
|
|
11356
11896
|
config.lang
|
|
@@ -11386,7 +11926,7 @@ function githubCommand(program2) {
|
|
|
11386
11926
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
11387
11927
|
if (prUrl && options.syncTasks !== false) {
|
|
11388
11928
|
const mergedSync = syncTasksPrMetadata(
|
|
11389
|
-
|
|
11929
|
+
path23.join(config.docsDir, paths.tasksPath),
|
|
11390
11930
|
prUrl,
|
|
11391
11931
|
"Approved",
|
|
11392
11932
|
config.lang
|
|
@@ -11630,7 +12170,7 @@ function docsCommand(program2) {
|
|
|
11630
12170
|
);
|
|
11631
12171
|
return;
|
|
11632
12172
|
}
|
|
11633
|
-
const relativeFromCwd =
|
|
12173
|
+
const relativeFromCwd = path23.relative(process.cwd(), loaded.entry.absolutePath);
|
|
11634
12174
|
console.log();
|
|
11635
12175
|
console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
11636
12176
|
console.log(
|
|
@@ -11710,7 +12250,7 @@ function detectCommand(program2) {
|
|
|
11710
12250
|
}
|
|
11711
12251
|
async function runDetect(options) {
|
|
11712
12252
|
const cwd = process.cwd();
|
|
11713
|
-
const targetCwd = options.dir ?
|
|
12253
|
+
const targetCwd = options.dir ? path23.resolve(cwd, options.dir) : cwd;
|
|
11714
12254
|
const config = await getConfig(targetCwd);
|
|
11715
12255
|
const detected = !!config;
|
|
11716
12256
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -11737,8 +12277,8 @@ async function runDetect(options) {
|
|
|
11737
12277
|
);
|
|
11738
12278
|
return;
|
|
11739
12279
|
}
|
|
11740
|
-
const configPath2 =
|
|
11741
|
-
const configFilePresent2 = await
|
|
12280
|
+
const configPath2 = path23.join(config.docsDir, ".lee-spec-kit.json");
|
|
12281
|
+
const configFilePresent2 = await fs17.pathExists(configPath2);
|
|
11742
12282
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
11743
12283
|
console.log(
|
|
11744
12284
|
JSON.stringify(
|
|
@@ -11771,8 +12311,8 @@ async function runDetect(options) {
|
|
|
11771
12311
|
console.log();
|
|
11772
12312
|
return;
|
|
11773
12313
|
}
|
|
11774
|
-
const configPath =
|
|
11775
|
-
const configFilePresent = await
|
|
12314
|
+
const configPath = path23.join(config.docsDir, ".lee-spec-kit.json");
|
|
12315
|
+
const configFilePresent = await fs17.pathExists(configPath);
|
|
11776
12316
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
11777
12317
|
console.log(chalk6.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
11778
12318
|
console.log(chalk6.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${config.docsDir}`));
|
|
@@ -11848,28 +12388,28 @@ function hasTemplateMarkers(content) {
|
|
|
11848
12388
|
return patterns.some((pattern) => pattern.test(content));
|
|
11849
12389
|
}
|
|
11850
12390
|
async function countFeatureDirs(docsDir, projectType) {
|
|
11851
|
-
const featuresRoot =
|
|
12391
|
+
const featuresRoot = path23.join(docsDir, "features");
|
|
11852
12392
|
if (projectType === "single") {
|
|
11853
12393
|
const dirs = await listSubdirectories(featuresRoot);
|
|
11854
|
-
return dirs.filter((value) =>
|
|
12394
|
+
return dirs.filter((value) => path23.basename(value) !== "feature-base").length;
|
|
11855
12395
|
}
|
|
11856
12396
|
const components = await listSubdirectories(featuresRoot);
|
|
11857
12397
|
let total = 0;
|
|
11858
12398
|
for (const componentDir of components) {
|
|
11859
|
-
const componentName =
|
|
12399
|
+
const componentName = path23.basename(componentDir).trim().toLowerCase();
|
|
11860
12400
|
if (!componentName || componentName === "feature-base") continue;
|
|
11861
12401
|
const dirs = await listSubdirectories(componentDir);
|
|
11862
|
-
total += dirs.filter((value) =>
|
|
12402
|
+
total += dirs.filter((value) => path23.basename(value) !== "feature-base").length;
|
|
11863
12403
|
}
|
|
11864
12404
|
return total;
|
|
11865
12405
|
}
|
|
11866
12406
|
async function hasUserPrdFile(prdDir) {
|
|
11867
|
-
if (!await
|
|
12407
|
+
if (!await fs17.pathExists(prdDir)) return false;
|
|
11868
12408
|
const files = await walkFiles(prdDir, {
|
|
11869
12409
|
extensions: [".md"],
|
|
11870
12410
|
ignoreDirs: ["node_modules"]
|
|
11871
12411
|
});
|
|
11872
|
-
return files.some((absolutePath) =>
|
|
12412
|
+
return files.some((absolutePath) => path23.basename(absolutePath).toLowerCase() !== "readme.md");
|
|
11873
12413
|
}
|
|
11874
12414
|
function finalizeChecks(checks) {
|
|
11875
12415
|
const summary = checks.reduce(
|
|
@@ -11998,8 +12538,8 @@ async function runOnboardChecks(config) {
|
|
|
11998
12538
|
});
|
|
11999
12539
|
}
|
|
12000
12540
|
}
|
|
12001
|
-
const constitutionPath =
|
|
12002
|
-
if (!await
|
|
12541
|
+
const constitutionPath = path23.join(docsDir, "agents", "constitution.md");
|
|
12542
|
+
if (!await fs17.pathExists(constitutionPath)) {
|
|
12003
12543
|
checks.push({
|
|
12004
12544
|
id: "constitution_exists",
|
|
12005
12545
|
status: "block",
|
|
@@ -12013,7 +12553,7 @@ async function runOnboardChecks(config) {
|
|
|
12013
12553
|
suggestedCommand: `npx lee-spec-kit update --agents`
|
|
12014
12554
|
});
|
|
12015
12555
|
} else {
|
|
12016
|
-
const content = await
|
|
12556
|
+
const content = await fs17.readFile(constitutionPath, "utf-8");
|
|
12017
12557
|
if (hasTemplateMarkers(content)) {
|
|
12018
12558
|
checks.push({
|
|
12019
12559
|
id: "constitution_filled",
|
|
@@ -12036,9 +12576,9 @@ async function runOnboardChecks(config) {
|
|
|
12036
12576
|
});
|
|
12037
12577
|
}
|
|
12038
12578
|
}
|
|
12039
|
-
const customPath =
|
|
12040
|
-
if (await
|
|
12041
|
-
const content = await
|
|
12579
|
+
const customPath = path23.join(docsDir, "agents", "custom.md");
|
|
12580
|
+
if (await fs17.pathExists(customPath)) {
|
|
12581
|
+
const content = await fs17.readFile(customPath, "utf-8");
|
|
12042
12582
|
if (hasTemplateMarkers(content)) {
|
|
12043
12583
|
checks.push({
|
|
12044
12584
|
id: "custom_optional",
|
|
@@ -12061,7 +12601,7 @@ async function runOnboardChecks(config) {
|
|
|
12061
12601
|
});
|
|
12062
12602
|
}
|
|
12063
12603
|
}
|
|
12064
|
-
const prdDir =
|
|
12604
|
+
const prdDir = path23.join(docsDir, "prd");
|
|
12065
12605
|
const featureCount = await countFeatureDirs(docsDir, config.projectType);
|
|
12066
12606
|
const prdReady = await hasUserPrdFile(prdDir);
|
|
12067
12607
|
if (!prdReady) {
|
|
@@ -12079,7 +12619,7 @@ async function runOnboardChecks(config) {
|
|
|
12079
12619
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
12080
12620
|
),
|
|
12081
12621
|
path: prdDir,
|
|
12082
|
-
suggestedCommand: `touch ${quotePath(
|
|
12622
|
+
suggestedCommand: `touch ${quotePath(path23.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
12083
12623
|
});
|
|
12084
12624
|
} else {
|
|
12085
12625
|
checks.push({
|
|
@@ -12263,17 +12803,6 @@ function normalizeDecision(raw) {
|
|
|
12263
12803
|
if (value === "blocked" || value === "block") return "blocked";
|
|
12264
12804
|
return null;
|
|
12265
12805
|
}
|
|
12266
|
-
function parseNonNegativeInt(raw, label) {
|
|
12267
|
-
if (raw === void 0) return null;
|
|
12268
|
-
const value = Number(raw);
|
|
12269
|
-
if (!Number.isInteger(value) || value < 0) {
|
|
12270
|
-
throw createCliError(
|
|
12271
|
-
"INVALID_ARGUMENT",
|
|
12272
|
-
`\`${label}\` must be a non-negative integer.`
|
|
12273
|
-
);
|
|
12274
|
-
}
|
|
12275
|
-
return value;
|
|
12276
|
-
}
|
|
12277
12806
|
function findSpecLineIndex(lines, keys) {
|
|
12278
12807
|
const escaped = keys.map((key) => escapeRegExp4(key));
|
|
12279
12808
|
const re = new RegExp(`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`);
|
|
@@ -12320,7 +12849,6 @@ function getPreferredKeys(lang) {
|
|
|
12320
12849
|
if (lang === "ko") {
|
|
12321
12850
|
return {
|
|
12322
12851
|
review: "PR \uC804 \uB9AC\uBDF0",
|
|
12323
|
-
findings: "PR \uC804 \uB9AC\uBDF0 Findings",
|
|
12324
12852
|
evidence: "PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
12325
12853
|
decision: "PR \uC804 \uB9AC\uBDF0 Decision",
|
|
12326
12854
|
prStatus: "PR \uC0C1\uD0DC"
|
|
@@ -12328,7 +12856,6 @@ function getPreferredKeys(lang) {
|
|
|
12328
12856
|
}
|
|
12329
12857
|
return {
|
|
12330
12858
|
review: "Pre-PR Review",
|
|
12331
|
-
findings: "Pre-PR Findings",
|
|
12332
12859
|
evidence: "Pre-PR Evidence",
|
|
12333
12860
|
decision: "Pre-PR Decision",
|
|
12334
12861
|
prStatus: "PR Status"
|
|
@@ -12336,28 +12863,30 @@ function getPreferredKeys(lang) {
|
|
|
12336
12863
|
}
|
|
12337
12864
|
function buildReportContent(input) {
|
|
12338
12865
|
const skills = input.skills.length > 0 ? input.skills.join(", ") : "code-review-excellence";
|
|
12339
|
-
return
|
|
12866
|
+
return `## Pre-PR Review Log (${input.date})
|
|
12340
12867
|
|
|
12341
|
-
-
|
|
12342
|
-
- Baseline
|
|
12343
|
-
- Skills
|
|
12344
|
-
-
|
|
12345
|
-
-
|
|
12346
|
-
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12868
|
+
- **Feature**: ${input.folderName}
|
|
12869
|
+
- **Baseline**: ${input.fallback}
|
|
12870
|
+
- **Skills**: ${skills}
|
|
12871
|
+
- **Decision**: ${input.decision}
|
|
12872
|
+
- **Note**: ${input.note}
|
|
12873
|
+
- **Trace**: pre-pr-review command executed and synced with tasks.md
|
|
12874
|
+
`;
|
|
12875
|
+
}
|
|
12876
|
+
function appendDecisionLog(content, entry) {
|
|
12877
|
+
const normalized = content.trimEnd();
|
|
12878
|
+
if (!normalized) return `${entry.trim()}
|
|
12879
|
+
`;
|
|
12880
|
+
return `${normalized}
|
|
12349
12881
|
|
|
12350
|
-
|
|
12351
|
-
- [x] Reviewed risk areas (regression, security, side effects, release readiness).
|
|
12352
|
-
- [x] Reviewed maintainability (structure, reuse, obsolete code cleanup).
|
|
12353
|
-
- [x] Reviewed test/verification coverage (or recorded reason if not run).
|
|
12882
|
+
${entry.trim()}
|
|
12354
12883
|
`;
|
|
12355
12884
|
}
|
|
12356
12885
|
function prePrReviewCommand(program2) {
|
|
12357
12886
|
program2.command("pre-pr-review [feature-name]").description("Run and record pre-PR review evidence for a feature").option("--component <component>", "Component name for multi projects").option(
|
|
12358
12887
|
"--decision <outcome>",
|
|
12359
12888
|
"Decision outcome: approve | changes_requested | blocked"
|
|
12360
|
-
).option("--
|
|
12889
|
+
).option("--note <text>", "Decision note text").option("--json", "Output JSON").action(async (featureName, options) => {
|
|
12361
12890
|
try {
|
|
12362
12891
|
await runPrePrReview(featureName, options);
|
|
12363
12892
|
} catch (error) {
|
|
@@ -12407,13 +12936,11 @@ async function runPrePrReview(featureName, options) {
|
|
|
12407
12936
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
12408
12937
|
);
|
|
12409
12938
|
}
|
|
12410
|
-
const tasksPath =
|
|
12411
|
-
const tasksContent = await
|
|
12939
|
+
const tasksPath = path23.join(feature.path, "tasks.md");
|
|
12940
|
+
const tasksContent = await fs17.readFile(tasksPath, "utf-8");
|
|
12412
12941
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
12413
12942
|
const preferred = getPreferredKeys(config.lang);
|
|
12414
12943
|
const date = getLocalDateString();
|
|
12415
|
-
const major = parseNonNegativeInt(options.major, "--major") ?? feature.prePrReview.findings?.major ?? 0;
|
|
12416
|
-
const minor = parseNonNegativeInt(options.minor, "--minor") ?? feature.prePrReview.findings?.minor ?? 0;
|
|
12417
12944
|
const explicitDecision = normalizeDecision(options.decision);
|
|
12418
12945
|
if (options.decision && !explicitDecision) {
|
|
12419
12946
|
throw createCliError(
|
|
@@ -12421,31 +12948,32 @@ async function runPrePrReview(featureName, options) {
|
|
|
12421
12948
|
"`--decision` must be one of: approve, changes_requested, blocked."
|
|
12422
12949
|
);
|
|
12423
12950
|
}
|
|
12424
|
-
const
|
|
12425
|
-
const decision = explicitDecision || inferredDecision;
|
|
12951
|
+
const decision = explicitDecision || feature.prePrReview.decisionOutcome || "approve";
|
|
12426
12952
|
if (!policy.decisionEnum.includes(decision)) {
|
|
12427
12953
|
throw createCliError(
|
|
12428
12954
|
"INVALID_ARGUMENT",
|
|
12429
12955
|
`Decision "${decision}" is not allowed by workflow.prePrReview.decisionEnum.`
|
|
12430
12956
|
);
|
|
12431
12957
|
}
|
|
12432
|
-
const note = options.note?.trim() || (decision === "approve" ? "baseline review completed
|
|
12433
|
-
const
|
|
12434
|
-
const
|
|
12958
|
+
const note = options.note?.trim() || (decision === "approve" ? "baseline review completed" : decision === "changes_requested" ? "follow-up changes are required before PR creation" : "blocked until prerequisite risk is resolved");
|
|
12959
|
+
const decisionsPath = path23.join(feature.path, "decisions.md");
|
|
12960
|
+
const decisionLogEntry = buildReportContent({
|
|
12435
12961
|
folderName: feature.folderName,
|
|
12436
12962
|
date,
|
|
12437
12963
|
decision,
|
|
12438
|
-
major,
|
|
12439
|
-
minor,
|
|
12440
12964
|
note,
|
|
12441
12965
|
fallback: policy.fallback,
|
|
12442
12966
|
skills: policy.skills
|
|
12443
12967
|
});
|
|
12444
|
-
await
|
|
12445
|
-
const
|
|
12446
|
-
|
|
12968
|
+
const decisionsContent = await fs17.pathExists(decisionsPath) ? await fs17.readFile(decisionsPath, "utf-8") : "";
|
|
12969
|
+
const nextDecisions = appendDecisionLog(decisionsContent, decisionLogEntry);
|
|
12970
|
+
if (nextDecisions !== decisionsContent) {
|
|
12971
|
+
await fs17.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
12972
|
+
}
|
|
12973
|
+
const decisionsPathFromDocs = normalizePathForDoc(
|
|
12974
|
+
path23.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
12447
12975
|
);
|
|
12448
|
-
const evidencePath =
|
|
12976
|
+
const evidencePath = path23.basename(config.docsDir) === "docs" ? normalizePathForDoc(path23.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
12449
12977
|
let nextTasks = tasksContent;
|
|
12450
12978
|
nextTasks = upsertSpecLine(
|
|
12451
12979
|
nextTasks,
|
|
@@ -12454,19 +12982,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
12454
12982
|
"Done",
|
|
12455
12983
|
["PR \uC0C1\uD0DC", "PR Status"]
|
|
12456
12984
|
);
|
|
12457
|
-
nextTasks = upsertSpecLine(
|
|
12458
|
-
nextTasks,
|
|
12459
|
-
["PR \uC804 \uB9AC\uBDF0 Findings", "Pre-PR Findings"],
|
|
12460
|
-
preferred.findings,
|
|
12461
|
-
`major=${major}, minor=${minor}`,
|
|
12462
|
-
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12463
|
-
);
|
|
12464
12985
|
nextTasks = upsertSpecLine(
|
|
12465
12986
|
nextTasks,
|
|
12466
12987
|
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"],
|
|
12467
12988
|
preferred.evidence,
|
|
12468
12989
|
evidencePath,
|
|
12469
|
-
["PR \uC804 \uB9AC\uBDF0
|
|
12990
|
+
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12470
12991
|
);
|
|
12471
12992
|
nextTasks = upsertSpecLine(
|
|
12472
12993
|
nextTasks,
|
|
@@ -12476,7 +12997,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
12476
12997
|
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"]
|
|
12477
12998
|
);
|
|
12478
12999
|
if (nextTasks !== tasksContent) {
|
|
12479
|
-
await
|
|
13000
|
+
await fs17.writeFile(tasksPath, nextTasks, "utf-8");
|
|
12480
13001
|
}
|
|
12481
13002
|
if (options.json) {
|
|
12482
13003
|
console.log(
|
|
@@ -12485,10 +13006,10 @@ async function runPrePrReview(featureName, options) {
|
|
|
12485
13006
|
status: "ok",
|
|
12486
13007
|
reasonCode: "PRE_PR_REVIEW_RECORDED",
|
|
12487
13008
|
feature: feature.folderName,
|
|
12488
|
-
reportPath: normalizePathForDoc(
|
|
13009
|
+
reportPath: normalizePathForDoc(decisionsPath),
|
|
13010
|
+
decisionsPath: normalizePathForDoc(decisionsPath),
|
|
12489
13011
|
evidencePath,
|
|
12490
13012
|
decision,
|
|
12491
|
-
findings: { major, minor },
|
|
12492
13013
|
tasksUpdated: nextTasks !== tasksContent
|
|
12493
13014
|
},
|
|
12494
13015
|
null,
|
|
@@ -12500,8 +13021,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
12500
13021
|
console.log();
|
|
12501
13022
|
console.log(chalk6.green(`\u2705 pre-pr-review completed: ${feature.folderName}`));
|
|
12502
13023
|
console.log(chalk6.gray(`- Decision: ${decision}`));
|
|
12503
|
-
console.log(chalk6.gray(`-
|
|
12504
|
-
console.log(chalk6.gray(`- Report: ${reportPath}`));
|
|
13024
|
+
console.log(chalk6.gray(`- Decisions log: ${decisionsPath}`));
|
|
12505
13025
|
if (nextTasks !== tasksContent) {
|
|
12506
13026
|
console.log(chalk6.gray(`- tasks.md updated: ${tasksPath}`));
|
|
12507
13027
|
}
|
|
@@ -12550,13 +13070,13 @@ ${version}
|
|
|
12550
13070
|
}
|
|
12551
13071
|
return `${ascii}${footer}`;
|
|
12552
13072
|
}
|
|
12553
|
-
var CACHE_FILE =
|
|
13073
|
+
var CACHE_FILE = path23.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
12554
13074
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
12555
13075
|
function getCurrentVersion() {
|
|
12556
13076
|
try {
|
|
12557
|
-
const packageJsonPath =
|
|
12558
|
-
if (
|
|
12559
|
-
const pkg =
|
|
13077
|
+
const packageJsonPath = path23.join(__dirname$1, "..", "package.json");
|
|
13078
|
+
if (fs17.existsSync(packageJsonPath)) {
|
|
13079
|
+
const pkg = fs17.readJsonSync(packageJsonPath);
|
|
12560
13080
|
return pkg.version;
|
|
12561
13081
|
}
|
|
12562
13082
|
} catch {
|
|
@@ -12565,8 +13085,8 @@ function getCurrentVersion() {
|
|
|
12565
13085
|
}
|
|
12566
13086
|
function readCache() {
|
|
12567
13087
|
try {
|
|
12568
|
-
if (
|
|
12569
|
-
return
|
|
13088
|
+
if (fs17.existsSync(CACHE_FILE)) {
|
|
13089
|
+
return fs17.readJsonSync(CACHE_FILE);
|
|
12570
13090
|
}
|
|
12571
13091
|
} catch {
|
|
12572
13092
|
}
|
|
@@ -12658,9 +13178,9 @@ function shouldCheckForUpdates() {
|
|
|
12658
13178
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
12659
13179
|
function getCliVersion() {
|
|
12660
13180
|
try {
|
|
12661
|
-
const packageJsonPath =
|
|
12662
|
-
if (
|
|
12663
|
-
const pkg =
|
|
13181
|
+
const packageJsonPath = path23.join(__dirname$1, "..", "package.json");
|
|
13182
|
+
if (fs17.existsSync(packageJsonPath)) {
|
|
13183
|
+
const pkg = fs17.readJsonSync(packageJsonPath);
|
|
12664
13184
|
if (pkg?.version) return String(pkg.version);
|
|
12665
13185
|
}
|
|
12666
13186
|
} catch {
|