dsh-plugin-teamflow 0.1.3 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  > 本插件首次公开发布版本为 **v0.1.0**;发布前的内部迭代(v0.3~v0.13)记录于 `AGENTS.md` §5,对外统一归到 v0.1.0。
4
4
 
5
+ ## [0.1.4] - 2026-08-29
6
+
7
+ ### 修复
8
+ - **分支决策死循环**:用户确认「新建分支」后重发启动参数仍会再次弹出分支决策(干净工作区场景)。修复:决策选项增加显式确认值,自定义分支名/工作区处理参数均视为已确认
9
+ - **护栏误杀大文件任务**:大文件「读-改-读-改」是正常模式(每次修改后必须重读确认),可能被误判为推理复读而中止。修复:复读判定升级为状态判定——有实际修改进展时不中止,仅纯复读(零进展)才中止
10
+ - **QA/验收只读任务不再被误杀**:执行测试脚本视为进展信号,只读分析任务(不做文件修改)不再被复读检测误伤
11
+ - **退化中止不再自动重试**:真退化(推理复读死循环)后自动重试大概率在污染会话内复现且持续烧钱——改为直接需人工介入,引导 `teamflow_resume`(全新会话续跑)
12
+ - **断点续跑尊重未闭环的 QA 缺陷**:阻断缺陷仍 open 时 resume 回到 QA 修复-复验闭环,不再带着已知缺陷直接进产品验收;验收失败过的 run 同样正确回 QA
13
+ - **历史失败记录保留**:resume 不再清除失败的阶段记录(审计可追溯)
14
+
15
+ ### 新增
16
+ - **提测门禁**:开发阶段有任务失败(哪怕 1 个)→ 需人工介入,不再自动进入 QA(失败任务是已知缺口,QA 检查必然重复报告);resume 精确补跑失败任务(已完成任务产物复用,不重跑)
17
+ - **README 界面预览**:5 张真实工作台截图(流水线视图/看板/阶段详情/看板任务详情/团队选择)
18
+
19
+ ### 其他
20
+ - pnpm-lockfile 同步(依赖调整后 CI 的 frozen-lockfile 校验失败,已修复)
21
+
5
22
  ## [0.1.3] - 2026-08-29
6
23
 
7
24
  ### 新增
package/README.en.md CHANGED
@@ -18,6 +18,28 @@ requirement → PRD (based on existing patterns / product memory, archived to pr
18
18
  → product acceptance (update product memory)
19
19
  ```
20
20
 
21
+ ## Screenshots
22
+
23
+ 1. Pipeline view — stage serpentine lanes + node cards (status / duration / tokens / subagent session)
24
+
25
+ ![Pipeline view](docs/screenshots/pipeline-view.png)
26
+
27
+ 2. Backlog board — draggable lanes for requirements / tasks / defects
28
+
29
+ ![Backlog board](docs/screenshots/board.png)
30
+
31
+ 3. Stage detail drawer — full stage artifacts + token breakdown + "🎬 jump to subagent session"
32
+
33
+ ![Stage detail](docs/screenshots/stage-detail.png)
34
+
35
+ 4. Board task detail — task-card drawer (requirement text / assignments / event timeline / subtasks / defects / tokens)
36
+
37
+ ![Board task detail](docs/screenshots/board-task-detail.png)
38
+
39
+ 5. Team selector — 🏭 button + team dropdown
40
+
41
+ ![Team selector](docs/screenshots/team-selector.png)
42
+
21
43
  ## Core Features
22
44
 
23
45
  - **Anti-fake-delivery**: ① Substantive validation — outputs containing rejection phrases ("I cannot complete", etc.) or below the per-stage length floor are treated as undelivered and routed to retry / human intervention; ② Token circuit breaker — a single stage accumulating 60k budget stops retries; ③ Context-exhaustion failures are not retried (retrying the same prompt likely reproduces); ④ Product-level concurrency lock — only one active pipeline per product at a time, preventing requirement state from stepping on itself; ⑤ Full stage outputs are retained (memory + disk) for the detail drawer and checkpoint resume.
package/README.md CHANGED
@@ -18,6 +18,28 @@ TeamFlow 团队研发流水线 —— DeepSeek Harness 可分发插件(`dsh pl
18
18
  → 产品验收(更新产品记忆)
19
19
  ```
20
20
 
21
+ ## 界面预览
22
+
23
+ 1. 流水线视图——阶段蛇形泳道 + 节点卡片(状态/耗时/token/子代理会话)
24
+
25
+ ![流水线视图](docs/screenshots/pipeline-view.png)
26
+
27
+ 2. Backlog 看板——需求/任务/缺陷拖拽泳道
28
+
29
+ ![Backlog 看板](docs/screenshots/board.png)
30
+
31
+ 3. 阶段详情抽屉——阶段性产物全文 + token 明细 +「🎬 跳转子代理会话」
32
+
33
+ ![阶段详情](docs/screenshots/stage-detail.png)
34
+
35
+ 4. 看板任务详情——任务卡抽屉(需求原文/分配/事件时间线/子卡/缺陷/token)
36
+
37
+ ![看板任务详情](docs/screenshots/board-task-detail.png)
38
+
39
+ 5. 团队选择——🏭 按钮 + 团队下拉
40
+
41
+ ![团队选择](docs/screenshots/team-selector.png)
42
+
21
43
  ## 核心特性
22
44
 
23
45
  - **防假交付**:① 实质校验——拒绝措辞("我无法完成"等)或低于阶段长度下限的输出视为未交付,走重试/需人工;② token 熔断——单阶段累计 60k 预算,超限停止重试;③ 上下文耗尽类失败不重试(重试同一 prompt 大概率复现);④ 产品级并发锁——同一产品同时只允许一条活跃流水线,防需求状态互踩;⑤ 阶段产物全文保留(内存 + 磁盘,供详情抽屉与断点续跑读取)。
package/lib/host.mjs CHANGED
@@ -971,6 +971,17 @@ function verifyReqBugs(journal) {
971
971
  }
972
972
  if (touched) store.persist();
973
973
  }
974
+ /** 该需求是否存在未闭环的阻断缺陷(P0/P1/P2 仍 open)——resume 断点定位与 QA 复用判定依赖。
975
+ * 实锤 run tf-mte906e9:QA 修复子代理失败 → run failed,但 QA 阶段本身 done(缺陷已登记),
976
+ * 旧 interruptedPhaseOf 直接定位产品验收 → 带缺陷代码进验收。
977
+ * ⚠️ store key 与 resumeRun/storeFor 解析一致(workspace || product || default),否则查错 store 误判无缺陷。 */
978
+ function hasOpenBlockingBugs(journal) {
979
+ try {
980
+ return storeFor(journal.workspace || journal.product || "default").bugs.some((b) => b.reqId === journal.reqId && b.status === "open" && b.severity !== "P3");
981
+ } catch (e) {
982
+ return false;
983
+ }
984
+ }
974
985
  /** 默认团队配置文件内容。 */
975
986
  const DEFAULT_TEAMS_FILE = {
976
987
  version: 1,
@@ -1195,16 +1206,25 @@ function totalTokensOf(usage) {
1195
1206
  * 的健康节奏工作到第 25 分钟被击落(tf-mt8fbavd dev 尝试一)。
1196
1207
  *
1197
1208
  * 设计原则:【只看进度信号,无任何时间配额】慢吞吐的合法任务永远不该被打断:
1198
- * A. 复读检测:滑动窗口内同一规范化流式片段出现 ≥ GUARD_REPEAT_LIMIT 次(逐字复读 = 真退化)
1199
- * outcome='degenerated'(豁免预算门,允许一次干净重试)。
1209
+ * A. 复读检测:滑动窗口内同一规范化流式片段出现 ≥ GUARD_REPEAT_LIMIT 次,且窗口内零变更进展
1210
+ * (无 edit/write 等写操作)→ 真退化(纯推理打转)→ outcome='degenerated'(豁免预算门,允许一次干净重试)。
1211
+ * ⚠️ 状态判定(实锤 run tf-mte906e9):大文件 read-edit 循环是正常模式——模型反复 read 同一大文件
1212
+ * (每次 edit 后内容已变,必须重读确认)、输出高度相似的「读后分析」,逐字片段在 400 条窗口内
1213
+ * 可累积 ≥12 次——伴随 edit/write 变更调用时只记录观察,不中止(否则大文件修改任务全被误杀)。
1200
1214
  * B. 挂死检测:连续 GUARD_SILENCE_MS 一个新事件都没有(provider 层挂起/连接静默死亡)
1201
1215
  * → outcome='stalled'(走正常预算门 → 熔断转人工,不自动重试烧钱)。
1202
1216
  * C. 空转检测:会话仍在产出事件,但连续 GUARD_NO_TOOL_MS 没有任何工具调用
1203
1217
  * (纯推理打转/改写式循环;正常 agent 每分钟都在调工具)→ outcome='stalled'。
1218
+ * 兜底关系:复读判定放宽后,edit 后陷入死循环的漏网场景由 C(长时间无工具调用)兜住。
1204
1219
  *
1205
1220
  * 中止方式:run.dispose() → run.result 结算。outcome 命名刻意避开 isUnretryable 的
1206
1221
  * /token|context|limit/ 正则;只有 'degenerated' 享受干净重试豁免(runner.withRetry)。
1207
1222
  */
1223
+ /** 进展工具(复读状态判定):变更类写操作 + 脚本执行。
1224
+ * 「有进展」= 大文件 read-edit 循环(dev)或只读分析任务的 read+跑脚本循环(QA/验收)均属正常模式;
1225
+ * 纯 read 循环(反复整读同一文件却无变更/无脚本执行)= 真退化。实锤 run tf-mte906e9:QA 重跑
1226
+ * 只读分析(不 edit)→ 旧判定「零变更进展」误杀,第 2 次 provider error 后 450k 熔断。 */
1227
+ const PROGRESS_TOOLS = /^(edit|write|create|apply_patch|patch|remove|delete|rm|mkdir|move|rename|append|bash|pwsh|shell|powershell)$/i;
1208
1228
  /** 与 metering 同款事件访问器(session.events 可能是数组或返回数组的函数)。 */
1209
1229
  function eventsOf(run) {
1210
1230
  const local = run && run.localAgent;
@@ -1267,6 +1287,8 @@ function startStageGuard(opts) {
1267
1287
  const warnedReads = /* @__PURE__ */ new Set();
1268
1288
  const scriptCounts = /* @__PURE__ */ new Map();
1269
1289
  const warnedScripts = /* @__PURE__ */ new Set();
1290
+ let lastMutationAt = 0;
1291
+ let repeatWarned = false;
1270
1292
  function warnOnce(key, set, message, hint) {
1271
1293
  if (set.has(key)) return;
1272
1294
  set.add(key);
@@ -1323,8 +1345,21 @@ function startStageGuard(opts) {
1323
1345
  const counts = /* @__PURE__ */ new Map();
1324
1346
  for (const w of window) counts.set(w, (counts.get(w) || 0) + 1);
1325
1347
  for (const [, n] of counts) if (n >= 12) {
1326
- fire(`推理复读(同一片段在近 ${window.length} 条流式片段中出现 ${n} 次)`, "degenerated");
1327
- return;
1348
+ if (lastMutationAt > 0) {
1349
+ if (!repeatWarned) {
1350
+ repeatWarned = true;
1351
+ try {
1352
+ journal.logs.push({
1353
+ t: Date.now(),
1354
+ level: "warn",
1355
+ message: `${label} 复读计数达阈值但检测到变更进展(read-edit 循环属正常模式),不中止;仅零变更进展的纯复读才中止`
1356
+ });
1357
+ } catch (e) {}
1358
+ }
1359
+ } else {
1360
+ fire(`推理复读(同一片段在近 ${window.length} 条流式片段中出现 ${n} 次,且窗口内零变更进展)`, "degenerated");
1361
+ return;
1362
+ }
1328
1363
  }
1329
1364
  observeToolCalls(newEvents);
1330
1365
  if (newEvents.some((ev) => ev?.type === "step/end")) flushReminders(run);
@@ -1332,9 +1367,12 @@ function startStageGuard(opts) {
1332
1367
  }
1333
1368
  for (const ev of events.slice(-200)) {
1334
1369
  const e = ev;
1335
- if (e && (e.type === "tool-call-chunks" || e.type === "tool/call")) {
1370
+ if (!e) continue;
1371
+ if (e.type === "tool-call-chunks" || e.type === "tool/call") {
1336
1372
  seenToolCall = true;
1337
1373
  lastToolSignalAt = Date.now();
1374
+ const d = e.data || e;
1375
+ if (d && typeof d.name === "string" && PROGRESS_TOOLS.test(d.name)) lastMutationAt = Date.now();
1338
1376
  break;
1339
1377
  }
1340
1378
  }
@@ -1605,7 +1643,20 @@ async function withRetry(journal, parent, label, phase, prompt, signal) {
1605
1643
  stageTokens
1606
1644
  };
1607
1645
  }
1608
- if (!!!(lastStage && lastStage.outcome === "degenerated") && stageTokens >= 6e4) {
1646
+ if (lastStage && lastStage.outcome === "degenerated") {
1647
+ journal.logs.push({
1648
+ t: Date.now(),
1649
+ level: "warn",
1650
+ message: `${label} 进行中护栏中止(退化/推理复读),不再自动重试(污染会话内重试大概率复现且烧钱);可 teamflow_resume 以全新会话续跑`
1651
+ });
1652
+ journal.humanIntervention = true;
1653
+ return {
1654
+ text: null,
1655
+ attempts,
1656
+ stageTokens
1657
+ };
1658
+ }
1659
+ if (stageTokens >= 6e4) {
1609
1660
  journal.logs.push({
1610
1661
  t: Date.now(),
1611
1662
  level: "error",
@@ -2579,6 +2630,7 @@ function buildResumeProducts(journal) {
2579
2630
  * 全部完成仍被中断(理论极端)→ 从产品验收继续。
2580
2631
  */
2581
2632
  function interruptedPhaseOf(journal) {
2633
+ if (hasOpenBlockingBugs(journal)) return "QA 测试";
2582
2634
  for (const phase of PHASE_ORDER) {
2583
2635
  const phaseStages = (journal.stages || []).filter((s) => s.phase === phase);
2584
2636
  if (phaseStages.length === 0) continue;
@@ -2586,6 +2638,24 @@ function interruptedPhaseOf(journal) {
2586
2638
  }
2587
2639
  return "产品验收";
2588
2640
  }
2641
+ /** 开发任务定义(单一来源):架构蓝图自动拆 > 调用方显式 tasks > 整体开发兜底。
2642
+ * resume 补跑与正常执行共用(defByTitle 按 title 匹配失败子卡)。 */
2643
+ function buildDevTaskDefs(journal, tasks) {
2644
+ const blueprintTasks = journal.blueprint && Array.isArray(journal.blueprint.tasks) && journal.blueprint.tasks.length ? journal.blueprint.tasks.map((t) => ({
2645
+ title: t.title || "开发任务",
2646
+ files: Array.isArray(t.files) ? t.files : [],
2647
+ spec: t.spec || ""
2648
+ })) : [];
2649
+ return blueprintTasks.length ? blueprintTasks : tasks.length > 0 ? tasks.map((t) => ({
2650
+ title: t.title,
2651
+ spec: t.spec,
2652
+ files: []
2653
+ })) : [{
2654
+ title: "整体开发",
2655
+ spec: "按技术方案/需求实现全部改动",
2656
+ files: []
2657
+ }];
2658
+ }
2589
2659
  /**
2590
2660
  * 执行流水线。resume = null 全新运行;resume = { phase, products } 从断点续跑:
2591
2661
  * phase 之前的阶段直接复用 products 产物(跳过执行),从 phase 阶段开始重跑。
@@ -2952,28 +3022,50 @@ async function executePipeline(journal, parent, requirement, options, signal, re
2952
3022
  let devResults = null;
2953
3023
  if (resumed("开发")) {
2954
3024
  devResults = resume.products.dev || [];
2955
- timeline.dev = devResults;
2956
- logSkip("开发");
3025
+ const failedSubs = (storeFor(scopeKey).tasks || []).filter((t) => t.reqId === journal.reqId && t.parentId === journal.taskId && (t.status === "failed" || t.failed === true));
3026
+ if (failedSubs.length > 0) {
3027
+ const defs = buildDevTaskDefs(journal, tasks);
3028
+ const defByTitle = new Map(defs.map((d) => [d.title, d]));
3029
+ const rerunDefs = failedSubs.map((s) => {
3030
+ const key = String(s.title || "").replace(/^开发 · /, "");
3031
+ return defByTitle.get(key) || {
3032
+ title: key,
3033
+ spec: s.spec || "按技术方案实现该任务改动",
3034
+ files: []
3035
+ };
3036
+ }).filter(Boolean);
3037
+ const reused = devResults.filter((r) => r && !rerunDefs.some((d) => d.title === r.title));
3038
+ journal.logs.push({
3039
+ t: Date.now(),
3040
+ level: "warn",
3041
+ message: `断点续跑开发:复用 ${reused.length} 个已完成任务,补跑 ${rerunDefs.length} 个失败任务`
3042
+ });
3043
+ const rerun = await runPool(rerunDefs, maxConcurrency, async (task) => {
3044
+ const devR = await withRetry(journal, parent, `开发 · ${task.title}(补跑)`, "开发", devPrompt(task, tech, prd, root, journal.id, state), signal);
3045
+ const ok = !!devR.text;
3046
+ return {
3047
+ title: task.title,
3048
+ failed: !ok,
3049
+ output: devR.text || "开发失败(Agent 未产出结果)"
3050
+ };
3051
+ });
3052
+ for (const t of rerun) {
3053
+ const sub = failedSubs.find((s) => String(s.title || "").replace(/^开发 · /, "") === t.title);
3054
+ if (sub) completeSubtask(journal, sub.id, t.failed, t.output ? snippet(t.output, 1e3) : null, null);
3055
+ }
3056
+ devResults = [...reused, ...rerun];
3057
+ timeline.dev = devResults;
3058
+ } else {
3059
+ timeline.dev = devResults;
3060
+ logSkip("开发");
3061
+ }
2957
3062
  } else {
2958
3063
  journal.logs.push({
2959
3064
  t: Date.now(),
2960
3065
  level: "phase",
2961
3066
  message: "进入阶段:开发"
2962
3067
  });
2963
- const blueprintTasks = journal.blueprint && Array.isArray(journal.blueprint.tasks) && journal.blueprint.tasks.length ? journal.blueprint.tasks.map((t) => ({
2964
- title: t.title || "开发任务",
2965
- files: Array.isArray(t.files) ? t.files : [],
2966
- spec: t.spec || ""
2967
- })) : [];
2968
- const devTaskDefs = blueprintTasks.length ? blueprintTasks : tasks.length > 0 ? tasks.map((t) => ({
2969
- title: t.title,
2970
- spec: t.spec,
2971
- files: []
2972
- })) : [{
2973
- title: "整体开发",
2974
- spec: "按技术方案/需求实现全部改动",
2975
- files: []
2976
- }];
3068
+ const devTaskDefs = buildDevTaskDefs(journal, tasks);
2977
3069
  const mergedDefs = [];
2978
3070
  for (const t of devTaskDefs) {
2979
3071
  const hit = t.files && t.files.length ? mergedDefs.find((m) => m.files.some((f) => t.files.includes(f))) : void 0;
@@ -2990,7 +3082,7 @@ async function executePipeline(journal, parent, requirement, options, signal, re
2990
3082
  journal.logs.push({
2991
3083
  t: Date.now(),
2992
3084
  level: "info",
2993
- message: `开发阶段开始,任务数:${mergedDefs.length}(并发 ${maxConcurrency}${blueprintTasks.length ? ",源自架构蓝图自动拆解" : ""})`
3085
+ message: `开发阶段开始,任务数:${mergedDefs.length}(并发 ${maxConcurrency}${journal.blueprint && Array.isArray(journal.blueprint.tasks) && journal.blueprint.tasks.length ? ",源自架构蓝图自动拆解" : ""})`
2994
3086
  });
2995
3087
  advanceTask(journal, "running", null, "开发开始(待办 → 开发中)", { by: "dev" });
2996
3088
  const subCards = mergedDefs.map((dt) => createSubtask(journal, dt.title, dt.spec));
@@ -3034,17 +3126,10 @@ async function executePipeline(journal, parent, requirement, options, signal, re
3034
3126
  }
3035
3127
  journal.logs.push({
3036
3128
  t: Date.now(),
3037
- level: "warn",
3038
- message: `开发完成,失败任务数:${failedCount}(需人工介入)`
3129
+ level: "error",
3130
+ message: `开发失败任务数:${failedCount}/${devResults.length}(提测门禁拦截):停止流水线,需人工介入——处理后可 teamflow_resume 从开发阶段补跑失败任务(已完成任务复用)`
3039
3131
  });
3040
- if (failedCount >= devResults.length) {
3041
- journal.logs.push({
3042
- t: Date.now(),
3043
- level: "error",
3044
- message: `开发任务全部失败(${failedCount}/${devResults.length}):停止流水线,需人工介入——可用 teamflow_resume 从开发阶段重跑`
3045
- });
3046
- throw new Error(`开发任务全部失败(${failedCount}/${devResults.length}):无产物可测,停止流水线`);
3047
- }
3132
+ throw new Error(`开发失败任务 ${failedCount}/${devResults.length}:提测门禁拦截,不进 QA(已知缺口,QA 检查轮必然重复报告)`);
3048
3133
  } else {
3049
3134
  advanceTask(journal, "testable", null, "开发完成待测试(开发中 → 待测试)", { by: "dev" });
3050
3135
  journal.logs.push({
@@ -3065,7 +3150,7 @@ async function executePipeline(journal, parent, requirement, options, signal, re
3065
3150
  message: "当前档位阶段集不含独立 QA:跳过(单点修复,开发自测兜底)"
3066
3151
  });
3067
3152
  qa = "(独立 QA 跳过:当前档位由开发自测兜底)";
3068
- } else if (resumed("QA 测试")) {
3153
+ } else if (resumed("QA 测试") && !hasOpenBlockingBugs(journal)) {
3069
3154
  qa = resume.products.qa;
3070
3155
  timeline.qa = qa;
3071
3156
  logSkip("QA 测试");
@@ -3451,7 +3536,7 @@ function resumeRun(runId, sessionId) {
3451
3536
  level: "warn",
3452
3537
  message: `断点续跑:从「${resumePhase}」继续(已完成阶段复用产物)`
3453
3538
  });
3454
- j.stages = (j.stages || []).filter((s) => s.status === "done");
3539
+ j.stages = (j.stages || []).filter((s) => s.status !== "running" && s.status !== "pending");
3455
3540
  runs.set(id, j);
3456
3541
  persistJournal(j);
3457
3542
  executePipeline(j, agent, j.requirement, j.options, void 0, {
@@ -3553,7 +3638,7 @@ function registerTools(ctx) {
3553
3638
  };
3554
3639
  T({
3555
3640
  name: "teamflow_start",
3556
- description: "Start the team R&D pipeline (background async): runs the stages per team config (PRD→design→tech→dev→QA→acceptance). Specify teamId (matches teams.json) or pick a team via the UI \"+\" button first so messages auto-match. Stage failures auto-retry; beyond threshold → rework/human intervention; per-stage token usage recorded. NOTE: after calling, the implementation work is done by pipeline subagents — the main thread MUST NOT write code or run verifications for it. requirement must be a faithful transcription of the user's words; do not invent file paths / tech claims without code verification (downstream stages build the PRD from it). Branch decision: when the return status is \"needs-decision\", ASK THE USER to pick one of the options (or take their custom input, e.g. a branch name), then RE-CALL this tool with the chosen branchPolicy / branchName / preAction / commitMessage parameters. Pass branchPolicy=\"keep\" when the user chooses to stay on the current branch.",
3641
+ description: "Start the team R&D pipeline (background async): runs the stages per team config (PRD→design→tech→dev→QA→acceptance). Specify teamId (matches teams.json) or pick a team via the UI \"+\" button first so messages auto-match. Stage failures auto-retry; beyond threshold → rework/human intervention; per-stage token usage recorded. NOTE: after calling, the implementation work is done by pipeline subagents — the main thread MUST NOT write code or run verifications for it. requirement must be a faithful transcription of the user's words; do not invent file paths / tech claims without code verification (downstream stages build the PRD from it). Branch decision: when the return status is \"needs-decision\", ASK THE USER to pick one of the options (or take their custom input, e.g. a branch name), then RE-CALL this tool passing the CHOSEN OPTION VALUE as branchPolicy (\"new\" = confirmed create branch, \"keep\" = stay), optionally combined with preAction / branchName / commitMessage. Pass branchPolicy=\"keep\" when the user chooses to stay on the current branch.",
3557
3642
  parameters: {
3558
3643
  requirement: {
3559
3644
  type: "string",
@@ -3590,7 +3675,7 @@ function registerTools(ctx) {
3590
3675
  },
3591
3676
  branchPolicy: {
3592
3677
  type: "string",
3593
- description: "Branch policy: \"auto\" (default) — create a feature branch feat/<branchName|slug> from the current HEAD; \"keep\" — stay on current branch. When auto and a decision is needed (dirty workspace / on main etc.), the tool returns needs-decision for you to ask the user first."
3678
+ description: "Branch policy: \"auto\" (default, triggers needs-decision when not yet confirmed) — create a feature branch feat/<branchName|slug> from the current HEAD; \"keep\" — stay on current branch; \"new\" — the confirmed value returned by needs-decision options (user already picked \"create branch\"), pass it back as-is to proceed. When auto and a decision is needed (dirty workspace / on main etc.), the tool returns needs-decision for you to ask the user first."
3594
3679
  },
3595
3680
  branchName: {
3596
3681
  type: "string",
@@ -3642,7 +3727,7 @@ function registerTools(ctx) {
3642
3727
  const opts = Array.isArray(value.options) ? value.options.map((o, i) => `${i + 1}. ${o.label}`).join("\n") : "";
3643
3728
  return [{
3644
3729
  type: "text",
3645
- text: `【分支决策】${value.question}\n${opts}\n(也可自定义输入)——请询问用户选择,确认后以 teamflow_start branchPolicy/branchName/preAction/commitMessage 参数重新调用。`
3730
+ text: `【分支决策】${value.question}\n${opts}\n(也可自定义输入)——请询问用户选择,确认后把所选选项的 value 作为 branchPolicy 重新调用 teamflow_start(如 'new'/'keep';脏工作区选项可拆为 branchPolicy + preAction 组合),自定义分支名则传 branchName。`
3646
3731
  }];
3647
3732
  }
3648
3733
  if (value && value.status === "needs-confirmation") return [{
@@ -3692,7 +3777,8 @@ function registerTools(ctx) {
3692
3777
  preAction: args.preAction === "stash" || args.preAction === "commit" ? args.preAction : null,
3693
3778
  commitMessage: typeof args.commitMessage === "string" && args.commitMessage.trim() ? args.commitMessage.trim() : null
3694
3779
  };
3695
- if (options.branchPolicy === "auto" && !options.preAction && exec && exec.agent) {
3780
+ const branchConfirmed = args.branchPolicy === "new";
3781
+ if (options.branchPolicy === "auto" && !branchConfirmed && !options.branchName && !options.preAction && exec && exec.agent) {
3696
3782
  const sc = workspaceScopeOf(exec.agent);
3697
3783
  if (sc.path) try {
3698
3784
  const s = runSanityCheck(sc.path);
@@ -3706,7 +3792,7 @@ function registerTools(ctx) {
3706
3792
  question = `工作区 ${sc.path} 当前在 main 分支(工作区干净)。流水线默认在特性分支上开发,请选择:`;
3707
3793
  optionsList = [{
3708
3794
  label: "基于 main 新建分支开发(推荐,分支名取需求 slug,可自定义)",
3709
- value: "auto"
3795
+ value: "new"
3710
3796
  }, {
3711
3797
  label: "直接在 main 上开发",
3712
3798
  value: "keep"
@@ -3734,7 +3820,7 @@ function registerTools(ctx) {
3734
3820
  value: "keep"
3735
3821
  }, {
3736
3822
  label: "基于当前分支再新建子分支开发",
3737
- value: "auto"
3823
+ value: "new"
3738
3824
  }];
3739
3825
  } else {
3740
3826
  question = `工作区 ${sc.path} 当前在特性分支 ${s.branch},且有 ${dirtyN} 处未提交改动。请选择启动方式:`;
@@ -3761,7 +3847,7 @@ function registerTools(ctx) {
3761
3847
  status: "needs-decision",
3762
3848
  question,
3763
3849
  options: optionsList,
3764
- note: "选项之外可自定义输入(如指定分支名)。确认选择后,请以 teamflow_start 的 branchPolicy / branchName / preAction / commitMessage 参数重新调用本工具。"
3850
+ note: "选项之外可自定义输入(如指定分支名)。确认选择后,请以 teamflow_start 的 branchPolicy(回传所选选项 value,如 new/keep)与 branchName/preAction/commitMessage 参数重新调用本工具。"
3765
3851
  };
3766
3852
  }
3767
3853
  } catch (e) {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-teamflow",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "TeamFlow 团队研发流水线(TypeScript):需求→PRD→设计→架构→技术方案→并行开发→QA→验收;backlog 持久化 + 断点续跑 + 完成汇报 + 防假交付;Web 团队工作台(conversation.view tab + 拖拽看板)",
5
5
  "type": "module",
6
6
  "main": "./lib/host.mjs",