yg-team-cli 2.7.1 → 2.8.0
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 +140 -0
- package/dist/cli.js +2080 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2035 -39
- 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: execa6 } = await import("execa");
|
|
383
|
+
const { stdout } = await execa6("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: execa6 } = await import("execa");
|
|
393
|
+
const { stdout } = await execa6("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 path26 = repository;
|
|
1011
|
+
if (path26.startsWith("git@")) {
|
|
1012
|
+
path26 = path26.replace(/^git@/, "");
|
|
1013
|
+
const colonIndex = path26.indexOf(":");
|
|
1014
1014
|
if (colonIndex !== -1) {
|
|
1015
|
-
|
|
1015
|
+
path26 = path26.substring(colonIndex + 1);
|
|
1016
1016
|
}
|
|
1017
1017
|
} else {
|
|
1018
|
-
|
|
1019
|
-
const parts =
|
|
1018
|
+
path26 = path26.replace(/^https?:\/\//, "");
|
|
1019
|
+
const parts = path26.split("/");
|
|
1020
1020
|
if (parts.length > 1) {
|
|
1021
|
-
|
|
1021
|
+
path26 = parts.slice(1).join("/");
|
|
1022
1022
|
}
|
|
1023
1023
|
}
|
|
1024
|
-
|
|
1025
|
-
return
|
|
1024
|
+
path26 = path26.replace(/\.git$/, "");
|
|
1025
|
+
return path26;
|
|
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 Command19 } from "commander";
|
|
1080
1080
|
import chalk4 from "chalk";
|
|
1081
|
-
import
|
|
1082
|
-
import
|
|
1081
|
+
import fs9 from "fs-extra";
|
|
1082
|
+
import path25 from "path";
|
|
1083
1083
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1084
1084
|
|
|
1085
1085
|
// src/commands/init.ts
|
|
@@ -4924,8 +4924,8 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
|
|
|
4924
4924
|
{
|
|
4925
4925
|
title: "\u521B\u5EFA hotfix \u5206\u652F",
|
|
4926
4926
|
task: async () => {
|
|
4927
|
-
const { execa:
|
|
4928
|
-
await
|
|
4927
|
+
const { execa: execa6 } = await import("execa");
|
|
4928
|
+
await execa6("git", ["checkout", "-b", branchName], { stdio: "inherit" });
|
|
4929
4929
|
}
|
|
4930
4930
|
},
|
|
4931
4931
|
{
|
|
@@ -4949,9 +4949,9 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
|
|
|
4949
4949
|
{
|
|
4950
4950
|
title: "\u521B\u5EFA\u521D\u59CB commit",
|
|
4951
4951
|
task: async () => {
|
|
4952
|
-
const { execa:
|
|
4953
|
-
await
|
|
4954
|
-
await
|
|
4952
|
+
const { execa: execa6 } = await import("execa");
|
|
4953
|
+
await execa6("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
|
|
4954
|
+
await execa6(
|
|
4955
4955
|
"git",
|
|
4956
4956
|
[
|
|
4957
4957
|
"commit",
|
|
@@ -6165,11 +6165,11 @@ async function verifyApiImplementationByPath(apiPath) {
|
|
|
6165
6165
|
return { status: "pending" };
|
|
6166
6166
|
}
|
|
6167
6167
|
const method = apiMatch[1].toUpperCase();
|
|
6168
|
-
const
|
|
6168
|
+
const path26 = "/" + apiMatch[2];
|
|
6169
6169
|
const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
|
|
6170
6170
|
for (const cf of controllerFiles) {
|
|
6171
6171
|
const content = await FileUtils.read(cf);
|
|
6172
|
-
if (content.includes(`"${
|
|
6172
|
+
if (content.includes(`"${path26}"`) || content.includes(`"${path26}/"`)) {
|
|
6173
6173
|
const annotationMap = {
|
|
6174
6174
|
GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
|
|
6175
6175
|
POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
|
|
@@ -7589,15 +7589,15 @@ async function syncTemplateVersions(aiMemoryFile, projectDir) {
|
|
|
7589
7589
|
await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
|
|
7590
7590
|
}
|
|
7591
7591
|
function extractRepoName(repository) {
|
|
7592
|
-
let
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
const parts =
|
|
7592
|
+
let path26 = repository;
|
|
7593
|
+
path26 = path26.replace(/^https?:\/\//, "");
|
|
7594
|
+
path26 = path26.replace(/^git@/, "");
|
|
7595
|
+
const parts = path26.split("/");
|
|
7596
7596
|
if (parts.length > 1) {
|
|
7597
|
-
|
|
7597
|
+
path26 = parts.slice(1).join("/");
|
|
7598
7598
|
}
|
|
7599
|
-
|
|
7600
|
-
return
|
|
7599
|
+
path26 = path26.replace(/\.git$/, "");
|
|
7600
|
+
return path26;
|
|
7601
7601
|
}
|
|
7602
7602
|
|
|
7603
7603
|
// src/commands/check-api.ts
|
|
@@ -7912,10 +7912,10 @@ function extractApisFromRegistry(registryContent) {
|
|
|
7912
7912
|
let match;
|
|
7913
7913
|
while ((match = apiRegex.exec(registryContent)) !== null) {
|
|
7914
7914
|
const method = match[1];
|
|
7915
|
-
const
|
|
7915
|
+
const path26 = match[2].trim();
|
|
7916
7916
|
const description = match[3].trim();
|
|
7917
|
-
const key = `${method}:${
|
|
7918
|
-
apis.set(key, { method, path:
|
|
7917
|
+
const key = `${method}:${path26}`;
|
|
7918
|
+
apis.set(key, { method, path: path26, description });
|
|
7919
7919
|
}
|
|
7920
7920
|
return apis;
|
|
7921
7921
|
}
|
|
@@ -8570,9 +8570,9 @@ async function compareTemplate(projectPath, type, localConfig, remoteTag, remote
|
|
|
8570
8570
|
}
|
|
8571
8571
|
}
|
|
8572
8572
|
if (!remoteCommit) {
|
|
8573
|
-
const { execa:
|
|
8573
|
+
const { execa: execa6 } = await import("execa");
|
|
8574
8574
|
const ref = remoteTag || remoteBranch || "HEAD";
|
|
8575
|
-
const { stdout } = await
|
|
8575
|
+
const { stdout } = await execa6("git", ["ls-remote", repository, ref], {
|
|
8576
8576
|
stdio: "pipe"
|
|
8577
8577
|
});
|
|
8578
8578
|
remoteCommit = stdout.split(" ")[0];
|
|
@@ -8728,10 +8728,2004 @@ var Table = class {
|
|
|
8728
8728
|
}
|
|
8729
8729
|
};
|
|
8730
8730
|
|
|
8731
|
+
// src/commands/dev-worktree.ts
|
|
8732
|
+
init_esm_shims();
|
|
8733
|
+
init_utils();
|
|
8734
|
+
init_logger();
|
|
8735
|
+
import { Command as Command18 } from "commander";
|
|
8736
|
+
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
|
+
|
|
8745
|
+
// src/types/worktree.ts
|
|
8746
|
+
init_esm_shims();
|
|
8747
|
+
var DEFAULT_WORKTREE_CONFIG = {
|
|
8748
|
+
baseDir: "../wt/",
|
|
8749
|
+
handoffDir: ".handoff/",
|
|
8750
|
+
templateDir: ".handoff/templates/"
|
|
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
|
+
},
|
|
8761
|
+
{
|
|
8762
|
+
role: "architect",
|
|
8763
|
+
displayName: "Architect",
|
|
8764
|
+
description: "\u6280\u672F\u67B6\u6784\u4E0E API \u8BBE\u8BA1",
|
|
8765
|
+
branchPrefix: "feat",
|
|
8766
|
+
worktreeSuffix: "arch",
|
|
8767
|
+
required: true
|
|
8768
|
+
},
|
|
8769
|
+
{
|
|
8770
|
+
role: "backend",
|
|
8771
|
+
displayName: "Backend",
|
|
8772
|
+
description: "\u540E\u7AEF\u5B9E\u73B0\u4E0E OpenAPI",
|
|
8773
|
+
branchPrefix: "feat",
|
|
8774
|
+
worktreeSuffix: "backend",
|
|
8775
|
+
required: true
|
|
8776
|
+
},
|
|
8777
|
+
{
|
|
8778
|
+
role: "frontend",
|
|
8779
|
+
displayName: "Frontend",
|
|
8780
|
+
description: "\u524D\u7AEF\u5B9E\u73B0",
|
|
8781
|
+
branchPrefix: "feat",
|
|
8782
|
+
worktreeSuffix: "frontend",
|
|
8783
|
+
required: true
|
|
8784
|
+
},
|
|
8785
|
+
{
|
|
8786
|
+
role: "qa",
|
|
8787
|
+
displayName: "QA/BDD",
|
|
8788
|
+
description: "BDD \u9A8C\u6536\u6D4B\u8BD5",
|
|
8789
|
+
branchPrefix: "test",
|
|
8790
|
+
worktreeSuffix: "qa",
|
|
8791
|
+
required: true
|
|
8792
|
+
},
|
|
8793
|
+
{
|
|
8794
|
+
role: "reviewer",
|
|
8795
|
+
displayName: "Reviewer",
|
|
8796
|
+
description: "\u4EE3\u7801\u5BA1\u67E5\u4E0E\u53D1\u5E03",
|
|
8797
|
+
branchPrefix: "integration",
|
|
8798
|
+
worktreeSuffix: "review",
|
|
8799
|
+
required: true
|
|
8800
|
+
}
|
|
8801
|
+
];
|
|
8802
|
+
|
|
8803
|
+
// src/lib/worktree-manager.ts
|
|
8804
|
+
init_logger();
|
|
8805
|
+
var WorktreeManager = class {
|
|
8806
|
+
repoRoot;
|
|
8807
|
+
wtBaseDir;
|
|
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;
|
|
9453
|
+
}
|
|
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
|
+
}
|
|
9789
|
+
function buildRolePrompt(role, data) {
|
|
9790
|
+
const config = getRolePrompt(role);
|
|
9791
|
+
if (!config) {
|
|
9792
|
+
throw new Error(`Unknown role: ${role}`);
|
|
9793
|
+
}
|
|
9794
|
+
let taskPrompt = config.taskPrompt;
|
|
9795
|
+
for (const [key, value] of Object.entries(data)) {
|
|
9796
|
+
taskPrompt = taskPrompt.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
9797
|
+
}
|
|
9798
|
+
return taskPrompt;
|
|
9799
|
+
}
|
|
9800
|
+
|
|
9801
|
+
// src/lib/phase-config.ts
|
|
9802
|
+
init_esm_shims();
|
|
9803
|
+
var PHASE_CONFIGS = {
|
|
9804
|
+
planning: {
|
|
9805
|
+
phase: "planning",
|
|
9806
|
+
displayName: "\u9700\u6C42\u4E0E BDD \u8BBE\u8BA1",
|
|
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");
|
|
10228
|
+
}
|
|
10229
|
+
/**
|
|
10230
|
+
* 从输出中提取生成的交接物文件
|
|
10231
|
+
*/
|
|
10232
|
+
extractArtifacts(output) {
|
|
10233
|
+
const artifacts = [];
|
|
10234
|
+
const filePatterns = [
|
|
10235
|
+
/\.handoff\/[^\s]+\.(md|yaml|json)/gi,
|
|
10236
|
+
/`(?:create|write|update):?\s*([^\s`]+)/gi
|
|
10237
|
+
];
|
|
10238
|
+
for (const pattern of filePatterns) {
|
|
10239
|
+
const matches = output.matchAll(pattern);
|
|
10240
|
+
for (const match of matches) {
|
|
10241
|
+
const file = match[1] || match[0];
|
|
10242
|
+
if (file && !artifacts.includes(file)) {
|
|
10243
|
+
artifacts.push(file);
|
|
10244
|
+
}
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
return artifacts;
|
|
10248
|
+
}
|
|
10249
|
+
/**
|
|
10250
|
+
* 生成任务报告
|
|
10251
|
+
*/
|
|
10252
|
+
generateReport(results) {
|
|
10253
|
+
const lines = [];
|
|
10254
|
+
lines.push("# \u5E76\u884C\u6267\u884C\u62A5\u544A");
|
|
10255
|
+
lines.push("");
|
|
10256
|
+
lines.push(`**\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`);
|
|
10257
|
+
lines.push(`**\u6210\u529F**: ${results.filter((r) => r.success).length}`);
|
|
10258
|
+
lines.push(`**\u5931\u8D25**: ${results.filter((r) => !r.success).length}`);
|
|
10259
|
+
lines.push("");
|
|
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
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
return lines.join("\n");
|
|
10283
|
+
}
|
|
10284
|
+
/**
|
|
10285
|
+
* 保存报告
|
|
10286
|
+
*/
|
|
10287
|
+
async saveReport(ticket, results) {
|
|
10288
|
+
const reportDir = path23.join(this.repoRoot, ".handoff", ticket, "reports");
|
|
10289
|
+
await fs8.ensureDir(reportDir);
|
|
10290
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
10291
|
+
const reportFile = path23.join(reportDir, `execution-${timestamp}.md`);
|
|
10292
|
+
const report = this.generateReport(results);
|
|
10293
|
+
await fs8.writeFile(reportFile, report, "utf-8");
|
|
10294
|
+
logger.success(`\u6267\u884C\u62A5\u544A\u5DF2\u4FDD\u5B58: ${reportFile}`);
|
|
10295
|
+
return reportFile;
|
|
10296
|
+
}
|
|
10297
|
+
};
|
|
10298
|
+
function createExecutor(repoRoot) {
|
|
10299
|
+
return new ConcurrentExecutor(repoRoot);
|
|
10300
|
+
}
|
|
10301
|
+
|
|
10302
|
+
// src/commands/dev-worktree.ts
|
|
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) => {
|
|
10304
|
+
try {
|
|
10305
|
+
logger.header("Worktree Orchestrator");
|
|
10306
|
+
logger.newLine();
|
|
10307
|
+
const hasTechStack = await FileUtils.exists("TECH_STACK.md");
|
|
10308
|
+
if (!hasTechStack) {
|
|
10309
|
+
logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
|
|
10310
|
+
logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
|
|
10311
|
+
process.exit(1);
|
|
10312
|
+
}
|
|
10313
|
+
logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
|
|
10314
|
+
const repoRoot = process.cwd();
|
|
10315
|
+
const worktreeManager = new WorktreeManager(repoRoot);
|
|
10316
|
+
const handoffManager = createHandoffManager(repoRoot);
|
|
10317
|
+
const executor2 = createExecutor(repoRoot);
|
|
10318
|
+
if (options.new) {
|
|
10319
|
+
await handleNewSession(worktreeManager, handoffManager, options, repoRoot);
|
|
10320
|
+
} else if (options.list) {
|
|
10321
|
+
await handleList(worktreeManager);
|
|
10322
|
+
} else if (options.status) {
|
|
10323
|
+
await handleStatus(worktreeManager, handoffManager, options);
|
|
10324
|
+
} else if (options.runPhase) {
|
|
10325
|
+
await handleRunPhase(worktreeManager, handoffManager, executor2, options);
|
|
10326
|
+
} else if (options.verify) {
|
|
10327
|
+
await handleVerify(worktreeManager, handoffManager, options);
|
|
10328
|
+
} else {
|
|
10329
|
+
await showMainMenu(worktreeManager, handoffManager, executor2, options);
|
|
10330
|
+
}
|
|
10331
|
+
logger.newLine();
|
|
10332
|
+
logger.success("Worktree Orchestrator \u64CD\u4F5C\u5B8C\u6210!");
|
|
10333
|
+
} catch (error) {
|
|
10334
|
+
logger.error(`\u64CD\u4F5C\u5931\u8D25: ${error.message}`);
|
|
10335
|
+
if (process.env.DEBUG) {
|
|
10336
|
+
console.error(error);
|
|
10337
|
+
}
|
|
10338
|
+
process.exit(1);
|
|
10339
|
+
}
|
|
10340
|
+
});
|
|
10341
|
+
async function showMainMenu(worktreeManager, handoffManager, executor2, options) {
|
|
10342
|
+
const choices = [
|
|
10343
|
+
{
|
|
10344
|
+
name: "\u{1F4CB} \u65B0\u5EFA\u5E76\u884C\u5F00\u53D1\u4F1A\u8BDD",
|
|
10345
|
+
value: "new"
|
|
10346
|
+
},
|
|
10347
|
+
{
|
|
10348
|
+
name: "\u{1F4C2} \u5217\u51FA\u6240\u6709 Worktree",
|
|
10349
|
+
value: "list"
|
|
10350
|
+
},
|
|
10351
|
+
{
|
|
10352
|
+
name: "\u{1F4CA} \u67E5\u770B\u4F1A\u8BDD\u72B6\u6001",
|
|
10353
|
+
value: "status"
|
|
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);
|
|
10394
|
+
}
|
|
10395
|
+
}
|
|
10396
|
+
async function handleNewSession(worktreeManager, handoffManager, options, repoRoot) {
|
|
10397
|
+
logger.step("\u65B0\u5EFA\u5E76\u884C\u5F00\u53D1\u4F1A\u8BDD");
|
|
10398
|
+
const questions = [
|
|
10399
|
+
{
|
|
10400
|
+
type: "input",
|
|
10401
|
+
name: "ticket",
|
|
10402
|
+
message: "\u8F93\u5165 TICKET ID (\u4F8B\u5982 USER-123):",
|
|
10403
|
+
validate: (input) => {
|
|
10404
|
+
if (!input.trim()) return "\u8BF7\u8F93\u5165 TICKET ID";
|
|
10405
|
+
return true;
|
|
10406
|
+
}
|
|
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;
|
|
10415
|
+
}
|
|
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
|
+
}
|
|
10430
|
+
];
|
|
10431
|
+
const answers = await inquirer13.prompt(questions);
|
|
10432
|
+
logger.newLine();
|
|
10433
|
+
logger.info(`\u521B\u5EFA\u4F1A\u8BDD: ${answers.ticket}`);
|
|
10434
|
+
logger.info(`\u76EE\u6807: ${answers.goal}`);
|
|
10435
|
+
logger.info(`\u89D2\u8272: ${answers.roles.join(", ")}`);
|
|
10436
|
+
logger.newLine();
|
|
10437
|
+
const { confirm } = await inquirer13.prompt([
|
|
10438
|
+
{
|
|
10439
|
+
type: "confirm",
|
|
10440
|
+
name: "confirm",
|
|
10441
|
+
message: "\u786E\u8BA4\u521B\u5EFA\u4F1A\u8BDD?",
|
|
10442
|
+
default: true
|
|
10443
|
+
}
|
|
10444
|
+
]);
|
|
10445
|
+
if (!confirm) {
|
|
10446
|
+
logger.info("\u5DF2\u53D6\u6D88");
|
|
10447
|
+
return;
|
|
10448
|
+
}
|
|
10449
|
+
try {
|
|
10450
|
+
const session = await worktreeManager.createSession({
|
|
10451
|
+
ticket: answers.ticket,
|
|
10452
|
+
goal: answers.goal,
|
|
10453
|
+
baseBranch: "main",
|
|
10454
|
+
enabledRoles: answers.roles
|
|
10455
|
+
});
|
|
10456
|
+
await handoffManager.init(answers.ticket, answers.roles);
|
|
10457
|
+
await generateInitialTemplates(handoffManager, answers.ticket, answers.goal);
|
|
10458
|
+
logger.newLine();
|
|
10459
|
+
logger.success("\u4F1A\u8BDD\u521B\u5EFA\u6210\u529F!");
|
|
10460
|
+
logger.info("");
|
|
10461
|
+
logger.info("Worktree \u76EE\u5F55:");
|
|
10462
|
+
for (const wt of session.worktrees) {
|
|
10463
|
+
logger.info(` - ${wt.name} (${wt.path})`);
|
|
10464
|
+
}
|
|
10465
|
+
logger.info("");
|
|
10466
|
+
logger.info("\u4EA4\u63A5\u7269\u76EE\u5F55:");
|
|
10467
|
+
logger.info(` .handoff/${answers.ticket}/`);
|
|
10468
|
+
const { runNow } = await inquirer13.prompt([
|
|
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"
|
|
10481
|
+
});
|
|
10482
|
+
}
|
|
10483
|
+
} catch (error) {
|
|
10484
|
+
logger.error(`\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: ${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;
|
|
10560
|
+
}
|
|
10561
|
+
const ticketChoices = sessions.map((s) => {
|
|
10562
|
+
const ticket2 = s.replace("session-", "").replace(".json", "");
|
|
10563
|
+
return { name: ticket2, value: ticket2 };
|
|
10564
|
+
});
|
|
10565
|
+
const { ticket } = await inquirer13.prompt([
|
|
10566
|
+
{
|
|
10567
|
+
type: "list",
|
|
10568
|
+
name: "ticket",
|
|
10569
|
+
message: "\u9009\u62E9\u4F1A\u8BDD:",
|
|
10570
|
+
choices: ticketChoices
|
|
10571
|
+
}
|
|
10572
|
+
]);
|
|
10573
|
+
const phaseChoices = PHASE_SEQUENCE.map((p, i) => ({
|
|
10574
|
+
name: `${i + 1}. ${getPhaseDisplayName(p)}`,
|
|
10575
|
+
value: String(i + 1)
|
|
10576
|
+
}));
|
|
10577
|
+
const { phaseNum } = await inquirer13.prompt([
|
|
10578
|
+
{
|
|
10579
|
+
type: "list",
|
|
10580
|
+
name: "phaseNum",
|
|
10581
|
+
message: "\u9009\u62E9\u9636\u6BB5:",
|
|
10582
|
+
choices: phaseChoices
|
|
10583
|
+
}
|
|
10584
|
+
]);
|
|
10585
|
+
await handleRunPhase(worktreeManager, handoffManager, executor2, {
|
|
10586
|
+
ticket,
|
|
10587
|
+
phase: phaseNum
|
|
10588
|
+
});
|
|
10589
|
+
}
|
|
10590
|
+
async function checkPhaseGate(ticket, phaseName, completedPhases) {
|
|
10591
|
+
const gateNum = getGateNumber(phaseName);
|
|
10592
|
+
const conditions = GATE_CONDITIONS[gateNum] || [];
|
|
10593
|
+
logger.newLine();
|
|
10594
|
+
console.log("\u2550".repeat(60));
|
|
10595
|
+
console.log(`\u{1F6AA} Gate ${gateNum} \u68C0\u67E5: ${getPhaseDisplayName(phaseName)}`);
|
|
10596
|
+
console.log("\u2550".repeat(60));
|
|
10597
|
+
console.log("");
|
|
10598
|
+
const canEnter = canEnterPhase(phaseName, completedPhases);
|
|
10599
|
+
if (!canEnter) {
|
|
10600
|
+
console.log("\u26A0\uFE0F \u524D\u7F6E\u9636\u6BB5\u672A\u5B8C\u6210:");
|
|
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)}`);
|
|
10605
|
+
}
|
|
10606
|
+
console.log("");
|
|
10607
|
+
logger.warn("\u8BF7\u5148\u5B8C\u6210\u524D\u7F6E\u9636\u6BB5\u540E\u518D\u8FDB\u5165\u6B64\u9636\u6BB5");
|
|
10608
|
+
return false;
|
|
10609
|
+
}
|
|
10610
|
+
console.log("\u2713 \u524D\u7F6E\u9636\u6BB5\u5DF2\u5168\u90E8\u5B8C\u6210");
|
|
10611
|
+
console.log("");
|
|
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}`);
|
|
10620
|
+
}
|
|
10621
|
+
}
|
|
10622
|
+
console.log("");
|
|
10623
|
+
console.log("\u2500".repeat(60));
|
|
10624
|
+
const { passed } = await inquirer13.prompt([
|
|
10625
|
+
{
|
|
10626
|
+
type: "confirm",
|
|
10627
|
+
name: "passed",
|
|
10628
|
+
message: "\u662F\u5426\u786E\u8BA4\u901A\u8FC7\u6B64 Gate? (\u786E\u8BA4\u540E\u9636\u6BB5\u5C06\u88AB\u6807\u8BB0\u4E3A\u5B8C\u6210)",
|
|
10629
|
+
default: false
|
|
10630
|
+
}
|
|
10631
|
+
]);
|
|
10632
|
+
return passed;
|
|
10633
|
+
}
|
|
10634
|
+
async function handleRunPhase(worktreeManager, handoffManager, executor2, options) {
|
|
10635
|
+
const { ticket, runPhase: phase, goal } = options;
|
|
10636
|
+
const phaseIndex = parseInt(phase) - 1;
|
|
10637
|
+
const phaseName = PHASE_SEQUENCE[phaseIndex];
|
|
10638
|
+
if (!phaseName) {
|
|
10639
|
+
logger.error(`\u65E0\u6548\u7684\u9636\u6BB5: ${phase}`);
|
|
10640
|
+
return;
|
|
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
|
|
10654
|
+
}
|
|
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;
|
|
10669
|
+
}
|
|
10670
|
+
try {
|
|
10671
|
+
const results = await executor2.executePhase(ticket, phaseName, {
|
|
10672
|
+
TICKET: ticket,
|
|
10673
|
+
GOAL: goal || state.ticket || "",
|
|
10674
|
+
PHASE: phaseName
|
|
10675
|
+
});
|
|
10676
|
+
await executor2.saveReport(ticket, results);
|
|
10677
|
+
await worktreeManager.updateSessionState(ticket, {
|
|
10678
|
+
currentPhase: phaseName
|
|
10679
|
+
});
|
|
10680
|
+
const allSuccess = results.every((r) => r.success);
|
|
10681
|
+
if (allSuccess) {
|
|
10682
|
+
logger.success("\u9636\u6BB5\u6267\u884C\u6210\u529F!");
|
|
10683
|
+
} else {
|
|
10684
|
+
logger.warn("\u90E8\u5206\u4EFB\u52A1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8F93\u51FA");
|
|
10685
|
+
}
|
|
10686
|
+
} catch (error) {
|
|
10687
|
+
logger.error(`\u9636\u6BB5\u6267\u884C\u5931\u8D25: ${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}`);
|
|
10721
|
+
}
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
|
|
8731
10725
|
// src/index.ts
|
|
8732
|
-
var __dirname2 =
|
|
8733
|
-
var pkg =
|
|
8734
|
-
var program = new
|
|
10726
|
+
var __dirname2 = path25.dirname(fileURLToPath2(import.meta.url));
|
|
10727
|
+
var pkg = fs9.readJsonSync(path25.join(__dirname2, "../package.json"));
|
|
10728
|
+
var program = new Command19();
|
|
8735
10729
|
program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
|
|
8736
10730
|
program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
|
|
8737
10731
|
program.addCommand(initCommand);
|
|
@@ -8752,6 +10746,7 @@ program.addCommand(logsCommand);
|
|
|
8752
10746
|
program.addCommand(updateCommand);
|
|
8753
10747
|
program.addCommand(configCommand);
|
|
8754
10748
|
program.addCommand(diffCommand);
|
|
10749
|
+
program.addCommand(devWorktreeCommand);
|
|
8755
10750
|
program.action(() => {
|
|
8756
10751
|
showHelp();
|
|
8757
10752
|
});
|
|
@@ -8778,6 +10773,7 @@ function showHelp() {
|
|
|
8778
10773
|
console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
|
|
8779
10774
|
console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
|
|
8780
10775
|
console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
|
|
10776
|
+
console.log(" team-cli dev:wt [options] Worktree Orchestrator - \u591A\u89D2\u8272\u5E76\u884C\u5F00\u53D1");
|
|
8781
10777
|
console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
|
|
8782
10778
|
console.log("");
|
|
8783
10779
|
console.log(chalk4.bold("\u793A\u4F8B:"));
|