dsh-plugin-teamflow 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/host.mjs +183 -198
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  > 本插件首次公开发布版本为 **v0.1.0**;发布前的内部迭代(v0.3~v0.13)记录于 `AGENTS.md` §5,对外统一归到 v0.1.0。
4
4
 
5
+ ## [0.1.5] - 2026-09-01
6
+
7
+ ### 新增
8
+ - **patch 档兑现「单 agent 直改 + 自测即交付」**:阶段集精简为确认单 → 单 agent 直改(2 段,无技术方案/QA/验收),开发完成(自测通过)即统一收口提交。UI 微调(按钮换位置/挪控件/改文案/间距/颜色)、常量调整、单点修复、回滚、笔误修正走最轻路径
9
+
10
+ ### 改进
11
+ - **需求分诊重构为模型主导**:档位判断交给模型(自然语言语义,天然双语);正则收窄为确定性护栏(架构信号强升/UI 不低于轻量档/显式设计升档)。UI 微调类需求(不改变行为/交互逻辑)不再误判标准档
12
+ - **分诊输出失败自动纠错重试**:只输出开场白无 JSON 时带提示重试一次,仍失败才走正则兜底
13
+
14
+ ### 修复
15
+ - **外部中止(aborted)不再误报「预算熔断」**:重启等外部中止单独标记,明确引导断点续跑(补跑失败任务,已完成任务复用)
16
+ - **patch 档不再误跑架构蓝图**(技术方案块补 `enabled('tech')` 门控)
17
+ - **分支名不再被档位词污染**(「显式的用 patch 模式」不再产出 `feat/patch`)
18
+ - **provider 错误细节记录**(阶段失败记录底层错误信息)
19
+
5
20
  ## [0.1.4] - 2026-08-29
6
21
 
7
22
  ### 修复
package/lib/host.mjs CHANGED
@@ -157,9 +157,7 @@ const STAGE_POLICY = {
157
157
  key: "scaffold",
158
158
  when: (o) => !!o.needScaffold
159
159
  },
160
- { key: "tech" },
161
- { key: "dev" },
162
- { key: "acceptance" }
160
+ { key: "dev" }
163
161
  ]
164
162
  };
165
163
  /** 按档位 + 条件展开实际执行阶段集(纯函数;未知档回退 full)。 */
@@ -266,7 +264,24 @@ function deriveBranchSlug(requirement, reqId, triageSlug, branchName) {
266
264
  if (branchName && /^[a-z0-9][a-z0-9-_]*$/i.test(branchName)) return String(branchName).replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 40);
267
265
  if (triageSlug && /^[a-z0-9-]{3,24}$/i.test(triageSlug)) return triageSlug;
268
266
  const en = String(requirement || "").match(/[a-zA-Z][a-zA-Z0-9-]{2,23}/g);
269
- if (en && en.length) return en[0].toLowerCase().slice(0, 40);
267
+ if (en && en.length) {
268
+ const NOISE = /* @__PURE__ */ new Set([
269
+ "patch",
270
+ "lite",
271
+ "tech",
272
+ "full",
273
+ "medium",
274
+ "mode",
275
+ "the",
276
+ "and",
277
+ "for",
278
+ "with",
279
+ "use",
280
+ "using"
281
+ ]);
282
+ const hit = en.find((w) => !NOISE.has(w.toLowerCase()));
283
+ if (hit) return hit.toLowerCase().slice(0, 40);
284
+ }
270
285
  const num = String(reqId || "").match(/\d+/);
271
286
  if (num) return `r${num[0]}`;
272
287
  return "feature";
@@ -1569,6 +1584,7 @@ async function runAgent(journal, parent, label, phase, prompt, signal) {
1569
1584
  }
1570
1585
  stage.status = "failed";
1571
1586
  stage.outcome = stop === "completed" && text ? "insubstantial" : stop || "error";
1587
+ const errDetail = result && result.error;
1572
1588
  if (stage.outcome === "insubstantial") {
1573
1589
  stage.summary = "产出未通过实质校验(含拒绝措辞或内容过短),视为未交付";
1574
1590
  journal.logs.push({
@@ -1577,11 +1593,11 @@ async function runAgent(journal, parent, label, phase, prompt, signal) {
1577
1593
  message: `${label} 产出未通过实质校验(拒绝措辞/内容过短)`
1578
1594
  });
1579
1595
  } else {
1580
- stage.summary = `未产出有效结果(stopReason=${stop || "unknown"})`;
1596
+ stage.summary = `未产出有效结果(stopReason=${stop || "unknown"}${errDetail ? `,error=${String(errDetail).slice(0, 200)}` : ""})`;
1581
1597
  journal.logs.push({
1582
1598
  t: Date.now(),
1583
1599
  level: "error",
1584
- message: `${label} 未产出有效结果(stopReason=${stop || "unknown"})`
1600
+ message: `${label} ${stage.summary}`
1585
1601
  });
1586
1602
  }
1587
1603
  return null;
@@ -1643,6 +1659,19 @@ async function withRetry(journal, parent, label, phase, prompt, signal) {
1643
1659
  stageTokens
1644
1660
  };
1645
1661
  }
1662
+ if (lastStage && lastStage.outcome === "aborted") {
1663
+ journal.logs.push({
1664
+ t: Date.now(),
1665
+ level: "warn",
1666
+ message: `${label} 被外部中止(aborted),未正常产出——非预算问题;可 teamflow_resume 续跑(补跑失败任务,已完成任务复用)`
1667
+ });
1668
+ journal.humanIntervention = true;
1669
+ return {
1670
+ text: null,
1671
+ attempts,
1672
+ stageTokens
1673
+ };
1674
+ }
1646
1675
  if (lastStage && lastStage.outcome === "degenerated") {
1647
1676
  journal.logs.push({
1648
1677
  t: Date.now(),
@@ -2168,8 +2197,8 @@ ${vision ? "[Visual re-check] If QA saved screenshots under the task folder, spo
2168
2197
  5. Chinese Markdown.
2169
2198
  6. [State] End with a state block (phase="acceptance"), summary = acceptance conclusion, verdict = "accepted/rework/reject/needs-human", extra.done = confirmation of this delivery.${STATE_BLOCK_INSTRUCTION}`;
2170
2199
  /** 需求分诊模型 prompt(模型驱动 triage;供 core/triage.runTriage 使用)。 */
2171
- const TRIAGE_PROMPT = (requirement, opts, pre) => `You are a senior research-dev triage analyst. Do ONE thing: analyze which pipeline mode this dev requirement fits, then give the conclusion. No code, no scope speculation.
2172
- [RAW REQUIREMENT]
2200
+ const TRIAGE_PROMPT = (requirement, opts, pre, retryHint) => `You are a senior research-dev triage analyst. Do ONE thing: analyze which pipeline mode this dev requirement fits, then give the conclusion. No code, no scope speculation.
2201
+ ${retryHint ? `[RETRY — YOUR LAST REPLY FAILED]\n${retryHint}\n` : ""}[RAW REQUIREMENT]
2173
2202
  ${requirement}
2174
2203
  ${pre.rationale.length ? `\n[REGEX PRE-FILTER SIGNALS (reference only; judge semantically, don't blindly follow)]\n${pre.rationale.join("\n")}` : ""}
2175
2204
  \n[OPTIONAL SIGNAL] UI work needed: ${opts && opts.needDesign ? "yes" : "not flagged"}
@@ -2183,12 +2212,12 @@ ${pre.rationale.length ? `\n[REGEX PRE-FILTER SIGNALS (reference only; judge sem
2183
2212
 
2184
2213
  [JUDGMENT POINTS]
2185
2214
  1. Distinguish "user-visible functional change" vs "internal tech change": refactors/optimizations, even large code volume, usually go tech, not full.
2186
- 2. UI/visual/interaction/buttons/pages involved → at least medium (excludes patch/tech).
2215
+ 2. **UI feature work** (new screens/components/interactions/pages) → at least medium (excludes patch/tech). **UI micro-adjustments** — moving a button, relocating a control, changing copy/labels, spacing/padding, color tweaks, "move the button", "switch the button position" — are patch-or-lite material, NOT medium: no new interaction logic, no design phase worth the token cost. The line: does it change behavior/interaction logic (medium) or just placement/appearance of existing elements (patch/lite)?
2187
2216
  3. hotfix/single-point/pure numeric/pure docs → patch; clear "add feature X" → pick lite/medium/full by size.
2188
2217
  4. Focused change (even with tests/regression) → lite/tech by nature; not necessarily full.
2189
2218
  5. [M1 ARCHITECTURE CRITERION (important)] **Architecture-level changes** — persistence/localStorage/database/standalone module/abstraction/cross-many-files without an existing reusable wrapper (like a localStorage wrapper, storage layer, state management) — even if they look like "small features", go **at least medium** (must pass the architecture stage and produce a blueprint, avoiding scattered local implementations by dev); such changes collapse under a light "micro feature" tier. Tech-driven rework (refactor/optimize/arch upgrade) is itself tech (tech also runs the lightweight blueprint now).
2190
2219
 
2191
- [OUTPUT] one JSON object only (no other text):
2220
+ [OUTPUT] JSON object ONLY no commentary, no preface, no closing text. The FIRST character of your reply must be '{'. Do NOT say anything like "here is the JSON" or "Let me output the JSON" — output the object itself:
2192
2221
  { "mode": "patch|lite|tech|medium|full", "slug": "<topic words> (3-24 lowercase letters/digits/hyphens, e.g. wallkick-toggle, 7bag-random; used to name the task folder)", "kind": "one-word nature", "needDesign": true|false, "complexity": "small|medium|large", "rationale": ["key argument 1","key argument 2"], "confidence": "high|medium|low" }`;
2193
2222
  /** tech 档 PRD:技术变更单(无功能 AC,重范围/目标/改动面/回归)。 */
2194
2223
  const techChangePrompt = (requirement, root, runId, state) => `You are the senior tech lead. The current workspace IS the target project. This is a **tech-driven rework** requirement (refactor/optimize/upgrade/architecture/dependencies/tech debt) — the product doesn't need a full feature PRD, but needs a **技术变更单** (tech change sheet) as the contract for dev/QA/acceptance and memory write-back.
@@ -2268,140 +2297,72 @@ const MODE_REGISTRY = {
2268
2297
  desc: "单行/常量/版本号/hotfix:单 agent 直改+自测即交付(无独立 QA)"
2269
2298
  }
2270
2299
  };
2271
- /** 关键词信号集(命中 加重某档倾向)。 */
2272
- const SIGNALS = [
2273
- {
2274
- mode: "patch",
2275
- words: [
2276
- "hotfix",
2277
- "热修",
2278
- "修复 bug",
2279
- "修 bug",
2280
- "改个 bug",
2281
- "版本号",
2282
- "常量",
2283
- "一行",
2284
- "bump",
2285
- "回滚",
2286
- "卫生",
2287
- "笔误",
2288
- "拼写"
2289
- ]
2290
- },
2291
- {
2292
- mode: "tech",
2293
- words: [
2294
- "重构",
2295
- "优化",
2296
- "升级",
2297
- "架构",
2298
- "性能",
2299
- "依赖",
2300
- "脚手架",
2301
- "缓存",
2302
- "清理",
2303
- "遗留债",
2304
- "工程",
2305
- "内部改造",
2306
- "技术债"
2307
- ]
2308
- },
2309
- {
2310
- mode: "lite",
2311
- words: [
2312
- "微功能",
2313
- "小功能",
2314
- "小增强",
2315
- "加个",
2316
- "补一个",
2317
- "轻微",
2318
- "顺手"
2319
- ]
2320
- },
2321
- {
2322
- mode: "medium",
2323
- words: [
2324
- "界面",
2325
- "UI",
2326
- "视觉",
2327
- "页面",
2328
- "按钮",
2329
- "样式",
2330
- "交互",
2331
- "布局",
2332
- "组件",
2333
- "持久化",
2334
- "localStorage",
2335
- "本地存储",
2336
- "存储",
2337
- "保存",
2338
- "恢复",
2339
- "存档",
2340
- "独立模块",
2341
- "抽象",
2342
- "存储层",
2343
- "sessionStorage",
2344
- "IndexedDB",
2345
- "跨模块",
2346
- "数据层"
2347
- ]
2348
- },
2349
- {
2350
- mode: "full",
2351
- words: [
2352
- "跨模块",
2353
- "完整",
2354
- "大型",
2355
- "对接",
2356
- "集成",
2357
- "模块化",
2358
- "重构为",
2359
- "二期",
2360
- "平台"
2361
- ]
2362
- }
2300
+ /** 确定性护栏关键词(双语,仅 fallback 兜底用;主路由是模型——TRIAGE_PROMPT 语义判断)。
2301
+ * 架构信号:持久化/存储/独立模块/抽象/跨模块——防「轻档位局部实现塌方」(M1 架构护栏)。
2302
+ * UI 信号:UI 相关需求不得落 patch/tech(无设计/QA 的档位)——最低 lite。 */
2303
+ const ARCH_SIGNALS = [
2304
+ "持久化",
2305
+ "存储",
2306
+ "保存",
2307
+ "恢复",
2308
+ "存档",
2309
+ "独立模块",
2310
+ "抽象",
2311
+ "存储层",
2312
+ "localStorage",
2313
+ "sessionStorage",
2314
+ "IndexedDB",
2315
+ "跨模块",
2316
+ "数据层",
2317
+ "persistence",
2318
+ "storage",
2319
+ "database",
2320
+ "数据库",
2321
+ "standalone module",
2322
+ "abstraction"
2323
+ ];
2324
+ const UI_SIGNALS = [
2325
+ "界面",
2326
+ "UI",
2327
+ "视觉",
2328
+ "页面",
2329
+ "按钮",
2330
+ "样式",
2331
+ "交互",
2332
+ "布局",
2333
+ "组件",
2334
+ "page",
2335
+ "button",
2336
+ "style",
2337
+ "layout",
2338
+ "component",
2339
+ "visual",
2340
+ "interaction"
2363
2341
  ];
2364
- /** 对原始需求做启发式分诊。返回建议 mode + 判定理由 + 置信。 */
2342
+ /** 对原始需求做启发式分诊(兜底路径专用)。返回建议 mode + 判定理由 + 置信。
2343
+ * 只做确定性护栏(架构强升/UI 禁轻档/needDesign 升档)——不再逐词匹配五档信号:
2344
+ * 主路由是模型(TRIAGE_PROMPT 语义判断,天然双语),正则兜底在模型不可用时宁重勿漏(默认 full)。 */
2365
2345
  function suggestMode(requirement, opts) {
2366
2346
  const text = String(requirement || "");
2367
- const hits = [];
2368
- for (const sig of SIGNALS) for (const w of sig.words) if (text.includes(w)) hits.push({
2369
- mode: sig.mode,
2370
- word: w
2371
- });
2372
- const rationale = hits.map((h) => `命中「${h.word}」→ ${h.mode}`);
2373
- const countOf = (m) => hits.filter((h) => h.mode === m).length;
2347
+ const rationale = [];
2374
2348
  let mode = "full";
2375
- if (countOf("patch") > 0) mode = "patch";
2376
- else if (countOf("tech") > 0 && countOf("medium") === 0 && countOf("full") === 0) mode = "tech";
2377
- else if (countOf("lite") > 0 && countOf("medium") === 0) mode = "lite";
2378
- else if (countOf("medium") > 0 || opts && opts.needDesign) mode = "medium";
2379
- else if (hits.length === 0) mode = "full";
2380
- if ((mode === "patch" || mode === "tech") && opts && opts.needDesign) mode = "medium";
2381
- if (mode === "patch" && (countOf("tech") > 0 || text.includes("新增") || text.includes("功能"))) mode = "lite";
2382
- const architectureSignals = [
2383
- "持久化",
2384
- "localStorage",
2385
- "本地存储",
2386
- "存储",
2387
- "保存",
2388
- "恢复",
2389
- "存档",
2390
- "独立模块",
2391
- "抽象",
2392
- "存储层",
2393
- "sessionStorage",
2394
- "IndexedDB",
2395
- "跨模块",
2396
- "数据层"
2397
- ];
2398
- if ((mode === "lite" || mode === "tech" || mode === "patch") && architectureSignals.some((w) => text.includes(w))) {
2349
+ const archHit = ARCH_SIGNALS.find((w) => text.includes(w));
2350
+ if (archHit) {
2351
+ mode = "medium";
2352
+ rationale.push(`架构护栏:需求含「${archHit}」→ 强升 medium(需架构阶段产蓝图,防塌)`);
2353
+ }
2354
+ const uiHit = !archHit ? UI_SIGNALS.find((w) => text.includes(w)) : void 0;
2355
+ if (uiHit) {
2356
+ mode = mode === "full" ? "lite" : mode;
2357
+ rationale.push(`UI 护栏:需求含「${uiHit}」→ 不低于 lite(UI 改动需 QA/验收)`);
2358
+ }
2359
+ if (opts && opts.needDesign && mode !== "medium") {
2399
2360
  mode = "medium";
2400
- rationale.push(`架构护栏:需求含「${architectureSignals.find((w) => text.includes(w))}」→ 强升 medium(需架构阶段)`);
2361
+ rationale.push("needDesign=true 强升 medium(显式要求设计阶段)");
2401
2362
  }
2402
- const kind = mode === "patch" ? "热修/单点" : mode === "tech" ? "技术驱动改造" : mode === "lite" ? "微功能" : mode === "medium" ? "标准功能(含UI)" : "完整需求";
2403
- const confidence = mode === "patch" ? "high" : hits.length >= 2 ? "medium" : "low";
2404
- if (rationale.length === 0) rationale.push("无强信号,默认 full(护栏优先)");
2363
+ if (rationale.length === 0) rationale.push("无强护栏信号,默认 full(模型不可用时宁重勿漏)");
2364
+ const kind = mode === "medium" ? "标准功能(含UI)" : mode === "lite" ? "微功能" : "完整需求";
2365
+ const confidence = mode === "medium" && !opts?.needDesign ? "medium" : "low";
2405
2366
  return {
2406
2367
  mode,
2407
2368
  kind,
@@ -2459,29 +2420,36 @@ function fallbackVerdict(requirement, opts) {
2459
2420
  source: "fallback"
2460
2421
  };
2461
2422
  }
2462
- /** 模型驱动分诊:spawn「分诊分析师」子代理思考一轮;子代理不可用/超时/解析失败 → 正则兜底。 */
2423
+ /** 模型驱动分诊:spawn「分诊分析师」子代理思考一轮;子代理不可用/超时/解析失败 → 正则兜底。
2424
+ * 解析失败先带纠错提示重试一次(实锤 run tf-mtfo8exi:模型输出「Let me output the JSON.」开场白
2425
+ * 后无 JSON——首轮输出预算被思考耗尽/模型停早;重试提示直接输出 JSON 对象本身)。 */
2463
2426
  async function runTriage(requirement, opts, parent, signal) {
2464
2427
  const subagents = runtime.subagents;
2465
2428
  if (!subagents || typeof subagents.start !== "function") return fallbackVerdict(requirement, opts);
2466
2429
  const pre = suggestMode(requirement, opts);
2467
- let run = null;
2468
- try {
2469
- run = await subagents.start(providerName(), {
2470
- label: "需求分诊",
2471
- prompt: [{
2472
- type: "text",
2473
- text: TRIAGE_PROMPT(requirement, opts, pre)
2474
- }],
2475
- parent,
2476
- signal
2477
- });
2478
- const result = await Promise.race([run.result, new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("triage 超时(90s)")), 9e4))]);
2479
- const parsed = parseVerdictText(extractText(result && result.output));
2480
- if (parsed) return parsed;
2481
- } catch (e) {} finally {
2482
- if (run && run.dispose) try {
2483
- await run.dispose();
2484
- } catch (e) {}
2430
+ for (let attempt = 1; attempt <= 2; attempt++) {
2431
+ let run = null;
2432
+ try {
2433
+ const hint = attempt > 1 ? "Your previous reply contained only a preface (e.g. \"Let me output the JSON.\") with NO JSON object — that is a failed reply. Reply now with the JSON object ITSELF as the first and only content, starting with {." : "";
2434
+ run = await subagents.start(providerName(), {
2435
+ label: "需求分诊",
2436
+ prompt: [{
2437
+ type: "text",
2438
+ text: TRIAGE_PROMPT(requirement, opts, pre, hint)
2439
+ }],
2440
+ parent,
2441
+ signal
2442
+ });
2443
+ const result = await Promise.race([run.result, new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("triage 超时(90s)")), 9e4))]);
2444
+ const parsed = parseVerdictText(extractText(result && result.output));
2445
+ if (parsed) return parsed;
2446
+ } catch (e) {
2447
+ if (attempt === 2) {}
2448
+ } finally {
2449
+ if (run && run.dispose) try {
2450
+ await run.dispose();
2451
+ } catch (e) {}
2452
+ }
2485
2453
  }
2486
2454
  return fallbackVerdict(requirement, opts);
2487
2455
  }
@@ -2976,48 +2944,50 @@ async function executePipeline(journal, parent, requirement, options, signal, re
2976
2944
  }
2977
2945
  }
2978
2946
  let tech = null;
2979
- if (resumed("技术方案")) {
2980
- tech = resume.products.tech;
2981
- timeline.tech = tech;
2982
- logSkip("技术方案");
2983
- } else {
2984
- const isHeavy = !options.lite && options.mode !== "tech" && options.mode !== "patch";
2985
- journal.logs.push({
2986
- t: Date.now(),
2987
- level: "phase",
2988
- message: isHeavy ? "进入阶段:技术方案" : "进入阶段:架构蓝图"
2989
- });
2990
- const label = isHeavy ? "高级全栈工程师 · 技术方案" : "架构师 · 架构蓝图";
2991
- const techR = await withRetry(journal, parent, label, "技术方案", isHeavy ? techPrompt(prd, design, scaffold, tasks, root, journal.id, state) : architectPrompt(prd, root, journal.id, state), signal);
2992
- if (!techR.text) throw stageFailError(label, techR);
2993
- tech = techR.text;
2994
- timeline.tech = tech;
2995
- mergeStageState("tech", tech);
2996
- let bd = extractBlueprint(tech);
2997
- if (!bd || bd.summary === void 0) try {
2998
- const techFile = journal.runDocs && journal.workspacePath ? `${journal.workspacePath}/${journal.runDocs}/TECHNICAL.md` : null;
2999
- if (techFile && existsSync(techFile)) bd = extractBlueprint(readFileSync(techFile, "utf8"));
3000
- if (bd) journal.logs.push({
2947
+ if (enabled("tech")) {
2948
+ if (resumed("技术方案")) {
2949
+ tech = resume.products.tech;
2950
+ timeline.tech = tech;
2951
+ logSkip("技术方案");
2952
+ } else {
2953
+ const isHeavy = !options.lite && options.mode !== "tech" && options.mode !== "patch";
2954
+ journal.logs.push({
3001
2955
  t: Date.now(),
3002
- level: "info",
3003
- message: "蓝图从任务夹 TECHNICAL.md 提取(模型把蓝图写进了文档而非回复输出)"
2956
+ level: "phase",
2957
+ message: isHeavy ? "进入阶段:技术方案" : "进入阶段:架构蓝图"
3004
2958
  });
3005
- } catch (e) {}
3006
- if (bd && bd.summary !== void 0) try {
3007
- state.__runCtx = state.__runCtx || {};
3008
- state.__runCtx.blueprint = bd.render;
3009
- journal.blueprint = {
3010
- modules: bd.modules,
3011
- tasks: bd.tasks
3012
- };
3013
- } catch (e) {}
3014
- else if (/<!-- blueprint -->/.test(String(tech))) journal.logs.push({
3015
- t: Date.now(),
3016
- level: "warn",
3017
- message: "技术方案蓝图块解析失败(JSON 畸形),开发任务将回退整体开发——TECHNICAL.md 蓝图块需人工检查"
3018
- });
3019
- noteTaskStageUsage(journal);
3020
- if (journal.cancelled) return;
2959
+ const label = isHeavy ? "高级全栈工程师 · 技术方案" : "架构师 · 架构蓝图";
2960
+ const techR = await withRetry(journal, parent, label, "技术方案", isHeavy ? techPrompt(prd, design, scaffold, tasks, root, journal.id, state) : architectPrompt(prd, root, journal.id, state), signal);
2961
+ if (!techR.text) throw stageFailError(label, techR);
2962
+ tech = techR.text;
2963
+ timeline.tech = tech;
2964
+ mergeStageState("tech", tech);
2965
+ let bd = extractBlueprint(tech);
2966
+ if (!bd || bd.summary === void 0) try {
2967
+ const techFile = journal.runDocs && journal.workspacePath ? `${journal.workspacePath}/${journal.runDocs}/TECHNICAL.md` : null;
2968
+ if (techFile && existsSync(techFile)) bd = extractBlueprint(readFileSync(techFile, "utf8"));
2969
+ if (bd) journal.logs.push({
2970
+ t: Date.now(),
2971
+ level: "info",
2972
+ message: "蓝图从任务夹 TECHNICAL.md 提取(模型把蓝图写进了文档而非回复输出)"
2973
+ });
2974
+ } catch (e) {}
2975
+ if (bd && bd.summary !== void 0) try {
2976
+ state.__runCtx = state.__runCtx || {};
2977
+ state.__runCtx.blueprint = bd.render;
2978
+ journal.blueprint = {
2979
+ modules: bd.modules,
2980
+ tasks: bd.tasks
2981
+ };
2982
+ } catch (e) {}
2983
+ else if (/<!-- blueprint -->/.test(String(tech))) journal.logs.push({
2984
+ t: Date.now(),
2985
+ level: "warn",
2986
+ message: "技术方案蓝图块解析失败(JSON 畸形),开发任务将回退整体开发——TECHNICAL.md 蓝图块需人工检查"
2987
+ });
2988
+ noteTaskStageUsage(journal);
2989
+ if (journal.cancelled) return;
2990
+ }
3021
2991
  }
3022
2992
  let devResults = null;
3023
2993
  if (resumed("开发")) {
@@ -3244,7 +3214,22 @@ async function executePipeline(journal, parent, requirement, options, signal, re
3244
3214
  if (journal.cancelled) return;
3245
3215
  }
3246
3216
  persistJournal(journal);
3247
- if (!qaBlocked) {
3217
+ if (!enabled("acceptance")) {
3218
+ journal.logs.push({
3219
+ t: Date.now(),
3220
+ level: "info",
3221
+ message: "patch 档交付完成:单 agent 直改 + 自测即收口(无独立 QA/验收)"
3222
+ });
3223
+ advanceTask(journal, "accepted", null, "patch 直改交付(自测通过)", { by: "dev" });
3224
+ const store = storeFor(scopeKey);
3225
+ const req = store.find("req", journal.reqId);
3226
+ if (req && req.status !== "accepted") {
3227
+ store.pushEvent(req, req.status, "accepted", "patch 交付通过(自测)");
3228
+ req.status = "accepted";
3229
+ store.persist();
3230
+ }
3231
+ journal.status = "completed";
3232
+ } else if (!qaBlocked) {
3248
3233
  journal.logs.push({
3249
3234
  t: Date.now(),
3250
3235
  level: "phase",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-teamflow",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "TeamFlow 团队研发流水线(TypeScript):需求→PRD→设计→架构→技术方案→并行开发→QA→验收;backlog 持久化 + 断点续跑 + 完成汇报 + 防假交付;Web 团队工作台(conversation.view tab + 拖拽看板)",
5
5
  "type": "module",
6
6
  "main": "./lib/host.mjs",