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/dist/cli.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: execa5 } = await import("execa");
383
- const { stdout } = await execa5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
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: execa5 } = await import("execa");
393
- const { stdout } = await execa5("git", ["rev-parse", "HEAD"], { cwd });
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
  };
@@ -4647,8 +4647,8 @@ var init_bugfix = __esm({
4647
4647
  {
4648
4648
  title: "\u521B\u5EFA hotfix \u5206\u652F",
4649
4649
  task: async () => {
4650
- const { execa: execa5 } = await import("execa");
4651
- await execa5("git", ["checkout", "-b", branchName], { stdio: "inherit" });
4650
+ const { execa: execa6 } = await import("execa");
4651
+ await execa6("git", ["checkout", "-b", branchName], { stdio: "inherit" });
4652
4652
  }
4653
4653
  },
4654
4654
  {
@@ -4672,9 +4672,9 @@ var init_bugfix = __esm({
4672
4672
  {
4673
4673
  title: "\u521B\u5EFA\u521D\u59CB commit",
4674
4674
  task: async () => {
4675
- const { execa: execa5 } = await import("execa");
4676
- await execa5("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
4677
- await execa5(
4675
+ const { execa: execa6 } = await import("execa");
4676
+ await execa6("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
4677
+ await execa6(
4678
4678
  "git",
4679
4679
  [
4680
4680
  "commit",
@@ -5754,11 +5754,11 @@ async function verifyApiImplementationByPath(apiPath) {
5754
5754
  return { status: "pending" };
5755
5755
  }
5756
5756
  const method = apiMatch[1].toUpperCase();
5757
- const path22 = "/" + apiMatch[2];
5757
+ const path26 = "/" + apiMatch[2];
5758
5758
  const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
5759
5759
  for (const cf of controllerFiles) {
5760
5760
  const content = await FileUtils.read(cf);
5761
- if (content.includes(`"${path22}"`) || content.includes(`"${path22}/"`)) {
5761
+ if (content.includes(`"${path26}"`) || content.includes(`"${path26}/"`)) {
5762
5762
  const annotationMap = {
5763
5763
  GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
5764
5764
  POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
@@ -7205,15 +7205,15 @@ async function syncTemplateVersions(aiMemoryFile, projectDir) {
7205
7205
  await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
7206
7206
  }
7207
7207
  function extractRepoName(repository) {
7208
- let path22 = repository;
7209
- path22 = path22.replace(/^https?:\/\//, "");
7210
- path22 = path22.replace(/^git@/, "");
7211
- const parts = path22.split("/");
7208
+ let path26 = repository;
7209
+ path26 = path26.replace(/^https?:\/\//, "");
7210
+ path26 = path26.replace(/^git@/, "");
7211
+ const parts = path26.split("/");
7212
7212
  if (parts.length > 1) {
7213
- path22 = parts.slice(1).join("/");
7213
+ path26 = parts.slice(1).join("/");
7214
7214
  }
7215
- path22 = path22.replace(/\.git$/, "");
7216
- return path22;
7215
+ path26 = path26.replace(/\.git$/, "");
7216
+ return path26;
7217
7217
  }
7218
7218
  var syncMemoryCommand;
7219
7219
  var init_sync_memory = __esm({
@@ -7505,10 +7505,10 @@ function extractApisFromRegistry(registryContent) {
7505
7505
  let match;
7506
7506
  while ((match = apiRegex.exec(registryContent)) !== null) {
7507
7507
  const method = match[1];
7508
- const path22 = match[2].trim();
7508
+ const path26 = match[2].trim();
7509
7509
  const description = match[3].trim();
7510
- const key = `${method}:${path22}`;
7511
- apis.set(key, { method, path: path22, description });
7510
+ const key = `${method}:${path26}`;
7511
+ apis.set(key, { method, path: path26, description });
7512
7512
  }
7513
7513
  return apis;
7514
7514
  }
@@ -8090,22 +8090,22 @@ var init_gitlab_api = __esm({
8090
8090
  * 从 Git URL 中提取项目路径
8091
8091
  */
8092
8092
  static parseProjectPath(repository) {
8093
- let path22 = repository;
8094
- if (path22.startsWith("git@")) {
8095
- path22 = path22.replace(/^git@/, "");
8096
- const colonIndex = path22.indexOf(":");
8093
+ let path26 = repository;
8094
+ if (path26.startsWith("git@")) {
8095
+ path26 = path26.replace(/^git@/, "");
8096
+ const colonIndex = path26.indexOf(":");
8097
8097
  if (colonIndex !== -1) {
8098
- path22 = path22.substring(colonIndex + 1);
8098
+ path26 = path26.substring(colonIndex + 1);
8099
8099
  }
8100
8100
  } else {
8101
- path22 = path22.replace(/^https?:\/\//, "");
8102
- const parts = path22.split("/");
8101
+ path26 = path26.replace(/^https?:\/\//, "");
8102
+ const parts = path26.split("/");
8103
8103
  if (parts.length > 1) {
8104
- path22 = parts.slice(1).join("/");
8104
+ path26 = parts.slice(1).join("/");
8105
8105
  }
8106
8106
  }
8107
- path22 = path22.replace(/\.git$/, "");
8108
- return path22;
8107
+ path26 = path26.replace(/\.git$/, "");
8108
+ return path26;
8109
8109
  }
8110
8110
  /**
8111
8111
  * 编码项目路径用于 API 请求
@@ -8604,9 +8604,9 @@ async function compareTemplate(projectPath, type, localConfig, remoteTag, remote
8604
8604
  }
8605
8605
  }
8606
8606
  if (!remoteCommit) {
8607
- const { execa: execa5 } = await import("execa");
8607
+ const { execa: execa6 } = await import("execa");
8608
8608
  const ref = remoteTag || remoteBranch || "HEAD";
8609
- const { stdout } = await execa5("git", ["ls-remote", repository, ref], {
8609
+ const { stdout } = await execa6("git", ["ls-remote", repository, ref], {
8610
8610
  stdio: "pipe"
8611
8611
  });
8612
8612
  remoteCommit = stdout.split(" ")[0];
@@ -8850,12 +8850,2050 @@ var init_diff = __esm({
8850
8850
  }
8851
8851
  });
8852
8852
 
8853
+ // src/types/worktree.ts
8854
+ var DEFAULT_WORKTREE_CONFIG, DEFAULT_ROLES;
8855
+ var init_worktree = __esm({
8856
+ "src/types/worktree.ts"() {
8857
+ "use strict";
8858
+ init_esm_shims();
8859
+ DEFAULT_WORKTREE_CONFIG = {
8860
+ baseDir: "../wt/",
8861
+ handoffDir: ".handoff/",
8862
+ templateDir: ".handoff/templates/"
8863
+ };
8864
+ DEFAULT_ROLES = [
8865
+ {
8866
+ role: "planner",
8867
+ displayName: "Planner",
8868
+ description: "\u9700\u6C42\u5206\u6790\u4E0E BDD \u573A\u666F\u8BBE\u8BA1",
8869
+ branchPrefix: "feat",
8870
+ worktreeSuffix: "planner",
8871
+ required: true
8872
+ },
8873
+ {
8874
+ role: "architect",
8875
+ displayName: "Architect",
8876
+ description: "\u6280\u672F\u67B6\u6784\u4E0E API \u8BBE\u8BA1",
8877
+ branchPrefix: "feat",
8878
+ worktreeSuffix: "arch",
8879
+ required: true
8880
+ },
8881
+ {
8882
+ role: "backend",
8883
+ displayName: "Backend",
8884
+ description: "\u540E\u7AEF\u5B9E\u73B0\u4E0E OpenAPI",
8885
+ branchPrefix: "feat",
8886
+ worktreeSuffix: "backend",
8887
+ required: true
8888
+ },
8889
+ {
8890
+ role: "frontend",
8891
+ displayName: "Frontend",
8892
+ description: "\u524D\u7AEF\u5B9E\u73B0",
8893
+ branchPrefix: "feat",
8894
+ worktreeSuffix: "frontend",
8895
+ required: true
8896
+ },
8897
+ {
8898
+ role: "qa",
8899
+ displayName: "QA/BDD",
8900
+ description: "BDD \u9A8C\u6536\u6D4B\u8BD5",
8901
+ branchPrefix: "test",
8902
+ worktreeSuffix: "qa",
8903
+ required: true
8904
+ },
8905
+ {
8906
+ role: "reviewer",
8907
+ displayName: "Reviewer",
8908
+ description: "\u4EE3\u7801\u5BA1\u67E5\u4E0E\u53D1\u5E03",
8909
+ branchPrefix: "integration",
8910
+ worktreeSuffix: "review",
8911
+ required: true
8912
+ }
8913
+ ];
8914
+ }
8915
+ });
8916
+
8917
+ // src/lib/worktree-manager.ts
8918
+ import { execa as execa5 } from "execa";
8919
+ import path21 from "path";
8920
+ import fs6 from "fs-extra";
8921
+ var WorktreeManager;
8922
+ var init_worktree_manager = __esm({
8923
+ "src/lib/worktree-manager.ts"() {
8924
+ "use strict";
8925
+ init_esm_shims();
8926
+ init_worktree();
8927
+ init_logger();
8928
+ WorktreeManager = class {
8929
+ repoRoot;
8930
+ wtBaseDir;
8931
+ roles;
8932
+ constructor(repoRoot) {
8933
+ this.repoRoot = repoRoot || process.cwd();
8934
+ this.wtBaseDir = path21.resolve(this.repoRoot, DEFAULT_WORKTREE_CONFIG.baseDir);
8935
+ this.roles = DEFAULT_ROLES;
8936
+ }
8937
+ /**
8938
+ * 创建新的 Worktree 会话
8939
+ */
8940
+ async createSession(config) {
8941
+ const { ticket, goal, baseBranch, enabledRoles } = config;
8942
+ const enabled = enabledRoles || this.roles.map((r) => r.role);
8943
+ logger.info(`\u521B\u5EFA Worktree \u4F1A\u8BDD: ${ticket}`);
8944
+ logger.info(`\u76EE\u6807: ${goal}`);
8945
+ logger.info(`\u57FA\u7840\u5206\u652F: ${baseBranch}`);
8946
+ await fs6.ensureDir(this.wtBaseDir);
8947
+ const worktrees = [];
8948
+ for (const roleConfig of this.roles) {
8949
+ if (!enabled.includes(roleConfig.role)) {
8950
+ continue;
8951
+ }
8952
+ const wtName = `${roleConfig.worktreeSuffix}-${ticket}`;
8953
+ const wtPath = path21.join(this.wtBaseDir, wtName);
8954
+ const branchName = `${roleConfig.branchPrefix}/${ticket}-${roleConfig.worktreeSuffix}`;
8955
+ const exists = await this.exists(wtName);
8956
+ if (exists) {
8957
+ logger.warn(`Worktree \u5DF2\u5B58\u5728: ${wtName}`);
8958
+ worktrees.push({
8959
+ role: roleConfig.role,
8960
+ name: wtName,
8961
+ path: path21.relative(this.repoRoot, wtPath),
8962
+ branch: branchName,
8963
+ status: "idle",
8964
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
8965
+ });
8966
+ continue;
8967
+ }
8968
+ try {
8969
+ await this.create(wtName, branchName, baseBranch);
8970
+ logger.success(`\u521B\u5EFA Worktree: ${wtName}`);
8971
+ worktrees.push({
8972
+ role: roleConfig.role,
8973
+ name: wtName,
8974
+ path: path21.relative(this.repoRoot, wtPath),
8975
+ branch: branchName,
8976
+ status: "idle",
8977
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
8978
+ });
8979
+ } catch (error) {
8980
+ logger.error(`\u521B\u5EFA Worktree \u5931\u8D25: ${wtName}`);
8981
+ throw error;
8982
+ }
8983
+ }
8984
+ const sessionConfig = {
8985
+ ticket,
8986
+ goal,
8987
+ baseBranch,
8988
+ worktrees,
8989
+ phase: "planning",
8990
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8991
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8992
+ };
8993
+ await this.saveSessionState(sessionConfig);
8994
+ return sessionConfig;
8995
+ }
8996
+ /**
8997
+ * 创建 worktree
8998
+ */
8999
+ async create(name, branch, baseBranch) {
9000
+ const wtPath = path21.join(this.wtBaseDir, name);
9001
+ if (await fs6.pathExists(wtPath)) {
9002
+ logger.warn(`Worktree \u5DF2\u5B58\u5728\uFF0C\u5148\u5220\u9664: ${name}`);
9003
+ await this.remove(name);
9004
+ }
9005
+ const args = ["worktree", "add", wtPath];
9006
+ if (baseBranch) {
9007
+ args.push(baseBranch);
9008
+ } else {
9009
+ args.push("-b", branch);
9010
+ }
9011
+ await this.runGitCommand(args);
9012
+ }
9013
+ /**
9014
+ * 删除 worktree
9015
+ */
9016
+ async remove(name, force) {
9017
+ const wtPath = path21.join(this.wtBaseDir, name);
9018
+ if (!fs6.pathExists(wtPath)) {
9019
+ logger.warn(`Worktree \u4E0D\u5B58\u5728: ${name}`);
9020
+ return;
9021
+ }
9022
+ const args = force ? ["worktree", "remove", "--force", wtPath] : ["worktree", "remove", wtPath];
9023
+ await this.runGitCommand(args);
9024
+ logger.success(`\u5220\u9664 Worktree: ${name}`);
9025
+ }
9026
+ /**
9027
+ * 列出所有 worktree
9028
+ */
9029
+ async list() {
9030
+ const worktrees = [];
9031
+ if (!fs6.pathExists(this.wtBaseDir)) {
9032
+ return [];
9033
+ }
9034
+ const entries = await fs6.readdir(this.wtBaseDir, { withFileTypes: true });
9035
+ for (const entry of entries) {
9036
+ if (!entry.isDirectory()) {
9037
+ continue;
9038
+ }
9039
+ const name = entry.name;
9040
+ const wtPath = path21.join(this.wtBaseDir, name);
9041
+ const isWorktree = await this.isWorktree(wtPath);
9042
+ if (!isWorktree) {
9043
+ continue;
9044
+ }
9045
+ const branch = await this.getBranch(wtPath);
9046
+ const status = await this.getWorktreeStatus(wtPath);
9047
+ worktrees.push({
9048
+ role: this.extractRole(name),
9049
+ name,
9050
+ path: path21.relative(this.repoRoot, wtPath),
9051
+ branch,
9052
+ status,
9053
+ lastActivity: await this.getLastActivity(wtPath)
9054
+ });
9055
+ }
9056
+ return worktrees;
9057
+ }
9058
+ /**
9059
+ * 切换到 worktree
9060
+ */
9061
+ async checkout(name) {
9062
+ const wtPath = path21.join(this.wtBaseDir, name);
9063
+ if (!fs6.pathExists(wtPath)) {
9064
+ throw new Error(`Worktree \u4E0D\u5B58\u5728: ${name}`);
9065
+ }
9066
+ logger.info(`\u5207\u6362\u5230 Worktree: ${name}`);
9067
+ logger.info(`\u8DEF\u5F84: ${wtPath}`);
9068
+ logger.info("");
9069
+ logger.info("\u8BF7\u5728\u65B0\u7684\u7EC8\u7AEF\u7A97\u53E3\u4E2D\u6267\u884C:");
9070
+ logger.info(` cd ${wtPath}`);
9071
+ logger.info(` git checkout ${await this.getBranch(wtPath)}`);
9072
+ logger.info("");
9073
+ logger.info("\u6216\u8005\u4F7F\u7528 IDE \u6253\u5F00\u8BE5\u76EE\u5F55\u7EE7\u7EED\u5F00\u53D1");
9074
+ }
9075
+ /**
9076
+ * 切换到 worktree 并执行命令
9077
+ */
9078
+ async exec(name, command) {
9079
+ const wtPath = path21.join(this.wtBaseDir, name);
9080
+ if (!fs6.pathExists(wtPath)) {
9081
+ throw new Error(`Worktree \u4E0D\u5B58\u5728: ${name}`);
9082
+ }
9083
+ return await this.runGitCommand(["-C", wtPath, ...command.split(" ")]);
9084
+ }
9085
+ /**
9086
+ * 检查 worktree 是否存在
9087
+ */
9088
+ async exists(name) {
9089
+ const wtPath = path21.join(this.wtBaseDir, name);
9090
+ return this.isWorktree(wtPath);
9091
+ }
9092
+ /**
9093
+ * 获取 worktree 路径
9094
+ */
9095
+ getPath(name) {
9096
+ return path21.join(this.wtBaseDir, name);
9097
+ }
9098
+ /**
9099
+ * 清理孤立 worktree
9100
+ */
9101
+ async prune() {
9102
+ await this.runGitCommand(["worktree", "prune"]);
9103
+ logger.success("\u6E05\u7406\u5B64\u7ACB worktree \u5B8C\u6210");
9104
+ }
9105
+ /**
9106
+ * 获取会话状态
9107
+ */
9108
+ async getSessionState(ticket) {
9109
+ const stateFile = this.getStateFilePath(ticket);
9110
+ if (!fs6.pathExists(stateFile)) {
9111
+ return null;
9112
+ }
9113
+ const content = await fs6.readFile(stateFile, "utf-8");
9114
+ return JSON.parse(content);
9115
+ }
9116
+ /**
9117
+ * 更新会话状态
9118
+ */
9119
+ async updateSessionState(ticket, updates) {
9120
+ const state = await this.getSessionState(ticket);
9121
+ if (!state) {
9122
+ throw new Error(`\u4F1A\u8BDD\u4E0D\u5B58\u5728: ${ticket}`);
9123
+ }
9124
+ const updated = {
9125
+ ...state,
9126
+ ...updates,
9127
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
9128
+ };
9129
+ await fs6.writeFile(
9130
+ this.getStateFilePath(ticket),
9131
+ JSON.stringify(updated, null, 2)
9132
+ );
9133
+ }
9134
+ /**
9135
+ * 获取指定角色的 worktree
9136
+ */
9137
+ async getWorktreeByRole(ticket, role) {
9138
+ const state = await this.getSessionState(ticket);
9139
+ if (!state) {
9140
+ return null;
9141
+ }
9142
+ return state.worktrees.get(role) || null;
9143
+ }
9144
+ /**
9145
+ * 初始化 handoff 目录
9146
+ */
9147
+ async initHandoffDirs(ticket) {
9148
+ const handoffBase = path21.join(this.repoRoot, ".handoff", ticket);
9149
+ const roles = this.roles.map((r) => r.role);
9150
+ for (const role of roles) {
9151
+ const roleDir = path21.join(handoffBase, role);
9152
+ await fs6.ensureDir(roleDir);
9153
+ }
9154
+ const bddDir = path21.join(handoffBase, "planner", "BDD_FEATURES");
9155
+ await fs6.ensureDir(bddDir);
9156
+ logger.success(`\u521D\u59CB\u5316 handoff \u76EE\u5F55: ${handoffBase}`);
9157
+ }
9158
+ // ========== 私有方法 ==========
9159
+ /**
9160
+ * 运行 git 命令
9161
+ */
9162
+ async runGitCommand(args) {
9163
+ try {
9164
+ const result = await execa5("git", args, {
9165
+ cwd: this.repoRoot,
9166
+ stdio: "pipe"
9167
+ });
9168
+ return {
9169
+ stdout: result.stdout || "",
9170
+ stderr: result.stderr || "",
9171
+ exitCode: result.exitCode || 0
9172
+ };
9173
+ } catch (error) {
9174
+ return {
9175
+ stdout: error.stdout || "",
9176
+ stderr: error.stderr || error.message,
9177
+ exitCode: error.exitCode || -1
9178
+ };
9179
+ }
9180
+ }
9181
+ /**
9182
+ * 检查路径是否是有效的 worktree
9183
+ */
9184
+ async isWorktree(wtPath) {
9185
+ const gitDir = path21.join(wtPath, ".git");
9186
+ if (!fs6.pathExists(gitDir)) {
9187
+ return false;
9188
+ }
9189
+ const stats = await fs6.stat(gitDir);
9190
+ if (!stats.isFile()) {
9191
+ return false;
9192
+ }
9193
+ const content = await fs6.readFile(gitDir, "utf-8");
9194
+ return content.includes("gitdir:");
9195
+ }
9196
+ /**
9197
+ * 获取 worktree 的分支
9198
+ */
9199
+ async getBranch(wtPath) {
9200
+ const result = await this.runGitCommand(["-C", wtPath, "rev-parse", "--abbrev-ref", "HEAD"]);
9201
+ return result.stdout.trim();
9202
+ }
9203
+ /**
9204
+ * 获取 worktree 的状态
9205
+ */
9206
+ async getWorktreeStatus(wtPath) {
9207
+ const status = await this.runGitCommand(["-C", wtPath, "status", "--porcelain"]);
9208
+ if (status.stdout.trim()) {
9209
+ return "active";
9210
+ }
9211
+ return "idle";
9212
+ }
9213
+ /**
9214
+ * 获取最后活动时间
9215
+ */
9216
+ async getLastActivity(wtPath) {
9217
+ const reflog = await this.runGitCommand(["-C", wtPath, "reflog", "-n", "1"]);
9218
+ if (!reflog.stdout) {
9219
+ return (/* @__PURE__ */ new Date()).toISOString();
9220
+ }
9221
+ const match = reflog.stdout.match(/HEAD@\{([^}]+)\}/);
9222
+ if (match) {
9223
+ return match[1];
9224
+ }
9225
+ return (/* @__PURE__ */ new Date()).toISOString();
9226
+ }
9227
+ /**
9228
+ * 从 worktree 名称提取角色
9229
+ */
9230
+ extractRole(name) {
9231
+ for (const roleConfig of this.roles) {
9232
+ if (name.startsWith(roleConfig.worktreeSuffix)) {
9233
+ return roleConfig.role;
9234
+ }
9235
+ }
9236
+ return "backend";
9237
+ }
9238
+ /**
9239
+ * 获取状态文件路径
9240
+ */
9241
+ getStateFilePath(ticket) {
9242
+ return path21.join(this.repoRoot, ".handoff", `session-${ticket}.json`);
9243
+ }
9244
+ /**
9245
+ * 保存会话状态
9246
+ */
9247
+ async saveSessionState(config) {
9248
+ const stateFile = this.getStateFilePath(config.ticket);
9249
+ const worktrees = /* @__PURE__ */ new Map();
9250
+ for (const wt of config.worktrees) {
9251
+ worktrees.set(wt.role, wt);
9252
+ }
9253
+ const sessionState = {
9254
+ ticket: config.ticket,
9255
+ currentPhase: config.phase,
9256
+ completedPhases: [],
9257
+ worktrees,
9258
+ results: /* @__PURE__ */ new Map(),
9259
+ gateStatuses: /* @__PURE__ */ new Map(),
9260
+ startedAt: config.createdAt,
9261
+ lastActivity: config.updatedAt
9262
+ };
9263
+ await fs6.ensureDir(path21.dirname(stateFile));
9264
+ await fs6.writeFile(stateFile, JSON.stringify(sessionState, null, 2));
9265
+ }
9266
+ };
9267
+ }
9268
+ });
9269
+
9270
+ // src/lib/handoff-manager.ts
9271
+ import fs7 from "fs-extra";
9272
+ import path22 from "path";
9273
+ function createHandoffManager(repoRoot) {
9274
+ return new HandoffManager(repoRoot);
9275
+ }
9276
+ var HandoffManager;
9277
+ var init_handoff_manager = __esm({
9278
+ "src/lib/handoff-manager.ts"() {
9279
+ "use strict";
9280
+ init_esm_shims();
9281
+ init_worktree();
9282
+ init_logger();
9283
+ HandoffManager = class {
9284
+ repoRoot;
9285
+ handoffDir;
9286
+ templateDir;
9287
+ constructor(repoRoot) {
9288
+ this.repoRoot = repoRoot || process.cwd();
9289
+ this.handoffDir = path22.join(this.repoRoot, DEFAULT_WORKTREE_CONFIG.handoffDir);
9290
+ this.templateDir = path22.join(this.repoRoot, DEFAULT_WORKTREE_CONFIG.templateDir);
9291
+ }
9292
+ /**
9293
+ * 初始化交接物目录
9294
+ */
9295
+ async init(ticket, enabledRoles) {
9296
+ const handoffBase = path22.join(this.handoffDir, ticket);
9297
+ const roles = enabledRoles || ["planner", "architect", "backend", "frontend", "qa", "reviewer"];
9298
+ logger.info(`\u521D\u59CB\u5316\u4EA4\u63A5\u7269\u76EE\u5F55: ${handoffBase}`);
9299
+ for (const role of roles) {
9300
+ const roleDir = path22.join(handoffBase, role);
9301
+ await fs7.ensureDir(roleDir);
9302
+ }
9303
+ const bddDir = path22.join(handoffBase, "planner", "BDD_FEATURES");
9304
+ await fs7.ensureDir(bddDir);
9305
+ logger.success(`\u4EA4\u63A5\u7269\u76EE\u5F55\u5DF2\u521B\u5EFA: ${handoffBase}`);
9306
+ }
9307
+ /**
9308
+ * 生成交接物清单
9309
+ */
9310
+ async generateManifest(ticket, phase) {
9311
+ const handoffDir = path22.join(this.handoffDir, ticket);
9312
+ const items = [];
9313
+ const phaseItems = this.getRequiredItemsByPhase(phase);
9314
+ for (const item of phaseItems) {
9315
+ const filePath = path22.join(handoffDir, item.role, item.file);
9316
+ items.push({
9317
+ id: `${ticket}-${item.role}-${item.type}`,
9318
+ role: item.role,
9319
+ file: item.file,
9320
+ type: item.type,
9321
+ description: item.description,
9322
+ status: fs7.pathExistsSync(filePath) ? "ready" : "pending",
9323
+ requiredBy: item.requiredBy
9324
+ });
9325
+ }
9326
+ const manifest = {
9327
+ ticket,
9328
+ phase,
9329
+ fromRole: "system",
9330
+ toRoles: items.map((i) => i.role),
9331
+ items,
9332
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9333
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9334
+ };
9335
+ await this.saveManifest(ticket, manifest);
9336
+ return manifest;
9337
+ }
9338
+ /**
9339
+ * 验证交接物完整性
9340
+ */
9341
+ async verify(ticket) {
9342
+ const manifest = await this.loadManifest(ticket);
9343
+ if (!manifest) {
9344
+ return {
9345
+ complete: false,
9346
+ items: [],
9347
+ missing: ["Manifest not found"]
9348
+ };
9349
+ }
9350
+ const missing = [];
9351
+ const handoffDir = path22.join(this.handoffDir, ticket);
9352
+ for (const item of manifest.items) {
9353
+ const filePath = path22.join(handoffDir, item.role, item.file);
9354
+ if (!fs7.existsSync(filePath)) {
9355
+ missing.push(`${item.role}/${item.file}`);
9356
+ item.status = "pending";
9357
+ } else {
9358
+ item.status = "ready";
9359
+ }
9360
+ }
9361
+ await this.saveManifest(ticket, manifest);
9362
+ return {
9363
+ complete: missing.length === 0,
9364
+ items: manifest.items,
9365
+ missing
9366
+ };
9367
+ }
9368
+ /**
9369
+ * 生成模板文件
9370
+ */
9371
+ async generateTemplate(ticket, role, templateType, data) {
9372
+ const templatePath = path22.join(this.templateDir, `${templateType}.md`);
9373
+ const outputPath = path22.join(this.handoffDir, ticket, role, `${templateType}.md`);
9374
+ if (!fs7.existsSync(templatePath)) {
9375
+ throw new Error(`\u6A21\u677F\u4E0D\u5B58\u5728: ${templatePath}`);
9376
+ }
9377
+ let content = await fs7.readFile(templatePath, "utf-8");
9378
+ for (const [key, value] of Object.entries(data)) {
9379
+ content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
9380
+ }
9381
+ await fs7.ensureDir(path22.dirname(outputPath));
9382
+ await fs7.writeFile(outputPath, content, "utf-8");
9383
+ logger.success(`\u751F\u6210\u6A21\u677F: ${outputPath}`);
9384
+ return outputPath;
9385
+ }
9386
+ /**
9387
+ * 生成 OpenAPI 模板
9388
+ */
9389
+ async generateOpenAPITemplate(ticket, data) {
9390
+ const templatePath = path22.join(this.templateDir, "API_OPENAPI.yaml");
9391
+ const outputPath = path22.join(this.handoffDir, ticket, "backend", "API_OPENAPI.yaml");
9392
+ if (!fs7.existsSync(templatePath)) {
9393
+ throw new Error(`\u6A21\u677F\u4E0D\u5B58\u5728: ${templatePath}`);
9394
+ }
9395
+ let content = await fs7.readFile(templatePath, "utf-8");
9396
+ for (const [key, value] of Object.entries(data)) {
9397
+ content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
9398
+ }
9399
+ await fs7.ensureDir(path22.dirname(outputPath));
9400
+ await fs7.writeFile(outputPath, content, "utf-8");
9401
+ logger.success(`\u751F\u6210 OpenAPI: ${outputPath}`);
9402
+ return outputPath;
9403
+ }
9404
+ /**
9405
+ * 生成 BDD Feature 模板
9406
+ */
9407
+ async generateBDDFeature(ticket, featureName, scenarios) {
9408
+ const outputPath = path22.join(
9409
+ this.handoffDir,
9410
+ ticket,
9411
+ "planner",
9412
+ "BDD_FEATURES",
9413
+ `${featureName}.feature`
9414
+ );
9415
+ const lines = [];
9416
+ lines.push(`# language: zh-CN`);
9417
+ lines.push(`@${ticket}`);
9418
+ lines.push(`\u529F\u80FD: ${featureName}`);
9419
+ lines.push("");
9420
+ for (const scenario of scenarios) {
9421
+ lines.push(` \u573A\u666F: ${scenario.title} [${scenario.priority}]`);
9422
+ for (const given of scenario.given) {
9423
+ lines.push(` \u5047\u5982 ${given}`);
9424
+ }
9425
+ for (const when of scenario.when) {
9426
+ lines.push(` \u5F53 ${when}`);
9427
+ }
9428
+ for (const then of scenario.then) {
9429
+ lines.push(` \u90A3\u4E48 ${then}`);
9430
+ }
9431
+ lines.push("");
9432
+ }
9433
+ await fs7.ensureDir(path22.dirname(outputPath));
9434
+ await fs7.writeFile(outputPath, lines.join("\n"), "utf-8");
9435
+ logger.success(`\u751F\u6210 BDD Feature: ${outputPath}`);
9436
+ return outputPath;
9437
+ }
9438
+ /**
9439
+ * 获取交接物清单
9440
+ */
9441
+ async loadManifest(ticket) {
9442
+ const manifestPath = path22.join(this.handoffDir, ticket, "MANIFEST.json");
9443
+ if (!fs7.existsSync(manifestPath)) {
9444
+ return null;
9445
+ }
9446
+ const content = await fs7.readFile(manifestPath, "utf-8");
9447
+ return JSON.parse(content);
9448
+ }
9449
+ /**
9450
+ * 保存交接物清单
9451
+ */
9452
+ async saveManifest(ticket, manifest) {
9453
+ const manifestPath = path22.join(this.handoffDir, ticket, "MANIFEST.json");
9454
+ await fs7.ensureDir(path22.dirname(manifestPath));
9455
+ await fs7.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
9456
+ }
9457
+ /**
9458
+ * 标记交接物已验证
9459
+ */
9460
+ async markVerified(ticket, file, verifiedBy) {
9461
+ const manifest = await this.loadManifest(ticket);
9462
+ if (!manifest) {
9463
+ throw new Error(`Manifest not found: ${ticket}`);
9464
+ }
9465
+ for (const item of manifest.items) {
9466
+ if (item.file === file) {
9467
+ item.status = "verified";
9468
+ item.verifiedBy = verifiedBy;
9469
+ item.verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
9470
+ break;
9471
+ }
9472
+ }
9473
+ await this.saveManifest(ticket, manifest);
9474
+ }
9475
+ /**
9476
+ * 获取指定角色的交接物
9477
+ */
9478
+ async getRoleHandoffs(ticket, role) {
9479
+ const roleDir = path22.join(this.handoffDir, ticket, role);
9480
+ if (!fs7.existsSync(roleDir)) {
9481
+ return [];
9482
+ }
9483
+ const files = await fs7.readdir(roleDir);
9484
+ return files.filter((f) => f.endsWith(".md") || f.endsWith(".yaml") || f.endsWith(".json"));
9485
+ }
9486
+ /**
9487
+ * 获取交接物摘要
9488
+ */
9489
+ async getSummary(ticket) {
9490
+ const manifest = await this.loadManifest(ticket);
9491
+ if (!manifest) {
9492
+ return {
9493
+ total: 0,
9494
+ ready: 0,
9495
+ pending: 0,
9496
+ verified: 0,
9497
+ byRole: {
9498
+ planner: 0,
9499
+ architect: 0,
9500
+ backend: 0,
9501
+ frontend: 0,
9502
+ qa: 0,
9503
+ reviewer: 0
9504
+ }
9505
+ };
9506
+ }
9507
+ const summary = {
9508
+ total: manifest.items.length,
9509
+ ready: 0,
9510
+ pending: 0,
9511
+ verified: 0,
9512
+ byRole: {
9513
+ planner: 0,
9514
+ architect: 0,
9515
+ backend: 0,
9516
+ frontend: 0,
9517
+ qa: 0,
9518
+ reviewer: 0
9519
+ }
9520
+ };
9521
+ for (const item of manifest.items) {
9522
+ if (item.status === "ready" || item.status === "verified") {
9523
+ summary.ready++;
9524
+ }
9525
+ if (item.status === "pending") {
9526
+ summary.pending++;
9527
+ }
9528
+ if (item.status === "verified") {
9529
+ summary.verified++;
9530
+ }
9531
+ summary.byRole[item.role]++;
9532
+ }
9533
+ return summary;
9534
+ }
9535
+ /**
9536
+ * 根据阶段获取所需的交接物列表
9537
+ */
9538
+ getRequiredItemsByPhase(phase) {
9539
+ const items = [];
9540
+ if (phase === "planning" || phase === "all") {
9541
+ items.push(
9542
+ { role: "planner", file: "SPEC.md", type: "spec", description: "\u529F\u80FD\u89C4\u683C\u6587\u6863", requiredBy: ["architect", "backend", "frontend"] },
9543
+ { role: "planner", file: "TASKS.md", type: "tasks", description: "\u4EFB\u52A1\u62C6\u5206", requiredBy: ["architect", "backend", "frontend"] },
9544
+ { role: "planner", file: "BDD_FEATURES/*.feature", type: "feature", description: "BDD \u573A\u666F", requiredBy: ["qa"] },
9545
+ { role: "planner", file: "BDD_TRACEABILITY.md", type: "bdd", description: "BDD \u8FFD\u6EAF\u77E9\u9635", requiredBy: ["qa"] }
9546
+ );
9547
+ }
9548
+ if (phase === "architecture" || phase === "all") {
9549
+ items.push(
9550
+ { role: "architect", file: "ADR.md", type: "adr", description: "\u67B6\u6784\u51B3\u7B56\u8BB0\u5F55", requiredBy: ["backend"] },
9551
+ { role: "architect", file: "API_RULES.md", type: "api", description: "API \u8BBE\u8BA1\u89C4\u5219", requiredBy: ["backend", "frontend"] },
9552
+ { role: "architect", file: "MODULE_OWNERSHIP.md", type: "other", description: "\u6A21\u5757\u5F52\u5C5E", requiredBy: ["backend", "frontend"] },
9553
+ { role: "architect", file: "IMPACT.md", type: "other", description: "\u5F71\u54CD\u9762\u8BC4\u4F30", requiredBy: ["reviewer"] }
9554
+ );
9555
+ }
9556
+ if (phase === "openapi" || phase === "all") {
9557
+ items.push(
9558
+ { role: "backend", file: "API_OPENAPI.yaml", type: "openapi", description: "OpenAPI \u89C4\u8303", requiredBy: ["frontend", "qa"] },
9559
+ { role: "backend", file: "API_EXAMPLES.md", type: "api", description: "API \u793A\u4F8B", requiredBy: ["frontend"] },
9560
+ { role: "backend", file: "MOCK_GUIDE.md", type: "other", description: "Mock \u4F7F\u7528\u6307\u5357", requiredBy: ["frontend"] }
9561
+ );
9562
+ }
9563
+ if (phase === "implementation" || phase === "all") {
9564
+ items.push(
9565
+ { role: "backend", file: "CHANGELOG_BACKEND.md", type: "changelog", description: "\u540E\u7AEF\u53D8\u66F4\u65E5\u5FD7", requiredBy: ["reviewer"] },
9566
+ { role: "backend", file: "TESTING_BACKEND.md", type: "testing", description: "\u540E\u7AEF\u6D4B\u8BD5\u62A5\u544A", requiredBy: ["qa"] },
9567
+ { role: "frontend", file: "CHANGELOG_FRONTEND.md", type: "changelog", description: "\u524D\u7AEF\u53D8\u66F4\u65E5\u5FD7", requiredBy: ["reviewer"] },
9568
+ { role: "frontend", file: "TESTING_FRONTEND.md", type: "testing", description: "\u524D\u7AEF\u6D4B\u8BD5\u62A5\u544A", requiredBy: ["qa"] }
9569
+ );
9570
+ }
9571
+ if (phase === "bdd" || phase === "all") {
9572
+ items.push(
9573
+ { role: "qa", file: "BDD_RUN_GUIDE.md", type: "report", description: "BDD \u6267\u884C\u6307\u5357", requiredBy: [] },
9574
+ { role: "qa", file: "BDD_REPORT.md", type: "report", description: "BDD \u9A8C\u6536\u62A5\u544A", requiredBy: ["reviewer"] },
9575
+ { role: "qa", file: "REGRESSION.md", type: "report", description: "\u56DE\u5F52\u6E05\u5355", requiredBy: ["qa"] }
9576
+ );
9577
+ }
9578
+ if (phase === "integration" || phase === "all") {
9579
+ items.push(
9580
+ { role: "reviewer", file: "REVIEW.md", type: "review", description: "\u5BA1\u67E5\u62A5\u544A", requiredBy: [] },
9581
+ { role: "reviewer", file: "RELEASE.md", type: "release", description: "\u53D1\u5E03\u65B9\u6848", requiredBy: [] },
9582
+ { role: "reviewer", file: "ROLLBACK.md", type: "rollback", description: "\u56DE\u6EDA\u65B9\u6848", requiredBy: [] }
9583
+ );
9584
+ }
9585
+ return items;
9586
+ }
9587
+ };
9588
+ }
9589
+ });
9590
+
9591
+ // src/lib/role-prompts.ts
9592
+ function getRolePrompt(role) {
9593
+ return ROLE_PROMPTS[role];
9594
+ }
9595
+ function buildRolePrompt(role, data) {
9596
+ const config = getRolePrompt(role);
9597
+ if (!config) {
9598
+ throw new Error(`Unknown role: ${role}`);
9599
+ }
9600
+ let taskPrompt = config.taskPrompt;
9601
+ for (const [key, value] of Object.entries(data)) {
9602
+ taskPrompt = taskPrompt.replace(new RegExp(`{{${key}}}`, "g"), value);
9603
+ }
9604
+ return taskPrompt;
9605
+ }
9606
+ var PLANNER_PROMPT, ARCHITECT_PROMPT, BACKEND_PROMPT, FRONTEND_PROMPT, QA_PROMPT, REVIEWER_PROMPT, ROLE_PROMPTS;
9607
+ var init_role_prompts = __esm({
9608
+ "src/lib/role-prompts.ts"() {
9609
+ "use strict";
9610
+ init_esm_shims();
9611
+ PLANNER_PROMPT = {
9612
+ role: "planner",
9613
+ displayName: "Planner",
9614
+ systemPrompt: `\u4F60\u662F Product Planner\uFF0C\u8D1F\u8D23\u9700\u6C42\u5206\u6790\u548C BDD \u573A\u666F\u8BBE\u8BA1\u3002
9615
+
9616
+ \u4F60\u7684\u804C\u8D23\uFF1A
9617
+ 1. \u5206\u6790 PRD\uFF0C\u7F16\u5199 SPEC.md
9618
+ 2. \u62C6\u89E3\u4EFB\u52A1\uFF0C\u7F16\u5199 TASKS.md
9619
+ 3. \u7F16\u5199 BDD \u573A\u666F\uFF08Gherkin \u683C\u5F0F\uFF09
9620
+ 4. \u521B\u5EFA BDD_TRACEABILITY.md
9621
+
9622
+ \u91CD\u8981\u89C4\u5219\uFF1A
9623
+ - \u53EA\u8F93\u51FA\u6587\u6863\uFF0C\u4E0D\u8981\u5199\u4E1A\u52A1\u4EE3\u7801
9624
+ - BDD \u573A\u666F\u5FC5\u987B Given/When/Then \u5B8C\u6574
9625
+ - \u573A\u666F\u5FC5\u987B\u53EF\u6D4B\u8BD5\u3001\u65E0\u6B67\u4E49
9626
+
9627
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9628
+ - TICKET: {{TICKET}}
9629
+ - \u76EE\u6807: {{GOAL}}
9630
+ - \u7EA6\u675F: {{CONSTRAINTS}}`,
9631
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u521B\u5EFA\u9700\u6C42\u6587\u6863\uFF1A
9632
+
9633
+ ## TICKET
9634
+ {{TICKET}}
9635
+
9636
+ ## \u76EE\u6807
9637
+ {{GOAL}}
9638
+
9639
+ ## \u7EA6\u675F
9640
+ {{CONSTRAINTS}}
9641
+
9642
+ ## \u5DF2\u6709\u4FE1\u606F
9643
+ {{CONTEXT}}
9644
+
9645
+ \u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/planner/ \u76EE\u5F55\uFF09\uFF1A
9646
+
9647
+ 1. **SPEC.md** - \u529F\u80FD\u89C4\u683C\u6587\u6863
9648
+ - \u76EE\u6807\u4E0E\u975E\u76EE\u6807
9649
+ - \u8303\u56F4\u4E0E\u7EA6\u675F
9650
+ - \u9A8C\u6536\u6807\u51C6\uFF08\u81F3\u5C11 5 \u6761\uFF09
9651
+ - \u4F9D\u8D56\u5173\u7CFB
9652
+
9653
+ 2. **TASKS.md** - \u4EFB\u52A1\u62C6\u5206
9654
+ - \u6309\u9636\u6BB5\u62C6\u5206\u4EFB\u52A1
9655
+ - \u6807\u6CE8\u6BCF\u4E2A\u4EFB\u52A1\u7684 Owner
9656
+ - \u5173\u8054 BDD \u573A\u666F
9657
+
9658
+ 3. **BDD_FEATURES/*.feature** - BDD \u573A\u666F
9659
+ - \u4E3B\u8DEF\u5F84\u573A\u666F\uFF08Must\uFF09
9660
+ - \u5F02\u5E38\u8DEF\u5F84\u573A\u666F\uFF08Should\uFF09
9661
+ - \u8FB9\u754C\u573A\u666F\uFF08May\uFF09
9662
+
9663
+ 4. **BDD_TRACEABILITY.md** - \u8FFD\u6EAF\u77E9\u9635
9664
+ - \u9A8C\u6536\u6807\u51C6 \u2194 BDD \u573A\u666F \u2194 \u4EFB\u52A1`,
9665
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u5185\u5BB9\uFF0CMarkdown \u683C\u5F0F\u3002`
9666
+ };
9667
+ ARCHITECT_PROMPT = {
9668
+ role: "architect",
9669
+ displayName: "Architect",
9670
+ systemPrompt: `\u4F60\u662F System Architect\uFF0C\u8D1F\u8D23\u6280\u672F\u67B6\u6784\u8BBE\u8BA1\u3002
9671
+
9672
+ \u4F60\u7684\u804C\u8D23\uFF1A
9673
+ 1. \u7F16\u5199 ADR\uFF08\u67B6\u6784\u51B3\u7B56\u8BB0\u5F55\uFF09
9674
+ 2. \u5B9A\u4E49 API \u89C4\u5219\uFF08\u9519\u8BEF\u7801\u3001\u9274\u6743\u3001\u5206\u9875\u7B49\uFF09
9675
+ 3. \u5212\u5206\u6A21\u5757\u8FB9\u754C
9676
+ 4. \u8BC4\u4F30\u5F71\u54CD\u9762\u548C\u98CE\u9669
9677
+
9678
+ \u91CD\u8981\u89C4\u5219\uFF1A
9679
+ - \u53EA\u8F93\u51FA\u8BBE\u8BA1\u6587\u6863\uFF0C\u4E0D\u8981\u5199\u5B9E\u73B0\u4EE3\u7801
9680
+ - API \u89C4\u5219\u5FC5\u987B\u5B8C\u6574\u3001\u53EF\u6267\u884C
9681
+ - \u6A21\u5757\u8FB9\u754C\u5FC5\u987B\u6E05\u6670\uFF0C\u907F\u514D\u51B2\u7A81
9682
+
9683
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9684
+ - TICKET: {{TICKET}}
9685
+ - \u76EE\u6807: {{GOAL}}`,
9686
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u521B\u5EFA\u67B6\u6784\u6587\u6863\uFF1A
9687
+
9688
+ ## TICKET
9689
+ {{TICKET}}
9690
+
9691
+ ## \u76EE\u6807
9692
+ {{GOAL}}
9693
+
9694
+ ## \u5DF2\u6709\u6587\u6863
9695
+ \u8BF7\u5148\u9605\u8BFB\uFF1A.handoff/{{TICKET}}/planner/SPEC.md
9696
+
9697
+ \u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/architect/ \u76EE\u5F55\uFF09\uFF1A
9698
+
9699
+ 1. **ADR.md** - \u67B6\u6784\u51B3\u7B56\u8BB0\u5F55
9700
+ - \u5173\u952E\u6280\u672F\u51B3\u7B56
9701
+ - \u51B3\u7B56\u7406\u7531
9702
+ - \u9884\u671F\u540E\u679C
9703
+
9704
+ 2. **API_RULES.md** - API \u8BBE\u8BA1\u89C4\u5219
9705
+ - \u9519\u8BEF\u7801\u89C4\u8303
9706
+ - \u9274\u6743\u7B56\u7565
9707
+ - \u5206\u9875\u89C4\u5219
9708
+ - \u5E42\u7B49\u6027
9709
+ - \u7248\u672C\u517C\u5BB9
9710
+
9711
+ 3. **MODULE_OWNERSHIP.md** - \u6A21\u5757\u5F52\u5C5E
9712
+ - \u76EE\u5F55\u7ED3\u6784
9713
+ - \u53D8\u66F4\u8FB9\u754C
9714
+ - \u8C01\u80FD\u6539\u4EC0\u4E48
9715
+
9716
+ 4. **IMPACT.md** - \u5F71\u54CD\u9762\u8BC4\u4F30
9717
+ - \u53D7\u5F71\u54CD\u6A21\u5757
9718
+ - \u6F5C\u5728\u98CE\u9669
9719
+ - \u56DE\u6EDA\u7B56\u7565`,
9720
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u5185\u5BB9\uFF0CMarkdown \u683C\u5F0F\u3002`
9721
+ };
9722
+ BACKEND_PROMPT = {
9723
+ role: "backend",
9724
+ displayName: "Backend Developer",
9725
+ systemPrompt: `\u4F60\u662F Backend Developer\uFF0C\u8D1F\u8D23\u540E\u7AEF\u5B9E\u73B0\u3002
9726
+
9727
+ \u4F60\u7684\u804C\u8D23\uFF1A
9728
+ 1. \u7F16\u5199 OpenAPI \u89C4\u8303
9729
+ 2. \u5B9E\u73B0 API Mock \u670D\u52A1
9730
+ 3. \u5B9E\u73B0\u771F\u5B9E\u540E\u7AEF\u4EE3\u7801
9731
+ 4. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
9732
+
9733
+ \u91CD\u8981\u89C4\u5219\uFF1A
9734
+ - \u5FC5\u987B\u5148\u5B8C\u6210 OpenAPI + Mock \u624D\u80FD\u5BF9\u63A5\u524D\u7AEF
9735
+ - API \u5FC5\u987B\u7B26\u5408 API_RULES.md \u89C4\u8303
9736
+ - \u4EE3\u7801\u5FC5\u987B\u6709\u6D4B\u8BD5\u8986\u76D6
9737
+
9738
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9739
+ - TICKET: {{TICKET}}
9740
+ - \u9636\u6BB5: {{PHASE}}`,
9741
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u5B8C\u6210\u540E\u7AEF\u5DE5\u4F5C\uFF1A
9742
+
9743
+ ## TICKET
9744
+ {{TICKET}}
9745
+
9746
+ ## \u9636\u6BB5
9747
+ {{PHASE}} (openapi | implementation)
9748
+
9749
+ ## \u5DF2\u6709\u6587\u6863
9750
+ \u8BF7\u5148\u9605\u8BFB\uFF1A
9751
+ - .handoff/{{TICKET}}/architect/API_RULES.md
9752
+ - .handoff/{{TICKET}}/architect/MODULE_OWNERSHIP.md
9753
+
9754
+ **\u5982\u679C\u662F OpenAPI \u9636\u6BB5\uFF1A**
9755
+
9756
+ \u8BF7\u8F93\u51FA\u4EE5\u4E0B\u6587\u6863\uFF08\u653E\u5728 .handoff/{{TICKET}}/backend/ \u76EE\u5F55\uFF09\uFF1A
9757
+
9758
+ 1. **API_OPENAPI.yaml** - OpenAPI 3.0 \u89C4\u8303
9759
+ - \u5B8C\u6574\u7684 API \u5B9A\u4E49
9760
+ - \u8BF7\u6C42/\u54CD\u5E94 Schema
9761
+ - \u8BA4\u8BC1\u8BF4\u660E
9762
+
9763
+ 2. **API_EXAMPLES.md** - API \u793A\u4F8B
9764
+ - \u8BF7\u6C42\u793A\u4F8B
9765
+ - \u54CD\u5E94\u793A\u4F8B
9766
+ - \u9519\u8BEF\u793A\u4F8B
9767
+
9768
+ 3. **MOCK_GUIDE.md** - Mock \u4F7F\u7528\u6307\u5357
9769
+ - \u5982\u4F55\u542F\u52A8 Mock
9770
+ - \u8986\u76D6\u7684\u63A5\u53E3\u6E05\u5355
9771
+ - \u793A\u4F8B\u6570\u636E
9772
+
9773
+ **\u5982\u679C\u662F Implementation \u9636\u6BB5\uFF1A**
9774
+
9775
+ \u8BF7\u5B8C\u6210\u540E\u7AEF\u5B9E\u73B0\uFF1A
9776
+
9777
+ 1. \u7F16\u5199 API \u5B9E\u73B0\u4EE3\u7801
9778
+ 2. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
9779
+ 3. \u66F4\u65B0 CHANGELOG_BACKEND.md
9780
+ 4. \u66F4\u65B0 TESTING_BACKEND.md`,
9781
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u548C\u4EE3\u7801\u3002`
9782
+ };
9783
+ FRONTEND_PROMPT = {
9784
+ role: "frontend",
9785
+ displayName: "Frontend Developer",
9786
+ systemPrompt: `\u4F60\u662F Frontend Developer\uFF0C\u8D1F\u8D23\u524D\u7AEF\u5B9E\u73B0\u3002
9787
+
9788
+ \u4F60\u7684\u804C\u8D23\uFF1A
9789
+ 1. \u57FA\u4E8E OpenAPI \u5BF9\u63A5\u524D\u7AEF
9790
+ 2. \u5B9E\u73B0 UI \u7EC4\u4EF6
9791
+ 3. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
9792
+
9793
+ \u91CD\u8981\u89C4\u5219\uFF1A
9794
+ - \u5FC5\u987B\u57FA\u4E8E Mock \u670D\u52A1\u5148\u5B8C\u6210\u8054\u8C03
9795
+ - \u5FC5\u987B\u7B26\u5408\u8BBE\u8BA1\u89C4\u8303
9796
+ - \u4EE3\u7801\u5FC5\u987B\u6709\u6D4B\u8BD5\u8986\u76D6
9797
+
9798
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9799
+ - TICKET: {{TICKET}}
9800
+ - \u9636\u6BB5: {{PHASE}}`,
9801
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u5B8C\u6210\u524D\u7AEF\u5DE5\u4F5C\uFF1A
9802
+
9803
+ ## TICKET
9804
+ {{TICKET}}
9805
+
9806
+ ## \u9636\u6BB5
9807
+ {{PHASE}}
9808
+
9809
+ ## \u5DF2\u6709\u6587\u6863/\u8D44\u6E90
9810
+ \u8BF7\u5148\u9605\u8BFB\uFF1A
9811
+ - .handoff/{{TICKET}}/backend/API_OPENAPI.yaml\uFF08\u5982\u679C\u5B58\u5728\uFF09
9812
+ - .handoff/{{TICKET}}/backend/MOCK_GUIDE.md\uFF08\u5982\u679C\u5B58\u5728\uFF09
9813
+
9814
+ \u8BF7\u5B8C\u6210\u540E\u7AEF\u5B9E\u73B0\uFF1A
9815
+
9816
+ 1. \u57FA\u4E8E Mock \u670D\u52A1\u5BF9\u63A5 API
9817
+ 2. \u5B9E\u73B0 UI \u7EC4\u4EF6
9818
+ 3. \u7F16\u5199\u5355\u5143\u6D4B\u8BD5
9819
+ 4. \u66F4\u65B0 CHANGELOG_FRONTEND.md
9820
+ 5. \u66F4\u65B0 TESTING_FRONTEND.md
9821
+
9822
+ \u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/frontend/ \u76EE\u5F55`,
9823
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u6587\u6863\u548C\u4EE3\u7801\u3002`
9824
+ };
9825
+ QA_PROMPT = {
9826
+ role: "qa",
9827
+ displayName: "QA Engineer",
9828
+ systemPrompt: `\u4F60\u662F QA Engineer\uFF0C\u8D1F\u8D23 BDD \u9A8C\u6536\u6D4B\u8BD5\u3002
9829
+
9830
+ \u4F60\u7684\u804C\u8D23\uFF1A
9831
+ 1. \u5C06 Gherkin \u573A\u666F\u8F6C\u4E3A\u53EF\u6267\u884C\u6B65\u9AA4
9832
+ 2. \u6267\u884C BDD \u6D4B\u8BD5
9833
+ 3. \u751F\u6210\u9A8C\u6536\u62A5\u544A
9834
+
9835
+ \u91CD\u8981\u89C4\u5219\uFF1A
9836
+ - \u4E25\u683C\u6309 BDD \u573A\u666F\u6267\u884C
9837
+ - \u5931\u8D25\u573A\u666F\u5FC5\u987B\u6709\u590D\u73B0\u6B65\u9AA4
9838
+ - \u62A5\u544A\u5FC5\u987B\u5305\u542B\u622A\u56FE/\u65E5\u5FD7\u6307\u5F15
9839
+
9840
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9841
+ - TICKET: {{TICKET}}`,
9842
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u6267\u884C BDD \u9A8C\u6536\uFF1A
9843
+
9844
+ ## TICKET
9845
+ {{TICKET}}
9846
+
9847
+ ## \u5DF2\u6709\u6587\u6863
9848
+ \u8BF7\u5148\u9605\u8BFB\uFF1A
9849
+ - .handoff/{{TICKET}}/planner/BDD_FEATURES/*.feature
9850
+ - .handoff/{{TICKET}}/planner/BDD_TRACEABILITY.md
9851
+
9852
+ ## \u4EFB\u52A1
9853
+
9854
+ 1. **BDD_RUN_GUIDE.md** - \u6267\u884C\u6307\u5357
9855
+ - \u73AF\u5883\u51C6\u5907
9856
+ - \u6570\u636E\u51C6\u5907
9857
+ - \u6267\u884C\u547D\u4EE4
9858
+
9859
+ 2. **\u6267\u884C BDD \u6D4B\u8BD5**
9860
+ - \u8BB0\u5F55\u6267\u884C\u7ED3\u679C
9861
+ - \u622A\u56FE/\u65E5\u5FD7
9862
+
9863
+ 3. **BDD_REPORT.md** - \u9A8C\u6536\u62A5\u544A
9864
+ - \u6267\u884C\u6458\u8981
9865
+ - \u6210\u529F\u573A\u666F
9866
+ - \u5931\u8D25\u573A\u666F\uFF08\u542B\u590D\u73B0\u6B65\u9AA4\uFF09
9867
+ - \u5EF6\u671F/\u964D\u7EA7\u8BB0\u5F55
9868
+
9869
+ 4. **REGRESSION.md** - \u56DE\u5F52\u6E05\u5355
9870
+ - \u9700\u8981\u56DE\u5F52\u7684\u6D4B\u8BD5\u9879
9871
+ - \u624B\u52A8\u6D4B\u8BD5\u6307\u5F15
9872
+
9873
+ \u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/qa/ \u76EE\u5F55`,
9874
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u9A8C\u6536\u62A5\u544A\u3002`
9875
+ };
9876
+ REVIEWER_PROMPT = {
9877
+ role: "reviewer",
9878
+ displayName: "Reviewer",
9879
+ systemPrompt: `\u4F60\u662F Code Reviewer\uFF0C\u8D1F\u8D23\u4EE3\u7801\u5BA1\u67E5\u4E0E\u53D1\u5E03\u3002
9880
+
9881
+ \u4F60\u7684\u804C\u8D23\uFF1A
9882
+ 1. \u4EE3\u7801\u5BA1\u67E5
9883
+ 2. \u95E8\u7981\u68C0\u67E5
9884
+ 3. \u53D1\u5E03\u4E0E\u56DE\u6EDA\u65B9\u6848
9885
+
9886
+ \u91CD\u8981\u89C4\u5219\uFF1A
9887
+ - \u4E25\u683C\u68C0\u67E5\u8D28\u91CF\u95E8\u7981
9888
+ - \u53D1\u5E03\u65B9\u6848\u5FC5\u987B\u53EF\u6267\u884C
9889
+ - \u56DE\u6EDA\u65B9\u6848\u5FC5\u987B\u6709\u9A8C\u8BC1\u6B65\u9AA4
9890
+
9891
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
9892
+ - TICKET: {{TICKET}}`,
9893
+ taskPrompt: `\u8BF7\u4E3A\u4EE5\u4E0B\u4EFB\u52A1\u6267\u884C\u5BA1\u67E5\u4E0E\u53D1\u5E03\u51C6\u5907\uFF1A
9894
+
9895
+ ## TICKET
9896
+ {{TICKET}}
9897
+
9898
+ ## \u5DF2\u6709\u6587\u6863
9899
+ \u8BF7\u5148\u9605\u8BFB\u6240\u6709 .handoff/{{TICKET}}/*/ \u4E0B\u7684\u6587\u6863
9900
+
9901
+ ## \u4EFB\u52A1
9902
+
9903
+ 1. **REVIEW.md** - \u5BA1\u67E5\u62A5\u544A
9904
+ - \u4EE3\u7801\u5BA1\u67E5 checklist
9905
+ - \u53D1\u73B0\u7684\u95EE\u9898
9906
+ - \u5BA1\u67E5\u7ED3\u8BBA
9907
+
9908
+ 2. **RELEASE.md** - \u53D1\u5E03\u65B9\u6848
9909
+ - \u53D1\u5E03\u6B65\u9AA4
9910
+ - \u76D1\u63A7\u9879
9911
+ - \u53D1\u5E03\u68C0\u67E5\u6E05\u5355
9912
+
9913
+ 3. **ROLLBACK.md** - \u56DE\u6EDA\u65B9\u6848
9914
+ - \u56DE\u6EDA\u89E6\u53D1\u6761\u4EF6
9915
+ - \u56DE\u6EDA\u6B65\u9AA4
9916
+ - \u6570\u636E\u6062\u590D\uFF08\u5982\u9700\u8981\uFF09
9917
+ - \u9A8C\u8BC1\u6B65\u9AA4
9918
+
9919
+ \u8BF7\u5C06\u4EA7\u7269\u653E\u5728 .handoff/{{TICKET}}/reviewer/ \u76EE\u5F55`,
9920
+ outputFormat: `\u8BF7\u8F93\u51FA\u5B8C\u6574\u7684\u5BA1\u67E5\u548C\u53D1\u5E03\u6587\u6863\u3002`
9921
+ };
9922
+ ROLE_PROMPTS = {
9923
+ planner: PLANNER_PROMPT,
9924
+ architect: ARCHITECT_PROMPT,
9925
+ backend: BACKEND_PROMPT,
9926
+ frontend: FRONTEND_PROMPT,
9927
+ qa: QA_PROMPT,
9928
+ reviewer: REVIEWER_PROMPT
9929
+ };
9930
+ }
9931
+ });
9932
+
9933
+ // src/lib/phase-config.ts
9934
+ function canEnterPhase(phase, completedPhases) {
9935
+ const config = PHASE_CONFIGS[phase];
9936
+ if (!config) return false;
9937
+ return config.dependsOn.every((dep) => completedPhases.includes(dep));
9938
+ }
9939
+ function getGateNumber(phase) {
9940
+ return PHASE_CONFIGS[phase]?.gateNumber || 0;
9941
+ }
9942
+ function getParallelRoles(phase) {
9943
+ return PHASE_CONFIGS[phase]?.parallelRoles || [];
9944
+ }
9945
+ function getPhaseDisplayName(phase) {
9946
+ return PHASE_CONFIGS[phase]?.displayName || phase;
9947
+ }
9948
+ function formatPhaseProgress(completedPhases) {
9949
+ const total = PHASE_SEQUENCE.length;
9950
+ const current = completedPhases.length;
9951
+ const progressBar = "\u2588".repeat(current) + "\u2591".repeat(total - current);
9952
+ return `[${progressBar}] ${current}/${total} \u9636\u6BB5\u5B8C\u6210`;
9953
+ }
9954
+ var PHASE_CONFIGS, PHASE_SEQUENCE, GATE_CONDITIONS;
9955
+ var init_phase_config = __esm({
9956
+ "src/lib/phase-config.ts"() {
9957
+ "use strict";
9958
+ init_esm_shims();
9959
+ PHASE_CONFIGS = {
9960
+ planning: {
9961
+ phase: "planning",
9962
+ displayName: "\u9700\u6C42\u4E0E BDD \u8BBE\u8BA1",
9963
+ description: "Planner \u521B\u5EFA SPEC\u3001TASKS\u3001BDD \u573A\u666F",
9964
+ gateNumber: 1,
9965
+ parallelRoles: [["planner"]],
9966
+ dependsOn: [],
9967
+ minDuration: 15,
9968
+ maxDuration: 60
9969
+ },
9970
+ architecture: {
9971
+ phase: "architecture",
9972
+ displayName: "\u67B6\u6784\u4E0E\u5951\u7EA6",
9973
+ description: "Architect \u521B\u5EFA ADR\u3001API \u89C4\u5219\u3001\u6A21\u5757\u5F52\u5C5E",
9974
+ gateNumber: 2,
9975
+ parallelRoles: [["architect"]],
9976
+ dependsOn: ["planning"],
9977
+ minDuration: 15,
9978
+ maxDuration: 45
9979
+ },
9980
+ openapi: {
9981
+ phase: "openapi",
9982
+ displayName: "OpenAPI + Mock",
9983
+ description: "Backend \u5148\u51FA OpenAPI + Mock\uFF0C\u524D\u7AEF\u624D\u80FD\u5BF9\u63A5",
9984
+ gateNumber: 3,
9985
+ parallelRoles: [["backend"]],
9986
+ dependsOn: ["architecture"],
9987
+ minDuration: 20,
9988
+ maxDuration: 90
9989
+ },
9990
+ implementation: {
9991
+ phase: "implementation",
9992
+ displayName: "\u5E76\u884C\u5B9E\u73B0",
9993
+ description: "Backend \u4E0E Frontend \u5E76\u884C\u5F00\u53D1",
9994
+ gateNumber: 4,
9995
+ parallelRoles: [["backend", "frontend"]],
9996
+ dependsOn: ["openapi"],
9997
+ minDuration: 60,
9998
+ maxDuration: 480
9999
+ },
10000
+ bdd: {
10001
+ phase: "bdd",
10002
+ displayName: "BDD \u9A8C\u6536",
10003
+ description: "QA \u6267\u884C BDD \u9A8C\u6536\u6D4B\u8BD5",
10004
+ gateNumber: 5,
10005
+ parallelRoles: [["qa"]],
10006
+ dependsOn: ["implementation"],
10007
+ minDuration: 30,
10008
+ maxDuration: 120
10009
+ },
10010
+ integration: {
10011
+ phase: "integration",
10012
+ displayName: "\u96C6\u6210\u4E0E\u53D1\u5E03",
10013
+ description: "\u6536\u655B\u5230 integration \u5206\u652F\uFF0C\u6267\u884C CI/CD",
10014
+ gateNumber: 6,
10015
+ parallelRoles: [["reviewer"]],
10016
+ dependsBy: ["bdd"],
10017
+ minDuration: 15,
10018
+ maxDuration: 60
10019
+ }
10020
+ };
10021
+ PHASE_SEQUENCE = [
10022
+ "planning",
10023
+ "architecture",
10024
+ "openapi",
10025
+ "implementation",
10026
+ "bdd",
10027
+ "integration"
10028
+ ];
10029
+ GATE_CONDITIONS = {
10030
+ 1: [
10031
+ {
10032
+ description: "SPEC.md \u5305\u542B\u76EE\u6807/\u975E\u76EE\u6807/\u8303\u56F4",
10033
+ status: "pending"
10034
+ },
10035
+ {
10036
+ description: "TASKS.md \u4EFB\u52A1\u62C6\u5206\u5230\u53EF\u72EC\u7ACB\u5408\u5E76",
10037
+ status: "pending"
10038
+ },
10039
+ {
10040
+ description: "BDD \u573A\u666F Given/When/Then \u5B8C\u6574",
10041
+ status: "pending"
10042
+ },
10043
+ {
10044
+ description: "\u9A8C\u6536\u6807\u51C6\u4E0E BDD \u573A\u666F\u4E00\u4E00\u5BF9\u5E94",
10045
+ status: "pending"
10046
+ }
10047
+ ],
10048
+ 2: [
10049
+ {
10050
+ description: "ADR \u51B3\u7B56\u8BB0\u5F55\u5B8C\u6574",
10051
+ status: "pending"
10052
+ },
10053
+ {
10054
+ description: "API_RULES \u5305\u542B\u9519\u8BEF\u7801\u89C4\u8303",
10055
+ status: "pending"
10056
+ },
10057
+ {
10058
+ description: "MODULE_OWNERSHIP \u76EE\u5F55\u8FB9\u754C\u660E\u786E",
10059
+ status: "pending"
10060
+ },
10061
+ {
10062
+ description: "IMPACT \u98CE\u9669\u8BC4\u4F30\u5B8C\u6210",
10063
+ status: "pending"
10064
+ }
10065
+ ],
10066
+ 3: [
10067
+ {
10068
+ description: "OpenAPI \u6587\u4EF6\u7B26\u5408\u89C4\u8303",
10069
+ status: "pending"
10070
+ },
10071
+ {
10072
+ description: "Mock \u670D\u52A1\u53EF\u542F\u52A8",
10073
+ status: "pending"
10074
+ },
10075
+ {
10076
+ description: "\u524D\u7AEF\u53EF\u7528 Mock \u5B8C\u6210\u4E3B\u8DEF\u5F84",
10077
+ status: "pending"
10078
+ },
10079
+ {
10080
+ description: "API_EXAMPLES \u5305\u542B\u5B8C\u6574\u793A\u4F8B",
10081
+ status: "pending"
10082
+ }
10083
+ ],
10084
+ 4: [
10085
+ {
10086
+ description: "\u540E\u7AEF\u5355\u5143\u6D4B\u8BD5\u8986\u76D6\u7387 > 70%",
10087
+ status: "pending"
10088
+ },
10089
+ {
10090
+ description: "\u524D\u7AEF\u5355\u5143\u6D4B\u8BD5\u8986\u76D6\u7387 > 70%",
10091
+ status: "pending"
10092
+ },
10093
+ {
10094
+ description: "\u9759\u6001\u68C0\u67E5 (Lint) \u901A\u8FC7",
10095
+ status: "pending"
10096
+ },
10097
+ {
10098
+ description: "\u63A5\u53E3\u5951\u7EA6\u4E0E OpenAPI \u4E00\u81F4",
10099
+ status: "pending"
10100
+ }
10101
+ ],
10102
+ 5: [
10103
+ {
10104
+ description: "Must \u7EA7\u522B\u573A\u666F 100% \u901A\u8FC7",
10105
+ status: "pending"
10106
+ },
10107
+ {
10108
+ description: "Should \u7EA7\u522B\u573A\u666F\u901A\u8FC7\u7387 > 90%",
10109
+ status: "pending"
10110
+ },
10111
+ {
10112
+ description: "BDD_REPORT \u5305\u542B\u5931\u8D25\u573A\u666F\u590D\u73B0",
10113
+ status: "pending"
10114
+ },
10115
+ {
10116
+ description: "\u56DE\u5F52\u6E05\u5355\u5DF2\u51C6\u5907",
10117
+ status: "pending"
10118
+ }
10119
+ ],
10120
+ 6: [
10121
+ {
10122
+ description: "integration \u5206\u652F CI \u901A\u8FC7",
10123
+ status: "pending"
10124
+ },
10125
+ {
10126
+ description: "\u5168\u91CF BDD \u901A\u8FC7",
10127
+ status: "pending"
10128
+ },
10129
+ {
10130
+ description: ".handoff \u6587\u4EF6\u9F50\u5168",
10131
+ status: "pending"
10132
+ },
10133
+ {
10134
+ description: "ROLLBACK.md \u53EF\u6267\u884C",
10135
+ status: "pending"
10136
+ }
10137
+ ]
10138
+ };
10139
+ }
10140
+ });
10141
+
10142
+ // src/lib/concurrent-executor.ts
10143
+ import { spawn } from "child_process";
10144
+ import path23 from "path";
10145
+ import fs8 from "fs-extra";
10146
+ function createExecutor(repoRoot) {
10147
+ return new ConcurrentExecutor(repoRoot);
10148
+ }
10149
+ var ConcurrentExecutor;
10150
+ var init_concurrent_executor = __esm({
10151
+ "src/lib/concurrent-executor.ts"() {
10152
+ "use strict";
10153
+ init_esm_shims();
10154
+ init_role_prompts();
10155
+ init_phase_config();
10156
+ init_logger();
10157
+ ConcurrentExecutor = class {
10158
+ repoRoot;
10159
+ constructor(repoRoot) {
10160
+ this.repoRoot = repoRoot || process.cwd();
10161
+ }
10162
+ /**
10163
+ * 并行执行多个角色任务
10164
+ */
10165
+ async executeParallel(configs) {
10166
+ console.log(`
10167
+ \u{1F680} \u542F\u52A8 ${configs.length} \u4E2A\u5E76\u884C\u4EFB\u52A1...
10168
+ `);
10169
+ const promises = configs.map(
10170
+ (config) => this.executeInWorktree(config)
10171
+ );
10172
+ const results = await Promise.all(promises);
10173
+ this.printSummary(results);
10174
+ return results;
10175
+ }
10176
+ /**
10177
+ * 在指定角色目录执行 Claude Code
10178
+ */
10179
+ async executeInWorktree(config) {
10180
+ const startTime = Date.now();
10181
+ const { role, prompt, systemPrompt, timeout = 30, cwd } = config;
10182
+ const rolePrompt = getRolePrompt(role);
10183
+ const finalSystemPrompt = systemPrompt || rolePrompt?.systemPrompt || "";
10184
+ const fullPrompt = `${finalSystemPrompt}
10185
+
10186
+ ---
10187
+
10188
+ \u5F53\u524D\u4EFB\u52A1\uFF1A
10189
+ ${prompt}
10190
+
10191
+ \u91CD\u8981\u63D0\u793A\uFF1A
10192
+ - \u53EA\u8F93\u51FA\u6587\u6863\u548C\u4EE3\u7801\uFF0C\u4E0D\u8981\u989D\u5916\u89E3\u91CA
10193
+ - \u5B8C\u6210\u540E\u7ACB\u5373\u9000\u51FA
10194
+ `;
10195
+ const workDir = cwd || this.repoRoot;
10196
+ logger.info(`[${role}] \u5F00\u59CB\u6267\u884C...`);
10197
+ return new Promise((resolve) => {
10198
+ const args = [
10199
+ "--print",
10200
+ // 非交互式
10201
+ "--output-format",
10202
+ "text",
10203
+ "--no-session-persistence",
10204
+ "--dangerously-skip-permissions"
10205
+ ];
10206
+ if (finalSystemPrompt) {
10207
+ args.push("--system-prompt", finalSystemPrompt);
10208
+ }
10209
+ const proc = spawn("claude", args, {
10210
+ cwd: workDir,
10211
+ stdio: ["pipe", "pipe", "pipe"],
10212
+ env: {
10213
+ ...process.env,
10214
+ FORCE_COLOR: "1"
10215
+ }
10216
+ });
10217
+ let stdout = "";
10218
+ let stderr = "";
10219
+ proc.stdout.on("data", (data) => {
10220
+ const text = data.toString();
10221
+ stdout += text;
10222
+ process.stdout.write(text);
10223
+ });
10224
+ proc.stderr.on("data", (data) => {
10225
+ stderr += data.toString();
10226
+ });
10227
+ proc.on("close", (code) => {
10228
+ const duration = Date.now() - startTime;
10229
+ const result = {
10230
+ role,
10231
+ worktreePath: workDir,
10232
+ success: code === 0,
10233
+ output: stdout || stderr,
10234
+ duration,
10235
+ exitCode: code || 0,
10236
+ artifacts: this.extractArtifacts(stdout)
10237
+ };
10238
+ const icon = result.success ? "\u2713" : "\u2717";
10239
+ const durationSec = (duration / 1e3).toFixed(1);
10240
+ console.log(` ${icon} [${role.padEnd(12)}] ${result.success ? "\u6210\u529F" : "\u5931\u8D25"} (${durationSec}s)`);
10241
+ resolve(result);
10242
+ });
10243
+ proc.on("error", (err) => {
10244
+ const duration = Date.now() - startTime;
10245
+ resolve({
10246
+ role,
10247
+ worktreePath: workDir,
10248
+ success: false,
10249
+ output: `Error: ${err.message}`,
10250
+ duration,
10251
+ exitCode: -1,
10252
+ error: err.message,
10253
+ artifacts: []
10254
+ });
10255
+ });
10256
+ proc.stdin.write(fullPrompt);
10257
+ proc.stdin.end();
10258
+ const timeoutMs = timeout * 60 * 1e3;
10259
+ setTimeout(() => {
10260
+ if (!proc.killed) {
10261
+ proc.kill("SIGTERM");
10262
+ resolve({
10263
+ role,
10264
+ worktreePath: workDir,
10265
+ success: false,
10266
+ output: "Timeout: Task exceeded maximum duration",
10267
+ duration: timeoutMs,
10268
+ exitCode: -1,
10269
+ error: "Timeout",
10270
+ artifacts: []
10271
+ });
10272
+ }
10273
+ }, timeoutMs);
10274
+ });
10275
+ }
10276
+ /**
10277
+ * 执行阶段任务
10278
+ */
10279
+ async executePhase(ticket, phase, taskData) {
10280
+ const phaseConfig = PHASE_CONFIGS[phase];
10281
+ if (!phaseConfig) {
10282
+ throw new Error(`Unknown phase: ${phase}`);
10283
+ }
10284
+ console.log(`
10285
+ \u{1F4CB} Phase ${phaseConfig.gateNumber}: ${phaseConfig.displayName}`);
10286
+ console.log(`${phaseConfig.description}
10287
+ `);
10288
+ const parallelRoleGroups = getParallelRoles(phase);
10289
+ const results = [];
10290
+ for (const roleGroup of parallelRoleGroups) {
10291
+ if (roleGroup.length === 1) {
10292
+ const role = roleGroup[0];
10293
+ const rolePrompt = getRolePrompt(role);
10294
+ if (!rolePrompt) {
10295
+ logger.warn(`Unknown role: ${role}`);
10296
+ continue;
10297
+ }
10298
+ const prompt = buildRolePrompt(role, {
10299
+ TICKET: ticket,
10300
+ ...taskData
10301
+ });
10302
+ const result = await this.executeInWorktree({
10303
+ role,
10304
+ prompt,
10305
+ systemPrompt: rolePrompt.systemPrompt
10306
+ });
10307
+ results.push(result);
10308
+ } else {
10309
+ const configs = roleGroup.map((role) => {
10310
+ const rolePrompt = getRolePrompt(role);
10311
+ if (!rolePrompt) {
10312
+ return null;
10313
+ }
10314
+ return {
10315
+ role,
10316
+ prompt: buildRolePrompt(role, {
10317
+ TICKET: ticket,
10318
+ ...taskData
10319
+ }),
10320
+ systemPrompt: rolePrompt.systemPrompt
10321
+ };
10322
+ }).filter(Boolean);
10323
+ const groupResults = await this.executeParallel(configs);
10324
+ results.push(...groupResults);
10325
+ }
10326
+ }
10327
+ return results;
10328
+ }
10329
+ /**
10330
+ * 执行完整流程
10331
+ */
10332
+ async executeFullFlow(ticket, goal, phases, options) {
10333
+ const allResults = /* @__PURE__ */ new Map();
10334
+ for (const phase of phases) {
10335
+ try {
10336
+ options?.onPhaseStart?.(phase);
10337
+ const results = await this.executePhase(ticket, phase, {
10338
+ TICKET: ticket,
10339
+ GOAL: goal,
10340
+ PHASE: phase
10341
+ });
10342
+ allResults.set(phase, results);
10343
+ options?.onPhaseComplete?.(phase, results);
10344
+ const allSuccess = results.every((r) => r.success);
10345
+ if (!allSuccess) {
10346
+ console.log(`
10347
+ \u26A0\uFE0F Phase ${phase} \u90E8\u5206\u4EFB\u52A1\u5931\u8D25`);
10348
+ console.log("\u662F\u5426\u7EE7\u7EED\u4E0B\u4E00\u4E2A\u9636\u6BB5\uFF1F(\u5EFA\u8BAE\u5148\u4FEE\u590D\u5931\u8D25\u7684\u4EFB\u52A1)");
10349
+ }
10350
+ } catch (error) {
10351
+ logger.error(`Phase ${phase} \u6267\u884C\u5931\u8D25: ${error}`);
10352
+ options?.onError?.(error);
10353
+ }
10354
+ }
10355
+ return allResults;
10356
+ }
10357
+ /**
10358
+ * 打印执行汇总
10359
+ */
10360
+ printSummary(results) {
10361
+ console.log("\n" + "\u2550".repeat(60));
10362
+ console.log("\u{1F4CA} \u5E76\u884C\u4EFB\u52A1\u6267\u884C\u7ED3\u679C");
10363
+ console.log("\u2550".repeat(60));
10364
+ let successCount = 0;
10365
+ let failCount = 0;
10366
+ for (const result of results) {
10367
+ const icon = result.success ? "\u2713" : "\u2717";
10368
+ const duration = (result.duration / 1e3).toFixed(1);
10369
+ console.log(` ${icon} [${result.role.padEnd(12)}] ${result.exitCode} (${duration}s)`);
10370
+ if (result.success) {
10371
+ successCount++;
10372
+ } else {
10373
+ failCount++;
10374
+ }
10375
+ }
10376
+ console.log("\u2500".repeat(60));
10377
+ console.log(` \u603B\u8BA1: ${successCount} \u6210\u529F, ${failCount} \u5931\u8D25`);
10378
+ console.log("\u2550".repeat(60) + "\n");
10379
+ }
10380
+ /**
10381
+ * 从输出中提取生成的交接物文件
10382
+ */
10383
+ extractArtifacts(output) {
10384
+ const artifacts = [];
10385
+ const filePatterns = [
10386
+ /\.handoff\/[^\s]+\.(md|yaml|json)/gi,
10387
+ /`(?:create|write|update):?\s*([^\s`]+)/gi
10388
+ ];
10389
+ for (const pattern of filePatterns) {
10390
+ const matches = output.matchAll(pattern);
10391
+ for (const match of matches) {
10392
+ const file = match[1] || match[0];
10393
+ if (file && !artifacts.includes(file)) {
10394
+ artifacts.push(file);
10395
+ }
10396
+ }
10397
+ }
10398
+ return artifacts;
10399
+ }
10400
+ /**
10401
+ * 生成任务报告
10402
+ */
10403
+ generateReport(results) {
10404
+ const lines = [];
10405
+ lines.push("# \u5E76\u884C\u6267\u884C\u62A5\u544A");
10406
+ lines.push("");
10407
+ lines.push(`**\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`);
10408
+ lines.push(`**\u6210\u529F**: ${results.filter((r) => r.success).length}`);
10409
+ lines.push(`**\u5931\u8D25**: ${results.filter((r) => !r.success).length}`);
10410
+ lines.push("");
10411
+ lines.push("## \u8BE6\u7EC6\u7ED3\u679C");
10412
+ lines.push("");
10413
+ for (const result of results) {
10414
+ lines.push(`### ${result.role}`);
10415
+ lines.push("");
10416
+ lines.push(`- **\u72B6\u6001**: ${result.success ? "\u2713 \u6210\u529F" : "\u2717 \u5931\u8D25"}`);
10417
+ lines.push(`- **\u8017\u65F6**: ${(result.duration / 1e3).toFixed(1)}s`);
10418
+ lines.push(`- **\u9000\u51FA\u7801**: ${result.exitCode}`);
10419
+ lines.push(`- **\u5DE5\u4F5C\u76EE\u5F55**: ${result.worktreePath}`);
10420
+ lines.push("");
10421
+ if (result.artifacts.length > 0) {
10422
+ lines.push("**\u751F\u6210\u7684\u4EA4\u63A5\u7269**:");
10423
+ for (const artifact of result.artifacts) {
10424
+ lines.push(`- ${artifact}`);
10425
+ }
10426
+ lines.push("");
10427
+ }
10428
+ if (result.error) {
10429
+ lines.push(`**\u9519\u8BEF**: ${result.error}`);
10430
+ lines.push("");
10431
+ }
10432
+ }
10433
+ return lines.join("\n");
10434
+ }
10435
+ /**
10436
+ * 保存报告
10437
+ */
10438
+ async saveReport(ticket, results) {
10439
+ const reportDir = path23.join(this.repoRoot, ".handoff", ticket, "reports");
10440
+ await fs8.ensureDir(reportDir);
10441
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
10442
+ const reportFile = path23.join(reportDir, `execution-${timestamp}.md`);
10443
+ const report = this.generateReport(results);
10444
+ await fs8.writeFile(reportFile, report, "utf-8");
10445
+ logger.success(`\u6267\u884C\u62A5\u544A\u5DF2\u4FDD\u5B58: ${reportFile}`);
10446
+ return reportFile;
10447
+ }
10448
+ };
10449
+ }
10450
+ });
10451
+
10452
+ // src/commands/dev-worktree.ts
10453
+ import { Command as Command18 } from "commander";
10454
+ import inquirer13 from "inquirer";
10455
+ import path24 from "path";
10456
+ async function showMainMenu(worktreeManager, handoffManager, executor2, options) {
10457
+ const choices = [
10458
+ {
10459
+ name: "\u{1F4CB} \u65B0\u5EFA\u5E76\u884C\u5F00\u53D1\u4F1A\u8BDD",
10460
+ value: "new"
10461
+ },
10462
+ {
10463
+ name: "\u{1F4C2} \u5217\u51FA\u6240\u6709 Worktree",
10464
+ value: "list"
10465
+ },
10466
+ {
10467
+ name: "\u{1F4CA} \u67E5\u770B\u4F1A\u8BDD\u72B6\u6001",
10468
+ value: "status"
10469
+ },
10470
+ {
10471
+ name: "\u25B6\uFE0F \u8FD0\u884C\u9636\u6BB5",
10472
+ value: "run"
10473
+ },
10474
+ {
10475
+ name: "\u2705 \u9A8C\u8BC1\u4EA4\u63A5\u7269",
10476
+ value: "verify"
10477
+ },
10478
+ {
10479
+ name: "\u{1F6AA} \u9000\u51FA",
10480
+ value: "exit"
10481
+ }
10482
+ ];
10483
+ const { action } = await inquirer13.prompt([
10484
+ {
10485
+ type: "list",
10486
+ name: "action",
10487
+ message: "\u9009\u62E9\u64CD\u4F5C:",
10488
+ choices
10489
+ }
10490
+ ]);
10491
+ switch (action) {
10492
+ case "new":
10493
+ await handleNewSession(worktreeManager, handoffManager, options, process.cwd());
10494
+ break;
10495
+ case "list":
10496
+ await handleList(worktreeManager);
10497
+ break;
10498
+ case "status":
10499
+ await handleStatus(worktreeManager, handoffManager, options);
10500
+ break;
10501
+ case "run":
10502
+ await handleRunPhaseSelect(worktreeManager, handoffManager, executor2, options);
10503
+ break;
10504
+ case "verify":
10505
+ await handleVerifySelect(worktreeManager, handoffManager, options);
10506
+ break;
10507
+ case "exit":
10508
+ process.exit(0);
10509
+ }
10510
+ }
10511
+ async function handleNewSession(worktreeManager, handoffManager, options, repoRoot) {
10512
+ logger.step("\u65B0\u5EFA\u5E76\u884C\u5F00\u53D1\u4F1A\u8BDD");
10513
+ const questions = [
10514
+ {
10515
+ type: "input",
10516
+ name: "ticket",
10517
+ message: "\u8F93\u5165 TICKET ID (\u4F8B\u5982 USER-123):",
10518
+ validate: (input) => {
10519
+ if (!input.trim()) return "\u8BF7\u8F93\u5165 TICKET ID";
10520
+ return true;
10521
+ }
10522
+ },
10523
+ {
10524
+ type: "input",
10525
+ name: "goal",
10526
+ message: "\u8F93\u5165\u4EFB\u52A1\u76EE\u6807:",
10527
+ validate: (input) => {
10528
+ if (!input.trim()) return "\u8BF7\u8F93\u5165\u4EFB\u52A1\u76EE\u6807";
10529
+ return true;
10530
+ }
10531
+ },
10532
+ {
10533
+ type: "checkbox",
10534
+ name: "roles",
10535
+ message: "\u9009\u62E9\u53C2\u4E0E\u89D2\u8272:",
10536
+ choices: [
10537
+ { name: "Planner (\u9700\u6C42\u4E0E BDD)", value: "planner", checked: true },
10538
+ { name: "Architect (\u67B6\u6784)", value: "architect", checked: true },
10539
+ { name: "Backend (\u540E\u7AEF)", value: "backend", checked: true },
10540
+ { name: "Frontend (\u524D\u7AEF)", value: "frontend", checked: true },
10541
+ { name: "QA (BDD \u9A8C\u6536)", value: "qa", checked: true },
10542
+ { name: "Reviewer (\u5BA1\u67E5)", value: "reviewer", checked: true }
10543
+ ]
10544
+ }
10545
+ ];
10546
+ const answers = await inquirer13.prompt(questions);
10547
+ logger.newLine();
10548
+ logger.info(`\u521B\u5EFA\u4F1A\u8BDD: ${answers.ticket}`);
10549
+ logger.info(`\u76EE\u6807: ${answers.goal}`);
10550
+ logger.info(`\u89D2\u8272: ${answers.roles.join(", ")}`);
10551
+ logger.newLine();
10552
+ const { confirm } = await inquirer13.prompt([
10553
+ {
10554
+ type: "confirm",
10555
+ name: "confirm",
10556
+ message: "\u786E\u8BA4\u521B\u5EFA\u4F1A\u8BDD?",
10557
+ default: true
10558
+ }
10559
+ ]);
10560
+ if (!confirm) {
10561
+ logger.info("\u5DF2\u53D6\u6D88");
10562
+ return;
10563
+ }
10564
+ try {
10565
+ const session = await worktreeManager.createSession({
10566
+ ticket: answers.ticket,
10567
+ goal: answers.goal,
10568
+ baseBranch: "main",
10569
+ enabledRoles: answers.roles
10570
+ });
10571
+ await handoffManager.init(answers.ticket, answers.roles);
10572
+ await generateInitialTemplates(handoffManager, answers.ticket, answers.goal);
10573
+ logger.newLine();
10574
+ logger.success("\u4F1A\u8BDD\u521B\u5EFA\u6210\u529F!");
10575
+ logger.info("");
10576
+ logger.info("Worktree \u76EE\u5F55:");
10577
+ for (const wt of session.worktrees) {
10578
+ logger.info(` - ${wt.name} (${wt.path})`);
10579
+ }
10580
+ logger.info("");
10581
+ logger.info("\u4EA4\u63A5\u7269\u76EE\u5F55:");
10582
+ logger.info(` .handoff/${answers.ticket}/`);
10583
+ const { runNow } = await inquirer13.prompt([
10584
+ {
10585
+ type: "confirm",
10586
+ name: "runNow",
10587
+ message: "\u662F\u5426\u7ACB\u5373\u5F00\u59CB\u8FD0\u884C Phase 1?",
10588
+ default: false
10589
+ }
10590
+ ]);
10591
+ if (runNow) {
10592
+ await handleRunPhase(worktreeManager, handoffManager, executor, {
10593
+ ticket: answers.ticket,
10594
+ goal: answers.goal,
10595
+ phase: "1"
10596
+ });
10597
+ }
10598
+ } catch (error) {
10599
+ logger.error(`\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: ${error}`);
10600
+ }
10601
+ }
10602
+ async function generateInitialTemplates(handoffManager, ticket, goal) {
10603
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
10604
+ await handoffManager.generateTemplate(ticket, "planner", "SPEC", {
10605
+ TICKET: ticket,
10606
+ TITLE: goal,
10607
+ OWNER: "\u5F85\u5B9A",
10608
+ DATE: today,
10609
+ CRITERIA_1: "[\u5F85\u586B\u5199]",
10610
+ CRITERIA_2: "[\u5F85\u586B\u5199]",
10611
+ CRITERIA_3: "[\u5F85\u586B\u5199]",
10612
+ DEPENDENCIES: "\u65E0",
10613
+ DEPENDED_BY: "\u5F85\u5B9A",
10614
+ FEATURE: "\u5F85\u5B9A"
10615
+ });
10616
+ await handoffManager.generateTemplate(ticket, "planner", "TASKS", {
10617
+ TICKET: ticket
10618
+ });
10619
+ }
10620
+ async function handleList(worktreeManager) {
10621
+ logger.step("\u5217\u51FA\u6240\u6709 Worktree");
10622
+ const worktrees = await worktreeManager.list();
10623
+ if (worktrees.length === 0) {
10624
+ logger.info("\u6CA1\u6709\u627E\u5230 Worktree");
10625
+ return;
10626
+ }
10627
+ logger.newLine();
10628
+ 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");
10629
+ console.log("\u2502 Worktree \u5217\u8868 \u2502");
10630
+ 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");
10631
+ for (const wt of worktrees) {
10632
+ const statusIcon = wt.status === "active" ? "\u25CF" : wt.status === "completed" ? "\u2713" : "\u25CB";
10633
+ console.log(`\u2502 ${statusIcon} ${wt.name.padEnd(20)} ${wt.branch.padEnd(25)} \u2502`);
10634
+ }
10635
+ 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");
10636
+ }
10637
+ async function handleStatus(worktreeManager, handoffManager, options) {
10638
+ logger.step("\u67E5\u770B\u4F1A\u8BDD\u72B6\u6001");
10639
+ const handoffDir = path24.join(process.cwd(), ".handoff");
10640
+ const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
10641
+ if (sessions.length === 0) {
10642
+ logger.info("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
10643
+ return;
10644
+ }
10645
+ const sessionChoices = sessions.map((s) => {
10646
+ const ticket2 = s.replace("session-", "").replace(".json", "");
10647
+ return { name: ticket2, value: ticket2 };
10648
+ });
10649
+ const { ticket } = await inquirer13.prompt([
10650
+ {
10651
+ type: "list",
10652
+ name: "ticket",
10653
+ message: "\u9009\u62E9\u4F1A\u8BDD:",
10654
+ choices: sessionChoices
10655
+ }
10656
+ ]);
10657
+ const state = await worktreeManager.getSessionState(ticket);
10658
+ if (!state) {
10659
+ logger.error("\u4F1A\u8BDD\u72B6\u6001\u4E0D\u5B58\u5728");
10660
+ return;
10661
+ }
10662
+ logger.newLine();
10663
+ console.log(`\u{1F4CB} \u4F1A\u8BDD: ${ticket}`);
10664
+ console.log(`\u{1F4CA} \u5F53\u524D\u9636\u6BB5: ${state.currentPhase}`);
10665
+ console.log(`\u2705 \u5DF2\u5B8C\u6210\u9636\u6BB5: ${state.completedPhases.length}`);
10666
+ console.log("");
10667
+ console.log(formatPhaseProgress(state.completedPhases));
10668
+ }
10669
+ async function handleRunPhaseSelect(worktreeManager, handoffManager, executor2, options) {
10670
+ const handoffDir = path24.join(process.cwd(), ".handoff");
10671
+ const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
10672
+ if (sessions.length === 0) {
10673
+ logger.error("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
10674
+ return;
10675
+ }
10676
+ const ticketChoices = sessions.map((s) => {
10677
+ const ticket2 = s.replace("session-", "").replace(".json", "");
10678
+ return { name: ticket2, value: ticket2 };
10679
+ });
10680
+ const { ticket } = await inquirer13.prompt([
10681
+ {
10682
+ type: "list",
10683
+ name: "ticket",
10684
+ message: "\u9009\u62E9\u4F1A\u8BDD:",
10685
+ choices: ticketChoices
10686
+ }
10687
+ ]);
10688
+ const phaseChoices = PHASE_SEQUENCE.map((p, i) => ({
10689
+ name: `${i + 1}. ${getPhaseDisplayName(p)}`,
10690
+ value: String(i + 1)
10691
+ }));
10692
+ const { phaseNum } = await inquirer13.prompt([
10693
+ {
10694
+ type: "list",
10695
+ name: "phaseNum",
10696
+ message: "\u9009\u62E9\u9636\u6BB5:",
10697
+ choices: phaseChoices
10698
+ }
10699
+ ]);
10700
+ await handleRunPhase(worktreeManager, handoffManager, executor2, {
10701
+ ticket,
10702
+ phase: phaseNum
10703
+ });
10704
+ }
10705
+ async function checkPhaseGate(ticket, phaseName, completedPhases) {
10706
+ const gateNum = getGateNumber(phaseName);
10707
+ const conditions = GATE_CONDITIONS[gateNum] || [];
10708
+ logger.newLine();
10709
+ console.log("\u2550".repeat(60));
10710
+ console.log(`\u{1F6AA} Gate ${gateNum} \u68C0\u67E5: ${getPhaseDisplayName(phaseName)}`);
10711
+ console.log("\u2550".repeat(60));
10712
+ console.log("");
10713
+ const canEnter = canEnterPhase(phaseName, completedPhases);
10714
+ if (!canEnter) {
10715
+ console.log("\u26A0\uFE0F \u524D\u7F6E\u9636\u6BB5\u672A\u5B8C\u6210:");
10716
+ const phaseConfig = PHASE_CONFIGS[phaseName];
10717
+ for (const dep of phaseConfig.dependsOn) {
10718
+ const isDone = completedPhases.includes(dep);
10719
+ console.log(` ${isDone ? "\u2713" : "\u2717"} ${getPhaseDisplayName(dep)}`);
10720
+ }
10721
+ console.log("");
10722
+ logger.warn("\u8BF7\u5148\u5B8C\u6210\u524D\u7F6E\u9636\u6BB5\u540E\u518D\u8FDB\u5165\u6B64\u9636\u6BB5");
10723
+ return false;
10724
+ }
10725
+ console.log("\u2713 \u524D\u7F6E\u9636\u6BB5\u5DF2\u5168\u90E8\u5B8C\u6210");
10726
+ console.log("");
10727
+ console.log("\u{1F4CB} Gate \u68C0\u67E5\u6E05\u5355:");
10728
+ console.log("");
10729
+ if (conditions.length === 0) {
10730
+ console.log(" (\u6B64\u9636\u6BB5\u65E0\u9700\u989D\u5916\u68C0\u67E5)");
10731
+ } else {
10732
+ for (let i = 0; i < conditions.length; i++) {
10733
+ const cond = conditions[i];
10734
+ console.log(` [ ] ${cond.description}`);
10735
+ }
10736
+ }
10737
+ console.log("");
10738
+ console.log("\u2500".repeat(60));
10739
+ const { passed } = await inquirer13.prompt([
10740
+ {
10741
+ type: "confirm",
10742
+ name: "passed",
10743
+ message: "\u662F\u5426\u786E\u8BA4\u901A\u8FC7\u6B64 Gate? (\u786E\u8BA4\u540E\u9636\u6BB5\u5C06\u88AB\u6807\u8BB0\u4E3A\u5B8C\u6210)",
10744
+ default: false
10745
+ }
10746
+ ]);
10747
+ return passed;
10748
+ }
10749
+ async function handleRunPhase(worktreeManager, handoffManager, executor2, options) {
10750
+ const { ticket, runPhase: phase, goal } = options;
10751
+ const phaseIndex = parseInt(phase) - 1;
10752
+ const phaseName = PHASE_SEQUENCE[phaseIndex];
10753
+ if (!phaseName) {
10754
+ logger.error(`\u65E0\u6548\u7684\u9636\u6BB5: ${phase}`);
10755
+ return;
10756
+ }
10757
+ logger.step(`\u8FD0\u884C Phase ${phase}: ${getPhaseDisplayName(phaseName)}`);
10758
+ const parallelRoles = getParallelRoles(phaseName);
10759
+ logger.info("\u5C06\u5E76\u884C\u6267\u884C\u7684\u89D2\u8272\u7EC4:");
10760
+ for (const group of parallelRoles) {
10761
+ logger.info(` - ${group.join(" + ")}`);
10762
+ }
10763
+ const { confirm } = await inquirer13.prompt([
10764
+ {
10765
+ type: "confirm",
10766
+ name: "confirm",
10767
+ message: "\u786E\u8BA4\u5F00\u59CB\u8FD0\u884C?",
10768
+ default: true
10769
+ }
10770
+ ]);
10771
+ if (!confirm) {
10772
+ logger.info("\u5DF2\u53D6\u6D88");
10773
+ return;
10774
+ }
10775
+ const state = await worktreeManager.getSessionState(ticket);
10776
+ if (!state) {
10777
+ logger.error("\u4F1A\u8BDD\u4E0D\u5B58\u5728");
10778
+ return;
10779
+ }
10780
+ const gatePassed = await checkPhaseGate(ticket, phaseName, state.completedPhases);
10781
+ if (!gatePassed) {
10782
+ logger.info("\u95E8\u7981\u68C0\u67E5\u672A\u901A\u8FC7\uFF0C\u8BF7\u5B8C\u6210\u524D\u7F6E\u6761\u4EF6\u540E\u91CD\u8BD5");
10783
+ return;
10784
+ }
10785
+ try {
10786
+ const results = await executor2.executePhase(ticket, phaseName, {
10787
+ TICKET: ticket,
10788
+ GOAL: goal || state.ticket || "",
10789
+ PHASE: phaseName
10790
+ });
10791
+ await executor2.saveReport(ticket, results);
10792
+ await worktreeManager.updateSessionState(ticket, {
10793
+ currentPhase: phaseName
10794
+ });
10795
+ const allSuccess = results.every((r) => r.success);
10796
+ if (allSuccess) {
10797
+ logger.success("\u9636\u6BB5\u6267\u884C\u6210\u529F!");
10798
+ } else {
10799
+ logger.warn("\u90E8\u5206\u4EFB\u52A1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8F93\u51FA");
10800
+ }
10801
+ } catch (error) {
10802
+ logger.error(`\u9636\u6BB5\u6267\u884C\u5931\u8D25: ${error}`);
10803
+ }
10804
+ }
10805
+ async function handleVerifySelect(worktreeManager, handoffManager, options) {
10806
+ const handoffDir = path24.join(process.cwd(), ".handoff");
10807
+ const sessions = await FileUtils.findFiles("session-*.json", handoffDir);
10808
+ if (sessions.length === 0) {
10809
+ logger.error("\u6CA1\u6709\u627E\u5230\u6D3B\u8DC3\u7684\u4F1A\u8BDD");
10810
+ return;
10811
+ }
10812
+ const ticketChoices = sessions.map((s) => {
10813
+ const ticket2 = s.replace("session-", "").replace(".json", "");
10814
+ return { name: ticket2, value: ticket2 };
10815
+ });
10816
+ const { ticket } = await inquirer13.prompt([
10817
+ {
10818
+ type: "list",
10819
+ name: "ticket",
10820
+ message: "\u9009\u62E9\u4F1A\u8BDD:",
10821
+ choices: ticketChoices
10822
+ }
10823
+ ]);
10824
+ await handleVerify(worktreeManager, handoffManager, { ticket });
10825
+ }
10826
+ async function handleVerify(worktreeManager, handoffManager, options) {
10827
+ const { ticket } = options;
10828
+ logger.step(`\u9A8C\u8BC1\u4EA4\u63A5\u7269: ${ticket}`);
10829
+ const result = await handoffManager.verify(ticket);
10830
+ if (result.complete) {
10831
+ logger.success("\u2705 \u6240\u6709\u4EA4\u63A5\u7269\u5DF2\u5C31\u7EEA!");
10832
+ } else {
10833
+ logger.warn(`\u26A0\uFE0F \u7F3A\u5C11 ${result.missing.length} \u4E2A\u4EA4\u63A5\u7269:`);
10834
+ for (const item of result.missing) {
10835
+ logger.info(` - ${item}`);
10836
+ }
10837
+ }
10838
+ }
10839
+ var devWorktreeCommand;
10840
+ var init_dev_worktree = __esm({
10841
+ "src/commands/dev-worktree.ts"() {
10842
+ "use strict";
10843
+ init_esm_shims();
10844
+ init_utils();
10845
+ init_logger();
10846
+ init_worktree_manager();
10847
+ init_handoff_manager();
10848
+ init_concurrent_executor();
10849
+ init_phase_config();
10850
+ 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) => {
10851
+ try {
10852
+ logger.header("Worktree Orchestrator");
10853
+ logger.newLine();
10854
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
10855
+ if (!hasTechStack) {
10856
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
10857
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
10858
+ process.exit(1);
10859
+ }
10860
+ logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
10861
+ const repoRoot = process.cwd();
10862
+ const worktreeManager = new WorktreeManager(repoRoot);
10863
+ const handoffManager = createHandoffManager(repoRoot);
10864
+ const executor2 = createExecutor(repoRoot);
10865
+ if (options.new) {
10866
+ await handleNewSession(worktreeManager, handoffManager, options, repoRoot);
10867
+ } else if (options.list) {
10868
+ await handleList(worktreeManager);
10869
+ } else if (options.status) {
10870
+ await handleStatus(worktreeManager, handoffManager, options);
10871
+ } else if (options.runPhase) {
10872
+ await handleRunPhase(worktreeManager, handoffManager, executor2, options);
10873
+ } else if (options.verify) {
10874
+ await handleVerify(worktreeManager, handoffManager, options);
10875
+ } else {
10876
+ await showMainMenu(worktreeManager, handoffManager, executor2, options);
10877
+ }
10878
+ logger.newLine();
10879
+ logger.success("Worktree Orchestrator \u64CD\u4F5C\u5B8C\u6210!");
10880
+ } catch (error) {
10881
+ logger.error(`\u64CD\u4F5C\u5931\u8D25: ${error.message}`);
10882
+ if (process.env.DEBUG) {
10883
+ console.error(error);
10884
+ }
10885
+ process.exit(1);
10886
+ }
10887
+ });
10888
+ }
10889
+ });
10890
+
8853
10891
  // src/index.ts
8854
10892
  var index_exports = {};
8855
- import { Command as Command18 } from "commander";
10893
+ import { Command as Command19 } from "commander";
8856
10894
  import chalk4 from "chalk";
8857
- import fs6 from "fs-extra";
8858
- import path21 from "path";
10895
+ import fs9 from "fs-extra";
10896
+ import path25 from "path";
8859
10897
  import { fileURLToPath as fileURLToPath2 } from "url";
8860
10898
  function showHelp() {
8861
10899
  console.log("");
@@ -8880,6 +10918,7 @@ function showHelp() {
8880
10918
  console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
8881
10919
  console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
8882
10920
  console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
10921
+ console.log(" team-cli dev:wt [options] Worktree Orchestrator - \u591A\u89D2\u8272\u5E76\u884C\u5F00\u53D1");
8883
10922
  console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
8884
10923
  console.log("");
8885
10924
  console.log(chalk4.bold("\u793A\u4F8B:"));
@@ -8938,9 +10977,10 @@ var init_index = __esm({
8938
10977
  init_update();
8939
10978
  init_config();
8940
10979
  init_diff();
8941
- __dirname2 = path21.dirname(fileURLToPath2(import.meta.url));
8942
- pkg = fs6.readJsonSync(path21.join(__dirname2, "../package.json"));
8943
- program = new Command18();
10980
+ init_dev_worktree();
10981
+ __dirname2 = path25.dirname(fileURLToPath2(import.meta.url));
10982
+ pkg = fs9.readJsonSync(path25.join(__dirname2, "../package.json"));
10983
+ program = new Command19();
8944
10984
  program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
8945
10985
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
8946
10986
  program.addCommand(initCommand);
@@ -8961,6 +11001,7 @@ var init_index = __esm({
8961
11001
  program.addCommand(updateCommand);
8962
11002
  program.addCommand(configCommand);
8963
11003
  program.addCommand(diffCommand);
11004
+ program.addCommand(devWorktreeCommand);
8964
11005
  program.action(() => {
8965
11006
  showHelp();
8966
11007
  });