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.
- package/CHANGELOG.md +15 -0
- package/lib/host.mjs +183 -198
- 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: "
|
|
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)
|
|
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}
|
|
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/
|
|
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]
|
|
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
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
-
/**
|
|
2342
|
+
/** 对原始需求做启发式分诊(兜底路径专用)。返回建议 mode + 判定理由 + 置信。
|
|
2343
|
+
* 只做确定性护栏(架构强升/UI 禁轻档/needDesign 升档)——不再逐词匹配五档信号:
|
|
2344
|
+
* 主路由是模型(TRIAGE_PROMPT 语义判断,天然双语),正则兜底在模型不可用时宁重勿漏(默认 full)。 */
|
|
2365
2345
|
function suggestMode(requirement, opts) {
|
|
2366
2346
|
const text = String(requirement || "");
|
|
2367
|
-
const
|
|
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
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
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(
|
|
2361
|
+
rationale.push("needDesign=true → 强升 medium(显式要求设计阶段)");
|
|
2401
2362
|
}
|
|
2402
|
-
|
|
2403
|
-
const
|
|
2404
|
-
|
|
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
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
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 (
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
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: "
|
|
3003
|
-
message: "
|
|
2956
|
+
level: "phase",
|
|
2957
|
+
message: isHeavy ? "进入阶段:技术方案" : "进入阶段:架构蓝图"
|
|
3004
2958
|
});
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
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 (!
|
|
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.
|
|
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",
|