yg-team-cli 2.8.0 → 2.8.1
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.md +17 -140
- package/dist/cli.js +488 -2792
- package/dist/cli.js.map +1 -1
- package/dist/index.js +438 -2728
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -379,8 +379,8 @@ var init_utils = __esm({
|
|
|
379
379
|
* 获取当前分支名
|
|
380
380
|
*/
|
|
381
381
|
static async getCurrentBranch(cwd = process.cwd()) {
|
|
382
|
-
const { execa:
|
|
383
|
-
const { stdout } = await
|
|
382
|
+
const { execa: execa5 } = await import("execa");
|
|
383
|
+
const { stdout } = await execa5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
384
384
|
cwd
|
|
385
385
|
});
|
|
386
386
|
return stdout.trim();
|
|
@@ -389,8 +389,8 @@ var init_utils = __esm({
|
|
|
389
389
|
* 获取当前 commit hash
|
|
390
390
|
*/
|
|
391
391
|
static async getCurrentCommit(cwd = process.cwd()) {
|
|
392
|
-
const { execa:
|
|
393
|
-
const { stdout } = await
|
|
392
|
+
const { execa: execa5 } = await import("execa");
|
|
393
|
+
const { stdout } = await execa5("git", ["rev-parse", "HEAD"], { cwd });
|
|
394
394
|
return stdout.trim().slice(0, 7);
|
|
395
395
|
}
|
|
396
396
|
};
|
|
@@ -1007,22 +1007,22 @@ var init_gitlab_api = __esm({
|
|
|
1007
1007
|
* 从 Git URL 中提取项目路径
|
|
1008
1008
|
*/
|
|
1009
1009
|
static parseProjectPath(repository) {
|
|
1010
|
-
let
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
const colonIndex =
|
|
1010
|
+
let path22 = repository;
|
|
1011
|
+
if (path22.startsWith("git@")) {
|
|
1012
|
+
path22 = path22.replace(/^git@/, "");
|
|
1013
|
+
const colonIndex = path22.indexOf(":");
|
|
1014
1014
|
if (colonIndex !== -1) {
|
|
1015
|
-
|
|
1015
|
+
path22 = path22.substring(colonIndex + 1);
|
|
1016
1016
|
}
|
|
1017
1017
|
} else {
|
|
1018
|
-
|
|
1019
|
-
const parts =
|
|
1018
|
+
path22 = path22.replace(/^https?:\/\//, "");
|
|
1019
|
+
const parts = path22.split("/");
|
|
1020
1020
|
if (parts.length > 1) {
|
|
1021
|
-
|
|
1021
|
+
path22 = parts.slice(1).join("/");
|
|
1022
1022
|
}
|
|
1023
1023
|
}
|
|
1024
|
-
|
|
1025
|
-
return
|
|
1024
|
+
path22 = path22.replace(/\.git$/, "");
|
|
1025
|
+
return path22;
|
|
1026
1026
|
}
|
|
1027
1027
|
/**
|
|
1028
1028
|
* 编码项目路径用于 API 请求
|
|
@@ -1076,10 +1076,10 @@ var init_gitlab_api = __esm({
|
|
|
1076
1076
|
// src/index.ts
|
|
1077
1077
|
init_esm_shims();
|
|
1078
1078
|
init_logger();
|
|
1079
|
-
import { Command as
|
|
1079
|
+
import { Command as Command22 } from "commander";
|
|
1080
1080
|
import chalk4 from "chalk";
|
|
1081
|
-
import
|
|
1082
|
-
import
|
|
1081
|
+
import fs6 from "fs-extra";
|
|
1082
|
+
import path21 from "path";
|
|
1083
1083
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1084
1084
|
|
|
1085
1085
|
// src/commands/init.ts
|
|
@@ -4653,30 +4653,9 @@ Generate a complete Spec document with the following sections:
|
|
|
4653
4653
|
\u5217\u51FA\u9700\u8981\u7684\u540E\u7AEF\u6A21\u5757\u7ED3\u6784
|
|
4654
4654
|
|
|
4655
4655
|
## \u9A8C\u6536\u6807\u51C6
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
\u8BF7\u4E3A\u6BCF\u4E2A\u4E3B\u8981\u529F\u80FD\u70B9\u751F\u6210 2-4 \u4E2A BDD \u573A\u666F\uFF0C\u683C\u5F0F\u5982\u4E0B\uFF1A
|
|
4660
|
-
|
|
4661
|
-
\`\`\`markdown
|
|
4662
|
-
#### \u573A\u666F 1: [\u573A\u666F\u540D\u79F0]
|
|
4663
|
-
**\u4F5C\u4E3A** [\u7528\u6237\u89D2\u8272]
|
|
4664
|
-
**\u6211\u5E0C\u671B** [\u5B8C\u6210\u7684\u52A8\u4F5C]
|
|
4665
|
-
**\u4EE5\u4FBF** [\u5B9E\u73B0\u7684\u4EF7\u503C]
|
|
4666
|
-
|
|
4667
|
-
- [GIVEN] \u524D\u7F6E\u6761\u4EF6\u63CF\u8FF0
|
|
4668
|
-
- [WHEN] \u7528\u6237\u52A8\u4F5C\u6216 API \u8C03\u7528
|
|
4669
|
-
- [THEN] \u9884\u671F\u7ED3\u679C
|
|
4670
|
-
|
|
4671
|
-
**\u72B6\u6001\u6D41\u8F6C**:
|
|
4672
|
-
- \`\u672A\u767B\u5F55\` \u2192 \`\u767B\u5F55\u4E2D\` \u2192 \`\u5DF2\u767B\u5F55\`
|
|
4673
|
-
\`\`\`
|
|
4674
|
-
|
|
4675
|
-
\u8981\u6C42\uFF1A
|
|
4676
|
-
1. \u6BCF\u4E2A\u573A\u666F\u8986\u76D6\u4E00\u4E2A\u5B8C\u6574\u7684\u7528\u6237\u6D41\u7A0B
|
|
4677
|
-
2. Given/When/Then \u7ED3\u6784\u6E05\u6670
|
|
4678
|
-
3. \u72B6\u6001\u6D41\u8F6C\u6807\u6CE8\u660E\u786E
|
|
4679
|
-
4. \u573A\u666F\u6570\u91CF\u6839\u636E\u529F\u80FD\u590D\u6742\u5EA6\u51B3\u5B9A\uFF08\u7B80\u5355\u529F\u80FD 2-3 \u4E2A\uFF0C\u590D\u6742\u529F\u80FD 4-6 \u4E2A\uFF09
|
|
4656
|
+
- [ ] \u9A8C\u6536\u6807\u51C6 1
|
|
4657
|
+
- [ ] \u9A8C\u6536\u6807\u51C6 2
|
|
4658
|
+
- [ ] \u9A8C\u6536\u6807\u51C6 3
|
|
4680
4659
|
|
|
4681
4660
|
## \u91CC\u7A0B\u7891 (Milestones)
|
|
4682
4661
|
> \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureSlug}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
|
|
@@ -4691,7 +4670,6 @@ IMPORTANT:
|
|
|
4691
4670
|
3. Priority should be justified based on feature importance
|
|
4692
4671
|
4. Work estimation should be realistic
|
|
4693
4672
|
5. Extract key requirements from the PRD accurately
|
|
4694
|
-
6. BDD scenarios should cover all user flows and edge cases
|
|
4695
4673
|
`;
|
|
4696
4674
|
}
|
|
4697
4675
|
function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
|
|
@@ -4924,8 +4902,8 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
|
|
|
4924
4902
|
{
|
|
4925
4903
|
title: "\u521B\u5EFA hotfix \u5206\u652F",
|
|
4926
4904
|
task: async () => {
|
|
4927
|
-
const { execa:
|
|
4928
|
-
await
|
|
4905
|
+
const { execa: execa5 } = await import("execa");
|
|
4906
|
+
await execa5("git", ["checkout", "-b", branchName], { stdio: "inherit" });
|
|
4929
4907
|
}
|
|
4930
4908
|
},
|
|
4931
4909
|
{
|
|
@@ -4949,9 +4927,9 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
|
|
|
4949
4927
|
{
|
|
4950
4928
|
title: "\u521B\u5EFA\u521D\u59CB commit",
|
|
4951
4929
|
task: async () => {
|
|
4952
|
-
const { execa:
|
|
4953
|
-
await
|
|
4954
|
-
await
|
|
4930
|
+
const { execa: execa5 } = await import("execa");
|
|
4931
|
+
await execa5("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
|
|
4932
|
+
await execa5(
|
|
4955
4933
|
"git",
|
|
4956
4934
|
[
|
|
4957
4935
|
"commit",
|
|
@@ -5103,322 +5081,7 @@ init_logger();
|
|
|
5103
5081
|
import { Command as Command7 } from "commander";
|
|
5104
5082
|
import inquirer6 from "inquirer";
|
|
5105
5083
|
import path12 from "path";
|
|
5106
|
-
|
|
5107
|
-
// src/lib/bdd-parser.ts
|
|
5108
|
-
init_esm_shims();
|
|
5109
|
-
var BDDParser = class {
|
|
5110
|
-
static SCENARIO_PATTERN = /^####?\s*场景\s*(\d+)[::]?\s*(.+)$/;
|
|
5111
|
-
static GIVEN_PATTERN = /^\*?\s*-\s*\[GIVEN\]\s*(.+)$/;
|
|
5112
|
-
static WHEN_PATTERN = /^\*?\s*-\s*\[WHEN\]\s*(.+)$/;
|
|
5113
|
-
static THEN_PATTERN = /^\*?\s*-\s*\[THEN\]\s*(.+)$/;
|
|
5114
|
-
static STATE_PATTERN = /^\*?\s*-\s*\[STATE\]\s*(.+)$/;
|
|
5115
|
-
static ROLE_PATTERN = /^\*\*作为\*\*[::]?\s*(.+)$/i;
|
|
5116
|
-
static WANT_PATTERN = /^\*\*我希望\*\*[::]?\s*(.+)$/i;
|
|
5117
|
-
static SO_THAT_PATTERN = /^\*\*以便\*\*[::]?\s*(.+)$/i;
|
|
5118
|
-
static TRANSITION_PATTERN = /`([^`]+)`\s*→\s*`([^`]+)`/;
|
|
5119
|
-
/**
|
|
5120
|
-
* 解析 BDD 场景
|
|
5121
|
-
*/
|
|
5122
|
-
static parseScenarios(content) {
|
|
5123
|
-
const scenarios = [];
|
|
5124
|
-
const lines = content.split("\n");
|
|
5125
|
-
let currentScenario = null;
|
|
5126
|
-
let section = null;
|
|
5127
|
-
let scenarioOrder = 0;
|
|
5128
|
-
for (let i = 0; i < lines.length; i++) {
|
|
5129
|
-
const line = lines[i].trim();
|
|
5130
|
-
const lineNum = i + 1;
|
|
5131
|
-
const scenarioMatch = line.match(this.SCENARIO_PATTERN);
|
|
5132
|
-
if (scenarioMatch) {
|
|
5133
|
-
if (currentScenario && currentScenario.title) {
|
|
5134
|
-
scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
|
|
5135
|
-
}
|
|
5136
|
-
scenarioOrder++;
|
|
5137
|
-
currentScenario = {
|
|
5138
|
-
id: `scenario-${scenarioOrder}`,
|
|
5139
|
-
title: scenarioMatch[2].trim(),
|
|
5140
|
-
order: scenarioOrder,
|
|
5141
|
-
given: [],
|
|
5142
|
-
when: [],
|
|
5143
|
-
then: [],
|
|
5144
|
-
stateTransitions: [],
|
|
5145
|
-
relatedApis: [],
|
|
5146
|
-
relatedFeatures: []
|
|
5147
|
-
};
|
|
5148
|
-
section = "title";
|
|
5149
|
-
continue;
|
|
5150
|
-
}
|
|
5151
|
-
if (line.match(/^\*\*作为\*\*[::]?/i)) {
|
|
5152
|
-
const match = line.match(this.ROLE_PATTERN);
|
|
5153
|
-
if (match && currentScenario) {
|
|
5154
|
-
currentScenario.asA = match[1].trim();
|
|
5155
|
-
}
|
|
5156
|
-
continue;
|
|
5157
|
-
}
|
|
5158
|
-
if (line.match(/^\*\*我希望\*\*[::]?/i)) {
|
|
5159
|
-
const match = line.match(this.WANT_PATTERN);
|
|
5160
|
-
if (match && currentScenario) {
|
|
5161
|
-
currentScenario.iWant = match[1].trim();
|
|
5162
|
-
}
|
|
5163
|
-
continue;
|
|
5164
|
-
}
|
|
5165
|
-
if (line.match(/^\*\*以便\*\*[::]?/i)) {
|
|
5166
|
-
const match = line.match(this.SO_THAT_PATTERN);
|
|
5167
|
-
if (match && currentScenario) {
|
|
5168
|
-
currentScenario.soThat = match[1].trim();
|
|
5169
|
-
}
|
|
5170
|
-
continue;
|
|
5171
|
-
}
|
|
5172
|
-
const givenMatch = line.match(this.GIVEN_PATTERN);
|
|
5173
|
-
const whenMatch = line.match(this.WHEN_PATTERN);
|
|
5174
|
-
const thenMatch = line.match(this.THEN_PATTERN);
|
|
5175
|
-
const stateMatch = line.match(this.STATE_PATTERN);
|
|
5176
|
-
if (givenMatch && currentScenario) {
|
|
5177
|
-
currentScenario.given.push({
|
|
5178
|
-
description: givenMatch[1].trim(),
|
|
5179
|
-
type: this.detectConditionType(givenMatch[1]),
|
|
5180
|
-
validation: this.extractValidation(givenMatch[1])
|
|
5181
|
-
});
|
|
5182
|
-
section = "given";
|
|
5183
|
-
continue;
|
|
5184
|
-
}
|
|
5185
|
-
if (whenMatch && currentScenario) {
|
|
5186
|
-
const action = this.parseAction(whenMatch[1].trim());
|
|
5187
|
-
currentScenario.when.push(action);
|
|
5188
|
-
if (action.api) {
|
|
5189
|
-
currentScenario.relatedApis.push(action.api);
|
|
5190
|
-
}
|
|
5191
|
-
section = "when";
|
|
5192
|
-
continue;
|
|
5193
|
-
}
|
|
5194
|
-
if (thenMatch && currentScenario) {
|
|
5195
|
-
currentScenario.then.push({
|
|
5196
|
-
description: thenMatch[1].trim(),
|
|
5197
|
-
type: this.detectResultType(thenMatch[1]),
|
|
5198
|
-
expectedBehavior: this.extractExpectedBehavior(thenMatch[1])
|
|
5199
|
-
});
|
|
5200
|
-
section = "then";
|
|
5201
|
-
continue;
|
|
5202
|
-
}
|
|
5203
|
-
if (stateMatch && currentScenario) {
|
|
5204
|
-
const transition = this.parseStateTransition(stateMatch[1].trim());
|
|
5205
|
-
if (transition) {
|
|
5206
|
-
currentScenario.stateTransitions.push(transition);
|
|
5207
|
-
}
|
|
5208
|
-
section = "state";
|
|
5209
|
-
continue;
|
|
5210
|
-
}
|
|
5211
|
-
if (line && !line.startsWith("#") && !line.startsWith("---") && currentScenario) {
|
|
5212
|
-
const trimmed = line.replace(/^[\s-]*/, "");
|
|
5213
|
-
if (trimmed && section === "given" && currentScenario.given.length > 0) {
|
|
5214
|
-
currentScenario.given[currentScenario.given.length - 1].description += " " + trimmed;
|
|
5215
|
-
} else if (trimmed && section === "when" && currentScenario.when.length > 0) {
|
|
5216
|
-
currentScenario.when[currentScenario.when.length - 1].description += " " + trimmed;
|
|
5217
|
-
} else if (trimmed && section === "then" && currentScenario.then.length > 0) {
|
|
5218
|
-
currentScenario.then[currentScenario.then.length - 1].description += " " + trimmed;
|
|
5219
|
-
}
|
|
5220
|
-
}
|
|
5221
|
-
}
|
|
5222
|
-
if (currentScenario && currentScenario.title) {
|
|
5223
|
-
scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
|
|
5224
|
-
}
|
|
5225
|
-
return scenarios;
|
|
5226
|
-
}
|
|
5227
|
-
/**
|
|
5228
|
-
* 从验收标准部分提取 BDD 场景
|
|
5229
|
-
*/
|
|
5230
|
-
static parseFromAcceptanceSection(acceptanceSection) {
|
|
5231
|
-
const bddSectionMatch = acceptanceSection.match(
|
|
5232
|
-
/(?:###\s*BDD[^\n]*|###\s*场景|###\s*行为驱动)[^]*(?=##|$)/i
|
|
5233
|
-
);
|
|
5234
|
-
if (bddSectionMatch) {
|
|
5235
|
-
return this.parseScenarios(bddSectionMatch[0]);
|
|
5236
|
-
}
|
|
5237
|
-
return this.convertFromLegacyCriteria(acceptanceSection);
|
|
5238
|
-
}
|
|
5239
|
-
/**
|
|
5240
|
-
* 从传统验收标准转换
|
|
5241
|
-
*/
|
|
5242
|
-
static convertFromLegacyCriteria(criteria) {
|
|
5243
|
-
const scenarios = [];
|
|
5244
|
-
const lines = criteria.split("\n");
|
|
5245
|
-
let scenarioId = 0;
|
|
5246
|
-
let currentScenario = null;
|
|
5247
|
-
for (const line of lines) {
|
|
5248
|
-
const criteriaMatch = line.match(/^-\s*\[\s*\]\s*(.+)$/);
|
|
5249
|
-
if (criteriaMatch) {
|
|
5250
|
-
if (!currentScenario) {
|
|
5251
|
-
scenarioId++;
|
|
5252
|
-
currentScenario = {
|
|
5253
|
-
id: `scenario-${scenarioId}`,
|
|
5254
|
-
title: `\u573A\u666F ${scenarioId}`,
|
|
5255
|
-
order: scenarioId,
|
|
5256
|
-
given: [],
|
|
5257
|
-
when: [],
|
|
5258
|
-
then: [],
|
|
5259
|
-
stateTransitions: []
|
|
5260
|
-
};
|
|
5261
|
-
}
|
|
5262
|
-
const text = criteriaMatch[1];
|
|
5263
|
-
const normalized = text.toLowerCase();
|
|
5264
|
-
if (normalized.includes("\u5DF2") || normalized.includes("\u6709") || normalized.includes("\u5B58\u5728")) {
|
|
5265
|
-
currentScenario.given.push({
|
|
5266
|
-
description: text,
|
|
5267
|
-
type: "data",
|
|
5268
|
-
validation: "\u68C0\u67E5\u6570\u636E\u5B58\u5728"
|
|
5269
|
-
});
|
|
5270
|
-
} else if (normalized.includes("\u5F53") || normalized.includes("\u63D0\u4EA4") || normalized.includes("\u70B9\u51FB")) {
|
|
5271
|
-
currentScenario.when.push({
|
|
5272
|
-
description: text,
|
|
5273
|
-
trigger: "user"
|
|
5274
|
-
});
|
|
5275
|
-
} else if (normalized.includes("\u5E94") || normalized.includes("\u8FD4\u56DE") || normalized.includes("\u663E\u793A")) {
|
|
5276
|
-
currentScenario.then.push({
|
|
5277
|
-
description: text,
|
|
5278
|
-
type: "response",
|
|
5279
|
-
expectedBehavior: text
|
|
5280
|
-
});
|
|
5281
|
-
}
|
|
5282
|
-
} else if (line.trim() === "" && currentScenario) {
|
|
5283
|
-
scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
|
|
5284
|
-
currentScenario = null;
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
if (currentScenario && currentScenario.title) {
|
|
5288
|
-
scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
|
|
5289
|
-
}
|
|
5290
|
-
return scenarios;
|
|
5291
|
-
}
|
|
5292
|
-
/**
|
|
5293
|
-
* 检测条件类型
|
|
5294
|
-
*/
|
|
5295
|
-
static detectConditionType(description) {
|
|
5296
|
-
const lower = description.toLowerCase();
|
|
5297
|
-
if (lower.includes("\u6570\u636E") || lower.includes("\u8BB0\u5F55") || lower.includes("\u8868")) return "data";
|
|
5298
|
-
if (lower.includes("\u72B6\u6001") || lower.includes("\u5DF2") || lower.includes("\u672A")) return "state";
|
|
5299
|
-
return "user";
|
|
5300
|
-
}
|
|
5301
|
-
/**
|
|
5302
|
-
* 检测结果类型
|
|
5303
|
-
*/
|
|
5304
|
-
static detectResultType(description) {
|
|
5305
|
-
const lower = description.toLowerCase();
|
|
5306
|
-
if (lower.includes("\u8FD4\u56DE") || lower.includes("\u54CD\u5E94") || lower.includes("\u72B6\u6001\u7801")) return "response";
|
|
5307
|
-
if (lower.includes("\u72B6\u6001") || lower.includes("\u53D8\u6210") || lower.includes("\u66F4\u65B0")) return "state";
|
|
5308
|
-
if (lower.includes("\u663E\u793A") || lower.includes("\u9875\u9762") || lower.includes("\u754C\u9762")) return "ui";
|
|
5309
|
-
return "data";
|
|
5310
|
-
}
|
|
5311
|
-
/**
|
|
5312
|
-
* 提取验证方式
|
|
5313
|
-
*/
|
|
5314
|
-
static extractValidation(description) {
|
|
5315
|
-
const match = description.match(/\(([^)]+)\)/);
|
|
5316
|
-
return match ? match[1] : "\u68C0\u67E5\u6761\u4EF6\u6EE1\u8DB3";
|
|
5317
|
-
}
|
|
5318
|
-
/**
|
|
5319
|
-
* 提取预期行为
|
|
5320
|
-
*/
|
|
5321
|
-
static extractExpectedBehavior(description) {
|
|
5322
|
-
return description.replace(/^(应该|应|应当|应该会|会)/, "").trim();
|
|
5323
|
-
}
|
|
5324
|
-
/**
|
|
5325
|
-
* 解析动作
|
|
5326
|
-
*/
|
|
5327
|
-
static parseAction(description) {
|
|
5328
|
-
const apiMatch = description.match(/(?:API|POST|GET|PUT|DELETE|PATCH)\s*[::]?\s*`([^`]+)`/i);
|
|
5329
|
-
const uiMatch = description.match(/(?:点击|输入|选择|提交|拖拽)[::]?\s*(.+)/);
|
|
5330
|
-
return {
|
|
5331
|
-
description,
|
|
5332
|
-
api: apiMatch?.[1],
|
|
5333
|
-
uiAction: uiMatch?.[1],
|
|
5334
|
-
trigger: apiMatch ? "api" : "user"
|
|
5335
|
-
};
|
|
5336
|
-
}
|
|
5337
|
-
/**
|
|
5338
|
-
* 解析状态流转
|
|
5339
|
-
*/
|
|
5340
|
-
static parseStateTransition(description) {
|
|
5341
|
-
const arrowMatch = description.match(/`([^`]+)`\s*(?:→|->)\s*`([^`]+)`/);
|
|
5342
|
-
if (arrowMatch) {
|
|
5343
|
-
return {
|
|
5344
|
-
from: arrowMatch[1],
|
|
5345
|
-
trigger: description.split(/→|->/)[1]?.trim() || "\u5B8C\u6210",
|
|
5346
|
-
to: arrowMatch[2]
|
|
5347
|
-
};
|
|
5348
|
-
}
|
|
5349
|
-
const fromMatch = description.match(/(?:从|起始于|开始于)\s*`([^`]+)`/);
|
|
5350
|
-
const toMatch = description.match(/(?:到|变为|结束于)\s*`([^`]+)`/);
|
|
5351
|
-
const triggerMatch = description.match(/通过|当|使用\s*[::]?\s*(.+?)(?:,|$)/);
|
|
5352
|
-
if (fromMatch && toMatch) {
|
|
5353
|
-
return {
|
|
5354
|
-
from: fromMatch[1],
|
|
5355
|
-
trigger: triggerMatch?.[1] || "\u5B8C\u6210",
|
|
5356
|
-
to: toMatch[1]
|
|
5357
|
-
};
|
|
5358
|
-
}
|
|
5359
|
-
return null;
|
|
5360
|
-
}
|
|
5361
|
-
/**
|
|
5362
|
-
* 完成场景构建
|
|
5363
|
-
*/
|
|
5364
|
-
static finalizeScenario(scenario, order) {
|
|
5365
|
-
return {
|
|
5366
|
-
id: scenario.id || `scenario-${order}`,
|
|
5367
|
-
title: scenario.title || `\u573A\u666F ${order}`,
|
|
5368
|
-
order: scenario.order || order,
|
|
5369
|
-
asA: scenario.asA || "\u7528\u6237",
|
|
5370
|
-
iWant: scenario.iWant || "",
|
|
5371
|
-
soThat: scenario.soThat || "",
|
|
5372
|
-
given: scenario.given || [],
|
|
5373
|
-
when: scenario.when || [],
|
|
5374
|
-
then: scenario.then || [],
|
|
5375
|
-
stateTransitions: scenario.stateTransitions || [],
|
|
5376
|
-
relatedApis: scenario.relatedApis || [],
|
|
5377
|
-
relatedFeatures: scenario.relatedFeatures || []
|
|
5378
|
-
};
|
|
5379
|
-
}
|
|
5380
|
-
/**
|
|
5381
|
-
* 生成场景的 Markdown
|
|
5382
|
-
*/
|
|
5383
|
-
static generateMarkdown(scenario) {
|
|
5384
|
-
const lines = [];
|
|
5385
|
-
lines.push(`#### \u573A\u666F ${scenario.order}: ${scenario.title}`);
|
|
5386
|
-
lines.push("");
|
|
5387
|
-
if (scenario.asA) {
|
|
5388
|
-
lines.push(`**\u4F5C\u4E3A** ${scenario.asA}`);
|
|
5389
|
-
}
|
|
5390
|
-
if (scenario.iWant) {
|
|
5391
|
-
lines.push(`**\u6211\u5E0C\u671B** ${scenario.iWant}`);
|
|
5392
|
-
}
|
|
5393
|
-
if (scenario.soThat) {
|
|
5394
|
-
lines.push(`**\u4EE5\u4FBF** ${scenario.soThat}`);
|
|
5395
|
-
}
|
|
5396
|
-
lines.push("");
|
|
5397
|
-
for (const given of scenario.given) {
|
|
5398
|
-
lines.push(`- [GIVEN] ${given.description}`);
|
|
5399
|
-
}
|
|
5400
|
-
lines.push("");
|
|
5401
|
-
for (const when of scenario.when) {
|
|
5402
|
-
lines.push(`- [WHEN] ${when.description}`);
|
|
5403
|
-
}
|
|
5404
|
-
lines.push("");
|
|
5405
|
-
for (const then of scenario.then) {
|
|
5406
|
-
lines.push(`- [THEN] ${then.description}`);
|
|
5407
|
-
}
|
|
5408
|
-
lines.push("");
|
|
5409
|
-
if (scenario.stateTransitions.length > 0) {
|
|
5410
|
-
lines.push("**\u72B6\u6001\u6D41\u8F6C**:");
|
|
5411
|
-
for (const trans of scenario.stateTransitions) {
|
|
5412
|
-
lines.push(`- \`${trans.from}\` \u2192 \`${trans.to}\` (\u89E6\u53D1: ${trans.trigger})`);
|
|
5413
|
-
}
|
|
5414
|
-
lines.push("");
|
|
5415
|
-
}
|
|
5416
|
-
return lines.join("\n");
|
|
5417
|
-
}
|
|
5418
|
-
};
|
|
5419
|
-
|
|
5420
|
-
// src/commands/accept.ts
|
|
5421
|
-
var acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").option("--mode <mode>", "\u9A8C\u6536\u6A21\u5F0F: traditional (\u4F20\u7EDF) \u6216 bdd (\u884C\u4E3A\u9A71\u52A8)", "traditional").option("--scenario <id>", "\u6307\u5B9A\u9A8C\u6536\u7684 BDD \u573A\u666F ID").action(async (specFile, options) => {
|
|
5084
|
+
var acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").action(async (specFile) => {
|
|
5422
5085
|
try {
|
|
5423
5086
|
logger.header("\u9A8C\u6536\u6A21\u5F0F");
|
|
5424
5087
|
logger.newLine();
|
|
@@ -5429,11 +5092,14 @@ var acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\
|
|
|
5429
5092
|
process.exit(1);
|
|
5430
5093
|
}
|
|
5431
5094
|
logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5095
|
+
const selectedSpec = await selectSpec2(specFile);
|
|
5096
|
+
const selectedMilestone = await selectMilestone2(selectedSpec);
|
|
5097
|
+
const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
|
|
5098
|
+
await generateAcceptanceReport(result);
|
|
5099
|
+
if (result.issues.length > 0) {
|
|
5100
|
+
await handleIssues(result);
|
|
5101
|
+
}
|
|
5102
|
+
await syncSpecStatus(selectedSpec, result);
|
|
5437
5103
|
logger.header("\u9A8C\u6536\u5B8C\u6210!");
|
|
5438
5104
|
logger.newLine();
|
|
5439
5105
|
} catch (error) {
|
|
@@ -5444,43 +5110,6 @@ var acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\
|
|
|
5444
5110
|
process.exit(1);
|
|
5445
5111
|
}
|
|
5446
5112
|
});
|
|
5447
|
-
async function runTraditionalAcceptance(defaultSpec) {
|
|
5448
|
-
const selectedSpec = await selectSpec2(defaultSpec);
|
|
5449
|
-
const selectedMilestone = await selectMilestone2(selectedSpec);
|
|
5450
|
-
const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
|
|
5451
|
-
await generateAcceptanceReport(result);
|
|
5452
|
-
if (result.issues.length > 0) {
|
|
5453
|
-
await handleIssues(result);
|
|
5454
|
-
}
|
|
5455
|
-
await syncSpecStatus(selectedSpec, result);
|
|
5456
|
-
}
|
|
5457
|
-
async function runBDDAcceptance(defaultSpec, scenarioId) {
|
|
5458
|
-
const selectedSpec = await selectSpec2(defaultSpec);
|
|
5459
|
-
const specContent = await FileUtils.read(selectedSpec);
|
|
5460
|
-
const scenarios = BDDParser.parseFromAcceptanceSection(specContent);
|
|
5461
|
-
if (scenarios.length === 0) {
|
|
5462
|
-
logger.warn("\u672A\u627E\u5230 BDD \u573A\u666F\uFF0C\u4F7F\u7528\u4F20\u7EDF\u9A8C\u6536\u6A21\u5F0F");
|
|
5463
|
-
logger.info('\u8BF7\u5148\u8FD0\u884C "team-cli split-prd" \u751F\u6210\u5305\u542B BDD \u573A\u666F\u7684 spec');
|
|
5464
|
-
await runTraditionalAcceptance(selectedSpec);
|
|
5465
|
-
return;
|
|
5466
|
-
}
|
|
5467
|
-
const targetScenarios = scenarioId ? scenarios.filter((s) => s.id === scenarioId || s.title.toLowerCase().includes(scenarioId.toLowerCase())) : scenarios;
|
|
5468
|
-
if (targetScenarios.length === 0) {
|
|
5469
|
-
throw new Error(`\u672A\u627E\u5230\u573A\u666F: ${scenarioId}`);
|
|
5470
|
-
}
|
|
5471
|
-
logger.newLine();
|
|
5472
|
-
logger.info(`\u627E\u5230 ${scenarios.length} \u4E2A BDD \u573A\u666F\uFF0C\u5C06\u9A8C\u6536 ${targetScenarios.length} \u4E2A`);
|
|
5473
|
-
const results = [];
|
|
5474
|
-
for (const scenario of targetScenarios) {
|
|
5475
|
-
logger.newLine();
|
|
5476
|
-
logger.info(`\u9A8C\u6536\u573A\u666F: ${scenario.title}`);
|
|
5477
|
-
const result = await checkBDDScenario(scenario, selectedSpec);
|
|
5478
|
-
results.push(result);
|
|
5479
|
-
printScenarioResult(result);
|
|
5480
|
-
}
|
|
5481
|
-
await generateBDDReport(selectedSpec, results);
|
|
5482
|
-
await syncBDDStatus(selectedSpec, results);
|
|
5483
|
-
}
|
|
5484
5113
|
async function selectSpec2(defaultSpec) {
|
|
5485
5114
|
logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
|
|
5486
5115
|
logger.newLine();
|
|
@@ -6027,377 +5656,6 @@ async function syncSpecStatus(specFile, result) {
|
|
|
6027
5656
|
}
|
|
6028
5657
|
}
|
|
6029
5658
|
}
|
|
6030
|
-
async function checkBDDScenario(scenario, specFile) {
|
|
6031
|
-
const result = {
|
|
6032
|
-
scenarioId: scenario.id,
|
|
6033
|
-
scenarioTitle: scenario.title,
|
|
6034
|
-
overallStatus: "pending",
|
|
6035
|
-
givenResults: [],
|
|
6036
|
-
whenResults: [],
|
|
6037
|
-
thenResults: [],
|
|
6038
|
-
stateTransitionResults: []
|
|
6039
|
-
};
|
|
6040
|
-
for (const given of scenario.given) {
|
|
6041
|
-
const checkResult = await verifyGivenCondition(given, specFile);
|
|
6042
|
-
result.givenResults.push(checkResult);
|
|
6043
|
-
}
|
|
6044
|
-
for (const when of scenario.when) {
|
|
6045
|
-
const checkResult = await verifyWhenAction(when, specFile);
|
|
6046
|
-
result.whenResults.push(checkResult);
|
|
6047
|
-
}
|
|
6048
|
-
for (const then of scenario.then) {
|
|
6049
|
-
const checkResult = await verifyThenResult(then, specFile);
|
|
6050
|
-
result.thenResults.push(checkResult);
|
|
6051
|
-
}
|
|
6052
|
-
for (const transition of scenario.stateTransitions) {
|
|
6053
|
-
const checkResult = await verifyStateTransition(transition, specFile);
|
|
6054
|
-
result.stateTransitionResults.push(checkResult);
|
|
6055
|
-
}
|
|
6056
|
-
const allResults = [
|
|
6057
|
-
...result.givenResults,
|
|
6058
|
-
...result.whenResults,
|
|
6059
|
-
...result.thenResults,
|
|
6060
|
-
...result.stateTransitionResults
|
|
6061
|
-
];
|
|
6062
|
-
if (allResults.every((r) => r.status === "pass")) {
|
|
6063
|
-
result.overallStatus = "pass";
|
|
6064
|
-
} else if (allResults.some((r) => r.status === "fail")) {
|
|
6065
|
-
result.overallStatus = "fail";
|
|
6066
|
-
} else {
|
|
6067
|
-
result.overallStatus = "pending";
|
|
6068
|
-
}
|
|
6069
|
-
return result;
|
|
6070
|
-
}
|
|
6071
|
-
async function verifyGivenCondition(given, specFile) {
|
|
6072
|
-
switch (given.type) {
|
|
6073
|
-
case "data":
|
|
6074
|
-
return verifyDataCondition(given);
|
|
6075
|
-
case "state":
|
|
6076
|
-
return verifyStateCondition(given);
|
|
6077
|
-
case "user":
|
|
6078
|
-
return verifyUserCondition(given);
|
|
6079
|
-
default:
|
|
6080
|
-
return { condition: given.description, status: "pending" };
|
|
6081
|
-
}
|
|
6082
|
-
}
|
|
6083
|
-
async function verifyDataCondition(given) {
|
|
6084
|
-
const tableMatch = given.description.match(/`?([a-z_]+)`?表/);
|
|
6085
|
-
const fieldMatch = given.description.match(/`?([a-z_]+)`?字段/);
|
|
6086
|
-
if (tableMatch) {
|
|
6087
|
-
const tableName = tableMatch[1];
|
|
6088
|
-
const tableFiles = await FileUtils.findFiles(`**/${tableName}.sql`, "database");
|
|
6089
|
-
const entityFiles = await FileUtils.findFiles(`**/${StringUtils.toPascalCase(tableName)}.java`, "backend/src");
|
|
6090
|
-
if (tableFiles.length > 0 || entityFiles.length > 0) {
|
|
6091
|
-
return {
|
|
6092
|
-
condition: given.description,
|
|
6093
|
-
status: "pass",
|
|
6094
|
-
evidence: tableFiles[0] || entityFiles[0]
|
|
6095
|
-
};
|
|
6096
|
-
}
|
|
6097
|
-
return {
|
|
6098
|
-
condition: given.description,
|
|
6099
|
-
status: "fail",
|
|
6100
|
-
evidence: `\u8868 ${tableName} \u672A\u627E\u5230`
|
|
6101
|
-
};
|
|
6102
|
-
}
|
|
6103
|
-
return { condition: given.description, status: "pending" };
|
|
6104
|
-
}
|
|
6105
|
-
async function verifyStateCondition(given) {
|
|
6106
|
-
const statusMatch = given.description.match(/`([^`]+)`状态/);
|
|
6107
|
-
const stateMatch = given.description.match(/`([^`]+)`/);
|
|
6108
|
-
const statusName = statusMatch?.[1] || stateMatch?.[1];
|
|
6109
|
-
if (statusName) {
|
|
6110
|
-
const enumFiles = [
|
|
6111
|
-
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
6112
|
-
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
|
|
6113
|
-
];
|
|
6114
|
-
for (const ef of enumFiles) {
|
|
6115
|
-
const content = await FileUtils.read(ef);
|
|
6116
|
-
if (content.includes(statusName)) {
|
|
6117
|
-
return {
|
|
6118
|
-
condition: given.description,
|
|
6119
|
-
status: "pass",
|
|
6120
|
-
evidence: ef
|
|
6121
|
-
};
|
|
6122
|
-
}
|
|
6123
|
-
}
|
|
6124
|
-
return {
|
|
6125
|
-
condition: given.description,
|
|
6126
|
-
status: "fail",
|
|
6127
|
-
evidence: `\u72B6\u6001 ${statusName} \u672A\u627E\u5230`
|
|
6128
|
-
};
|
|
6129
|
-
}
|
|
6130
|
-
return { condition: given.description, status: "pending" };
|
|
6131
|
-
}
|
|
6132
|
-
async function verifyUserCondition(given) {
|
|
6133
|
-
const userMatch = given.description.match(/用户.*?`([^`]+)`/);
|
|
6134
|
-
if (userMatch) {
|
|
6135
|
-
return {
|
|
6136
|
-
condition: given.description,
|
|
6137
|
-
status: "pending",
|
|
6138
|
-
evidence: "\u9700\u8981\u4EBA\u5DE5\u9A8C\u8BC1\u7528\u6237\u72B6\u6001"
|
|
6139
|
-
};
|
|
6140
|
-
}
|
|
6141
|
-
return { condition: given.description, status: "pending" };
|
|
6142
|
-
}
|
|
6143
|
-
async function verifyWhenAction(when, specFile) {
|
|
6144
|
-
if (when.api) {
|
|
6145
|
-
const apiCheck = await verifyApiImplementationByPath(when.api);
|
|
6146
|
-
return {
|
|
6147
|
-
action: when.description,
|
|
6148
|
-
status: apiCheck.status,
|
|
6149
|
-
apiCalled: when.api,
|
|
6150
|
-
responseCode: apiCheck.responseCode
|
|
6151
|
-
};
|
|
6152
|
-
}
|
|
6153
|
-
if (when.uiAction) {
|
|
6154
|
-
const uiCheck = await verifyUIComponent(when.uiAction);
|
|
6155
|
-
return {
|
|
6156
|
-
action: when.description,
|
|
6157
|
-
status: uiCheck.status
|
|
6158
|
-
};
|
|
6159
|
-
}
|
|
6160
|
-
return { action: when.description, status: "pending" };
|
|
6161
|
-
}
|
|
6162
|
-
async function verifyApiImplementationByPath(apiPath) {
|
|
6163
|
-
const apiMatch = apiPath.match(/(GET|POST|PUT|DELETE|PATCH)\s+\/?(.+)/i);
|
|
6164
|
-
if (!apiMatch) {
|
|
6165
|
-
return { status: "pending" };
|
|
6166
|
-
}
|
|
6167
|
-
const method = apiMatch[1].toUpperCase();
|
|
6168
|
-
const path26 = "/" + apiMatch[2];
|
|
6169
|
-
const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
|
|
6170
|
-
for (const cf of controllerFiles) {
|
|
6171
|
-
const content = await FileUtils.read(cf);
|
|
6172
|
-
if (content.includes(`"${path26}"`) || content.includes(`"${path26}/"`)) {
|
|
6173
|
-
const annotationMap = {
|
|
6174
|
-
GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
|
|
6175
|
-
POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
|
|
6176
|
-
PUT: ["@PutMapping", "@RequestMapping(method = RequestMethod.PUT)"],
|
|
6177
|
-
DELETE: ["@DeleteMapping", "@RequestMapping(method = RequestMethod.DELETE)"]
|
|
6178
|
-
};
|
|
6179
|
-
for (const annotation of annotationMap[method] || []) {
|
|
6180
|
-
if (content.includes(annotation)) {
|
|
6181
|
-
return { status: "pass", responseCode: 200 };
|
|
6182
|
-
}
|
|
6183
|
-
}
|
|
6184
|
-
}
|
|
6185
|
-
}
|
|
6186
|
-
return { status: "fail" };
|
|
6187
|
-
}
|
|
6188
|
-
async function verifyUIComponent(uiAction) {
|
|
6189
|
-
const componentFiles = await FileUtils.findFiles("**/*.tsx", "frontend/src");
|
|
6190
|
-
const viewFiles = await FileUtils.findFiles("**/*.vue", "frontend/src");
|
|
6191
|
-
const allFiles = [...componentFiles, ...viewFiles];
|
|
6192
|
-
for (const cf of allFiles) {
|
|
6193
|
-
const content = await FileUtils.read(cf);
|
|
6194
|
-
if (content.includes(uiAction) || content.toLowerCase().includes(uiAction.toLowerCase())) {
|
|
6195
|
-
return { status: "pass" };
|
|
6196
|
-
}
|
|
6197
|
-
}
|
|
6198
|
-
return { status: "pending" };
|
|
6199
|
-
}
|
|
6200
|
-
async function verifyThenResult(then, specFile) {
|
|
6201
|
-
switch (then.type) {
|
|
6202
|
-
case "response":
|
|
6203
|
-
return verifyResponseResult(then);
|
|
6204
|
-
case "state":
|
|
6205
|
-
return verifyStateResult(then);
|
|
6206
|
-
case "data":
|
|
6207
|
-
return verifyDataResult(then);
|
|
6208
|
-
case "ui":
|
|
6209
|
-
return verifyUIResult(then);
|
|
6210
|
-
default:
|
|
6211
|
-
return { result: then.description, status: "pending" };
|
|
6212
|
-
}
|
|
6213
|
-
}
|
|
6214
|
-
async function verifyResponseResult(then) {
|
|
6215
|
-
const codeMatch = then.description.match(/状态码\s*(\d+)/);
|
|
6216
|
-
const tokenMatch = then.description.match(/token/i);
|
|
6217
|
-
if (tokenMatch) {
|
|
6218
|
-
const tokenFiles = await FileUtils.findFiles("**/*Token*.java", "backend/src");
|
|
6219
|
-
if (tokenFiles.length > 0) {
|
|
6220
|
-
return { result: then.description, status: "pass", expectedValue: "JWT Token" };
|
|
6221
|
-
}
|
|
6222
|
-
return { result: then.description, status: "pending" };
|
|
6223
|
-
}
|
|
6224
|
-
if (codeMatch) {
|
|
6225
|
-
return { result: then.description, status: "pending", expectedValue: `HTTP ${codeMatch[1]}` };
|
|
6226
|
-
}
|
|
6227
|
-
return { result: then.description, status: "pending" };
|
|
6228
|
-
}
|
|
6229
|
-
async function verifyStateResult(then) {
|
|
6230
|
-
const stateMatch = then.description.match(/`([^`]+)`/);
|
|
6231
|
-
if (stateMatch) {
|
|
6232
|
-
const stateName = stateMatch[1];
|
|
6233
|
-
const enumFiles = [
|
|
6234
|
-
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
6235
|
-
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
|
|
6236
|
-
];
|
|
6237
|
-
for (const ef of enumFiles) {
|
|
6238
|
-
const content = await FileUtils.read(ef);
|
|
6239
|
-
if (content.includes(stateName)) {
|
|
6240
|
-
return { result: then.description, status: "pass", expectedValue: stateName };
|
|
6241
|
-
}
|
|
6242
|
-
}
|
|
6243
|
-
return { result: then.description, status: "fail", expectedValue: stateName };
|
|
6244
|
-
}
|
|
6245
|
-
return { result: then.description, status: "pending" };
|
|
6246
|
-
}
|
|
6247
|
-
async function verifyDataResult(then) {
|
|
6248
|
-
return { result: then.description, status: "pending" };
|
|
6249
|
-
}
|
|
6250
|
-
async function verifyUIResult(then) {
|
|
6251
|
-
return { result: then.description, status: "pending" };
|
|
6252
|
-
}
|
|
6253
|
-
async function verifyStateTransition(transition, specFile) {
|
|
6254
|
-
const { from, trigger, to } = transition;
|
|
6255
|
-
const enumFiles = [
|
|
6256
|
-
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
6257
|
-
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src"),
|
|
6258
|
-
...await FileUtils.findFiles("**/*State*.ts", "frontend/src")
|
|
6259
|
-
];
|
|
6260
|
-
const fromExists = await checkStateExists(from, enumFiles);
|
|
6261
|
-
const toExists = await checkStateExists(to, enumFiles);
|
|
6262
|
-
if (!fromExists || !toExists) {
|
|
6263
|
-
return {
|
|
6264
|
-
transition: `\`${from}\` \u2192 \`${to}\``,
|
|
6265
|
-
status: "fail",
|
|
6266
|
-
fromState: fromExists ? "\u2713" : "\u2717",
|
|
6267
|
-
toState: toExists ? "\u2713" : "\u2717"
|
|
6268
|
-
};
|
|
6269
|
-
}
|
|
6270
|
-
const serviceFiles = await FileUtils.findFiles("**/*Service*.java", "backend/src");
|
|
6271
|
-
for (const sf of serviceFiles) {
|
|
6272
|
-
const content = await FileUtils.read(sf);
|
|
6273
|
-
const methodPatterns = [
|
|
6274
|
-
new RegExp(`${from}\\s*\u2192\\s*${to}`, "i"),
|
|
6275
|
-
new RegExp(`from\\s*${from}\\s*to\\s*${to}`, "i")
|
|
6276
|
-
];
|
|
6277
|
-
for (const pattern of methodPatterns) {
|
|
6278
|
-
if (pattern.test(content)) {
|
|
6279
|
-
return {
|
|
6280
|
-
transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
|
|
6281
|
-
status: "pass",
|
|
6282
|
-
fromState: from,
|
|
6283
|
-
toState: to
|
|
6284
|
-
};
|
|
6285
|
-
}
|
|
6286
|
-
}
|
|
6287
|
-
}
|
|
6288
|
-
return {
|
|
6289
|
-
transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
|
|
6290
|
-
status: "pending",
|
|
6291
|
-
fromState: from,
|
|
6292
|
-
toState: to
|
|
6293
|
-
};
|
|
6294
|
-
}
|
|
6295
|
-
async function checkStateExists(state, files) {
|
|
6296
|
-
for (const file of files) {
|
|
6297
|
-
try {
|
|
6298
|
-
const content = await FileUtils.read(file);
|
|
6299
|
-
if (content.includes(state) || content.includes(`"${state}"`)) {
|
|
6300
|
-
return true;
|
|
6301
|
-
}
|
|
6302
|
-
} catch {
|
|
6303
|
-
continue;
|
|
6304
|
-
}
|
|
6305
|
-
}
|
|
6306
|
-
return false;
|
|
6307
|
-
}
|
|
6308
|
-
function printScenarioResult(result) {
|
|
6309
|
-
const icon = result.overallStatus === "pass" ? "\u2713" : result.overallStatus === "fail" ? "\u2717" : "\u25CB";
|
|
6310
|
-
console.log("");
|
|
6311
|
-
console.log(` ${icon} \u573A\u666F: ${result.scenarioTitle}`);
|
|
6312
|
-
const passGiven = result.givenResults.filter((r) => r.status === "pass").length;
|
|
6313
|
-
const passWhen = result.whenResults.filter((r) => r.status === "pass").length;
|
|
6314
|
-
const passThen = result.thenResults.filter((r) => r.status === "pass").length;
|
|
6315
|
-
const passTrans = result.stateTransitionResults.filter((r) => r.status === "pass").length;
|
|
6316
|
-
console.log(` Given: ${passGiven}/${result.givenResults.length}`);
|
|
6317
|
-
console.log(` When: ${passWhen}/${result.whenResults.length}`);
|
|
6318
|
-
console.log(` Then: ${passThen}/${result.thenResults.length}`);
|
|
6319
|
-
console.log(` State: ${passTrans}/${result.stateTransitionResults.length}`);
|
|
6320
|
-
}
|
|
6321
|
-
async function generateBDDReport(specFile, results) {
|
|
6322
|
-
const reportDir = "docs/bdd-reports";
|
|
6323
|
-
await FileUtils.ensureDir(reportDir);
|
|
6324
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6325
|
-
const specName = path12.basename(specFile, ".md");
|
|
6326
|
-
const reportFile = path12.join(reportDir, `${timestamp}_${specName}_bdd.md`);
|
|
6327
|
-
const lines = [];
|
|
6328
|
-
lines.push("# BDD \u9A8C\u6536\u62A5\u544A");
|
|
6329
|
-
lines.push("");
|
|
6330
|
-
lines.push(`**Spec \u6587\u4EF6**: ${specFile}`);
|
|
6331
|
-
lines.push(`**\u9A8C\u6536\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`);
|
|
6332
|
-
lines.push(`**\u573A\u666F\u6570\u91CF**: ${results.length}`);
|
|
6333
|
-
lines.push("");
|
|
6334
|
-
lines.push("## \u9A8C\u6536\u6C47\u603B");
|
|
6335
|
-
lines.push("");
|
|
6336
|
-
let totalPass = 0;
|
|
6337
|
-
let totalFail = 0;
|
|
6338
|
-
for (const r of results) {
|
|
6339
|
-
const statusIcon = r.overallStatus === "pass" ? "\u2713" : r.overallStatus === "fail" ? "\u2717" : "\u25CB";
|
|
6340
|
-
const givenIcon = r.givenResults.every((g) => g.status === "pass") ? "\u2713" : r.givenResults.some((g) => g.status === "fail") ? "\u2717" : "\u25CB";
|
|
6341
|
-
const whenIcon = r.whenResults.every((w) => w.status === "pass") ? "\u2713" : r.whenResults.some((w) => w.status === "fail") ? "\u2717" : "\u25CB";
|
|
6342
|
-
const thenIcon = r.thenResults.every((t) => t.status === "pass") ? "\u2713" : r.thenResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
|
|
6343
|
-
const transIcon = r.stateTransitionResults.every((t) => t.status === "pass") ? "\u2713" : r.stateTransitionResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
|
|
6344
|
-
lines.push(`- ${statusIcon} ${r.scenarioTitle} | Given:${givenIcon} When:${whenIcon} Then:${thenIcon} State:${transIcon}`);
|
|
6345
|
-
if (r.overallStatus === "pass") totalPass++;
|
|
6346
|
-
if (r.overallStatus === "fail") totalFail++;
|
|
6347
|
-
}
|
|
6348
|
-
lines.push("");
|
|
6349
|
-
lines.push(`**\u603B\u8BA1**: ${totalPass} \u4E2A\u901A\u8FC7, ${totalFail} \u4E2A\u5931\u8D25, ${results.length - totalPass - totalFail} \u4E2A\u5F85\u9A8C\u8BC1`);
|
|
6350
|
-
lines.push("");
|
|
6351
|
-
lines.push("## \u8BE6\u7EC6\u9A8C\u6536\u7ED3\u679C");
|
|
6352
|
-
lines.push("");
|
|
6353
|
-
for (const r of results) {
|
|
6354
|
-
lines.push(`### \u573A\u666F: ${r.scenarioTitle}`);
|
|
6355
|
-
lines.push("");
|
|
6356
|
-
lines.push("#### GIVEN (\u524D\u7F6E\u6761\u4EF6)");
|
|
6357
|
-
for (const g of r.givenResults) {
|
|
6358
|
-
const icon = g.status === "pass" ? "\u2713" : g.status === "fail" ? "\u2717" : "\u25CB";
|
|
6359
|
-
lines.push(`- ${icon} ${g.condition}`);
|
|
6360
|
-
if (g.evidence) {
|
|
6361
|
-
lines.push(` - \u8BC1\u636E: ${g.evidence}`);
|
|
6362
|
-
}
|
|
6363
|
-
}
|
|
6364
|
-
lines.push("");
|
|
6365
|
-
lines.push("#### WHEN (\u89E6\u53D1\u52A8\u4F5C)");
|
|
6366
|
-
for (const w of r.whenResults) {
|
|
6367
|
-
const icon = w.status === "pass" ? "\u2713" : w.status === "fail" ? "\u2717" : "\u25CB";
|
|
6368
|
-
lines.push(`- ${icon} ${w.action}`);
|
|
6369
|
-
if (w.apiCalled) {
|
|
6370
|
-
lines.push(` - API: ${w.apiCalled}`);
|
|
6371
|
-
}
|
|
6372
|
-
}
|
|
6373
|
-
lines.push("");
|
|
6374
|
-
lines.push("#### THEN (\u9884\u671F\u7ED3\u679C)");
|
|
6375
|
-
for (const t of r.thenResults) {
|
|
6376
|
-
const icon = t.status === "pass" ? "\u2713" : t.status === "fail" ? "\u2717" : "\u25CB";
|
|
6377
|
-
lines.push(`- ${icon} ${t.result}`);
|
|
6378
|
-
}
|
|
6379
|
-
lines.push("");
|
|
6380
|
-
lines.push("#### \u72B6\u6001\u6D41\u8F6C");
|
|
6381
|
-
for (const s of r.stateTransitionResults) {
|
|
6382
|
-
const icon = s.status === "pass" ? "\u2713" : s.status === "fail" ? "\u2717" : "\u25CB";
|
|
6383
|
-
lines.push(`- ${icon} ${s.transition}`);
|
|
6384
|
-
}
|
|
6385
|
-
lines.push("");
|
|
6386
|
-
lines.push("---");
|
|
6387
|
-
lines.push("");
|
|
6388
|
-
}
|
|
6389
|
-
await FileUtils.write(reportFile, lines.join("\n"));
|
|
6390
|
-
logger.success(`BDD \u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210: ${reportFile}`);
|
|
6391
|
-
}
|
|
6392
|
-
async function syncBDDStatus(specFile, results) {
|
|
6393
|
-
const allPassed = results.every((r) => r.overallStatus === "pass");
|
|
6394
|
-
if (allPassed) {
|
|
6395
|
-
logger.success("\u6240\u6709 BDD \u573A\u666F\u9A8C\u6536\u901A\u8FC7");
|
|
6396
|
-
} else {
|
|
6397
|
-
const failedCount = results.filter((r) => r.overallStatus === "fail").length;
|
|
6398
|
-
logger.warn(`${failedCount} \u4E2A\u573A\u666F\u9A8C\u6536\u672A\u901A\u8FC7\uFF0C\u9700\u8981\u4FEE\u590D`);
|
|
6399
|
-
}
|
|
6400
|
-
}
|
|
6401
5659
|
|
|
6402
5660
|
// src/commands/add-module.ts
|
|
6403
5661
|
init_esm_shims();
|
|
@@ -7589,15 +6847,15 @@ async function syncTemplateVersions(aiMemoryFile, projectDir) {
|
|
|
7589
6847
|
await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
|
|
7590
6848
|
}
|
|
7591
6849
|
function extractRepoName(repository) {
|
|
7592
|
-
let
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
const parts =
|
|
6850
|
+
let path22 = repository;
|
|
6851
|
+
path22 = path22.replace(/^https?:\/\//, "");
|
|
6852
|
+
path22 = path22.replace(/^git@/, "");
|
|
6853
|
+
const parts = path22.split("/");
|
|
7596
6854
|
if (parts.length > 1) {
|
|
7597
|
-
|
|
6855
|
+
path22 = parts.slice(1).join("/");
|
|
7598
6856
|
}
|
|
7599
|
-
|
|
7600
|
-
return
|
|
6857
|
+
path22 = path22.replace(/\.git$/, "");
|
|
6858
|
+
return path22;
|
|
7601
6859
|
}
|
|
7602
6860
|
|
|
7603
6861
|
// src/commands/check-api.ts
|
|
@@ -7912,10 +7170,10 @@ function extractApisFromRegistry(registryContent) {
|
|
|
7912
7170
|
let match;
|
|
7913
7171
|
while ((match = apiRegex.exec(registryContent)) !== null) {
|
|
7914
7172
|
const method = match[1];
|
|
7915
|
-
const
|
|
7173
|
+
const path22 = match[2].trim();
|
|
7916
7174
|
const description = match[3].trim();
|
|
7917
|
-
const key = `${method}:${
|
|
7918
|
-
apis.set(key, { method, path:
|
|
7175
|
+
const key = `${method}:${path22}`;
|
|
7176
|
+
apis.set(key, { method, path: path22, description });
|
|
7919
7177
|
}
|
|
7920
7178
|
return apis;
|
|
7921
7179
|
}
|
|
@@ -8570,9 +7828,9 @@ async function compareTemplate(projectPath, type, localConfig, remoteTag, remote
|
|
|
8570
7828
|
}
|
|
8571
7829
|
}
|
|
8572
7830
|
if (!remoteCommit) {
|
|
8573
|
-
const { execa:
|
|
7831
|
+
const { execa: execa5 } = await import("execa");
|
|
8574
7832
|
const ref = remoteTag || remoteBranch || "HEAD";
|
|
8575
|
-
const { stdout } = await
|
|
7833
|
+
const { stdout } = await execa5("git", ["ls-remote", repository, ref], {
|
|
8576
7834
|
stdio: "pipe"
|
|
8577
7835
|
});
|
|
8578
7836
|
remoteCommit = stdout.split(" ")[0];
|
|
@@ -8728,2004 +7986,448 @@ var Table = class {
|
|
|
8728
7986
|
}
|
|
8729
7987
|
};
|
|
8730
7988
|
|
|
8731
|
-
// src/commands/
|
|
7989
|
+
// src/commands/fe-start.ts
|
|
8732
7990
|
init_esm_shims();
|
|
8733
|
-
init_utils();
|
|
8734
7991
|
init_logger();
|
|
8735
7992
|
import { Command as Command18 } from "commander";
|
|
8736
7993
|
import inquirer13 from "inquirer";
|
|
8737
|
-
import path24 from "path";
|
|
8738
|
-
|
|
8739
|
-
// src/lib/worktree-manager.ts
|
|
8740
|
-
init_esm_shims();
|
|
8741
|
-
import { execa as execa5 } from "execa";
|
|
8742
|
-
import path21 from "path";
|
|
8743
|
-
import fs6 from "fs-extra";
|
|
8744
7994
|
|
|
8745
|
-
// src/
|
|
7995
|
+
// src/lib/fe-git.ts
|
|
8746
7996
|
init_esm_shims();
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
};
|
|
8752
|
-
var DEFAULT_ROLES = [
|
|
8753
|
-
{
|
|
8754
|
-
role: "planner",
|
|
8755
|
-
displayName: "Planner",
|
|
8756
|
-
description: "\u9700\u6C42\u5206\u6790\u4E0E BDD \u573A\u666F\u8BBE\u8BA1",
|
|
8757
|
-
branchPrefix: "feat",
|
|
8758
|
-
worktreeSuffix: "planner",
|
|
8759
|
-
required: true
|
|
8760
|
-
},
|
|
7997
|
+
init_utils();
|
|
7998
|
+
init_logger();
|
|
7999
|
+
var VALID_BRANCH_PREFIXES = ["feat", "fix", "hotfix", "refactor"];
|
|
8000
|
+
var DANGEROUS_FILE_RULES = [
|
|
8761
8001
|
{
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
description: "\u6280\u672F\u67B6\u6784\u4E0E API \u8BBE\u8BA1",
|
|
8765
|
-
branchPrefix: "feat",
|
|
8766
|
-
worktreeSuffix: "arch",
|
|
8767
|
-
required: true
|
|
8002
|
+
category: "\u6784\u5EFA\u4EA7\u7269",
|
|
8003
|
+
patterns: [/^dist\//, /^build\//, /^\.output\//]
|
|
8768
8004
|
},
|
|
8769
8005
|
{
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
description: "\u540E\u7AEF\u5B9E\u73B0\u4E0E OpenAPI",
|
|
8773
|
-
branchPrefix: "feat",
|
|
8774
|
-
worktreeSuffix: "backend",
|
|
8775
|
-
required: true
|
|
8006
|
+
category: "\u73AF\u5883\u53D8\u91CF",
|
|
8007
|
+
patterns: [/^\.env$/, /^\.env\./]
|
|
8776
8008
|
},
|
|
8777
8009
|
{
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
description: "\u524D\u7AEF\u5B9E\u73B0",
|
|
8781
|
-
branchPrefix: "feat",
|
|
8782
|
-
worktreeSuffix: "frontend",
|
|
8783
|
-
required: true
|
|
8010
|
+
category: "\u4F9D\u8D56\u76EE\u5F55",
|
|
8011
|
+
patterns: [/^node_modules\//]
|
|
8784
8012
|
},
|
|
8785
8013
|
{
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8014
|
+
category: "\u79D8\u94A5/\u8BC1\u4E66",
|
|
8015
|
+
patterns: [
|
|
8016
|
+
/\.pem$/,
|
|
8017
|
+
/\.key$/,
|
|
8018
|
+
/\.p12$/,
|
|
8019
|
+
/\.pfx$/,
|
|
8020
|
+
/\.cert$/,
|
|
8021
|
+
/credentials\.json$/,
|
|
8022
|
+
/_rsa$/,
|
|
8023
|
+
/id_ed25519$/
|
|
8024
|
+
]
|
|
8792
8025
|
},
|
|
8793
8026
|
{
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
description: "\u4EE3\u7801\u5BA1\u67E5\u4E0E\u53D1\u5E03",
|
|
8797
|
-
branchPrefix: "integration",
|
|
8798
|
-
worktreeSuffix: "review",
|
|
8799
|
-
required: true
|
|
8027
|
+
category: "\u654F\u611F\u914D\u7F6E",
|
|
8028
|
+
patterns: [/^\.npmrc$/, /^\.docker\/config\.json$/]
|
|
8800
8029
|
}
|
|
8801
8030
|
];
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
roles;
|
|
8809
|
-
constructor(repoRoot) {
|
|
8810
|
-
this.repoRoot = repoRoot || process.cwd();
|
|
8811
|
-
this.wtBaseDir = path21.resolve(this.repoRoot, DEFAULT_WORKTREE_CONFIG.baseDir);
|
|
8812
|
-
this.roles = DEFAULT_ROLES;
|
|
8813
|
-
}
|
|
8814
|
-
/**
|
|
8815
|
-
* 创建新的 Worktree 会话
|
|
8816
|
-
*/
|
|
8817
|
-
async createSession(config) {
|
|
8818
|
-
const { ticket, goal, baseBranch, enabledRoles } = config;
|
|
8819
|
-
const enabled = enabledRoles || this.roles.map((r) => r.role);
|
|
8820
|
-
logger.info(`\u521B\u5EFA Worktree \u4F1A\u8BDD: ${ticket}`);
|
|
8821
|
-
logger.info(`\u76EE\u6807: ${goal}`);
|
|
8822
|
-
logger.info(`\u57FA\u7840\u5206\u652F: ${baseBranch}`);
|
|
8823
|
-
await fs6.ensureDir(this.wtBaseDir);
|
|
8824
|
-
const worktrees = [];
|
|
8825
|
-
for (const roleConfig of this.roles) {
|
|
8826
|
-
if (!enabled.includes(roleConfig.role)) {
|
|
8827
|
-
continue;
|
|
8828
|
-
}
|
|
8829
|
-
const wtName = `${roleConfig.worktreeSuffix}-${ticket}`;
|
|
8830
|
-
const wtPath = path21.join(this.wtBaseDir, wtName);
|
|
8831
|
-
const branchName = `${roleConfig.branchPrefix}/${ticket}-${roleConfig.worktreeSuffix}`;
|
|
8832
|
-
const exists = await this.exists(wtName);
|
|
8833
|
-
if (exists) {
|
|
8834
|
-
logger.warn(`Worktree \u5DF2\u5B58\u5728: ${wtName}`);
|
|
8835
|
-
worktrees.push({
|
|
8836
|
-
role: roleConfig.role,
|
|
8837
|
-
name: wtName,
|
|
8838
|
-
path: path21.relative(this.repoRoot, wtPath),
|
|
8839
|
-
branch: branchName,
|
|
8840
|
-
status: "idle",
|
|
8841
|
-
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
8842
|
-
});
|
|
8843
|
-
continue;
|
|
8844
|
-
}
|
|
8845
|
-
try {
|
|
8846
|
-
await this.create(wtName, branchName, baseBranch);
|
|
8847
|
-
logger.success(`\u521B\u5EFA Worktree: ${wtName}`);
|
|
8848
|
-
worktrees.push({
|
|
8849
|
-
role: roleConfig.role,
|
|
8850
|
-
name: wtName,
|
|
8851
|
-
path: path21.relative(this.repoRoot, wtPath),
|
|
8852
|
-
branch: branchName,
|
|
8853
|
-
status: "idle",
|
|
8854
|
-
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
8855
|
-
});
|
|
8856
|
-
} catch (error) {
|
|
8857
|
-
logger.error(`\u521B\u5EFA Worktree \u5931\u8D25: ${wtName}`);
|
|
8858
|
-
throw error;
|
|
8859
|
-
}
|
|
8860
|
-
}
|
|
8861
|
-
const sessionConfig = {
|
|
8862
|
-
ticket,
|
|
8863
|
-
goal,
|
|
8864
|
-
baseBranch,
|
|
8865
|
-
worktrees,
|
|
8866
|
-
phase: "planning",
|
|
8867
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8868
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8869
|
-
};
|
|
8870
|
-
await this.saveSessionState(sessionConfig);
|
|
8871
|
-
return sessionConfig;
|
|
8872
|
-
}
|
|
8873
|
-
/**
|
|
8874
|
-
* 创建 worktree
|
|
8875
|
-
*/
|
|
8876
|
-
async create(name, branch, baseBranch) {
|
|
8877
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8878
|
-
if (await fs6.pathExists(wtPath)) {
|
|
8879
|
-
logger.warn(`Worktree \u5DF2\u5B58\u5728\uFF0C\u5148\u5220\u9664: ${name}`);
|
|
8880
|
-
await this.remove(name);
|
|
8881
|
-
}
|
|
8882
|
-
const args = ["worktree", "add", wtPath];
|
|
8883
|
-
if (baseBranch) {
|
|
8884
|
-
args.push(baseBranch);
|
|
8885
|
-
} else {
|
|
8886
|
-
args.push("-b", branch);
|
|
8887
|
-
}
|
|
8888
|
-
await this.runGitCommand(args);
|
|
8889
|
-
}
|
|
8890
|
-
/**
|
|
8891
|
-
* 删除 worktree
|
|
8892
|
-
*/
|
|
8893
|
-
async remove(name, force) {
|
|
8894
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8895
|
-
if (!fs6.pathExists(wtPath)) {
|
|
8896
|
-
logger.warn(`Worktree \u4E0D\u5B58\u5728: ${name}`);
|
|
8897
|
-
return;
|
|
8898
|
-
}
|
|
8899
|
-
const args = force ? ["worktree", "remove", "--force", wtPath] : ["worktree", "remove", wtPath];
|
|
8900
|
-
await this.runGitCommand(args);
|
|
8901
|
-
logger.success(`\u5220\u9664 Worktree: ${name}`);
|
|
8902
|
-
}
|
|
8903
|
-
/**
|
|
8904
|
-
* 列出所有 worktree
|
|
8905
|
-
*/
|
|
8906
|
-
async list() {
|
|
8907
|
-
const worktrees = [];
|
|
8908
|
-
if (!fs6.pathExists(this.wtBaseDir)) {
|
|
8909
|
-
return [];
|
|
8910
|
-
}
|
|
8911
|
-
const entries = await fs6.readdir(this.wtBaseDir, { withFileTypes: true });
|
|
8912
|
-
for (const entry of entries) {
|
|
8913
|
-
if (!entry.isDirectory()) {
|
|
8914
|
-
continue;
|
|
8915
|
-
}
|
|
8916
|
-
const name = entry.name;
|
|
8917
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8918
|
-
const isWorktree = await this.isWorktree(wtPath);
|
|
8919
|
-
if (!isWorktree) {
|
|
8920
|
-
continue;
|
|
8921
|
-
}
|
|
8922
|
-
const branch = await this.getBranch(wtPath);
|
|
8923
|
-
const status = await this.getWorktreeStatus(wtPath);
|
|
8924
|
-
worktrees.push({
|
|
8925
|
-
role: this.extractRole(name),
|
|
8926
|
-
name,
|
|
8927
|
-
path: path21.relative(this.repoRoot, wtPath),
|
|
8928
|
-
branch,
|
|
8929
|
-
status,
|
|
8930
|
-
lastActivity: await this.getLastActivity(wtPath)
|
|
8931
|
-
});
|
|
8932
|
-
}
|
|
8933
|
-
return worktrees;
|
|
8934
|
-
}
|
|
8935
|
-
/**
|
|
8936
|
-
* 切换到 worktree
|
|
8937
|
-
*/
|
|
8938
|
-
async checkout(name) {
|
|
8939
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8940
|
-
if (!fs6.pathExists(wtPath)) {
|
|
8941
|
-
throw new Error(`Worktree \u4E0D\u5B58\u5728: ${name}`);
|
|
8942
|
-
}
|
|
8943
|
-
logger.info(`\u5207\u6362\u5230 Worktree: ${name}`);
|
|
8944
|
-
logger.info(`\u8DEF\u5F84: ${wtPath}`);
|
|
8945
|
-
logger.info("");
|
|
8946
|
-
logger.info("\u8BF7\u5728\u65B0\u7684\u7EC8\u7AEF\u7A97\u53E3\u4E2D\u6267\u884C:");
|
|
8947
|
-
logger.info(` cd ${wtPath}`);
|
|
8948
|
-
logger.info(` git checkout ${await this.getBranch(wtPath)}`);
|
|
8949
|
-
logger.info("");
|
|
8950
|
-
logger.info("\u6216\u8005\u4F7F\u7528 IDE \u6253\u5F00\u8BE5\u76EE\u5F55\u7EE7\u7EED\u5F00\u53D1");
|
|
8951
|
-
}
|
|
8952
|
-
/**
|
|
8953
|
-
* 切换到 worktree 并执行命令
|
|
8954
|
-
*/
|
|
8955
|
-
async exec(name, command) {
|
|
8956
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8957
|
-
if (!fs6.pathExists(wtPath)) {
|
|
8958
|
-
throw new Error(`Worktree \u4E0D\u5B58\u5728: ${name}`);
|
|
8959
|
-
}
|
|
8960
|
-
return await this.runGitCommand(["-C", wtPath, ...command.split(" ")]);
|
|
8961
|
-
}
|
|
8962
|
-
/**
|
|
8963
|
-
* 检查 worktree 是否存在
|
|
8964
|
-
*/
|
|
8965
|
-
async exists(name) {
|
|
8966
|
-
const wtPath = path21.join(this.wtBaseDir, name);
|
|
8967
|
-
return this.isWorktree(wtPath);
|
|
8968
|
-
}
|
|
8969
|
-
/**
|
|
8970
|
-
* 获取 worktree 路径
|
|
8971
|
-
*/
|
|
8972
|
-
getPath(name) {
|
|
8973
|
-
return path21.join(this.wtBaseDir, name);
|
|
8974
|
-
}
|
|
8975
|
-
/**
|
|
8976
|
-
* 清理孤立 worktree
|
|
8977
|
-
*/
|
|
8978
|
-
async prune() {
|
|
8979
|
-
await this.runGitCommand(["worktree", "prune"]);
|
|
8980
|
-
logger.success("\u6E05\u7406\u5B64\u7ACB worktree \u5B8C\u6210");
|
|
8981
|
-
}
|
|
8982
|
-
/**
|
|
8983
|
-
* 获取会话状态
|
|
8984
|
-
*/
|
|
8985
|
-
async getSessionState(ticket) {
|
|
8986
|
-
const stateFile = this.getStateFilePath(ticket);
|
|
8987
|
-
if (!fs6.pathExists(stateFile)) {
|
|
8988
|
-
return null;
|
|
8989
|
-
}
|
|
8990
|
-
const content = await fs6.readFile(stateFile, "utf-8");
|
|
8991
|
-
return JSON.parse(content);
|
|
8992
|
-
}
|
|
8993
|
-
/**
|
|
8994
|
-
* 更新会话状态
|
|
8995
|
-
*/
|
|
8996
|
-
async updateSessionState(ticket, updates) {
|
|
8997
|
-
const state = await this.getSessionState(ticket);
|
|
8998
|
-
if (!state) {
|
|
8999
|
-
throw new Error(`\u4F1A\u8BDD\u4E0D\u5B58\u5728: ${ticket}`);
|
|
9000
|
-
}
|
|
9001
|
-
const updated = {
|
|
9002
|
-
...state,
|
|
9003
|
-
...updates,
|
|
9004
|
-
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
9005
|
-
};
|
|
9006
|
-
await fs6.writeFile(
|
|
9007
|
-
this.getStateFilePath(ticket),
|
|
9008
|
-
JSON.stringify(updated, null, 2)
|
|
9009
|
-
);
|
|
9010
|
-
}
|
|
9011
|
-
/**
|
|
9012
|
-
* 获取指定角色的 worktree
|
|
9013
|
-
*/
|
|
9014
|
-
async getWorktreeByRole(ticket, role) {
|
|
9015
|
-
const state = await this.getSessionState(ticket);
|
|
9016
|
-
if (!state) {
|
|
9017
|
-
return null;
|
|
9018
|
-
}
|
|
9019
|
-
return state.worktrees.get(role) || null;
|
|
9020
|
-
}
|
|
9021
|
-
/**
|
|
9022
|
-
* 初始化 handoff 目录
|
|
9023
|
-
*/
|
|
9024
|
-
async initHandoffDirs(ticket) {
|
|
9025
|
-
const handoffBase = path21.join(this.repoRoot, ".handoff", ticket);
|
|
9026
|
-
const roles = this.roles.map((r) => r.role);
|
|
9027
|
-
for (const role of roles) {
|
|
9028
|
-
const roleDir = path21.join(handoffBase, role);
|
|
9029
|
-
await fs6.ensureDir(roleDir);
|
|
9030
|
-
}
|
|
9031
|
-
const bddDir = path21.join(handoffBase, "planner", "BDD_FEATURES");
|
|
9032
|
-
await fs6.ensureDir(bddDir);
|
|
9033
|
-
logger.success(`\u521D\u59CB\u5316 handoff \u76EE\u5F55: ${handoffBase}`);
|
|
9034
|
-
}
|
|
9035
|
-
// ========== 私有方法 ==========
|
|
9036
|
-
/**
|
|
9037
|
-
* 运行 git 命令
|
|
9038
|
-
*/
|
|
9039
|
-
async runGitCommand(args) {
|
|
9040
|
-
try {
|
|
9041
|
-
const result = await execa5("git", args, {
|
|
9042
|
-
cwd: this.repoRoot,
|
|
9043
|
-
stdio: "pipe"
|
|
9044
|
-
});
|
|
9045
|
-
return {
|
|
9046
|
-
stdout: result.stdout || "",
|
|
9047
|
-
stderr: result.stderr || "",
|
|
9048
|
-
exitCode: result.exitCode || 0
|
|
9049
|
-
};
|
|
9050
|
-
} catch (error) {
|
|
9051
|
-
return {
|
|
9052
|
-
stdout: error.stdout || "",
|
|
9053
|
-
stderr: error.stderr || error.message,
|
|
9054
|
-
exitCode: error.exitCode || -1
|
|
9055
|
-
};
|
|
9056
|
-
}
|
|
9057
|
-
}
|
|
9058
|
-
/**
|
|
9059
|
-
* 检查路径是否是有效的 worktree
|
|
9060
|
-
*/
|
|
9061
|
-
async isWorktree(wtPath) {
|
|
9062
|
-
const gitDir = path21.join(wtPath, ".git");
|
|
9063
|
-
if (!fs6.pathExists(gitDir)) {
|
|
9064
|
-
return false;
|
|
9065
|
-
}
|
|
9066
|
-
const stats = await fs6.stat(gitDir);
|
|
9067
|
-
if (!stats.isFile()) {
|
|
9068
|
-
return false;
|
|
9069
|
-
}
|
|
9070
|
-
const content = await fs6.readFile(gitDir, "utf-8");
|
|
9071
|
-
return content.includes("gitdir:");
|
|
9072
|
-
}
|
|
9073
|
-
/**
|
|
9074
|
-
* 获取 worktree 的分支
|
|
9075
|
-
*/
|
|
9076
|
-
async getBranch(wtPath) {
|
|
9077
|
-
const result = await this.runGitCommand(["-C", wtPath, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
9078
|
-
return result.stdout.trim();
|
|
9079
|
-
}
|
|
9080
|
-
/**
|
|
9081
|
-
* 获取 worktree 的状态
|
|
9082
|
-
*/
|
|
9083
|
-
async getWorktreeStatus(wtPath) {
|
|
9084
|
-
const status = await this.runGitCommand(["-C", wtPath, "status", "--porcelain"]);
|
|
9085
|
-
if (status.stdout.trim()) {
|
|
9086
|
-
return "active";
|
|
9087
|
-
}
|
|
9088
|
-
return "idle";
|
|
9089
|
-
}
|
|
9090
|
-
/**
|
|
9091
|
-
* 获取最后活动时间
|
|
9092
|
-
*/
|
|
9093
|
-
async getLastActivity(wtPath) {
|
|
9094
|
-
const reflog = await this.runGitCommand(["-C", wtPath, "reflog", "-n", "1"]);
|
|
9095
|
-
if (!reflog.stdout) {
|
|
9096
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9097
|
-
}
|
|
9098
|
-
const match = reflog.stdout.match(/HEAD@\{([^}]+)\}/);
|
|
9099
|
-
if (match) {
|
|
9100
|
-
return match[1];
|
|
9101
|
-
}
|
|
9102
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9103
|
-
}
|
|
9104
|
-
/**
|
|
9105
|
-
* 从 worktree 名称提取角色
|
|
9106
|
-
*/
|
|
9107
|
-
extractRole(name) {
|
|
9108
|
-
for (const roleConfig of this.roles) {
|
|
9109
|
-
if (name.startsWith(roleConfig.worktreeSuffix)) {
|
|
9110
|
-
return roleConfig.role;
|
|
9111
|
-
}
|
|
9112
|
-
}
|
|
9113
|
-
return "backend";
|
|
9114
|
-
}
|
|
9115
|
-
/**
|
|
9116
|
-
* 获取状态文件路径
|
|
9117
|
-
*/
|
|
9118
|
-
getStateFilePath(ticket) {
|
|
9119
|
-
return path21.join(this.repoRoot, ".handoff", `session-${ticket}.json`);
|
|
9120
|
-
}
|
|
9121
|
-
/**
|
|
9122
|
-
* 保存会话状态
|
|
9123
|
-
*/
|
|
9124
|
-
async saveSessionState(config) {
|
|
9125
|
-
const stateFile = this.getStateFilePath(config.ticket);
|
|
9126
|
-
const worktrees = /* @__PURE__ */ new Map();
|
|
9127
|
-
for (const wt of config.worktrees) {
|
|
9128
|
-
worktrees.set(wt.role, wt);
|
|
9129
|
-
}
|
|
9130
|
-
const sessionState = {
|
|
9131
|
-
ticket: config.ticket,
|
|
9132
|
-
currentPhase: config.phase,
|
|
9133
|
-
completedPhases: [],
|
|
9134
|
-
worktrees,
|
|
9135
|
-
results: /* @__PURE__ */ new Map(),
|
|
9136
|
-
gateStatuses: /* @__PURE__ */ new Map(),
|
|
9137
|
-
startedAt: config.createdAt,
|
|
9138
|
-
lastActivity: config.updatedAt
|
|
9139
|
-
};
|
|
9140
|
-
await fs6.ensureDir(path21.dirname(stateFile));
|
|
9141
|
-
await fs6.writeFile(stateFile, JSON.stringify(sessionState, null, 2));
|
|
9142
|
-
}
|
|
9143
|
-
};
|
|
9144
|
-
|
|
9145
|
-
// src/lib/handoff-manager.ts
|
|
9146
|
-
init_esm_shims();
|
|
9147
|
-
import fs7 from "fs-extra";
|
|
9148
|
-
import path22 from "path";
|
|
9149
|
-
init_logger();
|
|
9150
|
-
var HandoffManager = class {
|
|
9151
|
-
repoRoot;
|
|
9152
|
-
handoffDir;
|
|
9153
|
-
templateDir;
|
|
9154
|
-
constructor(repoRoot) {
|
|
9155
|
-
this.repoRoot = repoRoot || process.cwd();
|
|
9156
|
-
this.handoffDir = path22.join(this.repoRoot, DEFAULT_WORKTREE_CONFIG.handoffDir);
|
|
9157
|
-
this.templateDir = path22.join(this.repoRoot, DEFAULT_WORKTREE_CONFIG.templateDir);
|
|
9158
|
-
}
|
|
9159
|
-
/**
|
|
9160
|
-
* 初始化交接物目录
|
|
9161
|
-
*/
|
|
9162
|
-
async init(ticket, enabledRoles) {
|
|
9163
|
-
const handoffBase = path22.join(this.handoffDir, ticket);
|
|
9164
|
-
const roles = enabledRoles || ["planner", "architect", "backend", "frontend", "qa", "reviewer"];
|
|
9165
|
-
logger.info(`\u521D\u59CB\u5316\u4EA4\u63A5\u7269\u76EE\u5F55: ${handoffBase}`);
|
|
9166
|
-
for (const role of roles) {
|
|
9167
|
-
const roleDir = path22.join(handoffBase, role);
|
|
9168
|
-
await fs7.ensureDir(roleDir);
|
|
9169
|
-
}
|
|
9170
|
-
const bddDir = path22.join(handoffBase, "planner", "BDD_FEATURES");
|
|
9171
|
-
await fs7.ensureDir(bddDir);
|
|
9172
|
-
logger.success(`\u4EA4\u63A5\u7269\u76EE\u5F55\u5DF2\u521B\u5EFA: ${handoffBase}`);
|
|
9173
|
-
}
|
|
9174
|
-
/**
|
|
9175
|
-
* 生成交接物清单
|
|
9176
|
-
*/
|
|
9177
|
-
async generateManifest(ticket, phase) {
|
|
9178
|
-
const handoffDir = path22.join(this.handoffDir, ticket);
|
|
9179
|
-
const items = [];
|
|
9180
|
-
const phaseItems = this.getRequiredItemsByPhase(phase);
|
|
9181
|
-
for (const item of phaseItems) {
|
|
9182
|
-
const filePath = path22.join(handoffDir, item.role, item.file);
|
|
9183
|
-
items.push({
|
|
9184
|
-
id: `${ticket}-${item.role}-${item.type}`,
|
|
9185
|
-
role: item.role,
|
|
9186
|
-
file: item.file,
|
|
9187
|
-
type: item.type,
|
|
9188
|
-
description: item.description,
|
|
9189
|
-
status: fs7.pathExistsSync(filePath) ? "ready" : "pending",
|
|
9190
|
-
requiredBy: item.requiredBy
|
|
9191
|
-
});
|
|
9192
|
-
}
|
|
9193
|
-
const manifest = {
|
|
9194
|
-
ticket,
|
|
9195
|
-
phase,
|
|
9196
|
-
fromRole: "system",
|
|
9197
|
-
toRoles: items.map((i) => i.role),
|
|
9198
|
-
items,
|
|
9199
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9200
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9201
|
-
};
|
|
9202
|
-
await this.saveManifest(ticket, manifest);
|
|
9203
|
-
return manifest;
|
|
9204
|
-
}
|
|
9205
|
-
/**
|
|
9206
|
-
* 验证交接物完整性
|
|
9207
|
-
*/
|
|
9208
|
-
async verify(ticket) {
|
|
9209
|
-
const manifest = await this.loadManifest(ticket);
|
|
9210
|
-
if (!manifest) {
|
|
9211
|
-
return {
|
|
9212
|
-
complete: false,
|
|
9213
|
-
items: [],
|
|
9214
|
-
missing: ["Manifest not found"]
|
|
9215
|
-
};
|
|
9216
|
-
}
|
|
9217
|
-
const missing = [];
|
|
9218
|
-
const handoffDir = path22.join(this.handoffDir, ticket);
|
|
9219
|
-
for (const item of manifest.items) {
|
|
9220
|
-
const filePath = path22.join(handoffDir, item.role, item.file);
|
|
9221
|
-
if (!fs7.existsSync(filePath)) {
|
|
9222
|
-
missing.push(`${item.role}/${item.file}`);
|
|
9223
|
-
item.status = "pending";
|
|
9224
|
-
} else {
|
|
9225
|
-
item.status = "ready";
|
|
9226
|
-
}
|
|
9227
|
-
}
|
|
9228
|
-
await this.saveManifest(ticket, manifest);
|
|
9229
|
-
return {
|
|
9230
|
-
complete: missing.length === 0,
|
|
9231
|
-
items: manifest.items,
|
|
9232
|
-
missing
|
|
9233
|
-
};
|
|
9234
|
-
}
|
|
9235
|
-
/**
|
|
9236
|
-
* 生成模板文件
|
|
9237
|
-
*/
|
|
9238
|
-
async generateTemplate(ticket, role, templateType, data) {
|
|
9239
|
-
const templatePath = path22.join(this.templateDir, `${templateType}.md`);
|
|
9240
|
-
const outputPath = path22.join(this.handoffDir, ticket, role, `${templateType}.md`);
|
|
9241
|
-
if (!fs7.existsSync(templatePath)) {
|
|
9242
|
-
throw new Error(`\u6A21\u677F\u4E0D\u5B58\u5728: ${templatePath}`);
|
|
9243
|
-
}
|
|
9244
|
-
let content = await fs7.readFile(templatePath, "utf-8");
|
|
9245
|
-
for (const [key, value] of Object.entries(data)) {
|
|
9246
|
-
content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
9247
|
-
}
|
|
9248
|
-
await fs7.ensureDir(path22.dirname(outputPath));
|
|
9249
|
-
await fs7.writeFile(outputPath, content, "utf-8");
|
|
9250
|
-
logger.success(`\u751F\u6210\u6A21\u677F: ${outputPath}`);
|
|
9251
|
-
return outputPath;
|
|
9252
|
-
}
|
|
9253
|
-
/**
|
|
9254
|
-
* 生成 OpenAPI 模板
|
|
9255
|
-
*/
|
|
9256
|
-
async generateOpenAPITemplate(ticket, data) {
|
|
9257
|
-
const templatePath = path22.join(this.templateDir, "API_OPENAPI.yaml");
|
|
9258
|
-
const outputPath = path22.join(this.handoffDir, ticket, "backend", "API_OPENAPI.yaml");
|
|
9259
|
-
if (!fs7.existsSync(templatePath)) {
|
|
9260
|
-
throw new Error(`\u6A21\u677F\u4E0D\u5B58\u5728: ${templatePath}`);
|
|
9261
|
-
}
|
|
9262
|
-
let content = await fs7.readFile(templatePath, "utf-8");
|
|
9263
|
-
for (const [key, value] of Object.entries(data)) {
|
|
9264
|
-
content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
9265
|
-
}
|
|
9266
|
-
await fs7.ensureDir(path22.dirname(outputPath));
|
|
9267
|
-
await fs7.writeFile(outputPath, content, "utf-8");
|
|
9268
|
-
logger.success(`\u751F\u6210 OpenAPI: ${outputPath}`);
|
|
9269
|
-
return outputPath;
|
|
9270
|
-
}
|
|
9271
|
-
/**
|
|
9272
|
-
* 生成 BDD Feature 模板
|
|
9273
|
-
*/
|
|
9274
|
-
async generateBDDFeature(ticket, featureName, scenarios) {
|
|
9275
|
-
const outputPath = path22.join(
|
|
9276
|
-
this.handoffDir,
|
|
9277
|
-
ticket,
|
|
9278
|
-
"planner",
|
|
9279
|
-
"BDD_FEATURES",
|
|
9280
|
-
`${featureName}.feature`
|
|
9281
|
-
);
|
|
9282
|
-
const lines = [];
|
|
9283
|
-
lines.push(`# language: zh-CN`);
|
|
9284
|
-
lines.push(`@${ticket}`);
|
|
9285
|
-
lines.push(`\u529F\u80FD: ${featureName}`);
|
|
9286
|
-
lines.push("");
|
|
9287
|
-
for (const scenario of scenarios) {
|
|
9288
|
-
lines.push(` \u573A\u666F: ${scenario.title} [${scenario.priority}]`);
|
|
9289
|
-
for (const given of scenario.given) {
|
|
9290
|
-
lines.push(` \u5047\u5982 ${given}`);
|
|
9291
|
-
}
|
|
9292
|
-
for (const when of scenario.when) {
|
|
9293
|
-
lines.push(` \u5F53 ${when}`);
|
|
9294
|
-
}
|
|
9295
|
-
for (const then of scenario.then) {
|
|
9296
|
-
lines.push(` \u90A3\u4E48 ${then}`);
|
|
9297
|
-
}
|
|
9298
|
-
lines.push("");
|
|
9299
|
-
}
|
|
9300
|
-
await fs7.ensureDir(path22.dirname(outputPath));
|
|
9301
|
-
await fs7.writeFile(outputPath, lines.join("\n"), "utf-8");
|
|
9302
|
-
logger.success(`\u751F\u6210 BDD Feature: ${outputPath}`);
|
|
9303
|
-
return outputPath;
|
|
9304
|
-
}
|
|
9305
|
-
/**
|
|
9306
|
-
* 获取交接物清单
|
|
9307
|
-
*/
|
|
9308
|
-
async loadManifest(ticket) {
|
|
9309
|
-
const manifestPath = path22.join(this.handoffDir, ticket, "MANIFEST.json");
|
|
9310
|
-
if (!fs7.existsSync(manifestPath)) {
|
|
9311
|
-
return null;
|
|
9312
|
-
}
|
|
9313
|
-
const content = await fs7.readFile(manifestPath, "utf-8");
|
|
9314
|
-
return JSON.parse(content);
|
|
9315
|
-
}
|
|
9316
|
-
/**
|
|
9317
|
-
* 保存交接物清单
|
|
9318
|
-
*/
|
|
9319
|
-
async saveManifest(ticket, manifest) {
|
|
9320
|
-
const manifestPath = path22.join(this.handoffDir, ticket, "MANIFEST.json");
|
|
9321
|
-
await fs7.ensureDir(path22.dirname(manifestPath));
|
|
9322
|
-
await fs7.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
9323
|
-
}
|
|
9324
|
-
/**
|
|
9325
|
-
* 标记交接物已验证
|
|
9326
|
-
*/
|
|
9327
|
-
async markVerified(ticket, file, verifiedBy) {
|
|
9328
|
-
const manifest = await this.loadManifest(ticket);
|
|
9329
|
-
if (!manifest) {
|
|
9330
|
-
throw new Error(`Manifest not found: ${ticket}`);
|
|
9331
|
-
}
|
|
9332
|
-
for (const item of manifest.items) {
|
|
9333
|
-
if (item.file === file) {
|
|
9334
|
-
item.status = "verified";
|
|
9335
|
-
item.verifiedBy = verifiedBy;
|
|
9336
|
-
item.verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9337
|
-
break;
|
|
9338
|
-
}
|
|
9339
|
-
}
|
|
9340
|
-
await this.saveManifest(ticket, manifest);
|
|
9341
|
-
}
|
|
9342
|
-
/**
|
|
9343
|
-
* 获取指定角色的交接物
|
|
9344
|
-
*/
|
|
9345
|
-
async getRoleHandoffs(ticket, role) {
|
|
9346
|
-
const roleDir = path22.join(this.handoffDir, ticket, role);
|
|
9347
|
-
if (!fs7.existsSync(roleDir)) {
|
|
9348
|
-
return [];
|
|
9349
|
-
}
|
|
9350
|
-
const files = await fs7.readdir(roleDir);
|
|
9351
|
-
return files.filter((f) => f.endsWith(".md") || f.endsWith(".yaml") || f.endsWith(".json"));
|
|
9352
|
-
}
|
|
9353
|
-
/**
|
|
9354
|
-
* 获取交接物摘要
|
|
9355
|
-
*/
|
|
9356
|
-
async getSummary(ticket) {
|
|
9357
|
-
const manifest = await this.loadManifest(ticket);
|
|
9358
|
-
if (!manifest) {
|
|
9359
|
-
return {
|
|
9360
|
-
total: 0,
|
|
9361
|
-
ready: 0,
|
|
9362
|
-
pending: 0,
|
|
9363
|
-
verified: 0,
|
|
9364
|
-
byRole: {
|
|
9365
|
-
planner: 0,
|
|
9366
|
-
architect: 0,
|
|
9367
|
-
backend: 0,
|
|
9368
|
-
frontend: 0,
|
|
9369
|
-
qa: 0,
|
|
9370
|
-
reviewer: 0
|
|
9371
|
-
}
|
|
9372
|
-
};
|
|
9373
|
-
}
|
|
9374
|
-
const summary = {
|
|
9375
|
-
total: manifest.items.length,
|
|
9376
|
-
ready: 0,
|
|
9377
|
-
pending: 0,
|
|
9378
|
-
verified: 0,
|
|
9379
|
-
byRole: {
|
|
9380
|
-
planner: 0,
|
|
9381
|
-
architect: 0,
|
|
9382
|
-
backend: 0,
|
|
9383
|
-
frontend: 0,
|
|
9384
|
-
qa: 0,
|
|
9385
|
-
reviewer: 0
|
|
9386
|
-
}
|
|
9387
|
-
};
|
|
9388
|
-
for (const item of manifest.items) {
|
|
9389
|
-
if (item.status === "ready" || item.status === "verified") {
|
|
9390
|
-
summary.ready++;
|
|
9391
|
-
}
|
|
9392
|
-
if (item.status === "pending") {
|
|
9393
|
-
summary.pending++;
|
|
9394
|
-
}
|
|
9395
|
-
if (item.status === "verified") {
|
|
9396
|
-
summary.verified++;
|
|
9397
|
-
}
|
|
9398
|
-
summary.byRole[item.role]++;
|
|
9399
|
-
}
|
|
9400
|
-
return summary;
|
|
9401
|
-
}
|
|
9402
|
-
/**
|
|
9403
|
-
* 根据阶段获取所需的交接物列表
|
|
9404
|
-
*/
|
|
9405
|
-
getRequiredItemsByPhase(phase) {
|
|
9406
|
-
const items = [];
|
|
9407
|
-
if (phase === "planning" || phase === "all") {
|
|
9408
|
-
items.push(
|
|
9409
|
-
{ role: "planner", file: "SPEC.md", type: "spec", description: "\u529F\u80FD\u89C4\u683C\u6587\u6863", requiredBy: ["architect", "backend", "frontend"] },
|
|
9410
|
-
{ role: "planner", file: "TASKS.md", type: "tasks", description: "\u4EFB\u52A1\u62C6\u5206", requiredBy: ["architect", "backend", "frontend"] },
|
|
9411
|
-
{ role: "planner", file: "BDD_FEATURES/*.feature", type: "feature", description: "BDD \u573A\u666F", requiredBy: ["qa"] },
|
|
9412
|
-
{ role: "planner", file: "BDD_TRACEABILITY.md", type: "bdd", description: "BDD \u8FFD\u6EAF\u77E9\u9635", requiredBy: ["qa"] }
|
|
9413
|
-
);
|
|
9414
|
-
}
|
|
9415
|
-
if (phase === "architecture" || phase === "all") {
|
|
9416
|
-
items.push(
|
|
9417
|
-
{ role: "architect", file: "ADR.md", type: "adr", description: "\u67B6\u6784\u51B3\u7B56\u8BB0\u5F55", requiredBy: ["backend"] },
|
|
9418
|
-
{ role: "architect", file: "API_RULES.md", type: "api", description: "API \u8BBE\u8BA1\u89C4\u5219", requiredBy: ["backend", "frontend"] },
|
|
9419
|
-
{ role: "architect", file: "MODULE_OWNERSHIP.md", type: "other", description: "\u6A21\u5757\u5F52\u5C5E", requiredBy: ["backend", "frontend"] },
|
|
9420
|
-
{ role: "architect", file: "IMPACT.md", type: "other", description: "\u5F71\u54CD\u9762\u8BC4\u4F30", requiredBy: ["reviewer"] }
|
|
9421
|
-
);
|
|
9422
|
-
}
|
|
9423
|
-
if (phase === "openapi" || phase === "all") {
|
|
9424
|
-
items.push(
|
|
9425
|
-
{ role: "backend", file: "API_OPENAPI.yaml", type: "openapi", description: "OpenAPI \u89C4\u8303", requiredBy: ["frontend", "qa"] },
|
|
9426
|
-
{ role: "backend", file: "API_EXAMPLES.md", type: "api", description: "API \u793A\u4F8B", requiredBy: ["frontend"] },
|
|
9427
|
-
{ role: "backend", file: "MOCK_GUIDE.md", type: "other", description: "Mock \u4F7F\u7528\u6307\u5357", requiredBy: ["frontend"] }
|
|
9428
|
-
);
|
|
9429
|
-
}
|
|
9430
|
-
if (phase === "implementation" || phase === "all") {
|
|
9431
|
-
items.push(
|
|
9432
|
-
{ role: "backend", file: "CHANGELOG_BACKEND.md", type: "changelog", description: "\u540E\u7AEF\u53D8\u66F4\u65E5\u5FD7", requiredBy: ["reviewer"] },
|
|
9433
|
-
{ role: "backend", file: "TESTING_BACKEND.md", type: "testing", description: "\u540E\u7AEF\u6D4B\u8BD5\u62A5\u544A", requiredBy: ["qa"] },
|
|
9434
|
-
{ role: "frontend", file: "CHANGELOG_FRONTEND.md", type: "changelog", description: "\u524D\u7AEF\u53D8\u66F4\u65E5\u5FD7", requiredBy: ["reviewer"] },
|
|
9435
|
-
{ role: "frontend", file: "TESTING_FRONTEND.md", type: "testing", description: "\u524D\u7AEF\u6D4B\u8BD5\u62A5\u544A", requiredBy: ["qa"] }
|
|
9436
|
-
);
|
|
9437
|
-
}
|
|
9438
|
-
if (phase === "bdd" || phase === "all") {
|
|
9439
|
-
items.push(
|
|
9440
|
-
{ role: "qa", file: "BDD_RUN_GUIDE.md", type: "report", description: "BDD \u6267\u884C\u6307\u5357", requiredBy: [] },
|
|
9441
|
-
{ role: "qa", file: "BDD_REPORT.md", type: "report", description: "BDD \u9A8C\u6536\u62A5\u544A", requiredBy: ["reviewer"] },
|
|
9442
|
-
{ role: "qa", file: "REGRESSION.md", type: "report", description: "\u56DE\u5F52\u6E05\u5355", requiredBy: ["qa"] }
|
|
9443
|
-
);
|
|
9444
|
-
}
|
|
9445
|
-
if (phase === "integration" || phase === "all") {
|
|
9446
|
-
items.push(
|
|
9447
|
-
{ role: "reviewer", file: "REVIEW.md", type: "review", description: "\u5BA1\u67E5\u62A5\u544A", requiredBy: [] },
|
|
9448
|
-
{ role: "reviewer", file: "RELEASE.md", type: "release", description: "\u53D1\u5E03\u65B9\u6848", requiredBy: [] },
|
|
9449
|
-
{ role: "reviewer", file: "ROLLBACK.md", type: "rollback", description: "\u56DE\u6EDA\u65B9\u6848", requiredBy: [] }
|
|
9450
|
-
);
|
|
9451
|
-
}
|
|
9452
|
-
return items;
|
|
8031
|
+
var UNSTABLE_SUFFIXES = ["-alpha", "-beta", "-dev", "-rc", "-canary", "-next"];
|
|
8032
|
+
async function ensureGitRepo() {
|
|
8033
|
+
const isRepo = await GitUtils.isGitRepo();
|
|
8034
|
+
if (!isRepo) {
|
|
8035
|
+
logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A Git \u4ED3\u5E93");
|
|
8036
|
+
process.exit(1);
|
|
9453
8037
|
}
|
|
9454
|
-
};
|
|
9455
|
-
function createHandoffManager(repoRoot) {
|
|
9456
|
-
return new HandoffManager(repoRoot);
|
|
9457
|
-
}
|
|
9458
|
-
|
|
9459
|
-
// src/lib/concurrent-executor.ts
|
|
9460
|
-
init_esm_shims();
|
|
9461
|
-
import { spawn } from "child_process";
|
|
9462
|
-
import path23 from "path";
|
|
9463
|
-
import fs8 from "fs-extra";
|
|
9464
|
-
|
|
9465
|
-
// src/lib/role-prompts.ts
|
|
9466
|
-
init_esm_shims();
|
|
9467
|
-
var PLANNER_PROMPT = {
|
|
9468
|
-
role: "planner",
|
|
9469
|
-
displayName: "Planner",
|
|
9470
|
-
systemPrompt: `\u4F60\u662F Product Planner\uFF0C\u8D1F\u8D23\u9700\u6C42\u5206\u6790\u548C BDD \u573A\u666F\u8BBE\u8BA1\u3002
|
|
9471
|
-
|
|
9472
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9473
|
-
1. \u5206\u6790 PRD\uFF0C\u7F16\u5199 SPEC.md
|
|
9474
|
-
2. \u62C6\u89E3\u4EFB\u52A1\uFF0C\u7F16\u5199 TASKS.md
|
|
9475
|
-
3. \u7F16\u5199 BDD \u573A\u666F\uFF08Gherkin \u683C\u5F0F\uFF09
|
|
9476
|
-
4. \u521B\u5EFA BDD_TRACEABILITY.md
|
|
9477
|
-
|
|
9478
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9479
|
-
- \u53EA\u8F93\u51FA\u6587\u6863\uFF0C\u4E0D\u8981\u5199\u4E1A\u52A1\u4EE3\u7801
|
|
9480
|
-
- BDD \u573A\u666F\u5FC5\u987B Given/When/Then \u5B8C\u6574
|
|
9481
|
-
- \u573A\u666F\u5FC5\u987B\u53EF\u6D4B\u8BD5\u3001\u65E0\u6B67\u4E49
|
|
9482
|
-
|
|
9483
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9484
|
-
- TICKET: {{TICKET}}
|
|
9485
|
-
- \u76EE\u6807: {{GOAL}}
|
|
9486
|
-
- \u7EA6\u675F: {{CONSTRAINTS}}`,
|
|
9487
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u521B\u5EFA\u9700\u6C42\u6587\u6863\uFF1A
|
|
9488
|
-
|
|
9489
|
-
## TICKET
|
|
9490
|
-
{{TICKET}}
|
|
9491
|
-
|
|
9492
|
-
## \u76EE\u6807
|
|
9493
|
-
{{GOAL}}
|
|
9494
|
-
|
|
9495
|
-
## \u7EA6\u675F
|
|
9496
|
-
{{CONSTRAINTS}}
|
|
9497
|
-
|
|
9498
|
-
## \u5DF2\u6709\u4FE1\u606F
|
|
9499
|
-
{{CONTEXT}}
|
|
9500
|
-
|
|
9501
|
-
\u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/planner/ \u76EE\u5F55\uFF09\uFF1A
|
|
9502
|
-
|
|
9503
|
-
1. **SPEC.md** - \u529F\u80FD\u89C4\u683C\u6587\u6863
|
|
9504
|
-
- \u76EE\u6807\u4E0E\u975E\u76EE\u6807
|
|
9505
|
-
- \u8303\u56F4\u4E0E\u7EA6\u675F
|
|
9506
|
-
- \u9A8C\u6536\u6807\u51C6\uFF08\u81F3\u5C11 5 \u6761\uFF09
|
|
9507
|
-
- \u4F9D\u8D56\u5173\u7CFB
|
|
9508
|
-
|
|
9509
|
-
2. **TASKS.md** - \u4EFB\u52A1\u62C6\u5206
|
|
9510
|
-
- \u6309\u9636\u6BB5\u62C6\u5206\u4EFB\u52A1
|
|
9511
|
-
- \u6807\u6CE8\u6BCF\u4E2A\u4EFB\u52A1\u7684 Owner
|
|
9512
|
-
- \u5173\u8054 BDD \u573A\u666F
|
|
9513
|
-
|
|
9514
|
-
3. **BDD_FEATURES/*.feature** - BDD \u573A\u666F
|
|
9515
|
-
- \u4E3B\u8DEF\u5F84\u573A\u666F\uFF08Must\uFF09
|
|
9516
|
-
- \u5F02\u5E38\u8DEF\u5F84\u573A\u666F\uFF08Should\uFF09
|
|
9517
|
-
- \u8FB9\u754C\u573A\u666F\uFF08May\uFF09
|
|
9518
|
-
|
|
9519
|
-
4. **BDD_TRACEABILITY.md** - \u8FFD\u6EAF\u77E9\u9635
|
|
9520
|
-
- \u9A8C\u6536\u6807\u51C6 \u2194 BDD \u573A\u666F \u2194 \u4EFB\u52A1`,
|
|
9521
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u5185\u5BB9\uFF0CMarkdown \u683C\u5F0F\u3002`
|
|
9522
|
-
};
|
|
9523
|
-
var ARCHITECT_PROMPT = {
|
|
9524
|
-
role: "architect",
|
|
9525
|
-
displayName: "Architect",
|
|
9526
|
-
systemPrompt: `\u4F60\u662F System Architect\uFF0C\u8D1F\u8D23\u6280\u672F\u67B6\u6784\u8BBE\u8BA1\u3002
|
|
9527
|
-
|
|
9528
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9529
|
-
1. \u7F16\u5199 ADR\uFF08\u67B6\u6784\u51B3\u7B56\u8BB0\u5F55\uFF09
|
|
9530
|
-
2. \u5B9A\u4E49 API \u89C4\u5219\uFF08\u9519\u8BEF\u7801\u3001\u9274\u6743\u3001\u5206\u9875\u7B49\uFF09
|
|
9531
|
-
3. \u5212\u5206\u6A21\u5757\u8FB9\u754C
|
|
9532
|
-
4. \u8BC4\u4F30\u5F71\u54CD\u9762\u548C\u98CE\u9669
|
|
9533
|
-
|
|
9534
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9535
|
-
- \u53EA\u8F93\u51FA\u8BBE\u8BA1\u6587\u6863\uFF0C\u4E0D\u8981\u5199\u5B9E\u73B0\u4EE3\u7801
|
|
9536
|
-
- API \u89C4\u5219\u5FC5\u987B\u5B8C\u6574\u3001\u53EF\u6267\u884C
|
|
9537
|
-
- \u6A21\u5757\u8FB9\u754C\u5FC5\u987B\u6E05\u6670\uFF0C\u907F\u514D\u51B2\u7A81
|
|
9538
|
-
|
|
9539
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9540
|
-
- TICKET: {{TICKET}}
|
|
9541
|
-
- \u76EE\u6807: {{GOAL}}`,
|
|
9542
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u521B\u5EFA\u67B6\u6784\u6587\u6863\uFF1A
|
|
9543
|
-
|
|
9544
|
-
## TICKET
|
|
9545
|
-
{{TICKET}}
|
|
9546
|
-
|
|
9547
|
-
## \u76EE\u6807
|
|
9548
|
-
{{GOAL}}
|
|
9549
|
-
|
|
9550
|
-
## \u5DF2\u6709\u6587\u6863
|
|
9551
|
-
\u8BF7\u5148\u9605\u8BFB\uFF1A.handoff/{{TICKET}}/planner/SPEC.md
|
|
9552
|
-
|
|
9553
|
-
\u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/architect/ \u76EE\u5F55\uFF09\uFF1A
|
|
9554
|
-
|
|
9555
|
-
1. **ADR.md** - \u67B6\u6784\u51B3\u7B56\u8BB0\u5F55
|
|
9556
|
-
- \u5173\u952E\u6280\u672F\u51B3\u7B56
|
|
9557
|
-
- \u51B3\u7B56\u7406\u7531
|
|
9558
|
-
- \u9884\u671F\u540E\u679C
|
|
9559
|
-
|
|
9560
|
-
2. **API_RULES.md** - API \u8BBE\u8BA1\u89C4\u5219
|
|
9561
|
-
- \u9519\u8BEF\u7801\u89C4\u8303
|
|
9562
|
-
- \u9274\u6743\u7B56\u7565
|
|
9563
|
-
- \u5206\u9875\u89C4\u5219
|
|
9564
|
-
- \u5E42\u7B49\u6027
|
|
9565
|
-
- \u7248\u672C\u517C\u5BB9
|
|
9566
|
-
|
|
9567
|
-
3. **MODULE_OWNERSHIP.md** - \u6A21\u5757\u5F52\u5C5E
|
|
9568
|
-
- \u76EE\u5F55\u7ED3\u6784
|
|
9569
|
-
- \u53D8\u66F4\u8FB9\u754C
|
|
9570
|
-
- \u8C01\u80FD\u6539\u4EC0\u4E48
|
|
9571
|
-
|
|
9572
|
-
4. **IMPACT.md** - \u5F71\u54CD\u9762\u8BC4\u4F30
|
|
9573
|
-
- \u53D7\u5F71\u54CD\u6A21\u5757
|
|
9574
|
-
- \u6F5C\u5728\u98CE\u9669
|
|
9575
|
-
- \u56DE\u6EDA\u7B56\u7565`,
|
|
9576
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u5185\u5BB9\uFF0CMarkdown \u683C\u5F0F\u3002`
|
|
9577
|
-
};
|
|
9578
|
-
var BACKEND_PROMPT = {
|
|
9579
|
-
role: "backend",
|
|
9580
|
-
displayName: "Backend Developer",
|
|
9581
|
-
systemPrompt: `\u4F60\u662F Backend Developer\uFF0C\u8D1F\u8D23\u540E\u7AEF\u5B9E\u73B0\u3002
|
|
9582
|
-
|
|
9583
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9584
|
-
1. \u7F16\u5199 OpenAPI \u89C4\u8303
|
|
9585
|
-
2. \u5B9E\u73B0 API Mock \u670D\u52A1
|
|
9586
|
-
3. \u5B9E\u73B0\u771F\u5B9E\u540E\u7AEF\u4EE3\u7801
|
|
9587
|
-
4. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
|
|
9588
|
-
|
|
9589
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9590
|
-
- \u5FC5\u987B\u5148\u5B8C\u6210 OpenAPI + Mock \u624D\u80FD\u5BF9\u63A5\u524D\u7AEF
|
|
9591
|
-
- API \u5FC5\u987B\u7B26\u5408 API_RULES.md \u89C4\u8303
|
|
9592
|
-
- \u4EE3\u7801\u5FC5\u987B\u6709\u6D4B\u8BD5\u8986\u76D6
|
|
9593
|
-
|
|
9594
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9595
|
-
- TICKET: {{TICKET}}
|
|
9596
|
-
- \u9636\u6BB5: {{PHASE}}`,
|
|
9597
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u5B8C\u6210\u540E\u7AEF\u5DE5\u4F5C\uFF1A
|
|
9598
|
-
|
|
9599
|
-
## TICKET
|
|
9600
|
-
{{TICKET}}
|
|
9601
|
-
|
|
9602
|
-
## \u9636\u6BB5
|
|
9603
|
-
{{PHASE}} (openapi | implementation)
|
|
9604
|
-
|
|
9605
|
-
## \u5DF2\u6709\u6587\u6863
|
|
9606
|
-
\u8BF7\u5148\u9605\u8BFB\uFF1A
|
|
9607
|
-
- .handoff/{{TICKET}}/architect/API_RULES.md
|
|
9608
|
-
- .handoff/{{TICKET}}/architect/MODULE_OWNERSHIP.md
|
|
9609
|
-
|
|
9610
|
-
**\u5982\u679C\u662F OpenAPI \u9636\u6BB5\uFF1A**
|
|
9611
|
-
|
|
9612
|
-
\u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/backend/ \u76EE\u5F55\uFF09\uFF1A
|
|
9613
|
-
|
|
9614
|
-
1. **API_OPENAPI.yaml** - OpenAPI 3.0 \u89C4\u8303
|
|
9615
|
-
- \u5B8C\u6574\u7684 API \u5B9A\u4E49
|
|
9616
|
-
- \u8BF7\u6C42/\u54CD\u5E94 Schema
|
|
9617
|
-
- \u8BA4\u8BC1\u8BF4\u660E
|
|
9618
|
-
|
|
9619
|
-
2. **API_EXAMPLES.md** - API \u793A\u4F8B
|
|
9620
|
-
- \u8BF7\u6C42\u793A\u4F8B
|
|
9621
|
-
- \u54CD\u5E94\u793A\u4F8B
|
|
9622
|
-
- \u9519\u8BEF\u793A\u4F8B
|
|
9623
|
-
|
|
9624
|
-
3. **MOCK_GUIDE.md** - Mock \u4F7F\u7528\u6307\u5357
|
|
9625
|
-
- \u5982\u4F55\u542F\u52A8 Mock
|
|
9626
|
-
- \u8986\u76D6\u7684\u63A5\u53E3\u6E05\u5355
|
|
9627
|
-
- \u793A\u4F8B\u6570\u636E
|
|
9628
|
-
|
|
9629
|
-
**\u5982\u679C\u662F Implementation \u9636\u6BB5\uFF1A**
|
|
9630
|
-
|
|
9631
|
-
\u8BF7\u5B8C\u6210\u540E\u7AEF\u5B9E\u73B0\uFF1A
|
|
9632
|
-
|
|
9633
|
-
1. \u7F16\u5199 API \u5B9E\u73B0\u4EE3\u7801
|
|
9634
|
-
2. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
|
|
9635
|
-
3. \u66F4\u65B0 CHANGELOG_BACKEND.md
|
|
9636
|
-
4. \u66F4\u65B0 TESTING_BACKEND.md`,
|
|
9637
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u548C\u4EE3\u7801\u3002`
|
|
9638
|
-
};
|
|
9639
|
-
var FRONTEND_PROMPT = {
|
|
9640
|
-
role: "frontend",
|
|
9641
|
-
displayName: "Frontend Developer",
|
|
9642
|
-
systemPrompt: `\u4F60\u662F Frontend Developer\uFF0C\u8D1F\u8D23\u524D\u7AEF\u5B9E\u73B0\u3002
|
|
9643
|
-
|
|
9644
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9645
|
-
1. \u57FA\u4E8E OpenAPI \u5BF9\u63A5\u524D\u7AEF
|
|
9646
|
-
2. \u5B9E\u73B0 UI \u7EC4\u4EF6
|
|
9647
|
-
3. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
|
|
9648
|
-
|
|
9649
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9650
|
-
- \u5FC5\u987B\u57FA\u4E8E Mock \u670D\u52A1\u5148\u5B8C\u6210\u8054\u8C03
|
|
9651
|
-
- \u5FC5\u987B\u7B26\u5408\u8BBE\u8BA1\u89C4\u8303
|
|
9652
|
-
- \u4EE3\u7801\u5FC5\u987B\u6709\u6D4B\u8BD5\u8986\u76D6
|
|
9653
|
-
|
|
9654
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9655
|
-
- TICKET: {{TICKET}}
|
|
9656
|
-
- \u9636\u6BB5: {{PHASE}}`,
|
|
9657
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u5B8C\u6210\u524D\u7AEF\u5DE5\u4F5C\uFF1A
|
|
9658
|
-
|
|
9659
|
-
## TICKET
|
|
9660
|
-
{{TICKET}}
|
|
9661
|
-
|
|
9662
|
-
## \u9636\u6BB5
|
|
9663
|
-
{{PHASE}}
|
|
9664
|
-
|
|
9665
|
-
## \u5DF2\u6709\u6587\u6863/\u8D44\u6E90
|
|
9666
|
-
\u8BF7\u5148\u9605\u8BFB\uFF1A
|
|
9667
|
-
- .handoff/{{TICKET}}/backend/API_OPENAPI.yaml\uFF08\u5982\u679C\u5B58\u5728\uFF09
|
|
9668
|
-
- .handoff/{{TICKET}}/backend/MOCK_GUIDE.md\uFF08\u5982\u679C\u5B58\u5728\uFF09
|
|
9669
|
-
|
|
9670
|
-
\u8BF7\u5B8C\u6210\u540E\u7AEF\u5B9E\u73B0\uFF1A
|
|
9671
|
-
|
|
9672
|
-
1. \u57FA\u4E8E Mock \u670D\u52A1\u5BF9\u63A5 API
|
|
9673
|
-
2. \u5B9E\u73B0 UI \u7EC4\u4EF6
|
|
9674
|
-
3. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
|
|
9675
|
-
4. \u66F4\u65B0 CHANGELOG_FRONTEND.md
|
|
9676
|
-
5. \u66F4\u65B0 TESTING_FRONTEND.md
|
|
9677
|
-
|
|
9678
|
-
\u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/frontend/ \u76EE\u5F55`,
|
|
9679
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u548C\u4EE3\u7801\u3002`
|
|
9680
|
-
};
|
|
9681
|
-
var QA_PROMPT = {
|
|
9682
|
-
role: "qa",
|
|
9683
|
-
displayName: "QA Engineer",
|
|
9684
|
-
systemPrompt: `\u4F60\u662F QA Engineer\uFF0C\u8D1F\u8D23 BDD \u9A8C\u6536\u6D4B\u8BD5\u3002
|
|
9685
|
-
|
|
9686
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9687
|
-
1. \u5C06 Gherkin \u573A\u666F\u8F6C\u4E3A\u53EF\u6267\u884C\u6B65\u9AA4
|
|
9688
|
-
2. \u6267\u884C BDD \u6D4B\u8BD5
|
|
9689
|
-
3. \u751F\u6210\u9A8C\u6536\u62A5\u544A
|
|
9690
|
-
|
|
9691
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9692
|
-
- \u4E25\u683C\u6309 BDD \u573A\u666F\u6267\u884C
|
|
9693
|
-
- \u5931\u8D25\u573A\u666F\u5FC5\u987B\u6709\u590D\u73B0\u6B65\u9AA4
|
|
9694
|
-
- \u62A5\u544A\u5FC5\u987B\u5305\u542B\u622A\u56FE/\u65E5\u5FD7\u6307\u5F15
|
|
9695
|
-
|
|
9696
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9697
|
-
- TICKET: {{TICKET}}`,
|
|
9698
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u6267\u884C BDD \u9A8C\u6536\uFF1A
|
|
9699
|
-
|
|
9700
|
-
## TICKET
|
|
9701
|
-
{{TICKET}}
|
|
9702
|
-
|
|
9703
|
-
## \u5DF2\u6709\u6587\u6863
|
|
9704
|
-
\u8BF7\u5148\u9605\u8BFB\uFF1A
|
|
9705
|
-
- .handoff/{{TICKET}}/planner/BDD_FEATURES/*.feature
|
|
9706
|
-
- .handoff/{{TICKET}}/planner/BDD_TRACEABILITY.md
|
|
9707
|
-
|
|
9708
|
-
## \u4EFB\u52A1
|
|
9709
|
-
|
|
9710
|
-
1. **BDD_RUN_GUIDE.md** - \u6267\u884C\u6307\u5357
|
|
9711
|
-
- \u73AF\u5883\u51C6\u5907
|
|
9712
|
-
- \u6570\u636E\u51C6\u5907
|
|
9713
|
-
- \u6267\u884C\u547D\u4EE4
|
|
9714
|
-
|
|
9715
|
-
2. **\u6267\u884C BDD \u6D4B\u8BD5**
|
|
9716
|
-
- \u8BB0\u5F55\u6267\u884C\u7ED3\u679C
|
|
9717
|
-
- \u622A\u56FE/\u65E5\u5FD7
|
|
9718
|
-
|
|
9719
|
-
3. **BDD_REPORT.md** - \u9A8C\u6536\u62A5\u544A
|
|
9720
|
-
- \u6267\u884C\u6458\u8981
|
|
9721
|
-
- \u6210\u529F\u573A\u666F
|
|
9722
|
-
- \u5931\u8D25\u573A\u666F\uFF08\u542B\u590D\u73B0\u6B65\u9AA4\uFF09
|
|
9723
|
-
- \u5EF6\u671F/\u964D\u7EA7\u8BB0\u5F55
|
|
9724
|
-
|
|
9725
|
-
4. **REGRESSION.md** - \u56DE\u5F52\u6E05\u5355
|
|
9726
|
-
- \u9700\u8981\u56DE\u5F52\u7684\u6D4B\u8BD5\u9879
|
|
9727
|
-
- \u624B\u52A8\u6D4B\u8BD5\u6307\u5F15
|
|
9728
|
-
|
|
9729
|
-
\u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/qa/ \u76EE\u5F55`,
|
|
9730
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u9A8C\u6536\u62A5\u544A\u3002`
|
|
9731
|
-
};
|
|
9732
|
-
var REVIEWER_PROMPT = {
|
|
9733
|
-
role: "reviewer",
|
|
9734
|
-
displayName: "Reviewer",
|
|
9735
|
-
systemPrompt: `\u4F60\u662F Code Reviewer\uFF0C\u8D1F\u8D23\u4EE3\u7801\u5BA1\u67E5\u4E0E\u53D1\u5E03\u3002
|
|
9736
|
-
|
|
9737
|
-
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
9738
|
-
1. \u4EE3\u7801\u5BA1\u67E5
|
|
9739
|
-
2. \u95E8\u7981\u68C0\u67E5
|
|
9740
|
-
3. \u53D1\u5E03\u4E0E\u56DE\u6EDA\u65B9\u6848
|
|
9741
|
-
|
|
9742
|
-
\u91CD\u8981\u89C4\u5219\uFF1A
|
|
9743
|
-
- \u4E25\u683C\u68C0\u67E5\u8D28\u91CF\u95E8\u7981
|
|
9744
|
-
- \u53D1\u5E03\u65B9\u6848\u5FC5\u987B\u53EF\u6267\u884C
|
|
9745
|
-
- \u56DE\u6EDA\u65B9\u6848\u5FC5\u987B\u6709\u9A8C\u8BC1\u6B65\u9AA4
|
|
9746
|
-
|
|
9747
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
9748
|
-
- TICKET: {{TICKET}}`,
|
|
9749
|
-
taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u6267\u884C\u5BA1\u67E5\u4E0E\u53D1\u5E03\u51C6\u5907\uFF1A
|
|
9750
|
-
|
|
9751
|
-
## TICKET
|
|
9752
|
-
{{TICKET}}
|
|
9753
|
-
|
|
9754
|
-
## \u5DF2\u6709\u6587\u6863
|
|
9755
|
-
\u8BF7\u5148\u9605\u8BFB\u6240\u6709 .handoff/{{TICKET}}/*/ \u4E0B\u7684\u6587\u6863
|
|
9756
|
-
|
|
9757
|
-
## \u4EFB\u52A1
|
|
9758
|
-
|
|
9759
|
-
1. **REVIEW.md** - \u5BA1\u67E5\u62A5\u544A
|
|
9760
|
-
- \u4EE3\u7801\u5BA1\u67E5 checklist
|
|
9761
|
-
- \u53D1\u73B0\u7684\u95EE\u9898
|
|
9762
|
-
- \u5BA1\u67E5\u7ED3\u8BBA
|
|
9763
|
-
|
|
9764
|
-
2. **RELEASE.md** - \u53D1\u5E03\u65B9\u6848
|
|
9765
|
-
- \u53D1\u5E03\u6B65\u9AA4
|
|
9766
|
-
- \u76D1\u63A7\u9879
|
|
9767
|
-
- \u53D1\u5E03\u68C0\u67E5\u6E05\u5355
|
|
9768
|
-
|
|
9769
|
-
3. **ROLLBACK.md** - \u56DE\u6EDA\u65B9\u6848
|
|
9770
|
-
- \u56DE\u6EDA\u89E6\u53D1\u6761\u4EF6
|
|
9771
|
-
- \u56DE\u6EDA\u6B65\u9AA4
|
|
9772
|
-
- \u6570\u636E\u6062\u590D\uFF08\u5982\u9700\u8981\uFF09
|
|
9773
|
-
- \u9A8C\u8BC1\u6B65\u9AA4
|
|
9774
|
-
|
|
9775
|
-
\u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/reviewer/ \u76EE\u5F55`,
|
|
9776
|
-
outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u5BA1\u67E5\u548C\u53D1\u5E03\u6587\u6863\u3002`
|
|
9777
|
-
};
|
|
9778
|
-
var ROLE_PROMPTS = {
|
|
9779
|
-
planner: PLANNER_PROMPT,
|
|
9780
|
-
architect: ARCHITECT_PROMPT,
|
|
9781
|
-
backend: BACKEND_PROMPT,
|
|
9782
|
-
frontend: FRONTEND_PROMPT,
|
|
9783
|
-
qa: QA_PROMPT,
|
|
9784
|
-
reviewer: REVIEWER_PROMPT
|
|
9785
|
-
};
|
|
9786
|
-
function getRolePrompt(role) {
|
|
9787
|
-
return ROLE_PROMPTS[role];
|
|
9788
8038
|
}
|
|
9789
|
-
function
|
|
9790
|
-
const
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
taskPrompt = taskPrompt.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
8039
|
+
async function ensureCleanWorkingTree() {
|
|
8040
|
+
const { execa: execa5 } = await import("execa");
|
|
8041
|
+
const { stdout } = await execa5("git", ["status", "--porcelain"]);
|
|
8042
|
+
if (stdout.trim()) {
|
|
8043
|
+
logger.error("\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF1A");
|
|
8044
|
+
stdout.trim().split("\n").forEach((line) => logger.info(` ${line.trim()}`));
|
|
8045
|
+
process.exit(1);
|
|
9797
8046
|
}
|
|
9798
|
-
return taskPrompt;
|
|
9799
8047
|
}
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
description: "Planner \u521B\u5EFA SPEC\u3001TASKS\u3001BDD \u573A\u666F",
|
|
9808
|
-
gateNumber: 1,
|
|
9809
|
-
parallelRoles: [["planner"]],
|
|
9810
|
-
dependsOn: [],
|
|
9811
|
-
minDuration: 15,
|
|
9812
|
-
maxDuration: 60
|
|
9813
|
-
},
|
|
9814
|
-
architecture: {
|
|
9815
|
-
phase: "architecture",
|
|
9816
|
-
displayName: "\u67B6\u6784\u4E0E\u5951\u7EA6",
|
|
9817
|
-
description: "Architect \u521B\u5EFA ADR\u3001API \u89C4\u5219\u3001\u6A21\u5757\u5F52\u5C5E",
|
|
9818
|
-
gateNumber: 2,
|
|
9819
|
-
parallelRoles: [["architect"]],
|
|
9820
|
-
dependsOn: ["planning"],
|
|
9821
|
-
minDuration: 15,
|
|
9822
|
-
maxDuration: 45
|
|
9823
|
-
},
|
|
9824
|
-
openapi: {
|
|
9825
|
-
phase: "openapi",
|
|
9826
|
-
displayName: "OpenAPI + Mock",
|
|
9827
|
-
description: "Backend \u5148\u51FA OpenAPI + Mock\uFF0C\u524D\u7AEF\u624D\u80FD\u5BF9\u63A5",
|
|
9828
|
-
gateNumber: 3,
|
|
9829
|
-
parallelRoles: [["backend"]],
|
|
9830
|
-
dependsOn: ["architecture"],
|
|
9831
|
-
minDuration: 20,
|
|
9832
|
-
maxDuration: 90
|
|
9833
|
-
},
|
|
9834
|
-
implementation: {
|
|
9835
|
-
phase: "implementation",
|
|
9836
|
-
displayName: "\u5E76\u884C\u5B9E\u73B0",
|
|
9837
|
-
description: "Backend \u4E0E Frontend \u5E76\u884C\u5F00\u53D1",
|
|
9838
|
-
gateNumber: 4,
|
|
9839
|
-
parallelRoles: [["backend", "frontend"]],
|
|
9840
|
-
dependsOn: ["openapi"],
|
|
9841
|
-
minDuration: 60,
|
|
9842
|
-
maxDuration: 480
|
|
9843
|
-
},
|
|
9844
|
-
bdd: {
|
|
9845
|
-
phase: "bdd",
|
|
9846
|
-
displayName: "BDD \u9A8C\u6536",
|
|
9847
|
-
description: "QA \u6267\u884C BDD \u9A8C\u6536\u6D4B\u8BD5",
|
|
9848
|
-
gateNumber: 5,
|
|
9849
|
-
parallelRoles: [["qa"]],
|
|
9850
|
-
dependsOn: ["implementation"],
|
|
9851
|
-
minDuration: 30,
|
|
9852
|
-
maxDuration: 120
|
|
9853
|
-
},
|
|
9854
|
-
integration: {
|
|
9855
|
-
phase: "integration",
|
|
9856
|
-
displayName: "\u96C6\u6210\u4E0E\u53D1\u5E03",
|
|
9857
|
-
description: "\u6536\u655B\u5230 integration \u5206\u652F\uFF0C\u6267\u884C CI/CD",
|
|
9858
|
-
gateNumber: 6,
|
|
9859
|
-
parallelRoles: [["reviewer"]],
|
|
9860
|
-
dependsBy: ["bdd"],
|
|
9861
|
-
minDuration: 15,
|
|
9862
|
-
maxDuration: 60
|
|
9863
|
-
}
|
|
9864
|
-
};
|
|
9865
|
-
var PHASE_SEQUENCE = [
|
|
9866
|
-
"planning",
|
|
9867
|
-
"architecture",
|
|
9868
|
-
"openapi",
|
|
9869
|
-
"implementation",
|
|
9870
|
-
"bdd",
|
|
9871
|
-
"integration"
|
|
9872
|
-
];
|
|
9873
|
-
var GATE_CONDITIONS = {
|
|
9874
|
-
1: [
|
|
9875
|
-
{
|
|
9876
|
-
description: "SPEC.md \u5305\u542B\u76EE\u6807/\u975E\u76EE\u6807/\u8303\u56F4",
|
|
9877
|
-
status: "pending"
|
|
9878
|
-
},
|
|
9879
|
-
{
|
|
9880
|
-
description: "TASKS.md \u4EFB\u52A1\u62C6\u5206\u5230\u53EF\u72EC\u7ACB\u5408\u5E76",
|
|
9881
|
-
status: "pending"
|
|
9882
|
-
},
|
|
9883
|
-
{
|
|
9884
|
-
description: "BDD \u573A\u666F Given/When/Then \u5B8C\u6574",
|
|
9885
|
-
status: "pending"
|
|
9886
|
-
},
|
|
9887
|
-
{
|
|
9888
|
-
description: "\u9A8C\u6536\u6807\u51C6\u4E0E BDD \u573A\u666F\u4E00\u4E00\u5BF9\u5E94",
|
|
9889
|
-
status: "pending"
|
|
9890
|
-
}
|
|
9891
|
-
],
|
|
9892
|
-
2: [
|
|
9893
|
-
{
|
|
9894
|
-
description: "ADR \u51B3\u7B56\u8BB0\u5F55\u5B8C\u6574",
|
|
9895
|
-
status: "pending"
|
|
9896
|
-
},
|
|
9897
|
-
{
|
|
9898
|
-
description: "API_RULES \u5305\u542B\u9519\u8BEF\u7801\u89C4\u8303",
|
|
9899
|
-
status: "pending"
|
|
9900
|
-
},
|
|
9901
|
-
{
|
|
9902
|
-
description: "MODULE_OWNERSHIP \u76EE\u5F55\u8FB9\u754C\u660E\u786E",
|
|
9903
|
-
status: "pending"
|
|
9904
|
-
},
|
|
9905
|
-
{
|
|
9906
|
-
description: "IMPACT \u98CE\u9669\u8BC4\u4F30\u5B8C\u6210",
|
|
9907
|
-
status: "pending"
|
|
9908
|
-
}
|
|
9909
|
-
],
|
|
9910
|
-
3: [
|
|
9911
|
-
{
|
|
9912
|
-
description: "OpenAPI \u6587\u4EF6\u7B26\u5408\u89C4\u8303",
|
|
9913
|
-
status: "pending"
|
|
9914
|
-
},
|
|
9915
|
-
{
|
|
9916
|
-
description: "Mock \u670D\u52A1\u53EF\u542F\u52A8",
|
|
9917
|
-
status: "pending"
|
|
9918
|
-
},
|
|
9919
|
-
{
|
|
9920
|
-
description: "\u524D\u7AEF\u53EF\u7528 Mock \u5B8C\u6210\u4E3B\u8DEF\u5F84",
|
|
9921
|
-
status: "pending"
|
|
9922
|
-
},
|
|
9923
|
-
{
|
|
9924
|
-
description: "API_EXAMPLES \u5305\u542B\u5B8C\u6574\u793A\u4F8B",
|
|
9925
|
-
status: "pending"
|
|
9926
|
-
}
|
|
9927
|
-
],
|
|
9928
|
-
4: [
|
|
9929
|
-
{
|
|
9930
|
-
description: "\u540E\u7AEF\u5355\u5143\u6D4B\u8BD5\u8986\u76D6\u7387 > 70%",
|
|
9931
|
-
status: "pending"
|
|
9932
|
-
},
|
|
9933
|
-
{
|
|
9934
|
-
description: "\u524D\u7AEF\u5355\u5143\u6D4B\u8BD5\u8986\u76D6\u7387 > 70%",
|
|
9935
|
-
status: "pending"
|
|
9936
|
-
},
|
|
9937
|
-
{
|
|
9938
|
-
description: "\u9759\u6001\u68C0\u67E5 (Lint) \u901A\u8FC7",
|
|
9939
|
-
status: "pending"
|
|
9940
|
-
},
|
|
9941
|
-
{
|
|
9942
|
-
description: "\u63A5\u53E3\u5951\u7EA6\u4E0E OpenAPI \u4E00\u81F4",
|
|
9943
|
-
status: "pending"
|
|
9944
|
-
}
|
|
9945
|
-
],
|
|
9946
|
-
5: [
|
|
9947
|
-
{
|
|
9948
|
-
description: "Must \u7EA7\u522B\u573A\u666F 100% \u901A\u8FC7",
|
|
9949
|
-
status: "pending"
|
|
9950
|
-
},
|
|
9951
|
-
{
|
|
9952
|
-
description: "Should \u7EA7\u522B\u573A\u666F\u901A\u8FC7\u7387 > 90%",
|
|
9953
|
-
status: "pending"
|
|
9954
|
-
},
|
|
9955
|
-
{
|
|
9956
|
-
description: "BDD_REPORT \u5305\u542B\u5931\u8D25\u573A\u666F\u590D\u73B0",
|
|
9957
|
-
status: "pending"
|
|
9958
|
-
},
|
|
9959
|
-
{
|
|
9960
|
-
description: "\u56DE\u5F52\u6E05\u5355\u5DF2\u51C6\u5907",
|
|
9961
|
-
status: "pending"
|
|
9962
|
-
}
|
|
9963
|
-
],
|
|
9964
|
-
6: [
|
|
9965
|
-
{
|
|
9966
|
-
description: "integration \u5206\u652F CI \u901A\u8FC7",
|
|
9967
|
-
status: "pending"
|
|
9968
|
-
},
|
|
9969
|
-
{
|
|
9970
|
-
description: "\u5168\u91CF BDD \u901A\u8FC7",
|
|
9971
|
-
status: "pending"
|
|
9972
|
-
},
|
|
9973
|
-
{
|
|
9974
|
-
description: ".handoff \u6587\u4EF6\u9F50\u5168",
|
|
9975
|
-
status: "pending"
|
|
9976
|
-
},
|
|
9977
|
-
{
|
|
9978
|
-
description: "ROLLBACK.md \u53EF\u6267\u884C",
|
|
9979
|
-
status: "pending"
|
|
9980
|
-
}
|
|
9981
|
-
]
|
|
9982
|
-
};
|
|
9983
|
-
function canEnterPhase(phase, completedPhases) {
|
|
9984
|
-
const config = PHASE_CONFIGS[phase];
|
|
9985
|
-
if (!config) return false;
|
|
9986
|
-
return config.dependsOn.every((dep) => completedPhases.includes(dep));
|
|
9987
|
-
}
|
|
9988
|
-
function getGateNumber(phase) {
|
|
9989
|
-
return PHASE_CONFIGS[phase]?.gateNumber || 0;
|
|
9990
|
-
}
|
|
9991
|
-
function getParallelRoles(phase) {
|
|
9992
|
-
return PHASE_CONFIGS[phase]?.parallelRoles || [];
|
|
9993
|
-
}
|
|
9994
|
-
function getPhaseDisplayName(phase) {
|
|
9995
|
-
return PHASE_CONFIGS[phase]?.displayName || phase;
|
|
9996
|
-
}
|
|
9997
|
-
function formatPhaseProgress(completedPhases) {
|
|
9998
|
-
const total = PHASE_SEQUENCE.length;
|
|
9999
|
-
const current = completedPhases.length;
|
|
10000
|
-
const progressBar = "\u2588".repeat(current) + "\u2591".repeat(total - current);
|
|
10001
|
-
return `[${progressBar}] ${current}/${total} \u9636\u6BB5\u5B8C\u6210`;
|
|
10002
|
-
}
|
|
10003
|
-
|
|
10004
|
-
// src/lib/concurrent-executor.ts
|
|
10005
|
-
init_logger();
|
|
10006
|
-
var ConcurrentExecutor = class {
|
|
10007
|
-
repoRoot;
|
|
10008
|
-
constructor(repoRoot) {
|
|
10009
|
-
this.repoRoot = repoRoot || process.cwd();
|
|
10010
|
-
}
|
|
10011
|
-
/**
|
|
10012
|
-
* 并行执行多个角色任务
|
|
10013
|
-
*/
|
|
10014
|
-
async executeParallel(configs) {
|
|
10015
|
-
console.log(`
|
|
10016
|
-
\u{1F680} \u542F\u52A8 ${configs.length} \u4E2A\u5E76\u884C\u4EFB\u52A1...
|
|
10017
|
-
`);
|
|
10018
|
-
const promises = configs.map(
|
|
10019
|
-
(config) => this.executeInWorktree(config)
|
|
10020
|
-
);
|
|
10021
|
-
const results = await Promise.all(promises);
|
|
10022
|
-
this.printSummary(results);
|
|
10023
|
-
return results;
|
|
10024
|
-
}
|
|
10025
|
-
/**
|
|
10026
|
-
* 在指定角色目录执行 Claude Code
|
|
10027
|
-
*/
|
|
10028
|
-
async executeInWorktree(config) {
|
|
10029
|
-
const startTime = Date.now();
|
|
10030
|
-
const { role, prompt, systemPrompt, timeout = 30, cwd } = config;
|
|
10031
|
-
const rolePrompt = getRolePrompt(role);
|
|
10032
|
-
const finalSystemPrompt = systemPrompt || rolePrompt?.systemPrompt || "";
|
|
10033
|
-
const fullPrompt = `${finalSystemPrompt}
|
|
10034
|
-
|
|
10035
|
-
---
|
|
10036
|
-
|
|
10037
|
-
\u5F53\u524D\u4EFB\u52A1\uFF1A
|
|
10038
|
-
${prompt}
|
|
10039
|
-
|
|
10040
|
-
\u91CD\u8981\u63D0\u793A\uFF1A
|
|
10041
|
-
- \u53EA\u8F93\u51FA\u6587\u6863\u548C\u4EE3\u7801\uFF0C\u4E0D\u8981\u989D\u5916\u89E3\u91CA
|
|
10042
|
-
- \u5B8C\u6210\u540E\u7ACB\u5373\u9000\u51FA
|
|
10043
|
-
`;
|
|
10044
|
-
const workDir = cwd || this.repoRoot;
|
|
10045
|
-
logger.info(`[${role}] \u5F00\u59CB\u6267\u884C...`);
|
|
10046
|
-
return new Promise((resolve) => {
|
|
10047
|
-
const args = [
|
|
10048
|
-
"--print",
|
|
10049
|
-
// 非交互式
|
|
10050
|
-
"--output-format",
|
|
10051
|
-
"text",
|
|
10052
|
-
"--no-session-persistence",
|
|
10053
|
-
"--dangerously-skip-permissions"
|
|
10054
|
-
];
|
|
10055
|
-
if (finalSystemPrompt) {
|
|
10056
|
-
args.push("--system-prompt", finalSystemPrompt);
|
|
10057
|
-
}
|
|
10058
|
-
const proc = spawn("claude", args, {
|
|
10059
|
-
cwd: workDir,
|
|
10060
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
10061
|
-
env: {
|
|
10062
|
-
...process.env,
|
|
10063
|
-
FORCE_COLOR: "1"
|
|
10064
|
-
}
|
|
10065
|
-
});
|
|
10066
|
-
let stdout = "";
|
|
10067
|
-
let stderr = "";
|
|
10068
|
-
proc.stdout.on("data", (data) => {
|
|
10069
|
-
const text = data.toString();
|
|
10070
|
-
stdout += text;
|
|
10071
|
-
process.stdout.write(text);
|
|
10072
|
-
});
|
|
10073
|
-
proc.stderr.on("data", (data) => {
|
|
10074
|
-
stderr += data.toString();
|
|
10075
|
-
});
|
|
10076
|
-
proc.on("close", (code) => {
|
|
10077
|
-
const duration = Date.now() - startTime;
|
|
10078
|
-
const result = {
|
|
10079
|
-
role,
|
|
10080
|
-
worktreePath: workDir,
|
|
10081
|
-
success: code === 0,
|
|
10082
|
-
output: stdout || stderr,
|
|
10083
|
-
duration,
|
|
10084
|
-
exitCode: code || 0,
|
|
10085
|
-
artifacts: this.extractArtifacts(stdout)
|
|
10086
|
-
};
|
|
10087
|
-
const icon = result.success ? "\u2713" : "\u2717";
|
|
10088
|
-
const durationSec = (duration / 1e3).toFixed(1);
|
|
10089
|
-
console.log(` ${icon} [${role.padEnd(12)}] ${result.success ? "\u6210\u529F" : "\u5931\u8D25"} (${durationSec}s)`);
|
|
10090
|
-
resolve(result);
|
|
10091
|
-
});
|
|
10092
|
-
proc.on("error", (err) => {
|
|
10093
|
-
const duration = Date.now() - startTime;
|
|
10094
|
-
resolve({
|
|
10095
|
-
role,
|
|
10096
|
-
worktreePath: workDir,
|
|
10097
|
-
success: false,
|
|
10098
|
-
output: `Error: ${err.message}`,
|
|
10099
|
-
duration,
|
|
10100
|
-
exitCode: -1,
|
|
10101
|
-
error: err.message,
|
|
10102
|
-
artifacts: []
|
|
10103
|
-
});
|
|
10104
|
-
});
|
|
10105
|
-
proc.stdin.write(fullPrompt);
|
|
10106
|
-
proc.stdin.end();
|
|
10107
|
-
const timeoutMs = timeout * 60 * 1e3;
|
|
10108
|
-
setTimeout(() => {
|
|
10109
|
-
if (!proc.killed) {
|
|
10110
|
-
proc.kill("SIGTERM");
|
|
10111
|
-
resolve({
|
|
10112
|
-
role,
|
|
10113
|
-
worktreePath: workDir,
|
|
10114
|
-
success: false,
|
|
10115
|
-
output: "Timeout: Task exceeded maximum duration",
|
|
10116
|
-
duration: timeoutMs,
|
|
10117
|
-
exitCode: -1,
|
|
10118
|
-
error: "Timeout",
|
|
10119
|
-
artifacts: []
|
|
10120
|
-
});
|
|
10121
|
-
}
|
|
10122
|
-
}, timeoutMs);
|
|
10123
|
-
});
|
|
10124
|
-
}
|
|
10125
|
-
/**
|
|
10126
|
-
* 执行阶段任务
|
|
10127
|
-
*/
|
|
10128
|
-
async executePhase(ticket, phase, taskData) {
|
|
10129
|
-
const phaseConfig = PHASE_CONFIGS[phase];
|
|
10130
|
-
if (!phaseConfig) {
|
|
10131
|
-
throw new Error(`Unknown phase: ${phase}`);
|
|
10132
|
-
}
|
|
10133
|
-
console.log(`
|
|
10134
|
-
\u{1F4CB} Phase ${phaseConfig.gateNumber}: ${phaseConfig.displayName}`);
|
|
10135
|
-
console.log(`${phaseConfig.description}
|
|
10136
|
-
`);
|
|
10137
|
-
const parallelRoleGroups = getParallelRoles(phase);
|
|
10138
|
-
const results = [];
|
|
10139
|
-
for (const roleGroup of parallelRoleGroups) {
|
|
10140
|
-
if (roleGroup.length === 1) {
|
|
10141
|
-
const role = roleGroup[0];
|
|
10142
|
-
const rolePrompt = getRolePrompt(role);
|
|
10143
|
-
if (!rolePrompt) {
|
|
10144
|
-
logger.warn(`Unknown role: ${role}`);
|
|
10145
|
-
continue;
|
|
10146
|
-
}
|
|
10147
|
-
const prompt = buildRolePrompt(role, {
|
|
10148
|
-
TICKET: ticket,
|
|
10149
|
-
...taskData
|
|
10150
|
-
});
|
|
10151
|
-
const result = await this.executeInWorktree({
|
|
10152
|
-
role,
|
|
10153
|
-
prompt,
|
|
10154
|
-
systemPrompt: rolePrompt.systemPrompt
|
|
10155
|
-
});
|
|
10156
|
-
results.push(result);
|
|
10157
|
-
} else {
|
|
10158
|
-
const configs = roleGroup.map((role) => {
|
|
10159
|
-
const rolePrompt = getRolePrompt(role);
|
|
10160
|
-
if (!rolePrompt) {
|
|
10161
|
-
return null;
|
|
10162
|
-
}
|
|
10163
|
-
return {
|
|
10164
|
-
role,
|
|
10165
|
-
prompt: buildRolePrompt(role, {
|
|
10166
|
-
TICKET: ticket,
|
|
10167
|
-
...taskData
|
|
10168
|
-
}),
|
|
10169
|
-
systemPrompt: rolePrompt.systemPrompt
|
|
10170
|
-
};
|
|
10171
|
-
}).filter(Boolean);
|
|
10172
|
-
const groupResults = await this.executeParallel(configs);
|
|
10173
|
-
results.push(...groupResults);
|
|
10174
|
-
}
|
|
10175
|
-
}
|
|
10176
|
-
return results;
|
|
10177
|
-
}
|
|
10178
|
-
/**
|
|
10179
|
-
* 执行完整流程
|
|
10180
|
-
*/
|
|
10181
|
-
async executeFullFlow(ticket, goal, phases, options) {
|
|
10182
|
-
const allResults = /* @__PURE__ */ new Map();
|
|
10183
|
-
for (const phase of phases) {
|
|
10184
|
-
try {
|
|
10185
|
-
options?.onPhaseStart?.(phase);
|
|
10186
|
-
const results = await this.executePhase(ticket, phase, {
|
|
10187
|
-
TICKET: ticket,
|
|
10188
|
-
GOAL: goal,
|
|
10189
|
-
PHASE: phase
|
|
10190
|
-
});
|
|
10191
|
-
allResults.set(phase, results);
|
|
10192
|
-
options?.onPhaseComplete?.(phase, results);
|
|
10193
|
-
const allSuccess = results.every((r) => r.success);
|
|
10194
|
-
if (!allSuccess) {
|
|
10195
|
-
console.log(`
|
|
10196
|
-
\u26A0\uFE0F Phase ${phase} \u90E8\u5206\u4EFB\u52A1\u5931\u8D25`);
|
|
10197
|
-
console.log("\u662F\u5426\u7EE7\u7EED\u4E0B\u4E00\u4E2A\u9636\u6BB5\uFF1F(\u5EFA\u8BAE\u5148\u4FEE\u590D\u5931\u8D25\u7684\u4EFB\u52A1)");
|
|
10198
|
-
}
|
|
10199
|
-
} catch (error) {
|
|
10200
|
-
logger.error(`Phase ${phase} \u6267\u884C\u5931\u8D25: ${error}`);
|
|
10201
|
-
options?.onError?.(error);
|
|
10202
|
-
}
|
|
10203
|
-
}
|
|
10204
|
-
return allResults;
|
|
10205
|
-
}
|
|
10206
|
-
/**
|
|
10207
|
-
* 打印执行汇总
|
|
10208
|
-
*/
|
|
10209
|
-
printSummary(results) {
|
|
10210
|
-
console.log("\n" + "\u2550".repeat(60));
|
|
10211
|
-
console.log("\u{1F4CA} \u5E76\u884C\u4EFB\u52A1\u6267\u884C\u7ED3\u679C");
|
|
10212
|
-
console.log("\u2550".repeat(60));
|
|
10213
|
-
let successCount = 0;
|
|
10214
|
-
let failCount = 0;
|
|
10215
|
-
for (const result of results) {
|
|
10216
|
-
const icon = result.success ? "\u2713" : "\u2717";
|
|
10217
|
-
const duration = (result.duration / 1e3).toFixed(1);
|
|
10218
|
-
console.log(` ${icon} [${result.role.padEnd(12)}] ${result.exitCode} (${duration}s)`);
|
|
10219
|
-
if (result.success) {
|
|
10220
|
-
successCount++;
|
|
10221
|
-
} else {
|
|
10222
|
-
failCount++;
|
|
10223
|
-
}
|
|
10224
|
-
}
|
|
10225
|
-
console.log("\u2500".repeat(60));
|
|
10226
|
-
console.log(` \u603B\u8BA1: ${successCount} \u6210\u529F, ${failCount} \u5931\u8D25`);
|
|
10227
|
-
console.log("\u2550".repeat(60) + "\n");
|
|
8048
|
+
async function ensureOnBranch(prefixes = [...VALID_BRANCH_PREFIXES]) {
|
|
8049
|
+
const branch = await getCurrentBranch();
|
|
8050
|
+
const prefix = getBranchPrefix(branch);
|
|
8051
|
+
if (!prefix || !prefixes.includes(prefix)) {
|
|
8052
|
+
logger.error(`\u5F53\u524D\u5206\u652F "${branch}" \u4E0D\u662F\u5408\u6CD5\u7684\u5DE5\u4F5C\u5206\u652F`);
|
|
8053
|
+
logger.info(`\u5408\u6CD5\u524D\u7F00: ${prefixes.join(", ")}`);
|
|
8054
|
+
process.exit(1);
|
|
10228
8055
|
}
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
const
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
artifacts.push(file);
|
|
10244
|
-
}
|
|
8056
|
+
return branch;
|
|
8057
|
+
}
|
|
8058
|
+
async function ensureBranchExists(branch) {
|
|
8059
|
+
if (await branchExistsLocal(branch)) {
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
const { execa: execa5 } = await import("execa");
|
|
8063
|
+
if (await branchExistsRemote(branch)) {
|
|
8064
|
+
try {
|
|
8065
|
+
await execa5("git", ["checkout", "-b", branch, `origin/${branch}`]);
|
|
8066
|
+
const current = await getCurrentBranch();
|
|
8067
|
+
if (current === branch) {
|
|
8068
|
+
await execa5("git", ["checkout", "-"]).catch(() => {
|
|
8069
|
+
});
|
|
10245
8070
|
}
|
|
8071
|
+
return;
|
|
8072
|
+
} catch {
|
|
10246
8073
|
}
|
|
10247
|
-
return artifacts;
|
|
10248
8074
|
}
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
lines.push("## \u8BE6\u7EC6\u7ED3\u679C");
|
|
10261
|
-
lines.push("");
|
|
10262
|
-
for (const result of results) {
|
|
10263
|
-
lines.push(`### ${result.role}`);
|
|
10264
|
-
lines.push("");
|
|
10265
|
-
lines.push(`- **\u72B6\u6001**: ${result.success ? "\u2713 \u6210\u529F" : "\u2717 \u5931\u8D25"}`);
|
|
10266
|
-
lines.push(`- **\u8017\u65F6**: ${(result.duration / 1e3).toFixed(1)}s`);
|
|
10267
|
-
lines.push(`- **\u9000\u51FA\u7801**: ${result.exitCode}`);
|
|
10268
|
-
lines.push(`- **\u5DE5\u4F5C\u76EE\u5F55**: ${result.worktreePath}`);
|
|
10269
|
-
lines.push("");
|
|
10270
|
-
if (result.artifacts.length > 0) {
|
|
10271
|
-
lines.push("**\u751F\u6210\u7684\u4EA4\u63A5\u7269**:");
|
|
10272
|
-
for (const artifact of result.artifacts) {
|
|
10273
|
-
lines.push(`- ${artifact}`);
|
|
10274
|
-
}
|
|
10275
|
-
lines.push("");
|
|
10276
|
-
}
|
|
10277
|
-
if (result.error) {
|
|
10278
|
-
lines.push(`**\u9519\u8BEF**: ${result.error}`);
|
|
10279
|
-
lines.push("");
|
|
10280
|
-
}
|
|
8075
|
+
if (branch === "develop") {
|
|
8076
|
+
logger.warn("develop \u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u5C06\u4ECE master \u81EA\u52A8\u521B\u5EFA");
|
|
8077
|
+
try {
|
|
8078
|
+
await execa5("git", ["checkout", "-b", "develop", "master"]);
|
|
8079
|
+
await execa5("git", ["push", "-u", "origin", "develop"]);
|
|
8080
|
+
await execa5("git", ["checkout", "-"]).catch(() => {
|
|
8081
|
+
});
|
|
8082
|
+
return;
|
|
8083
|
+
} catch (err) {
|
|
8084
|
+
logger.error(`\u521B\u5EFA develop \u5206\u652F\u5931\u8D25: ${err.message}`);
|
|
8085
|
+
process.exit(1);
|
|
10281
8086
|
}
|
|
10282
|
-
return lines.join("\n");
|
|
10283
8087
|
}
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
8088
|
+
logger.error(`\u5206\u652F "${branch}" \u4E0D\u5B58\u5728\uFF08\u672C\u5730\u548C\u8FDC\u7AEF\u5747\u672A\u627E\u5230\uFF09`);
|
|
8089
|
+
process.exit(1);
|
|
8090
|
+
}
|
|
8091
|
+
async function getCurrentBranch() {
|
|
8092
|
+
return GitUtils.getCurrentBranch();
|
|
8093
|
+
}
|
|
8094
|
+
function getBranchPrefix(branch) {
|
|
8095
|
+
const match = branch.match(/^(feat|fix|hotfix|refactor)\//);
|
|
8096
|
+
return match ? match[1] : null;
|
|
8097
|
+
}
|
|
8098
|
+
async function branchExistsLocal(branch) {
|
|
8099
|
+
const { execa: execa5 } = await import("execa");
|
|
8100
|
+
try {
|
|
8101
|
+
const { stdout } = await execa5("git", ["branch", "--list", branch]);
|
|
8102
|
+
return stdout.trim().length > 0;
|
|
8103
|
+
} catch {
|
|
8104
|
+
return false;
|
|
10296
8105
|
}
|
|
10297
|
-
};
|
|
10298
|
-
function createExecutor(repoRoot) {
|
|
10299
|
-
return new ConcurrentExecutor(repoRoot);
|
|
10300
8106
|
}
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
var devWorktreeCommand = new Command18("dev:wt").alias("dev-worktree").description("Worktree Orchestrator - \u591A\u89D2\u8272\u5E76\u884C\u5F00\u53D1\u7F16\u6392\u5668").option("--new", "\u521B\u5EFA\u65B0\u7684\u5E76\u884C\u5F00\u53D1\u4F1A\u8BDD").option("--ticket <id>", "TICKET ID").option("--goal <text>", "\u4EFB\u52A1\u76EE\u6807").option("--list", "\u5217\u51FA\u6240\u6709 worktree").option("--status", "\u67E5\u770B\u4F1A\u8BDD\u72B6\u6001").option("--run-phase <n>", "\u8FD0\u884C\u6307\u5B9A\u9636\u6BB5").option("--verify", "\u9A8C\u8BC1\u4EA4\u63A5\u7269").action(async (options) => {
|
|
8107
|
+
async function branchExistsRemote(branch) {
|
|
8108
|
+
const { execa: execa5 } = await import("execa");
|
|
10304
8109
|
try {
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
const
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
8110
|
+
const { stdout } = await execa5("git", ["ls-remote", "--heads", "origin", branch]);
|
|
8111
|
+
return stdout.trim().length > 0;
|
|
8112
|
+
} catch {
|
|
8113
|
+
return false;
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
async function scanBranchDiffForDangerousFiles(baseBranch, targetBranch) {
|
|
8117
|
+
const { execa: execa5 } = await import("execa");
|
|
8118
|
+
let diffFiles;
|
|
8119
|
+
try {
|
|
8120
|
+
const { stdout } = await execa5("git", [
|
|
8121
|
+
"diff",
|
|
8122
|
+
"--name-only",
|
|
8123
|
+
`${baseBranch}...${targetBranch}`
|
|
8124
|
+
]);
|
|
8125
|
+
diffFiles = stdout.trim().split("\n").filter(Boolean);
|
|
8126
|
+
} catch {
|
|
8127
|
+
return [];
|
|
8128
|
+
}
|
|
8129
|
+
const result = [];
|
|
8130
|
+
for (const rule of DANGEROUS_FILE_RULES) {
|
|
8131
|
+
const matched = diffFiles.filter(
|
|
8132
|
+
(file) => rule.patterns.some((pattern) => pattern.test(file))
|
|
8133
|
+
);
|
|
8134
|
+
if (matched.length > 0) {
|
|
8135
|
+
result.push({ category: rule.category, files: matched });
|
|
10330
8136
|
}
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
8137
|
+
}
|
|
8138
|
+
return result;
|
|
8139
|
+
}
|
|
8140
|
+
function reportDangerousFilesAndExit(dangerous) {
|
|
8141
|
+
logger.error("\u68C0\u6D4B\u5230\u5371\u9669\u6587\u4EF6\uFF0C\u5DF2\u62E6\u622A\u64CD\u4F5C\uFF1A");
|
|
8142
|
+
for (const group of dangerous) {
|
|
8143
|
+
logger.warn(` [${group.category}]`);
|
|
8144
|
+
group.files.forEach((f) => logger.info(` ${f}`));
|
|
8145
|
+
}
|
|
8146
|
+
logger.info("\u8BF7\u4ECE\u5206\u652F\u4E2D\u79FB\u9664\u4EE5\u4E0A\u6587\u4EF6\u540E\u91CD\u8BD5");
|
|
8147
|
+
process.exit(1);
|
|
8148
|
+
}
|
|
8149
|
+
async function checkUnstableDependencies() {
|
|
8150
|
+
const pkgPath = "package.json";
|
|
8151
|
+
if (!await FileUtils.exists(pkgPath)) {
|
|
8152
|
+
return [];
|
|
8153
|
+
}
|
|
8154
|
+
const pkg2 = JSON.parse(await FileUtils.read(pkgPath));
|
|
8155
|
+
const allDeps = {
|
|
8156
|
+
...pkg2.dependencies || {},
|
|
8157
|
+
...pkg2.devDependencies || {}
|
|
8158
|
+
};
|
|
8159
|
+
const unstable = [];
|
|
8160
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
8161
|
+
if (UNSTABLE_SUFFIXES.some((suffix) => version.includes(suffix))) {
|
|
8162
|
+
unstable.push({ name, version });
|
|
10337
8163
|
}
|
|
8164
|
+
}
|
|
8165
|
+
return unstable;
|
|
8166
|
+
}
|
|
8167
|
+
async function readVersion(pkgPath = "package.json") {
|
|
8168
|
+
if (!await FileUtils.exists(pkgPath)) {
|
|
8169
|
+
logger.error("package.json \u4E0D\u5B58\u5728");
|
|
10338
8170
|
process.exit(1);
|
|
10339
8171
|
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
}
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
name: "\u25B6\uFE0F \u8FD0\u884C\u9636\u6BB5",
|
|
10357
|
-
value: "run"
|
|
10358
|
-
},
|
|
10359
|
-
{
|
|
10360
|
-
name: "\u2705 \u9A8C\u8BC1\u4EA4\u63A5\u7269",
|
|
10361
|
-
value: "verify"
|
|
10362
|
-
},
|
|
10363
|
-
{
|
|
10364
|
-
name: "\u{1F6AA} \u9000\u51FA",
|
|
10365
|
-
value: "exit"
|
|
10366
|
-
}
|
|
10367
|
-
];
|
|
10368
|
-
const { action } = await inquirer13.prompt([
|
|
10369
|
-
{
|
|
10370
|
-
type: "list",
|
|
10371
|
-
name: "action",
|
|
10372
|
-
message: "\u9009\u62E9\u64CD\u4F5C:",
|
|
10373
|
-
choices
|
|
10374
|
-
}
|
|
10375
|
-
]);
|
|
10376
|
-
switch (action) {
|
|
10377
|
-
case "new":
|
|
10378
|
-
await handleNewSession(worktreeManager, handoffManager, options, process.cwd());
|
|
10379
|
-
break;
|
|
10380
|
-
case "list":
|
|
10381
|
-
await handleList(worktreeManager);
|
|
10382
|
-
break;
|
|
10383
|
-
case "status":
|
|
10384
|
-
await handleStatus(worktreeManager, handoffManager, options);
|
|
10385
|
-
break;
|
|
10386
|
-
case "run":
|
|
10387
|
-
await handleRunPhaseSelect(worktreeManager, handoffManager, executor2, options);
|
|
10388
|
-
break;
|
|
10389
|
-
case "verify":
|
|
10390
|
-
await handleVerifySelect(worktreeManager, handoffManager, options);
|
|
10391
|
-
break;
|
|
10392
|
-
case "exit":
|
|
10393
|
-
process.exit(0);
|
|
8172
|
+
const pkg2 = JSON.parse(await FileUtils.read(pkgPath));
|
|
8173
|
+
return pkg2.version || "0.0.0";
|
|
8174
|
+
}
|
|
8175
|
+
function bumpVersion(currentVersion, type) {
|
|
8176
|
+
const parts = currentVersion.split(".").map(Number);
|
|
8177
|
+
if (parts.length !== 3 || parts.some(isNaN)) {
|
|
8178
|
+
logger.error(`\u65E0\u6548\u7684\u7248\u672C\u53F7: ${currentVersion}`);
|
|
8179
|
+
process.exit(1);
|
|
8180
|
+
}
|
|
8181
|
+
switch (type) {
|
|
8182
|
+
case "major":
|
|
8183
|
+
return `${parts[0] + 1}.0.0`;
|
|
8184
|
+
case "minor":
|
|
8185
|
+
return `${parts[0]}.${parts[1] + 1}.0`;
|
|
8186
|
+
case "patch":
|
|
8187
|
+
return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
|
|
10394
8188
|
}
|
|
10395
8189
|
}
|
|
10396
|
-
async function
|
|
10397
|
-
|
|
10398
|
-
const
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
8190
|
+
async function setVersion(version, pkgPath = "package.json") {
|
|
8191
|
+
const content = await FileUtils.read(pkgPath);
|
|
8192
|
+
const pkg2 = JSON.parse(content);
|
|
8193
|
+
pkg2.version = version;
|
|
8194
|
+
await FileUtils.write(pkgPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
8195
|
+
}
|
|
8196
|
+
|
|
8197
|
+
// src/commands/fe-start.ts
|
|
8198
|
+
var feStartCommand = new Command18("fe-start").description("\u521B\u5EFA\u524D\u7AEF\u5DE5\u4F5C\u5206\u652F (\u4ECE master \u5207\u51FA)").argument("<name>", "\u5206\u652F\u540D\u79F0").option("--type <type>", "\u5206\u652F\u7C7B\u578B (feat|fix|hotfix|refactor)").action(async (name, opts) => {
|
|
8199
|
+
try {
|
|
8200
|
+
const { execa: execa5 } = await import("execa");
|
|
8201
|
+
await ensureGitRepo();
|
|
8202
|
+
let branchType = opts.type;
|
|
8203
|
+
if (!branchType) {
|
|
8204
|
+
const answer = await inquirer13.prompt([
|
|
8205
|
+
{
|
|
8206
|
+
type: "list",
|
|
8207
|
+
name: "type",
|
|
8208
|
+
message: "\u8BF7\u9009\u62E9\u5206\u652F\u7C7B\u578B:",
|
|
8209
|
+
choices: [...VALID_BRANCH_PREFIXES]
|
|
8210
|
+
}
|
|
8211
|
+
]);
|
|
8212
|
+
branchType = answer.type;
|
|
8213
|
+
}
|
|
8214
|
+
const branchFullName = `${branchType}/${name}`;
|
|
8215
|
+
let didStash = false;
|
|
8216
|
+
const { stdout: statusOutput } = await execa5("git", ["status", "--porcelain"]);
|
|
8217
|
+
if (statusOutput.trim()) {
|
|
8218
|
+
logger.warn("\u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u6682\u5B58\u5230 stash...");
|
|
8219
|
+
await execa5("git", ["stash"]);
|
|
8220
|
+
didStash = true;
|
|
8221
|
+
}
|
|
8222
|
+
await ensureBranchExists("master");
|
|
8223
|
+
await execa5("git", ["checkout", "master"]);
|
|
8224
|
+
logger.step("\u62C9\u53D6 master \u6700\u65B0\u4EE3\u7801...");
|
|
8225
|
+
await execa5("git", ["pull", "origin", "master"]);
|
|
8226
|
+
const localExists = await branchExistsLocal(branchFullName);
|
|
8227
|
+
const remoteExists = await branchExistsRemote(branchFullName);
|
|
8228
|
+
if (localExists || remoteExists) {
|
|
8229
|
+
const { overwrite } = await inquirer13.prompt([
|
|
8230
|
+
{
|
|
8231
|
+
type: "confirm",
|
|
8232
|
+
name: "overwrite",
|
|
8233
|
+
message: `\u5206\u652F "${branchFullName}" \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6?`,
|
|
8234
|
+
default: false
|
|
8235
|
+
}
|
|
8236
|
+
]);
|
|
8237
|
+
if (!overwrite) {
|
|
8238
|
+
logger.warn("\u5DF2\u53D6\u6D88\u64CD\u4F5C");
|
|
8239
|
+
if (didStash) {
|
|
8240
|
+
await execa5("git", ["stash", "pop"]);
|
|
8241
|
+
}
|
|
8242
|
+
return;
|
|
10406
8243
|
}
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
type: "input",
|
|
10410
|
-
name: "goal",
|
|
10411
|
-
message: "\u8F93\u5165\u4EFB\u52A1\u76EE\u6807:",
|
|
10412
|
-
validate: (input) => {
|
|
10413
|
-
if (!input.trim()) return "\u8BF7\u8F93\u5165\u4EFB\u52A1\u76EE\u6807";
|
|
10414
|
-
return true;
|
|
8244
|
+
if (localExists) {
|
|
8245
|
+
await execa5("git", ["branch", "-D", branchFullName]);
|
|
10415
8246
|
}
|
|
10416
|
-
},
|
|
10417
|
-
{
|
|
10418
|
-
type: "checkbox",
|
|
10419
|
-
name: "roles",
|
|
10420
|
-
message: "\u9009\u62E9\u53C2\u4E0E\u89D2\u8272:",
|
|
10421
|
-
choices: [
|
|
10422
|
-
{ name: "Planner (\u9700\u6C42\u4E0E BDD)", value: "planner", checked: true },
|
|
10423
|
-
{ name: "Architect (\u67B6\u6784)", value: "architect", checked: true },
|
|
10424
|
-
{ name: "Backend (\u540E\u7AEF)", value: "backend", checked: true },
|
|
10425
|
-
{ name: "Frontend (\u524D\u7AEF)", value: "frontend", checked: true },
|
|
10426
|
-
{ name: "QA (BDD \u9A8C\u6536)", value: "qa", checked: true },
|
|
10427
|
-
{ name: "Reviewer (\u5BA1\u67E5)", value: "reviewer", checked: true }
|
|
10428
|
-
]
|
|
10429
8247
|
}
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
const { confirm } = await inquirer13.prompt([
|
|
10438
|
-
{
|
|
10439
|
-
type: "confirm",
|
|
10440
|
-
name: "confirm",
|
|
10441
|
-
message: "\u786E\u8BA4\u521B\u5EFA\u4F1A\u8BDD?",
|
|
10442
|
-
default: true
|
|
8248
|
+
await execa5("git", ["checkout", "-b", branchFullName]);
|
|
8249
|
+
if (didStash) {
|
|
8250
|
+
try {
|
|
8251
|
+
await execa5("git", ["stash", "pop"]);
|
|
8252
|
+
} catch (e) {
|
|
8253
|
+
logger.warn(`\u6062\u590D stash \u65F6\u51FA\u73B0\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406: ${e.message}`);
|
|
8254
|
+
}
|
|
10443
8255
|
}
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
logger.
|
|
10447
|
-
|
|
8256
|
+
logger.success(`\u5DE5\u4F5C\u5206\u652F "${branchFullName}" \u521B\u5EFA\u6210\u529F (\u57FA\u4E8E master)`);
|
|
8257
|
+
} catch (error) {
|
|
8258
|
+
logger.error(`\u521B\u5EFA\u5DE5\u4F5C\u5206\u652F\u5931\u8D25: ${error.message}`);
|
|
8259
|
+
process.exit(1);
|
|
10448
8260
|
}
|
|
8261
|
+
});
|
|
8262
|
+
|
|
8263
|
+
// src/commands/fe-dev.ts
|
|
8264
|
+
init_esm_shims();
|
|
8265
|
+
init_logger();
|
|
8266
|
+
import { Command as Command19 } from "commander";
|
|
8267
|
+
var feDevCommand = new Command19("fe-dev").description("\u5408\u5165 develop \u5206\u652F\u63D0\u6D4B").action(async () => {
|
|
10449
8268
|
try {
|
|
10450
|
-
const
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
logger.
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
}
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
{
|
|
10470
|
-
type: "confirm",
|
|
10471
|
-
name: "runNow",
|
|
10472
|
-
message: "\u662F\u5426\u7ACB\u5373\u5F00\u59CB\u8FD0\u884C Phase 1?",
|
|
10473
|
-
default: false
|
|
10474
|
-
}
|
|
10475
|
-
]);
|
|
10476
|
-
if (runNow) {
|
|
10477
|
-
await handleRunPhase(worktreeManager, handoffManager, executor, {
|
|
10478
|
-
ticket: answers.ticket,
|
|
10479
|
-
goal: answers.goal,
|
|
10480
|
-
phase: "1"
|
|
8269
|
+
const { execa: execa5 } = await import("execa");
|
|
8270
|
+
await ensureGitRepo();
|
|
8271
|
+
await ensureCleanWorkingTree();
|
|
8272
|
+
const currentBranch = await ensureOnBranch();
|
|
8273
|
+
logger.step("\u626B\u63CF\u5371\u9669\u6587\u4EF6...");
|
|
8274
|
+
const dangerous = await scanBranchDiffForDangerousFiles("develop", currentBranch);
|
|
8275
|
+
if (dangerous.length > 0) {
|
|
8276
|
+
reportDangerousFilesAndExit(dangerous);
|
|
8277
|
+
}
|
|
8278
|
+
await ensureBranchExists("develop");
|
|
8279
|
+
logger.step("\u5207\u6362\u5230 develop \u5206\u652F...");
|
|
8280
|
+
await execa5("git", ["checkout", "develop"]);
|
|
8281
|
+
logger.step("\u62C9\u53D6\u6700\u65B0 develop...");
|
|
8282
|
+
await execa5("git", ["pull", "origin", "develop"]);
|
|
8283
|
+
logger.step(`\u5408\u5E76 ${currentBranch} \u5230 develop...`);
|
|
8284
|
+
try {
|
|
8285
|
+
await execa5("git", ["merge", currentBranch, "--no-ff"]);
|
|
8286
|
+
} catch (mergeErr) {
|
|
8287
|
+
await execa5("git", ["merge", "--abort"]).catch(() => {
|
|
10481
8288
|
});
|
|
8289
|
+
await execa5("git", ["checkout", currentBranch]);
|
|
8290
|
+
logger.error(`\u5408\u5E76 ${currentBranch} \u5230 develop \u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
|
|
8291
|
+
logger.info("\u8BF7\u624B\u52A8\u89E3\u51B3\u540E\u91CD\u8BD5");
|
|
8292
|
+
process.exit(1);
|
|
10482
8293
|
}
|
|
8294
|
+
logger.step("\u63A8\u9001 develop \u5230\u8FDC\u7AEF...");
|
|
8295
|
+
await execa5("git", ["push", "origin", "develop"]);
|
|
8296
|
+
logger.step(`\u5207\u56DE ${currentBranch}...`);
|
|
8297
|
+
await execa5("git", ["checkout", currentBranch]);
|
|
8298
|
+
logger.success(`\u5DF2\u5408\u5165 develop \u5E76\u63A8\u9001\uFF0C\u5DF2\u5207\u56DE ${currentBranch}`);
|
|
10483
8299
|
} catch (error) {
|
|
10484
|
-
logger.error(
|
|
10485
|
-
|
|
10486
|
-
}
|
|
10487
|
-
async function generateInitialTemplates(handoffManager, ticket, goal) {
|
|
10488
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
10489
|
-
await handoffManager.generateTemplate(ticket, "planner", "SPEC", {
|
|
10490
|
-
TICKET: ticket,
|
|
10491
|
-
TITLE: goal,
|
|
10492
|
-
OWNER: "\u5F85\u5B9A",
|
|
10493
|
-
DATE: today,
|
|
10494
|
-
CRITERIA_1: "[\u5F85\u586B\u5199]",
|
|
10495
|
-
CRITERIA_2: "[\u5F85\u586B\u5199]",
|
|
10496
|
-
CRITERIA_3: "[\u5F85\u586B\u5199]",
|
|
10497
|
-
DEPENDENCIES: "\u65E0",
|
|
10498
|
-
DEPENDED_BY: "\u5F85\u5B9A",
|
|
10499
|
-
FEATURE: "\u5F85\u5B9A"
|
|
10500
|
-
});
|
|
10501
|
-
await handoffManager.generateTemplate(ticket, "planner", "TASKS", {
|
|
10502
|
-
TICKET: ticket
|
|
10503
|
-
});
|
|
10504
|
-
}
|
|
10505
|
-
async function handleList(worktreeManager) {
|
|
10506
|
-
logger.step("\u5217\u51FA\u6240\u6709 Worktree");
|
|
10507
|
-
const worktrees = await worktreeManager.list();
|
|
10508
|
-
if (worktrees.length === 0) {
|
|
10509
|
-
logger.info("\u6CA1\u6709\u627E\u5230 Worktree");
|
|
10510
|
-
return;
|
|
10511
|
-
}
|
|
10512
|
-
logger.newLine();
|
|
10513
|
-
console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
10514
|
-
console.log("\u2502 Worktree \u5217\u8868 \u2502");
|
|
10515
|
-
console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
10516
|
-
for (const wt of worktrees) {
|
|
10517
|
-
const statusIcon = wt.status === "active" ? "\u25CF" : wt.status === "completed" ? "\u2713" : "\u25CB";
|
|
10518
|
-
console.log(`\u2502 ${statusIcon} ${wt.name.padEnd(20)} ${wt.branch.padEnd(25)} \u2502`);
|
|
10519
|
-
}
|
|
10520
|
-
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
10521
|
-
}
|
|
10522
|
-
async function handleStatus(worktreeManager, handoffManager, options) {
|
|
10523
|
-
logger.step("\u67E5\u770B\u4F1A\u8BDD\u72B6\u6001");
|
|
10524
|
-
const handoffDir = path24.join(process.cwd(), ".handoff");
|
|
10525
|
-
const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
|
|
10526
|
-
if (sessions.length === 0) {
|
|
10527
|
-
logger.info("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
|
|
10528
|
-
return;
|
|
10529
|
-
}
|
|
10530
|
-
const sessionChoices = sessions.map((s) => {
|
|
10531
|
-
const ticket2 = s.replace("session-", "").replace(".json", "");
|
|
10532
|
-
return { name: ticket2, value: ticket2 };
|
|
10533
|
-
});
|
|
10534
|
-
const { ticket } = await inquirer13.prompt([
|
|
10535
|
-
{
|
|
10536
|
-
type: "list",
|
|
10537
|
-
name: "ticket",
|
|
10538
|
-
message: "\u9009\u62E9\u4F1A\u8BDD:",
|
|
10539
|
-
choices: sessionChoices
|
|
10540
|
-
}
|
|
10541
|
-
]);
|
|
10542
|
-
const state = await worktreeManager.getSessionState(ticket);
|
|
10543
|
-
if (!state) {
|
|
10544
|
-
logger.error("\u4F1A\u8BDD\u72B6\u6001\u4E0D\u5B58\u5728");
|
|
10545
|
-
return;
|
|
10546
|
-
}
|
|
10547
|
-
logger.newLine();
|
|
10548
|
-
console.log(`\u{1F4CB} \u4F1A\u8BDD: ${ticket}`);
|
|
10549
|
-
console.log(`\u{1F4CA} \u5F53\u524D\u9636\u6BB5: ${state.currentPhase}`);
|
|
10550
|
-
console.log(`\u2705 \u5DF2\u5B8C\u6210\u9636\u6BB5: ${state.completedPhases.length}`);
|
|
10551
|
-
console.log("");
|
|
10552
|
-
console.log(formatPhaseProgress(state.completedPhases));
|
|
10553
|
-
}
|
|
10554
|
-
async function handleRunPhaseSelect(worktreeManager, handoffManager, executor2, options) {
|
|
10555
|
-
const handoffDir = path24.join(process.cwd(), ".handoff");
|
|
10556
|
-
const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
|
|
10557
|
-
if (sessions.length === 0) {
|
|
10558
|
-
logger.error("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
|
|
10559
|
-
return;
|
|
8300
|
+
logger.error(`fe-dev \u6267\u884C\u5931\u8D25: ${error.message}`);
|
|
8301
|
+
process.exit(1);
|
|
10560
8302
|
}
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
8303
|
+
});
|
|
8304
|
+
|
|
8305
|
+
// src/commands/fe-release.ts
|
|
8306
|
+
init_esm_shims();
|
|
8307
|
+
init_logger();
|
|
8308
|
+
import { Command as Command20 } from "commander";
|
|
8309
|
+
import inquirer14 from "inquirer";
|
|
8310
|
+
var feReleaseCommand = new Command20("fe-release").argument("[version]", "\u6307\u5B9A\u7248\u672C\u53F7").option("--minor", "\u9012\u589E minor \u7248\u672C").option("--major", "\u9012\u589E major \u7248\u672C").description("\u521B\u5EFA release \u5206\u652F (\u4ECE master \u5207\u51FA\u5E76\u5408\u5165\u5F53\u524D\u7279\u6027)").action(async (versionArg, opts) => {
|
|
8311
|
+
try {
|
|
8312
|
+
const { execa: execa5 } = await import("execa");
|
|
8313
|
+
logger.step("\u68C0\u67E5 Git \u4ED3\u5E93\u72B6\u6001...");
|
|
8314
|
+
await ensureGitRepo();
|
|
8315
|
+
await ensureCleanWorkingTree();
|
|
8316
|
+
const currentBranch = await ensureOnBranch();
|
|
8317
|
+
logger.step("\u68C0\u67E5\u4E0D\u7A33\u5B9A\u4F9D\u8D56...");
|
|
8318
|
+
const unstable = await checkUnstableDependencies();
|
|
8319
|
+
if (unstable.length > 0) {
|
|
8320
|
+
unstable.forEach((dep) => {
|
|
8321
|
+
logger.warn(` ${dep.name}: ${dep.version}`);
|
|
8322
|
+
});
|
|
8323
|
+
const { shouldContinue } = await inquirer14.prompt([
|
|
8324
|
+
{
|
|
8325
|
+
type: "confirm",
|
|
8326
|
+
name: "shouldContinue",
|
|
8327
|
+
message: "\u68C0\u6D4B\u5230\u4E0D\u7A33\u5B9A\u4F9D\u8D56\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",
|
|
8328
|
+
default: false
|
|
8329
|
+
}
|
|
8330
|
+
]);
|
|
8331
|
+
if (!shouldContinue) {
|
|
8332
|
+
process.exit(1);
|
|
8333
|
+
}
|
|
10571
8334
|
}
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
}));
|
|
10577
|
-
const { phaseNum } = await inquirer13.prompt([
|
|
10578
|
-
{
|
|
10579
|
-
type: "list",
|
|
10580
|
-
name: "phaseNum",
|
|
10581
|
-
message: "\u9009\u62E9\u9636\u6BB5:",
|
|
10582
|
-
choices: phaseChoices
|
|
8335
|
+
logger.step("\u626B\u63CF\u5371\u9669\u6587\u4EF6...");
|
|
8336
|
+
const dangerous = await scanBranchDiffForDangerousFiles("master", currentBranch);
|
|
8337
|
+
if (dangerous.length > 0) {
|
|
8338
|
+
reportDangerousFilesAndExit(dangerous);
|
|
10583
8339
|
}
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
const phaseConfig = PHASE_CONFIGS[phaseName];
|
|
10602
|
-
for (const dep of phaseConfig.dependsOn) {
|
|
10603
|
-
const isDone = completedPhases.includes(dep);
|
|
10604
|
-
console.log(` ${isDone ? "\u2713" : "\u2717"} ${getPhaseDisplayName(dep)}`);
|
|
8340
|
+
logger.step("\u786E\u4FDD master \u5206\u652F\u5B58\u5728...");
|
|
8341
|
+
await ensureBranchExists("master");
|
|
8342
|
+
logger.step("\u5207\u6362\u5230 master \u5E76\u62C9\u53D6\u6700\u65B0\u4EE3\u7801...");
|
|
8343
|
+
await execa5("git", ["checkout", "master"]);
|
|
8344
|
+
await execa5("git", ["pull", "origin", "master"]);
|
|
8345
|
+
let version;
|
|
8346
|
+
if (versionArg) {
|
|
8347
|
+
version = versionArg;
|
|
8348
|
+
} else {
|
|
8349
|
+
const currentVersion = await readVersion();
|
|
8350
|
+
if (opts.major) {
|
|
8351
|
+
version = bumpVersion(currentVersion, "major");
|
|
8352
|
+
} else if (opts.minor) {
|
|
8353
|
+
version = bumpVersion(currentVersion, "minor");
|
|
8354
|
+
} else {
|
|
8355
|
+
version = bumpVersion(currentVersion, "patch");
|
|
8356
|
+
}
|
|
10605
8357
|
}
|
|
10606
|
-
|
|
10607
|
-
logger.
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
console.log("\u{1F4CB} Gate \u68C0\u67E5\u6E05\u5355:");
|
|
10613
|
-
console.log("");
|
|
10614
|
-
if (conditions.length === 0) {
|
|
10615
|
-
console.log(" (\u6B64\u9636\u6BB5\u65E0\u9700\u989D\u5916\u68C0\u67E5)");
|
|
10616
|
-
} else {
|
|
10617
|
-
for (let i = 0; i < conditions.length; i++) {
|
|
10618
|
-
const cond = conditions[i];
|
|
10619
|
-
console.log(` [ ] ${cond.description}`);
|
|
8358
|
+
const releaseBranch = `release/${version}`;
|
|
8359
|
+
logger.step(`\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B58\u5728 ${releaseBranch}...`);
|
|
8360
|
+
if (await branchExistsRemote(releaseBranch)) {
|
|
8361
|
+
logger.error(`\u8FDC\u7AEF\u5DF2\u5B58\u5728 ${releaseBranch} \u5206\u652F`);
|
|
8362
|
+
await execa5("git", ["checkout", currentBranch]);
|
|
8363
|
+
process.exit(1);
|
|
10620
8364
|
}
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
8365
|
+
logger.step(`\u521B\u5EFA\u5206\u652F ${releaseBranch}...`);
|
|
8366
|
+
await execa5("git", ["checkout", "-b", releaseBranch]);
|
|
8367
|
+
logger.step(`\u5408\u5E76 ${currentBranch} \u5230 ${releaseBranch}...`);
|
|
8368
|
+
try {
|
|
8369
|
+
await execa5("git", ["merge", currentBranch, "--no-ff"]);
|
|
8370
|
+
} catch (mergeErr) {
|
|
8371
|
+
logger.error(`\u5408\u5E76 ${currentBranch} \u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
|
|
8372
|
+
await execa5("git", ["merge", "--abort"]).catch(() => {
|
|
8373
|
+
});
|
|
8374
|
+
await execa5("git", ["checkout", currentBranch]);
|
|
8375
|
+
await execa5("git", ["branch", "-D", releaseBranch]).catch(() => {
|
|
8376
|
+
});
|
|
8377
|
+
process.exit(1);
|
|
10630
8378
|
}
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
logger.error(`\
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
logger.step(`\u8FD0\u884C Phase ${phase}: ${getPhaseDisplayName(phaseName)}`);
|
|
10643
|
-
const parallelRoles = getParallelRoles(phaseName);
|
|
10644
|
-
logger.info("\u5C06\u5E76\u884C\u6267\u884C\u7684\u89D2\u8272\u7EC4:");
|
|
10645
|
-
for (const group of parallelRoles) {
|
|
10646
|
-
logger.info(` - ${group.join(" + ")}`);
|
|
10647
|
-
}
|
|
10648
|
-
const { confirm } = await inquirer13.prompt([
|
|
10649
|
-
{
|
|
10650
|
-
type: "confirm",
|
|
10651
|
-
name: "confirm",
|
|
10652
|
-
message: "\u786E\u8BA4\u5F00\u59CB\u8FD0\u884C?",
|
|
10653
|
-
default: true
|
|
8379
|
+
logger.step(`\u5199\u5165\u7248\u672C\u53F7 ${version}...`);
|
|
8380
|
+
await setVersion(version);
|
|
8381
|
+
await execa5("git", ["add", "package.json"]);
|
|
8382
|
+
await execa5("git", ["commit", "-m", `chore: bump version to ${version}`]);
|
|
8383
|
+
logger.step(`\u63A8\u9001 ${releaseBranch} \u5230\u8FDC\u7AEF...`);
|
|
8384
|
+
await execa5("git", ["push", "origin", releaseBranch]);
|
|
8385
|
+
logger.success(`${releaseBranch} \u521B\u5EFA\u5E76\u63A8\u9001\u6210\u529F`);
|
|
8386
|
+
} catch (error) {
|
|
8387
|
+
logger.error(`\u521B\u5EFA release \u5206\u652F\u5931\u8D25: ${error.message}`);
|
|
8388
|
+
if (process.env.DEBUG) {
|
|
8389
|
+
console.error(error);
|
|
10654
8390
|
}
|
|
10655
|
-
|
|
10656
|
-
if (!confirm) {
|
|
10657
|
-
logger.info("\u5DF2\u53D6\u6D88");
|
|
10658
|
-
return;
|
|
10659
|
-
}
|
|
10660
|
-
const state = await worktreeManager.getSessionState(ticket);
|
|
10661
|
-
if (!state) {
|
|
10662
|
-
logger.error("\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
10663
|
-
return;
|
|
10664
|
-
}
|
|
10665
|
-
const gatePassed = await checkPhaseGate(ticket, phaseName, state.completedPhases);
|
|
10666
|
-
if (!gatePassed) {
|
|
10667
|
-
logger.info("\u95E8\u7981\u68C0\u67E5\u672A\u901A\u8FC7\uFF0C\u8BF7\u5B8C\u6210\u524D\u7F6E\u6761\u4EF6\u540E\u91CD\u8BD5");
|
|
10668
|
-
return;
|
|
8391
|
+
process.exit(1);
|
|
10669
8392
|
}
|
|
8393
|
+
});
|
|
8394
|
+
|
|
8395
|
+
// src/commands/fe-sync.ts
|
|
8396
|
+
init_esm_shims();
|
|
8397
|
+
init_logger();
|
|
8398
|
+
import { Command as Command21 } from "commander";
|
|
8399
|
+
var feSyncCommand = new Command21("fe-sync").description("\u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230\u5F53\u524D\u5206\u652F").action(async () => {
|
|
10670
8400
|
try {
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
await
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
logger.
|
|
8401
|
+
await ensureGitRepo();
|
|
8402
|
+
await ensureCleanWorkingTree();
|
|
8403
|
+
const currentBranch = await ensureOnBranch();
|
|
8404
|
+
const { execa: execa5 } = await import("execa");
|
|
8405
|
+
logger.step("\u6B63\u5728\u62C9\u53D6 origin/master \u6700\u65B0\u4EE3\u7801...");
|
|
8406
|
+
await execa5("git", ["fetch", "origin", "master"]);
|
|
8407
|
+
logger.step("\u6B63\u5728\u5408\u5E76 origin/master \u5230\u5F53\u524D\u5206\u652F...");
|
|
8408
|
+
try {
|
|
8409
|
+
await execa5("git", ["merge", "origin/master", "--no-ff"]);
|
|
8410
|
+
} catch (mergeErr) {
|
|
8411
|
+
await execa5("git", ["merge", "--abort"]).catch(() => {
|
|
8412
|
+
});
|
|
8413
|
+
logger.error(`\u5408\u5E76\u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
|
|
8414
|
+
logger.info("\u8BF7\u624B\u52A8\u89E3\u51B3\u540E\u91CD\u8BD5");
|
|
8415
|
+
process.exit(1);
|
|
10685
8416
|
}
|
|
8417
|
+
logger.success("\u5DF2\u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230 " + currentBranch);
|
|
10686
8418
|
} catch (error) {
|
|
10687
|
-
logger.error(`\
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
async function handleVerifySelect(worktreeManager, handoffManager, options) {
|
|
10691
|
-
const handoffDir = path24.join(process.cwd(), ".handoff");
|
|
10692
|
-
const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
|
|
10693
|
-
if (sessions.length === 0) {
|
|
10694
|
-
logger.error("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
|
|
10695
|
-
return;
|
|
10696
|
-
}
|
|
10697
|
-
const ticketChoices = sessions.map((s) => {
|
|
10698
|
-
const ticket2 = s.replace("session-", "").replace(".json", "");
|
|
10699
|
-
return { name: ticket2, value: ticket2 };
|
|
10700
|
-
});
|
|
10701
|
-
const { ticket } = await inquirer13.prompt([
|
|
10702
|
-
{
|
|
10703
|
-
type: "list",
|
|
10704
|
-
name: "ticket",
|
|
10705
|
-
message: "\u9009\u62E9\u4F1A\u8BDD:",
|
|
10706
|
-
choices: ticketChoices
|
|
10707
|
-
}
|
|
10708
|
-
]);
|
|
10709
|
-
await handleVerify(worktreeManager, handoffManager, { ticket });
|
|
10710
|
-
}
|
|
10711
|
-
async function handleVerify(worktreeManager, handoffManager, options) {
|
|
10712
|
-
const { ticket } = options;
|
|
10713
|
-
logger.step(`\u9A8C\u8BC1\u4EA4\u63A5\u7269: ${ticket}`);
|
|
10714
|
-
const result = await handoffManager.verify(ticket);
|
|
10715
|
-
if (result.complete) {
|
|
10716
|
-
logger.success("\u2705 \u6240\u6709\u4EA4\u63A5\u7269\u5DF2\u5C31\u7EEA!");
|
|
10717
|
-
} else {
|
|
10718
|
-
logger.warn(`\u26A0\uFE0F \u7F3A\u5C11 ${result.missing.length} \u4E2A\u4EA4\u63A5\u7269:`);
|
|
10719
|
-
for (const item of result.missing) {
|
|
10720
|
-
logger.info(` - ${item}`);
|
|
8419
|
+
logger.error(`\u540C\u6B65\u5931\u8D25: ${error.message}`);
|
|
8420
|
+
if (process.env.DEBUG) {
|
|
8421
|
+
console.error(error);
|
|
10721
8422
|
}
|
|
8423
|
+
process.exit(1);
|
|
10722
8424
|
}
|
|
10723
|
-
}
|
|
8425
|
+
});
|
|
10724
8426
|
|
|
10725
8427
|
// src/index.ts
|
|
10726
|
-
var __dirname2 =
|
|
10727
|
-
var pkg =
|
|
10728
|
-
var program = new
|
|
8428
|
+
var __dirname2 = path21.dirname(fileURLToPath2(import.meta.url));
|
|
8429
|
+
var pkg = fs6.readJsonSync(path21.join(__dirname2, "../package.json"));
|
|
8430
|
+
var program = new Command22();
|
|
10729
8431
|
program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
|
|
10730
8432
|
program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
|
|
10731
8433
|
program.addCommand(initCommand);
|
|
@@ -10746,7 +8448,10 @@ program.addCommand(logsCommand);
|
|
|
10746
8448
|
program.addCommand(updateCommand);
|
|
10747
8449
|
program.addCommand(configCommand);
|
|
10748
8450
|
program.addCommand(diffCommand);
|
|
10749
|
-
program.addCommand(
|
|
8451
|
+
program.addCommand(feStartCommand);
|
|
8452
|
+
program.addCommand(feDevCommand);
|
|
8453
|
+
program.addCommand(feReleaseCommand);
|
|
8454
|
+
program.addCommand(feSyncCommand);
|
|
10750
8455
|
program.action(() => {
|
|
10751
8456
|
showHelp();
|
|
10752
8457
|
});
|
|
@@ -10773,7 +8478,12 @@ function showHelp() {
|
|
|
10773
8478
|
console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
|
|
10774
8479
|
console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
|
|
10775
8480
|
console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
|
|
10776
|
-
console.log("
|
|
8481
|
+
console.log("");
|
|
8482
|
+
console.log(chalk4.bold("\u524D\u7AEF Git \u5DE5\u4F5C\u6D41:"));
|
|
8483
|
+
console.log(" team-cli fe-start <name> \u521B\u5EFA\u524D\u7AEF\u5DE5\u4F5C\u5206\u652F (\u4ECE master \u5207\u51FA)");
|
|
8484
|
+
console.log(" team-cli fe-dev \u5408\u5165 develop \u5206\u652F\u63D0\u6D4B");
|
|
8485
|
+
console.log(" team-cli fe-release [version] \u521B\u5EFA release \u5206\u652F");
|
|
8486
|
+
console.log(" team-cli fe-sync \u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230\u5F53\u524D\u5206\u652F");
|
|
10777
8487
|
console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
|
|
10778
8488
|
console.log("");
|
|
10779
8489
|
console.log(chalk4.bold("\u793A\u4F8B:"));
|