@yangdcm/dsh-expert-team 1.3.11 → 1.3.13
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 +46 -0
- package/README.en.md +95 -23
- package/README.md +100 -28
- package/client.js +48 -14
- package/lib/command.js +91 -17
- package/lib/interception.js +6 -4
- package/package.json +7 -3
- package/skills/expert-team/SKILL.md +31 -30
- package/skills/expert-team/assets/templates/SUMMARY.md +2 -2
- package/skills/expert-team/assets/templates//344/273/273/345/212/241/347/234/213/346/235/277.md +1 -1
- package/skills/expert-team/references/EFFICIENCY.md +1 -1
- package/skills/expert-team/references/LOGGING.md +7 -7
- package/skills/expert-team/references/PERSIST.md +9 -9
- package/skills/expert-team/references/PIPELINE.md +1 -1
- package/skills/expert-team/references/ROLES.md +32 -32
- package/skills/expert-team/references/WORKSPACE.md +19 -19
- package/skills/expert-team/references/workflow.team.js +6 -6
package/lib/command.js
CHANGED
|
@@ -1144,6 +1144,10 @@ async function scaffoldRun(cwd, mode) {
|
|
|
1144
1144
|
// Copy the canonical templates (single source of truth).
|
|
1145
1145
|
// `AUTHORITY.md`(单源化权威表)随 run 一起发:**空着也有意义** —— 它带着「该怎么写」的说明与校验规则
|
|
1146
1146
|
// (设计稿 §十二:真实 run 的 64% 返工来自「同一事实多份拷贝 / 多写者」,本表把它提到事前)。
|
|
1147
|
+
// ⚠️ 本常量 = **从 `templates/` 复制出来的文件**(13 项)。它与"run 目录必需文件清单"(两个 e2e
|
|
1148
|
+
// 测试里的 14 项)**不是同一个集合**:那边多一个 `RUN.log.md`(由 run log 写入器创建)、这边就是
|
|
1149
|
+
// 模板全集。两者关系由 `artifact-ownership.test.mjs` 的「工件清单一致」断言钉住,避免下次又被
|
|
1150
|
+
// 当成同一件事去"对齐"(2026-09-15 阶段 C 的口径核查)。
|
|
1147
1151
|
const templates = ['TASK.md', 'ROSTER.json', 'STATE.json', '任务看板.md', 'SPEC.md', 'PLAN.md', 'RESEARCH.md', 'TASKS.json', 'REVIEW.md', 'TEST.md', 'SUMMARY.md', 'RETRO.md', 'AUTHORITY.md'];
|
|
1148
1152
|
for (const t of templates) {
|
|
1149
1153
|
try {
|
|
@@ -2781,6 +2785,18 @@ async function listRunsInWorkspace(ws) {
|
|
|
2781
2785
|
}
|
|
2782
2786
|
return out0;
|
|
2783
2787
|
}
|
|
2788
|
+
// ── `/state?section=` 的解析(纯函数,便于单测)──────────────────────────────
|
|
2789
|
+
// 语义(2026-09-15 性能修复 #3,渐进式状态):
|
|
2790
|
+
// 缺省 / `all` ⇒ **全部分节**(向后兼容:老客户端、脚本、既有测试一律不受影响);
|
|
2791
|
+
// 否则只含列出的分节。`summary` 永远在内 —— 它是"便宜基线"(阶段/进度/计数),不是可选块。
|
|
2792
|
+
// 未知名字**忽略**而不是 400:多传一个参数不该把面板打死(本仓的失败哲学是"如实降级")。
|
|
2793
|
+
export function parseStateSections(raw) {
|
|
2794
|
+
const s = String(raw ?? '').trim().toLowerCase();
|
|
2795
|
+
const all = s === '' || s === 'all';
|
|
2796
|
+
const names = s.split(',').map((x) => x.trim()).filter(Boolean);
|
|
2797
|
+
const want = (n) => all || names.includes(n);
|
|
2798
|
+
return { all, want, included: ['summary'].concat(['people', 'feed', 'artifacts'].filter(want)) };
|
|
2799
|
+
}
|
|
2784
2800
|
// Snapshot one run's full team state from a specific workspace.
|
|
2785
2801
|
async function snapshotRun(ws, run) {
|
|
2786
2802
|
const dir = join(ws, 'team', run);
|
|
@@ -4968,7 +4984,7 @@ function warnLeadToolFaceOnce(status, detail) {
|
|
|
4968
4984
|
LEAD_TOOLFACE_WARNED.add(status);
|
|
4969
4985
|
console.warn(`[expert-team] lead 工具面**未**收窄(${status}):${detail}`);
|
|
4970
4986
|
}
|
|
4971
|
-
export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, subHeaderIndex, sessionExists, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, resolveRosterDefaults, rosterSettings, createRun, watchScript, canvasPollMs, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings, detectOptionalPlugins, OPTIONAL_PLUGINS, hintOptionalPluginsOnce, _resetOptionalHintOnce, scheduleOptionalPluginCheck, recheckOptionalPlugins, OPTIONAL_PROBE_DELAYS_MS, loaderEntryNames, hindsightToolReady, costMeterReady, listSubagentStatusBySession, cachedListSessions, _resetListSessionsCache, LIST_SESSIONS_TTL_MS, SUB_HEADER_MEMO, SUB_HEADER_STATS, _resetSubHeaderMemo, sessionsRootStamp, resetRoleReadBudget, roleReadBudgetSnapshot, syncRolePending, _resetRolePending, ROLE_READ_BUDGET_PER_REQUEST, ROLE_READ_LOG_CACHE: SUB_ROLE_LOG_CACHE };
|
|
4987
|
+
export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, subHeaderIndex, sessionExists, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, parseStateSections, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, resolveRosterDefaults, rosterSettings, createRun, watchScript, canvasPollMs, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings, detectOptionalPlugins, OPTIONAL_PLUGINS, hintOptionalPluginsOnce, _resetOptionalHintOnce, scheduleOptionalPluginCheck, recheckOptionalPlugins, OPTIONAL_PROBE_DELAYS_MS, loaderEntryNames, hindsightToolReady, costMeterReady, listSubagentStatusBySession, cachedListSessions, _resetListSessionsCache, LIST_SESSIONS_TTL_MS, SUB_HEADER_MEMO, SUB_HEADER_STATS, _resetSubHeaderMemo, sessionsRootStamp, resetRoleReadBudget, roleReadBudgetSnapshot, syncRolePending, _resetRolePending, ROLE_READ_BUDGET_PER_REQUEST, ROLE_READ_LOG_CACHE: SUB_ROLE_LOG_CACHE };
|
|
4972
4988
|
|
|
4973
4989
|
export function apply(ctx, config) {
|
|
4974
4990
|
// 留一份 config:设置在运行时改变(官方面板 / 浮层)时要**用同一份 config** 重算上限与档位门,
|
|
@@ -5256,6 +5272,27 @@ export function apply(ctx, config) {
|
|
|
5256
5272
|
const runParam = (url.searchParams.get('run') ?? '').trim();
|
|
5257
5273
|
const wsParam = (url.searchParams.get('workspace') ?? '').trim();
|
|
5258
5274
|
if (/[\\/]/.test(runParam)) { json(400, { ok: false, error: 'bad params' }); return; }
|
|
5275
|
+
// ── 渐进式状态(2026-09-15 性能修复 #3)────────────────────────────
|
|
5276
|
+
// 为什么:完整负载的成本随「本会话子代理数 × 日志体量」线性增长,而活跃写入会不断把
|
|
5277
|
+
// 目录戳 `(mtime,size)` 打失效(真机实测:小会话 311 ms;20+ 子代理的重会话 2.1–6.2 s)。
|
|
5278
|
+
// 可出现实需要"首屏就能看见"的只有**阶段/进度/计数**(便宜),"人/料/事件流"可以晚一拍、
|
|
5279
|
+
// 低频拉。于是给同一个端点加 `?section=`:
|
|
5280
|
+
// `summary`(便宜基线,首屏用)|`people`(成员/角色/时间线,最贵)|
|
|
5281
|
+
// `feed`(每 agent 最近事件)|`artifacts`(RUN.log 尾 + 工作区改动文件)
|
|
5282
|
+
// 兼容性第一:**不传 section(或 section=all)= 完整负载与字段语义完全不变**,
|
|
5283
|
+
// 所以老客户端、既有测试、脚本一律不受影响;`sections`/`degraded` 都是**新增**字段。
|
|
5284
|
+
// 纪律:截断**必须**上报(不许静默丢,也不许把"截断"伪装成"就这么多")。
|
|
5285
|
+
const secRaw = String(url.searchParams.get('section') ?? '');
|
|
5286
|
+
const secRequested = secRaw.trim() !== '';
|
|
5287
|
+
const secParsed = parseStateSections(secRaw);
|
|
5288
|
+
const want = secParsed.want;
|
|
5289
|
+
const included = secParsed.included;
|
|
5290
|
+
// 硬上限:角色解析最贵(每条子会话一次全量日志读),超限只解析前 N 条并如实标注。
|
|
5291
|
+
const MAX_ROLE_SUBS = Math.max(1, Number((typeof process !== 'undefined' && process.env && process.env.DSH_EXPERT_TEAM_MAX_ROLE_SUBS) || 0) || 60);
|
|
5292
|
+
// people 分节的软期限:超时后跳过更贵的可选增强(workflow 元数据),同样如实标注。
|
|
5293
|
+
const PEOPLE_DEADLINE_MS = Math.max(200, Number((typeof process !== 'undefined' && process.env && process.env.DSH_EXPERT_TEAM_PEOPLE_DEADLINE_MS) || 0) || 2500);
|
|
5294
|
+
const peopleStartedAt = Date.now();
|
|
5295
|
+
const degraded = [];
|
|
5259
5296
|
const reg = (await registeredWorkspaces()).filter(Boolean);
|
|
5260
5297
|
const workspaces = [...new Set([...(cwd ? [cwd] : []), ...reg])];
|
|
5261
5298
|
if (!workspaces.length) { json(200, { ok: false, runs: [], runsAvailable: 0, error: 'no workspace registered — 请先在某项目里运行一次 /team <task>' }); return; }
|
|
@@ -5306,9 +5343,12 @@ export function apply(ctx, config) {
|
|
|
5306
5343
|
: runOwnerSession(sel.runId);
|
|
5307
5344
|
let peopleSid = owner.sid || sid;
|
|
5308
5345
|
const knownIds = [...membersFromState(sel.stateMembers).byRole.values()];
|
|
5309
|
-
|
|
5346
|
+
// 只有 people 分节才解析人员:这一步要枚举子会话 durable 清单(重会话里这是整条链上
|
|
5347
|
+
// 最大的一块)。首屏用 summary 时不付这个成本 —— `agents` 返回空数组,
|
|
5348
|
+
// 由客户端 `sections` 字段知道自己拿的是"摘要",UI 如实显示"正在加载成员"。
|
|
5349
|
+
let subs = want('people') ? await listSubagentStatusBySession(ctx, peopleSid, knownIds) : [];
|
|
5310
5350
|
// 归属会话拿不到人时退回请求会话(否则面板整块空掉),但**如实标注**这不是归属会话的人。
|
|
5311
|
-
if (!subs.length && peopleSid !== sid) {
|
|
5351
|
+
if (want('people') && !subs.length && peopleSid !== sid) {
|
|
5312
5352
|
const fallback = await listSubagentStatusBySession(ctx, sid, knownIds);
|
|
5313
5353
|
if (fallback.length) { subs = fallback; peopleSid = sid; }
|
|
5314
5354
|
}
|
|
@@ -5330,9 +5370,20 @@ export function apply(ctx, config) {
|
|
|
5330
5370
|
// log (covers interrupted fan-outs, which have no chat card); then
|
|
5331
5371
|
// ② the child's own role prompt (roleFromChildLog).
|
|
5332
5372
|
mark('subs');
|
|
5333
|
-
const wfLabels = await workflowChildLabels(ctx, peopleSid);
|
|
5373
|
+
const wfLabels = want('people') ? await workflowChildLabels(ctx, peopleSid) : {};
|
|
5334
5374
|
mark('wfLabels');
|
|
5335
|
-
|
|
5375
|
+
// 角色解析是全链最贵的一段(每条子会话一次全量日志读)。两道闸:
|
|
5376
|
+
// ① 只在 people 分节里做;② 硬上限 `MAX_ROLE_SUBS`(超限只解析前 N 条)。
|
|
5377
|
+
// 被上限挡住的条数**如实进 `degraded`**;"还没解析"(rolesDeferred,队列长度)与
|
|
5378
|
+
// "解析不出来"(unresolved)仍然分得开(两种零可区分)。
|
|
5379
|
+
if (want('people')) {
|
|
5380
|
+
if (subs.length > MAX_ROLE_SUBS) {
|
|
5381
|
+
await resolveSubRoles(ctx, subs.slice(0, MAX_ROLE_SUBS), wfLabels);
|
|
5382
|
+
degraded.push('roles:' + (subs.length - MAX_ROLE_SUBS));
|
|
5383
|
+
} else {
|
|
5384
|
+
await resolveSubRoles(ctx, subs, wfLabels);
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5336
5387
|
mark('roles');
|
|
5337
5388
|
// R12 + R13 + R17(F-1 · P0 跨 run 成员串号):**显示与写盘都只吃过滤后的 `subById`**。
|
|
5338
5389
|
// R13 裁决:只严写盘的话,数据污染修好了但用户看到的串号现象一点没变 —— 面板照样把
|
|
@@ -5346,8 +5397,8 @@ export function apply(ctx, config) {
|
|
|
5346
5397
|
// 过滤后的候选按**创建时间升序** ⇒ 随后的"先到先占"即"同角色取创建更早者"。
|
|
5347
5398
|
// 精确路径(STATE.members 非空 ⇒ 里面的 id 本来就是本 run 登记过的)不进过滤,
|
|
5348
5399
|
// 二次轮询不会丢已登记成员。
|
|
5349
|
-
let subById;
|
|
5350
|
-
try {
|
|
5400
|
+
let subById = new Map();
|
|
5401
|
+
if (want('people')) try {
|
|
5351
5402
|
if (membersFromState(sel.stateMembers).byRole.size) {
|
|
5352
5403
|
subById = buildRoleSubMap(subs, sel.stateMembers, wfLabels); // 已登记:不过滤、不丢
|
|
5353
5404
|
} else {
|
|
@@ -5365,11 +5416,13 @@ export function apply(ctx, config) {
|
|
|
5365
5416
|
if (scoped.rejected > 0) sel.membersUnresolved = true;
|
|
5366
5417
|
}
|
|
5367
5418
|
} catch { subById = new Map(); sel.membersUnresolved = true; /* 过滤自身出错 ⇒ 一律不登记 */ }
|
|
5419
|
+
// ⚠️ 登记块只在 people 分节里跑:summary 时 subs 为空 ⇒ deriveMemberEntries 会算出
|
|
5420
|
+
// "清空成员"并覆写 STATE.json(轮询触发的写)。跳过它同时避免"GET 改写 run 工件"。
|
|
5368
5421
|
// 派工即登记(C3):把 role→真实 agentId 落盘到 STATE.members。
|
|
5369
5422
|
// SKILL §7.22 要求这件事,但代码从没有写路径 ⇒ 实测所有 run 的 stateMembers 恒空。
|
|
5370
5423
|
// host 本就算出了 subById,直接登记:① 归属从"猜"变"精确";② 并行 run 不再串号。
|
|
5371
5424
|
// **只在变化时写**(3s 轮询下否则会持续改写工件),且 best-effort 不影响读。
|
|
5372
|
-
try {
|
|
5425
|
+
if (want('people')) try {
|
|
5373
5426
|
const derived = deriveMemberEntries(subById, sel.stateMembers);
|
|
5374
5427
|
if (derived.changed) {
|
|
5375
5428
|
const stPath = join(sel.workspace, 'team', sel.runId, 'STATE.json');
|
|
@@ -5386,12 +5439,17 @@ export function apply(ctx, config) {
|
|
|
5386
5439
|
// 「任务为什么会中断」的诚实答案:一次 fan-out 若从未写 run-end,其编排
|
|
5387
5440
|
// 工具调用就没有返回(回合被打断/进程退出),成员会永远停在「进行中」。
|
|
5388
5441
|
// 这里把每次扇出的命运一并交给面板显示。
|
|
5389
|
-
|
|
5442
|
+
// ⚠️ workflow 元数据两段(见下)只在 people 分节、且**未超软期限**时才算:
|
|
5443
|
+
// 它们要遍历该会话的 workflow 事件(重会话里是大头之一),而首屏并不需要。
|
|
5444
|
+
// 超期限 ⇒ 跳过并如实进 `degraded`(不静默、也不假装算过)。
|
|
5445
|
+
const peopleWithinDeadline = want('people') && (Date.now() - peopleStartedAt < PEOPLE_DEADLINE_MS);
|
|
5446
|
+
if (want('people') && !peopleWithinDeadline) degraded.push('wf:deadline');
|
|
5447
|
+
if (peopleWithinDeadline) sel.wfRuns = await workflowRuns(ctx, peopleSid, subs);
|
|
5390
5448
|
// 全景图的分层依据:workflow 派生的子代理**没有会话 header**(`createdAt` 全 0,
|
|
5391
5449
|
// 这正是流转图退化成「无创建时间记录,无法分批」的根因);它们唯一可辩护的
|
|
5392
5450
|
// 时间来自 `tool-workflow/agent-start` 事件本身。同时给出逐子代理的结算态,
|
|
5393
5451
|
// 让画布能把"被中断、从未结算"的节点画成红色,而不是永远转圈。
|
|
5394
|
-
sel.wfChildren = await workflowChildMeta(ctx, peopleSid);
|
|
5452
|
+
if (peopleWithinDeadline) sel.wfChildren = await workflowChildMeta(ctx, peopleSid);
|
|
5395
5453
|
// R13(lead 裁决 notes②):**显示**也走同一份 run 作用域过滤结果 `subById`。
|
|
5396
5454
|
// R12 只严了写盘,面板在 members 为空时仍会把别的 run 的活人显示成本 run 成员
|
|
5397
5455
|
// ⇒ 数据污染修好了,但**用户看到的串号现象一点没变**(而 membersUnresolved
|
|
@@ -5399,7 +5457,10 @@ export function apply(ctx, config) {
|
|
|
5399
5457
|
// 归属不上的活人 ⇒ 该角色显示为**未启动**(active:false),并保留 membersUnresolved。
|
|
5400
5458
|
// 精确路径(STATE.members 非空)不进过滤 ⇒ subById 直接来自 buildRoleSubMap,
|
|
5401
5459
|
// 已登记成员的显示不丢(R12 第 4 点)。
|
|
5402
|
-
|
|
5460
|
+
// ⚠️ 只在 people 分节里 enrich:summary 时 `subById` 是空 Map,enrich 会把**每个成员**
|
|
5461
|
+
// 都标成"未启动"(把名册信息也一起糊掉)。summary 保留 snapshotRun 给的名册视图,
|
|
5462
|
+
// 活跃态缺失就如实缺失(由客户端按 `sections` 显示"正在加载成员")。
|
|
5463
|
+
if (want('people')) sel.members = enrichMembers(sel.runId, sel.roles, sel.members, subById, sel.agents);
|
|
5403
5464
|
// cost/model plan: expose each role's planned model so the overlay
|
|
5404
5465
|
// shows the scheme even before dispatch (live model wins once running).
|
|
5405
5466
|
const modelPlan = sel.models || modelPlanFor(sel.roles || []);
|
|
@@ -5420,7 +5481,9 @@ export function apply(ctx, config) {
|
|
|
5420
5481
|
const timingIdx = subHeaderIndex(ctx, subs, wfLabels);
|
|
5421
5482
|
const timingOf = {};
|
|
5422
5483
|
for (const s of subs) timingOf[String(s.id || '')] = timingIdx.get(String(s.id || '')) || { createdAt: 0, parentId: '', depth: 0 };
|
|
5423
|
-
|
|
5484
|
+
// summary 分节返回**空数组**(不是"没有成员"):客户端据 `sections` 区分
|
|
5485
|
+
// "摘要里本来就没有这一块"与"真的一个成员都没有"(两种零可区分)。
|
|
5486
|
+
sel.agents = want('people') ? subs.map((s) => {
|
|
5424
5487
|
const sid2 = String(s.id || '');
|
|
5425
5488
|
const t = timingOf[sid2] || { createdAt: 0, parentId: '', depth: 0 };
|
|
5426
5489
|
// 列表阶段(listSessions 分支)已经带出 header 字段时优先用它,省一次读
|
|
@@ -5433,12 +5496,13 @@ export function apply(ctx, config) {
|
|
|
5433
5496
|
// UI 用它区分"0 = 无时间"与"真的是 0";缺字段时客户端也可用 createdAt>0 兜底
|
|
5434
5497
|
hasTimestamp: createdAt > 0
|
|
5435
5498
|
};
|
|
5436
|
-
});
|
|
5499
|
+
}) : [];
|
|
5437
5500
|
// 实时兜底视图:`TASKS.json` 为空但确有子代理在跑时(典型:lead 用 `workflow`
|
|
5438
5501
|
// 扇出却没按协议回写任务),把活子代理投影成只读"实时任务",免得面板
|
|
5439
5502
|
// 任务清单/依赖图/全景全空 —— 用户看到的是"有人在跑,面板却什么都没有"。
|
|
5440
5503
|
// 只在真的有活子代理时投影;行上带 live:true,UI 会明确标注这不是 TASKS.json。
|
|
5441
|
-
|
|
5504
|
+
// (`subs` 非空本身就意味着在 people 分节里,另外再显式判一次免得将来改坏。)
|
|
5505
|
+
if (want('people') && !taskList(sel.tasks).length && subs.length) {
|
|
5442
5506
|
sel.tasksLive = subs.filter((s) => s && s.id).map((s, i) => ({
|
|
5443
5507
|
id: 'L' + (i + 1),
|
|
5444
5508
|
title: String(s.label || '').trim() || (roleOfSub(s) || 'agent') + ' · ' + String(s.id).slice(0, 8),
|
|
@@ -5452,8 +5516,9 @@ export function apply(ctx, config) {
|
|
|
5452
5516
|
}
|
|
5453
5517
|
// Live per-agent activity feed (Qoder-style): last tool events
|
|
5454
5518
|
// per subagent session, keyed by agent id.
|
|
5519
|
+
// 只在 feed 分节里收集(它按子会话遍历活动缓存;summary 首屏不需要)。
|
|
5455
5520
|
const feed = {};
|
|
5456
|
-
for (const s of subs) {
|
|
5521
|
+
if (want('feed')) for (const s of subs) {
|
|
5457
5522
|
const arr = ACTIVITY.get(s.id);
|
|
5458
5523
|
if (arr && arr.length) feed[s.id] = arr.slice(-12);
|
|
5459
5524
|
}
|
|
@@ -5486,8 +5551,11 @@ export function apply(ctx, config) {
|
|
|
5486
5551
|
try { sel.warnings = checkKindWarnings(taskList(sel.tasks)); } catch { sel.warnings = []; }
|
|
5487
5552
|
// Live monitoring: RUN.log feed (what just happened) + git
|
|
5488
5553
|
// changed files (what is being touched right now).
|
|
5489
|
-
|
|
5490
|
-
|
|
5554
|
+
// 只在 artifacts 分节里读(`liveFiles` 要跑 git、`runLogTail` 要读日志尾部)。
|
|
5555
|
+
if (want('artifacts')) {
|
|
5556
|
+
try { sel.logTail = await runLogTail(join(sel.workspace, 'team', sel.runId)); } catch { sel.logTail = ''; }
|
|
5557
|
+
try { sel.files = await liveFiles(sel.workspace, sel.runId); } catch { sel.files = []; }
|
|
5558
|
+
}
|
|
5491
5559
|
// **GET 不写 run 工件**(2026-09-13 修):这里原先每 ≤5 分钟触发一次
|
|
5492
5560
|
// `autoAggregate`(`Date.now() - lastAggAt > 5*60*1000`),于是浮层每 3 秒的轮询
|
|
5493
5561
|
// 会**周期性改写 `team/METRICS.md` 与 `team/LEARNINGS.md`** —— 而 `LEARNINGS.md`
|
|
@@ -5507,6 +5575,12 @@ export function apply(ctx, config) {
|
|
|
5507
5575
|
json(200, Object.assign(
|
|
5508
5576
|
{ ok: true, runs, workspaces, cwd, agents: sel.agents, rolesDeferred: budget.deferred },
|
|
5509
5577
|
sel,
|
|
5578
|
+
// 新增字段(只在相关时出现,默认负载与老客户端保持不变):
|
|
5579
|
+
// sections —— 显式要了分节时,告诉调用方"这份负载包含哪些块"(缺的块 ≠ 空数据,
|
|
5580
|
+
// 客户端据此区分"摘要里没有"与"真的是空的");
|
|
5581
|
+
// degraded —— 命中硬上限/软期限而**少算了东西**时的如实标注(不许静默丢)。
|
|
5582
|
+
secRequested ? { sections: included } : null,
|
|
5583
|
+
degraded.length ? { degraded: true, degradedReason: degraded.join(','), sections: included } : null,
|
|
5510
5584
|
prof ? { profile: Object.assign({}, prof.steps, { rolesBudget: budget }) } : null,
|
|
5511
5585
|
));
|
|
5512
5586
|
} catch (e) {
|
package/lib/interception.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
// `/team check` 事后报红、或等用户走查才发现。
|
|
4
4
|
//
|
|
5
5
|
// 为什么是"落地后纠偏"而不是"写前拒绝"(设计见 docs/Qoder对标/07-B2-1真拦截设计稿.md §二):
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// 本插件**不注册任何模型可调用的工具**;协议已单源化为 **R1(角色写自己的 run 工件)**,
|
|
7
|
+
// 且 lead 的工具面本就**没有** `write`(`lib/lead-toolface.js` @ 34 行把 `write` 列入
|
|
8
|
+
// `LEAD_DENY_CANDIDATES`)⇒ 本拦截器对角色写 run 工件是**放行**的:`runScopedTarget`
|
|
9
|
+
// 只判 `TASKS.json`/`SPEC.md`,其余工件 `kind:'other'` 不拦(见本文件 @ 59-73 与 @ 191 行)。
|
|
10
|
+
// 因此这里只做"落地后 block + 反馈":写仍然发生(不破坏任何现存写法),
|
|
11
|
+
// 但工具结果变成 isError,且逐条告诉模型哪条不合法。
|
|
10
12
|
//
|
|
11
13
|
// 与既有仲裁的关系(`lib/command.js:68` 的 L1-4′:「schema 告警**只上报、不阻断**」):
|
|
12
14
|
// 本模块**不推翻**该仲裁 —— schema 形状告警仍然只上报。这里只拦**两个永不合法**的
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangdcm/dsh-expert-team",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.13",
|
|
4
4
|
"description": "dsh「专家团」bundle:一句自然语言自动组建/持久化一支 12 角色多智能体团队,共享工作区协议 + 阶段门控编排 + 结构化交接 + 质量门禁/自动调度,实现者直接改代码并产出持久工件;带 live 团队浮层(质量门禁/覆盖率/工件预览)。 · Role-based multi-agent expert team for DeepSeek Harness: one sentence in, a staged and gated team delivery out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"workflow",
|
|
17
17
|
"subagent",
|
|
18
18
|
"code-review",
|
|
19
|
-
"quality-gate"
|
|
19
|
+
"quality-gate",
|
|
20
|
+
"role-based",
|
|
21
|
+
"spec-driven",
|
|
22
|
+
"dag",
|
|
23
|
+
"agentic-workflow"
|
|
20
24
|
],
|
|
21
25
|
"repository": {
|
|
22
26
|
"type": "git",
|
|
@@ -84,7 +88,7 @@
|
|
|
84
88
|
"check:name": "node scripts/rename-package.mjs --check",
|
|
85
89
|
"test:regression": "node regression.test.mjs",
|
|
86
90
|
"test:e2e": "node e2e.test.mjs",
|
|
87
|
-
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs",
|
|
91
|
+
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs && node state-sections.test.mjs && node artifact-ownership.test.mjs",
|
|
88
92
|
"test:toolcard": "node tool-card-status.test.mjs",
|
|
89
93
|
"test:flow": "node flow.test.mjs",
|
|
90
94
|
"test:decide": "node plan-decide.test.mjs",
|
|
@@ -8,7 +8,7 @@ whenToUse: 用户用 /team 发起、要求组建「专家团」,**或(自动
|
|
|
8
8
|
|
|
9
9
|
你是「专家团」的**编排者(lead/orchestrator)**。你的职责不是亲自写所有代码,而是组建一支角色分工明确的专家团,用下面的协议把一份目标从需求推进到交付。所有面向模型的角色都跑在 subagent 平面;你的 token 只花在编排、门控交接与最终裁决上。
|
|
10
10
|
|
|
11
|
-
> **交互与语言(必须)**:所有面向用户的内容——澄清/确认问题、执行方案汇总、门控确认、状态更新、交付总结、`SUMMARY.md
|
|
11
|
+
> **交互与语言(必须)**:所有面向用户的内容——澄清/确认问题、执行方案汇总、门控确认、状态更新、交付总结、`SUMMARY.md`、`任务看板.md` 正文——一律使用**中文**;只有技术标识、代码、命令、数据字段、文件名(`SPEC.md`/`PLAN.md` 等)保留英文。不要让用户看到英文的交互文案。
|
|
12
12
|
|
|
13
13
|
## 0. 先读运行目录
|
|
14
14
|
|
|
@@ -16,15 +16,15 @@ whenToUse: 用户用 /team 发起、要求组建「专家团」,**或(自动
|
|
|
16
16
|
|
|
17
17
|
- `TASK.md` — 目标、模式(one-shot / persist)、交付口径(code+artifacts / artifacts-only)、固定角色。
|
|
18
18
|
- `ROSTER.json` — 角色编制与成员映射。
|
|
19
|
-
- `STATE.json` — 当前 `phase`、`status
|
|
19
|
+
- `STATE.json` — 当前 `phase`、`status`、成员列表。**唯一写者是运行时**(`/team` 命令、`/decide`·`/plan` 等路由、派工登记都由运行时回写):lead **只读**它做门控,**不要自己 `write`/`edit` 它**(见 §2 的 R1)。
|
|
20
20
|
|
|
21
21
|
`<run-dir>` 是 `/team` 命令返回的绝对路径(形如 `<cwd>/team/<run-id>`)。若未给定,用 `team/<slug>/`。
|
|
22
22
|
|
|
23
23
|
> **自动拉起(无 /team 时的自组队,必须)**:当用户目标满足 whenToUse 的专家团场景但**没有 `/team` 命令**时,你**直接启动专家团**,不要问用户要不要:
|
|
24
|
-
> 1. **自建 run
|
|
24
|
+
> 1. **自建 run 目录(你只派工、不落盘)**:由你派工的**首个成员**(有 `write` 的角色,见 §2 R1)按 `assets/templates/` 模板把骨架落到 `team/<run-id>/` 下:`TASK.md`、`ROSTER.json`、`任务看板.md`、`SPEC.md`、`PLAN.md`、`RESEARCH.md`、`TASKS.json`、`REVIEW.md`、`TEST.md`、`SUMMARY.md`、`RETRO.md`、`AUTHORITY.md` —— **与运行时 templates 常量的 13 个实体同集合**(真源:`lib/command.js` 的 `const templates = [`);`STATE.json` 与 `RUN.log.md` **由运行时创建/维护**,成员不要写(结构与字段**逐字对照 `assets/templates/` 模板**;`ROSTER.agents` 用固定名字池 Alex/Sam/Tina/Jack/Eric/Lee/Taylor/Felix/Jay/Robin/Jimmy/Bill/James/Jason/Eva/Leo/Mia/Owen/Zoe/Ivy 按角色序取,`ROSTER.models` 按 §1 模型计划;`STATE.members=[]`、`coverage=[]`、`pendingDecision=null`;runId 用 `YYYY-MM-DD-HHMMSS` 短横线式且 **<cwd>/team/ 下唯一**)。建好即视为本次 run 的「运行目录」,后续协议完全一致。
|
|
25
25
|
> 2. **REPOWIKI**:缺则扫描 README/依赖/docs/目录树写 `team/REPOWIKI.md`(或至少先读仓库现状)。
|
|
26
26
|
> 3. 然后**直接进入 §1 角色编制与首步派工**(与命令启动完全相同的编排;网关/门禁/浮层只读文件即可识别你建的 run)。
|
|
27
|
-
> 4.
|
|
27
|
+
> 4. **自检(读,不是写)**:骨架落盘后**读**一遍 `TASK/ROSTER/STATE/任务看板` 与模板字段是否一致(lead 只有读权);后续每轮浮层/`/team check` 会把违规标出来(self 语义内检查)。
|
|
28
28
|
|
|
29
29
|
> **项目上下文(Repowiki,必须)**:
|
|
30
30
|
> - 若 `<cwd>/team/REPOWIKI.md` 存在(由 `/team index` 生成):**clarify 前先读它**——README/依赖清单/docs/项目经验/两层目录树就是项目知识;project 变更后重跑 `/team index` 刷新。
|
|
@@ -82,20 +82,21 @@ whenToUse: 用户用 /team 发起、要求组建「专家团」,**或(自动
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
## 2. 共享工作区协议(workspace protocol)
|
|
85
|
-
> **写前必读(dsh 写保护 FS_NOT_OBSERVED
|
|
85
|
+
> **写前必读(dsh 写保护 FS_NOT_OBSERVED —— 给落盘角色的提示)**:`write` 一个**已存在**的文件前,必须先 `read` 一次它(哪怕只是读占位符)——否则报 `cannot overwrite existing … without reading it first`。跨 agent 协作(A 角色落的盘、B 角色续写)必踩这条:**先读再写,不要猜内容**。
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
团队通过 `<run-dir>/` 下的工件共享上下文,而不是互相看对方的完整对话历史。规则:
|
|
89
89
|
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
90
|
+
- **R1(唯一权威表述 · 本节是唯一权威处,其它文件只许引用、不得另写定义)**:**run 工件一律由产出它的角色自己 `write` 到 `<run-dir>/`**;角色只回 `path` + 摘要 + `verdict`;lead **没有 `write` 工具,只读工件做门控与裁决**;交付时用 `dsh_im_return_file` 把关键工件发给用户。
|
|
91
|
+
- 每个角色**只读它上一阶段产出的工件 + 直接输入**,结论写进 `<run-dir>/` 的对应工件(写法见上一条 R1),返回值只带 `path` + 摘要 + `verdict` 这些小字段。
|
|
92
|
+
- **任务看板(唯一名 = `任务看板.md`)**:`team/<run-id>/任务看板.md`(任务计划 + 状态表 + 当前阶段)**由你指派的一名成员维护**(有 `write` 的那个,通常 pm;每个阶段结束时更新一次),把「任务安排 + 执行进度」写进该工件(工件以 `path` 可核验,交付时由 lead 用 `dsh_im_return_file` 发给用户;「聊天框可点击产出文件行」的机制未独立证实,不作为承诺)。
|
|
93
|
+
- **为什么要角色落盘**:工件必须成为**产出者本轮的产出文件** —— `path` 可核验、可复算,lead 只按 `path` 读盘做门控与裁决;交付时由 lead 用 `dsh_im_return_file` 把关键工件直接发给用户。⚠️ **如实标注**:(该"只认 lead 的 write"机制未独立证实,见 `RESEARCH.md` §7),本协议不依赖它。
|
|
94
|
+
- 交接走两条通道:**结构化返回值**(`path` + 摘要 + `verdict`)+ **工件文件**(产出角色落盘)。两者必须一致。
|
|
94
95
|
- 工件 schema 见 `references/WORKSPACE.md`。`TASKS.json` 是 implement 阶段的唯一事实来源:每条任务带 `id/kind/owner/spec/acceptance/inScope/verify/contract/dependsOn/attempt/round/verdict/status`。质量 kind 走结构化合同,普通 `work` 可自由文本。
|
|
95
96
|
|
|
96
97
|
## 3. 阶段流水线(pipeline)
|
|
97
98
|
|
|
98
|
-
严格按序推进,每阶段有**门控**:上一阶段的验收产物齐备才进入下一阶段。细节与门控条件见 `references/PIPELINE.md
|
|
99
|
+
严格按序推进,每阶段有**门控**:上一阶段的验收产物齐备才进入下一阶段。细节与门控条件见 `references/PIPELINE.md`。**每个阶段角色返回后,工件由该角色自己 `write` 到 `<run-dir>/`(§2 R1);你(lead)按它返回的 `path` 读盘门控。**
|
|
99
100
|
|
|
100
101
|
```
|
|
101
102
|
clarify → research → design → spec-review → implement(DAG并行) → review → test → deliver
|
|
@@ -103,37 +104,37 @@ clarify → research → design → spec-review → implement(DAG并行) → rev
|
|
|
103
104
|
|
|
104
105
|
> **首产物优先(规则 32)**:上面这条流水线是**交付顺序**,不是"开工顺序"。`run:started` 起 **10 分钟内**必须先产出一个最小可运行骨架并记 `first-runnable`,它**不等任何阶段门** —— 顺序是给交付物排的,不是给"能跑起来"排的。
|
|
105
106
|
|
|
106
|
-
- **clarify**(pm):澄清歧义 → 返回 `SPEC.md` 内容(Ultra Spec:功能目标/验收标准/业务规则/**边界与禁止项**/边界 Case/安全边界三级权限/测试计划)与 `PLAN.md` 骨架 →
|
|
107
|
+
- **clarify**(pm):澄清歧义 → 返回 `SPEC.md` 内容(Ultra Spec:功能目标/验收标准/业务规则/**边界与禁止项**/边界 Case/安全边界三级权限/测试计划)与 `PLAN.md` 骨架 → **pm 自己落盘**(§2 R1)。歧义必须用 `ask_user_question` 问,不要猜。**每抛出一个问题就给 `RUN.log.md` 追加一行 `ask:clarify`(一个问题一行,不要合并)。**
|
|
107
108
|
- **「边界十问」必须逐条问用户,或显式标注「用户未指定 ⇒ 按禁止处理」**(这是本包头号返工源的解药,见 §7.29):① **自反关系**(能否回复/点赞/关注/加好友/拉黑**自己**)② **归属·跨父级**(子对象必须属于同一父资源?跨帖 parentId 拒不拒)③ **终态不可变**(已删/已隐藏/已归档还能被交互吗)④ **越权**(改删他人资源 → 403 还是 404)⑤ **幂等**(重复提交会不会产生第二条/重复计数)⑥ **基数上限**(单用户对单对象最多几次)⑦ **级联与计数**(父删后子计数归零?会不会残留脏值/负数)⑧ **并发同键**(两个并发同键请求只允许一条落库?)⑨ **权限升降级**(降级/封禁/退出后既有交互是否立即失效)⑩ **可见性**(软删对象的可见边界、占位根、孤儿子回复)。
|
|
108
109
|
- 每条边界必须写成「**禁止什么 → 期望拒绝(HTTP 状态 + 码 + 文案)→ 验收方式**」;**没有拒绝码的边界视为未定义**(模板见 `assets/templates/SPEC.md` 的「边界与禁止项(强制 · 沉默 ≠ 允许)」章节)。
|
|
109
|
-
- **research**(researcher,涉存量代码时):定位代码、梳理依赖、环境检查 →
|
|
110
|
-
- **design**(architect):返回 `PLAN.md` 设计段(模块边界、接口契约用 JSON Schema、数据流、风险)与 `TASKS.json` 任务拆解(含 `dependsOn`)→
|
|
110
|
+
- **research**(researcher,涉存量代码时):定位代码、梳理依赖、环境检查 → 产出 `RESEARCH.md` → **researcher 自己落盘**(§2 R1)。纯新项目可跳过。
|
|
111
|
+
- **design**(architect):返回 `PLAN.md` 设计段(模块边界、接口契约用 JSON Schema、数据流、风险)与 `TASKS.json` 任务拆解(含 `dependsOn`)→ **architect 自己落盘**(§2 R1)。implementer 只认这份契约。
|
|
111
112
|
- **spec-review**(reviewer + 安全/性能补位):写代码前对 Spec 多视角并行交叉审查 + 反向推导剔除幻觉误报。Spec 是最大杠杆。
|
|
112
113
|
- **「沉默清单」是必产出(第五道验证,必须)**:现有三道验证(代码 vs 规格 / 规格 vs 自身一致性 / 契约 vs 实现)**全部默认规格是对的**,所以「规格没写 ⇒ 被当作允许 ⇒ 被实现出来」这条最贵的失效模式**没有任何一道闸门能拦**。spec-review 必须额外列出「**规格未规定、但实现或交互上可选的行为**」,逐条给出「建议裁定(允许/禁止)+ 依据 + 风险」,交 PM/用户裁定。**沉默不得作为通过理由。**(实证:评论功能因规格沉默而允许「回复自己的评论」,UI 还把「回复」画进自己的菜单,qa 350 条断言对此覆盖 0,最后由**用户走查**才发现并追加一整轮 repair。)
|
|
113
114
|
- **反向推导(撤销幻觉)必须前置到派工前**:先逐条回源核实再报,撤销项与理由写进 `REVIEW.md`,**不要把未核实的疑似问题派成返工任务**;并上报**撤销率 = 撤销数 / 自报数**(实测三轮分别 87.5% / 88.9% / 90% —— 首轮 finding 约四成是幻觉,这是返工的主要噪声源)。
|
|
114
|
-
-
|
|
115
|
+
- **把 reviewer 上报的撤销数写入 `TASKS.json` 的动作按 §2 由产出角色落盘**(在该 review 任务上写 `revertedFindings: <撤销数>`,或给单条 finding 标 `reverted: true` / `severity: 'reverted'`);**lead 只负责给出该数值**。**这是接线项、不是可选项**——METRICS 的「评审效率(轮次 / 撤销率)」一节只认这两个来源,不回写就恒显示「暂无撤销登记」,P4 的噪声治理等于没有数据(与本仓「函数写出来了但没人调用」的 D7 同类病)。
|
|
115
116
|
- **finding 编号必须跨轮稳定**(如 `FIND-3`,下一轮原样沿用、不要重写措辞):`FINDING_REOPENED` 门禁按标题归一分组来识别「同一条又回来了」,**每轮换标题会让这道门禁恒不命中**(实测:某真实 run 各轮 finding 标题互不相同 ⇒ V2 命中 0 条,而 V1 命中 2 条)。
|
|
116
117
|
- **方案确认门**(plan-approval gate,**必须**):`spec-review` 通过后、进入 `implement` 前,你(lead)用**中文**把 `SPEC.md` + `PLAN.md` + `TASKS.json` 汇总成一份「执行方案」(范围/关键决策/任务清单/风险),用 `ask_user_question` 让用户确认:
|
|
117
|
-
-
|
|
118
|
+
- **同时把 `pendingDecision` 交由运行时写入 `STATE.json`(`STATE.json` 的唯一写者是运行时,见 §2 R1)**(`{title, prompt, options:[{id,label}]}`,如 `执行`/`修改方案`),这样右上「专家团」浮层会同步呈现候选项供用户点选;用户在浮层/聊天任一处选择都会记入 `RUN.log` + `DECISIONS.md` 并清除 `pendingDecision`。
|
|
118
119
|
- 用户选「**执行**」→ 进入 implement。
|
|
119
120
|
- 用户选「**修改方案**」→ 把意见转给 pm/architect 修订 `SPEC/PLAN/TASKS`,改完**回到本确认门**再确认。
|
|
120
121
|
- **未获用户确认不得进入 implement,也不得开始改代码。** 权限不明、范围不清时宁可在这一点反复确认,也不要直接开工。**每轮的 lead 在继续前先读 `DECISIONS.md`/`STATE.pendingDecision` 是否已被用户拍板。**
|
|
121
|
-
- **implement**(backend + frontend,可加补位角色,**按 DAG 并行**):只按 `TASKS.json` 里属于自己 owner 的任务改代码,按 `dependsOn` 排依赖顺序;跨角色接口以 `PLAN.md` 契约为准。实现者返回各任务 status(带当前 `attemptId`)与 `changedPaths
|
|
122
|
-
- **review**(reviewer):只读审代码(正确性/安全/性能/架构一致性),返回 `REVIEW.md` 内容与 `verdict`(`pass|needs_revision|reject`)→
|
|
122
|
+
- **implement**(backend + frontend,可加补位角色,**按 DAG 并行**):只按 `TASKS.json` 里属于自己 owner 的任务改代码,按 `dependsOn` 排依赖顺序;跨角色接口以 `PLAN.md` 契约为准。实现者返回各任务 status(带当前 `attemptId`)与 `changedPaths`,**`TASKS.json` 的更新(见 §2)由产出角色自己落盘**:状态机 `pending→claimed→in_progress→completed|failed|cancelled`,依赖只认上游 `completed`,`verify` 命令通过且 `changedPaths` 落 `inScope` 才 `completed`,旧 `attemptId` 迟到写拒绝。**模糊选择**抛候选项交 lead(也写入 `pendingDecision`)让用户拍板。
|
|
123
|
+
- **review**(reviewer):只读审代码(正确性/安全/性能/架构一致性),返回 `REVIEW.md` 内容与 `verdict`(`pass|needs_revision|reject`)→ **`REVIEW.md`(见 §2)由产出角色自己落盘**:只有 `pass` 才 `completed`;非 pass 必须带 findings 并以 `failed` 记,**由你(lead)裁决后,把新建 `repair-N` / `review-N+1` 写成派工;`TASKS.json` 的落盘按 §2**(`repair-N` 依赖指向被审实现,不依赖 failed review;`review-N+1` 针对最新 attempt,别用 reassign 重跑旧 review,reviewer 不审自己),`round` 递增至 `maxReviewRounds`。**到 `maxReviewRounds` 仍非 pass → 由你(lead)裁决升级并把 `STATE.pendingDecision` 交由运行时写入(`STATE.json` 的唯一写者是运行时)(title「审查到上限」,options:[继续审/停止])升级到用户**:选「继续」→ **`pendingDecision` 被消费后 V1 即不再报**(这是运行时唯一可用的豁免;`ROUND_LIMITS` 只在 `apply(ctx, config)` 解析一次,**没有 per-run 覆写**,真要调高上限只能改 `config.limits` 或 env 后重启)→ 再继续自动修复链;选「停止」→ 该项记 `failed` 并在 `SUMMARY.md` 如实标注「停于 N 轮审查未过」,停止该项自动循环。盯逻辑别纠结样式;只重跑受影响角色。
|
|
123
124
|
- **收敛口径(防止「无限抛光」,必须)**:① 只有 **P1/P2 + 可机判项**阻塞 `pass`;**P3/文字/风格项不阻塞**,进 backlog 并计入 `SUMMARY.md`;② **同一 finding 连续两轮未闭环 ⇒ 判为「规格歧义」**,写 `pendingDecision` 升级用户裁定,**不再派修复**(继续派只会再产一轮新 finding);**finding 必须带跨轮稳定的编号与标题**(如 `FIND-3 未校验 token`,下一轮原样沿用)——`FINDING_REOPENED` 门禁正是按标题归一分组来识别"同一条又回来了",**每轮换标题会让这道门禁恒不命中**(实测:某 run 各轮标题互不相同 ⇒ V2 在真实数据上 0 命中,而 V1 命中 2 条);③ `verify` **未实跑**的项不得计入 pass(如实标注「未实跑」);④ 轮次上限现在是**代码强制**(`ROUND_LIMITS`,默认 review/test 各 3;违规码 `REWORK_LOOP_UNESCALATED` / `FINDING_REOPENED`,写侧拒绝码 `REWORK_LOOP_LIMIT`)——到顶的正确动作是**先升级用户**,不是「再来一轮」。
|
|
124
125
|
- **返工循环的真实成因(实测,不是实现者不行)**:某 run 68 任务 / 32 条 repair / maxRound=8,评审每轮都在**新增** finding(20 条 → 新增 6 → 11 项 → 新增 3)且每轮撤销约 8 条幻觉 ⇒ **验收面无界 + 噪声制造返工**。解法是「边界前置到 SPEC」(§7.29)与「撤销前置到派工前」,不是加班修更多轮。
|
|
125
|
-
- **test**(qa + 可选 ui验证补位):按验收标准**自动生成用例→运行**,返回 `TEST.md` 内容与各用例通过/失败 →
|
|
126
|
-
- **deliver**(lead =
|
|
127
|
-
-
|
|
126
|
+
- **test**(qa + 可选 ui验证补位):按验收标准**自动生成用例→运行**,返回 `TEST.md` 内容与各用例通过/失败 → **`TEST.md`(见 §2)由产出角色自己落盘**:失败项自动新建 `repair-N`(kind=`verification`,依赖指向对应实现任务)→ 实现角色修复 → **重新派 qa 跑**,直到通过或 `maxTestRounds`(默认 3),到顶升级用户;测试失败不得当通过,`verify`/用例命令进 `TASKS.json`。
|
|
127
|
+
- **deliver**(lead = 你):汇总全部工件,跑一次构建/测试做最终校验;`RETRO.md` 与 `LEARNINGS.md` 的**内容由 lead 口述,由 lead 指派的一名有 `write` 的成员落盘**(`TASK.md` 的交付结论同理;`STATE.json` 只由运行时写)。
|
|
128
|
+
- **交付前硬门禁(由 qa 全跑,不得凭"看起来对"置 complete)**:若本项目提供门禁命令就**必须跑它**——**交付前的六道 `npm run gate` 由 `qa` 全跑**(无 shell 的角色不得被要求跑命令)——并把关键输出贴进 `TEST.md`;**门禁非 0 不得把 `status` 置为 `complete`**(先在 `TASK.md` 如实写明未通过项与建议,再交付)。本包(`@yangdcm/dsh-expert-team`)的门禁是 **`npm run gate`**,它包含**六道**检查:`gate:preset`(preset 字段漂移)· `gate:evidence`(**引用/锚点可机器判定**:编造锚点或不存在的文件即失败)· `gate:sync`(**三副本一致性**:改了源码但运行时未同步即失败)· `gate:bypass`(**直写棘轮**:绕过受控写入口的新增直写即失败)· `test:all`(全部测试套件;**具体数量以 `package.json` 为准,不要写死数字**——本条曾长期写着「9 个」而实际已 43 个)· **`gate:mutation`(变异验证:确认这些测试真的能失败)**。任一非 0 就先修再交付。
|
|
128
129
|
- **引用纪律**:工件里的代码引用一律写 `<相对路径> · <锚点(函数名/字段名/唯一字符串)> @ <行号>`,**锚点是主证据、行号只是阅读辅助**;`gate:evidence` 就是它的机器化校验。**禁止**写「哈希未变 ⇒ 全部行号有效」这类断言(并发写入会让行号分钟级失效)。
|
|
129
130
|
- **路径必须写全(带目录)**:裸文件名会被后缀匹配到 `packages/dsh-expert-team/skills/expert-team/assets/templates/` 下的同名模板上 ⇒ 引用 run 目录的 JSON 要写 `team/<runId>/TASKS.json · <锚点>` 这种全路径形态。**在文档里描述"错误写法"时也不要用可被解析的形态**(改成散文,或放进 ``` 围栏块——**成对闭合**的围栏内一律豁免;**未闭合**的围栏不豁免,属 fail-closed,防止漏写一个 ``` 就把其后所有真引用一次吞掉)。
|
|
130
131
|
**⚠️ 这条规则已被违反 5~6 次**(`REPAIR-1.md` / `REPAIR-2.md`·`REVIEW-2.md` / `RUN.log.md` / `TEST-B05.md` / 本文件又两次),**全部源于同一个动作:记录/更正缺陷时把缺陷原文照抄了一遍** ⇒ 转述本身又成了可解析引用。两个硬化措施:① **转述与反例一律放进围栏块**(`RUN.log.md` 这类被逐行解析的文件例外——改用不可解析的散文,别用围栏,否则会破坏行式格式);② **门禁暂时跑不动时(例如并发方正在改 `scripts/check-evidence.mjs`),不要在同一批里写"含被引用坏写法"的工件**——宁可把那条日志推迟到门禁能跑之后(实测踩过:追加日志与跑门禁在同一脚本里,而门禁当时报 `loadBaseline is not defined`,坏引用就这样溜进去了)。lead 本人曾连踩五次,其中一次正是"提醒别人别这么写"的那句话本身。
|
|
131
|
-
-
|
|
132
|
+
- **落盘后自跑门禁:有 shell 的落盘者自跑;无 shell 的角色不得被要求跑命令,由 qa 代跑**——每落盘一份工件,有 shell 的落盘者就立刻自跑 `npm run gate:evidence`(无 shell 的角色改由 qa 代跑),不要等 QA 来报;红了就看汇总区的 `- 仅片段命中(弱证据…)` 那一行。
|
|
132
133
|
- **弱证据棘轮(2026-09-12 起)**:门禁按**整条锚点逐字**匹配,片段命中只算「弱证据」(`fragmentOnly`)并受 `regression.fixtures/evidence-fragment-baseline.json` 棘轮约束 —— **超过基线即 exit 1**,基线缺失按 0 处理(fail-closed)。所以新增引用必须整条能搜到;关键字或过短串(如单独的 function、files)不算证据。
|
|
133
134
|
- **锚点内部不得嵌行号**(2026-09-12 起,实测踩过):行号只能出现在**尾部那一个行号槽**里。写成 `cordis.patch.yml · insert:(expert-team-command @ 15-16 / expert-team-bundle @ 23-24)@ 11-24` 这种「括号里也塞行号」的形式,会让 `15-16`/`23-24` 被当成**必须逐字存在的锚点 token**——而它们只是行号,源码里当然搜不到 ⇒ 判 MISSING。**canonical 形式**:`packages/dsh-expert-team/cordis.patch.yml · insert:(expert-team-command / expert-team-bundle)@ 11-24`(把逐项行号去掉,只留整块的行号;逐项位置用文字说明)。
|
|
134
135
|
- **门禁"绿"的含义取决于它的匹配语义**:本仓曾长期用「任一片段命中即算命中」,于是编造的 `function QANonexistentProbe` 因 function 一词存在而被判命中(QA 实测 4/4 全绿、约 39–52 条历史锚点靠片段蒙过)。**把门禁绿当证据之前,先问它会不会也放行假的**——只验证"它能抓真缺陷"(正向变异体)是不够的,还要验证"它不会漏"(假阴性探针)。
|
|
135
136
|
- **自学习闭环:两处必须自己做完并留痕**(SKILL 只把义务写在这里,**没有任何代码会替你检查**,见 LOGGING.md):
|
|
136
|
-
① 团队/流程级经验 → 追加到**全局** `~/.dsh/expert-team/LEARNINGS.md`;项目级 → `<cwd>/team/LEARNINGS.md`(后者 `/team learn`
|
|
137
|
+
① 团队/流程级经验 → 追加到**全局** `~/.dsh/expert-team/LEARNINGS.md`;项目级 → `<cwd>/team/LEARNINGS.md`(后者 `/team learn` 会自动蒸馏;两份的**内容由 lead 口述,由 lead 指派的一名有 `write` 的成员落盘**);
|
|
137
138
|
② 用 `hindsight_ingest_document` 以「专家团经验 · `<runId>`」落库一次。
|
|
138
139
|
并在 `RETRO.md` 里写明这两步**已完成**,便于事后核验。
|
|
139
140
|
|
|
@@ -143,7 +144,7 @@ clarify → research → design → spec-review → implement(DAG并行) → rev
|
|
|
143
144
|
|
|
144
145
|
- **run 开始前**:读 `<cwd>/team/LEARNINGS.md`(若存在),把相关经验融入本次编排。`/team` 命令**已把既往经验摘要随任务消息注入**,请**优先复用**这些「既往经验」,并在第一轮编排里明确体现(不必等自己重读文件)。
|
|
145
146
|
- **运行中**:每完成一个阶段/角色/决策/卡点,按 LOGGING.md 的事件约定给 `<run-dir>/RUN.log.md` 追加一行(`phase:*`、`role:*`、`decision`、`error` 等)。**卡点写 `error:<子类>`(如 `error:workflow`/`error:external-write`)、拍板写 `decision:<来源>`(如 `decision:user`)、clarify 每问一条写 `ask:clarify`——聚合器按「事件族」统计,子类保留用于定位根因。**
|
|
146
|
-
- **deliver
|
|
147
|
+
- **deliver**:`<run-dir>/RETRO.md`(快/慢/卡点)与 `<cwd>/team/LEARNINGS.md` 的追加,**内容由 lead 口述,由 lead 指派的一名有 `write` 的成员落盘**。经验分两层(见 LOGGING.md):**团队/流程级**(跨项目可复用的编排教训)→ 追加到全局 `~/.dsh/expert-team/LEARNINGS.md`;**项目级**(本项目专属坑/环境/约定)→ 追加到 `<cwd>/team/LEARNINGS.md`。
|
|
147
148
|
- **落 Hindsight**(deliver 时):用 `hindsight_ingest_document` 把本次可复用经验(RETRO 要点 + 蒸馏的 LEARNINGS)以标题「专家团经验 · <runId>」保存一次,供跨项目召回;不要倒原始大输出。
|
|
148
149
|
- 卡点与返工**必须如实记**,不得省略。
|
|
149
150
|
|
|
@@ -161,12 +162,12 @@ clarify → research → design → spec-review → implement(DAG并行) → rev
|
|
|
161
162
|
- 交付口径 `code+artifacts`:实现者直接改工作区代码;`artifacts-only`:只产出 `SPEC/PLAN/REVIEW/TEST`,不动代码。
|
|
162
163
|
- deliver 阶段必须跑一次真实校验(`bash` 跑 test/build/lint 中任务适用者),把结果写进 `TEST.md`,不要只凭“看起来对”。
|
|
163
164
|
- **可复现校验环境(DevContainer)**:项目要求特定运行时/依赖版本(node/python/go/rust/php 等),先 `/team devcontainer --write` 生成 `<cwd>/.devcontainer/devcontainer.json`(按项目语言自动选官方镜像),在容器内跑 test/build 作为 verify 证据;本机没有容器环境时,在受控 shell 跑 verify 并记录结果(安全守界见 EFFICIENCY §11)——**环境差异是 verify 失败的最大来源,不能靠“我机器上能跑”交付**。
|
|
164
|
-
- 完成标志:`STATE.json`
|
|
165
|
+
- 完成标志:`STATE.json` 被**运行时**置为 `phase: deliver, status: complete`(lead 只读确认,不代写),`TASK.md` 末尾的交付结论由指派的有 `write` 成员落盘(见 §2)。
|
|
165
166
|
- **交付前先清违规(硬门禁)**:置 `status: complete` 前先自检——`verify` 命令、review/requirements 的 `verdict=pass`、`inScope` 越界、dependency gate——任何一项不满足都是**违规**,会被 `/team status` 的「⚠违规N」与浮层红条当场标出。有违规时不交付:能补的补(跑 verify、补 verdict、改回越界文件、等依赖完成),补不了的在交付结论里如实说明并给出 `repair` 建议,绝不能用「看起来完成了」糊过去。
|
|
166
|
-
- **任务总结(SUMMARY.md)**:deliver
|
|
167
|
-
-
|
|
167
|
+
- **任务总结(SUMMARY.md)**:deliver 时 `team/<run-id>/SUMMARY.md` 的**内容由 lead 口述,由 lead 指派的一名有 `write` 的成员落盘**,给用户一份交付总结(交付时由 lead 用 `dsh_im_return_file` 发给用户;工件以 `path` 可核验——「聊天框可点击产出文件行」的机制未独立证实,不作为承诺)——每个任务的结果 / 改动文件 / commit / 评审与测试结论。结构见 `assets/templates/SUMMARY.md`。
|
|
168
|
+
- **任务看板(`任务看板.md`)**:全程维护 `team/<run-id>/任务看板.md`(任务计划 + 状态表:待开始/进行中/已完成 + 当前阶段),随进度更新——**由你指派的一名有 `write` 的成员落盘**(见 §2),把「任务安排 + 执行状态」写进该工件(工件以 `path` 可核验;「聊天框可点击产出文件行」的机制未独立证实,不作为承诺)。结构见 `assets/templates/任务看板.md`。
|
|
168
169
|
- **可视化画布(`/team canvas`)**:需要「一眼看到团队+任务+阶段」时,可用 `/team canvas [<run>]` 生成自包含 HTML 团队画布(roster + DAG 任务看板 + 阶段步进器)并提示用户点开;画布数据来自 `team/<run>/` 的工件与当前 run 状态。
|
|
169
|
-
-
|
|
170
|
+
- **工件点名规范(只写文件名)**:工件以 `path` 可核验;交付时由 lead 用 `dsh_im_return_file` 发给用户。「聊天框可点击产出文件行」的机制**未独立证实,不作为承诺**(见 §2 R1 的如实标注)。在最终回复/交付总结里用**行内代码**点名工件时仍**只写文件名(basename)**——如 `SUMMARY.md`、`任务看板.md`、`SPEC.md`(每个文件名仅出现一次),这是**便于识别与 `path` 可核验**的书写约定;**不要写带目录的完整路径**(如 `team/<run>/SUMMARY.md`)——完整路径需要展示时用普通文本写在括号里(如 `SUMMARY.md`(team/run/SUMMARY.md))。浮层「料」页工件栏随时可预览,不依赖此机制。
|
|
170
171
|
|
|
171
172
|
## 7. 效率规则(必须遵守)
|
|
172
173
|
|
|
@@ -180,9 +181,9 @@ clarify → research → design → spec-review → implement(DAG并行) → rev
|
|
|
180
181
|
7. **限深**:委派深度 ≤ 1(仅编排者委派,角色不再往下委派);除非用户明确要求更大规模。
|
|
181
182
|
8. **稳定前缀**:角色 prompt 固定、工件按固定路径读写,避免重复注入易变文本。
|
|
182
183
|
9. **不空转**:Spec 一次写透(Ultra Spec),review 返工只重跑受影响角色。
|
|
183
|
-
10. **自动调度 + 派工即回写(禁止状态冻结)**:任务走状态机 `pending→claimed→in_progress→completed|failed|cancelled`,依赖只认上游 `completed`,成员 idle 后自动领下一题,`attemptId`
|
|
184
|
+
10. **自动调度 + 派工即回写(禁止状态冻结)**:任务走状态机 `pending→claimed→in_progress→completed|failed|cancelled`,依赖只认上游 `completed`,成员 idle 后自动领下一题,`attemptId` 迟到写拒绝——别逐个点名,让空闲成员自己领活。**每次派工和每次结算都必须立即让 `TASKS.json` 的状态被更新(落盘按 §2 由产出角色执行,lead 只读核对与裁决)**:派工→该任务 `claimed/in_progress`;成员完成→`status=completed` + `changedPaths` + `verify` 命令与结果,再派下一题。**只更新任务看板/聊天不等于更新状态**——阶段进入 implement 后,依赖已就绪却仍 `pending` 的任务会被 `/team check` 与浮层红条当场判为「状态冻结」违规;deliver 前存在未终态任务同样违规。
|
|
184
185
|
- **分工(2026-09-13 起,代码接手了一半)**:**派工 → `in_progress` + `owner` 已由代码自动完成**(挂在宿主 `tools/post-execute` 上,见 `lib/dispatch-ledger.js`)。触发条件:工具名是 `subagent*`、**`label` 里带任务 id**、且该 id 在 `TASKS.json` 里**逐字存在**;只翻 `pending → in_progress`,终态不动、别人正在做的不覆盖、`owner` 只在缺失时写。**代价与义务**:① **`label` 必须带任务 id**(如 `【后端工程师】实现 T24`)—— 代码只解析 `label`、**不解析 prompt**(提示词里「依赖 T01 已完成」这类提及会把别人的活也标成在做),没带 id 就退化为"不记账";② 没带 `--run` 的会话归属也由代码处理,**只认本会话自己的 run**(多会话并行时不会去改别人的台账)。
|
|
185
|
-
-
|
|
186
|
+
- **代码不管的那一半仍然是你的义务**:**裁决并给出结算口径**(`completed` + `changedPaths` + `verify` 结果 + `round`/`verdict` 的数值与判定;写入 `TASKS.json` 的动作按 §2 由产出角色落盘)、repair 链的新建(写成派工,落盘按 §2)、越界审计 —— 这些都需要你的判断,代码不做。**「派工自动记账」不等于「你不用给出结算口径」。**
|
|
186
187
|
11. **质量门禁到共识**:review/requirements 只有 `verdict=pass` 才 `completed`;非 pass 自动 `repair-N`+`review-N+1`(independent、针对最新 attempt),到 `maxReviewRounds` 升级用户;reviewer 不审自己;完成时按 `changedPaths` 做过越界审计。
|
|
187
188
|
12. **开跑先派工:双研究并行 + 同步澄清(Qoder 同款)**:`/team` 创建、自动拉起或 `/team resume` 后,**第一条消息内立即派工**——首步固定拆成**两项并行研究**(同批启动,不等对方):①`[researcher] 研究现有项目全貌`(读 REPOWIKI + 代码/文档/依赖/存量约束)②`[researcher] 调研竞品与行业`(动态补位调研角色,见 §1 补位规则;无竞品需求时合并为一项)。**同一响应内**再抛 3-5 个关键澄清问题(`ask_user_question`,影响目标/范围/口径的决策点,如「重构范围是否含 admin」「兼容旧数据吗」)——研究工作与确认问题**并行**,不空等。浮层「已派 0/N」且无任务在跑是不正常状态,必须先把它变 ≥1。派工顺序按「已安排任务链」:调研 → 设计 → 实现(DAG 并行)→ 评审 → 测试 → 交付。**澄清前先摸底**:研究 pre-check(现状/缺口)先于口径提问(历史 run:先摸清「加密链路已存在、缺口=中间件不记密文」后一问即准)。
|
|
188
189
|
13. **不 sleep 等活(禁止空转轮询)**:派活后**禁止** `bash sleep 240` 这类盲等——后台成员完成会发**结算通知**(自动到达);中途看进度用 `list_agents`(成员状态)或看浮层(任务详情有「谁在做/改哪些文件/运行日志」实时信息)或读 `RUN.log.md`;确实要同步阻塞收结果时用 `job_output(<jobId>, {wait:true})`,不要 sleep。等待消息里只做有用的事(继续编排下一层任务、读工件),不空转。
|
|
@@ -198,7 +199,7 @@ clarify → research → design → spec-review → implement(DAG并行) → rev
|
|
|
198
199
|
- 完成:`✅ 完成 → 🔎 研究员:REVIEW-SPEC 已产出`
|
|
199
200
|
规则:播报行放在动作之前/与动作同一轮、一个动作一行、**不写成长段落**;阶段推进与关键门禁必播报,例行文件操作不播报。**播报与一切面向用户的文案里,阶段名一律写中文汉字**(澄清 / 调研 / 设计 / 规格评审 / 方案确认 / 实现 / 审查 / 测试 / 交付),不要写 clarify、design 这类英文阶段 id。
|
|
200
201
|
18. **lint+静态+build 通过 ≠ 运行时通过(交付前必须 smoke)**:`php -l`/静态/build 全过不代表能跑(历史实锤:Hyperf `use function` 漏写导致 `Call to undefined function`,三关全过却运行时崩)。交付/验收必须含**运行时 smoke**:迁移可执行 + 服务可启动 + 关键路径 HTTP 冒烟(健康看 `Server: <框架>` 头 + trace_id,异常路径特征早识别);QA 对运行时/外部依赖项**如实标注「未实跑」**,不得把 lint/build 通过谎报为运行时通过。
|
|
201
|
-
19. **计划门:先暂存草稿,再由用户授「批准并运行」(Approve & Run)(AgentTeams 借鉴,必须)**:进入方案确认门时,除写 `STATE.pendingDecision`(浮层卡片 + 输入框横幅)外,还必须把**可编辑的计划草稿**写进 `STATE.draft = { roles:[...], tasks:[{id,owner,title,dependsOn,...}] }`(写入方式二选一:① `POST /plugins/dsh-expert-team/plan`(推荐,会做校验:owner 必须在 roles 内、dependsOn 必须指向存在的任务 id、状态归一为 pending);②
|
|
202
|
+
19. **计划门:先暂存草稿,再由用户授「批准并运行」(Approve & Run)(AgentTeams 借鉴,必须)**:进入方案确认门时,除写 `STATE.pendingDecision`(浮层卡片 + 输入框横幅)外,还必须把**可编辑的计划草稿**写进 `STATE.draft = { roles:[...], tasks:[{id,owner,title,dependsOn,...}] }`(写入方式二选一:① `POST /plugins/dsh-expert-team/plan`(推荐,会做校验:owner 必须在 roles 内、dependsOn 必须指向存在的任务 id、状态归一为 pending);② 由**运行时**写入(`STATE.json` 的唯一写者是运行时))。用户在浮层「事」页签可**增删角色、改任务 owner/依赖**;**只有**用户点 **「✅ 批准并运行」**(该路由把 `draft.tasks` 落入 `TASKS.json`、按 `draft.roles` 更新 `ROSTER.json`、清 `pendingDecision`)之后才允许 implement 派工。用户点 **「🗑 丢弃」** 后 `STATE.planDiscarded` 置位——**此后禁止自动重建同一目标的团队**,除非用户明确再次要求;被丢弃的草稿不得在下一轮"悄悄复活"。
|
|
202
203
|
|
|
203
204
|
20. **dsh 升级后先校验预设(字段漂移会让整个 preset 挂不上,且毫无征兆)**:agent 预设是**手写副本**,dsh 升级可能改动插件 config 字段——实测 `@deepseek-ai/dsh-persona` 在 0.1.5-rc.1 把 `text` 改成**必填**的 `prefix`(+`suffix`),而组合**只在会话创建/恢复时读一次**,所以失败表现是「**新建工作区/恢复会话直接报 `preset "expert-team" failed to mount … invalid config: $.prefix missing required value`**」,正在运行的会话却完全正常,用户只会说「加好工作区没反应」。**动作**:升级 dsh(或改动预设)后,立刻跑 `node scripts/validate-agent-preset.mjs`(本包随附:逐行用插件**真实 Config schema** 校验,可离线复现上面的报错),通过后再新建会话;**不等用户来报**。
|
|
204
205
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 任务总结({{run-id}})
|
|
2
2
|
|
|
3
|
-
> deliver 阶段由 lead
|
|
4
|
-
> ⚠️ 工件引用只用**文件名**(如 `SUMMARY.md
|
|
3
|
+
> deliver 阶段由 lead 口述内容、指派一名有 `write` 的角色落盘(见 SKILL.md §2),让用户在聊天框可点击预览交付结果。
|
|
4
|
+
> ⚠️ 工件引用只用**文件名**(如 `SUMMARY.md`/`任务看板.md`/`SPEC.md`,一个名字一次);带目录的完整路径不会变成可点击。
|
|
5
5
|
|
|
6
6
|
## 结论
|
|
7
7
|
|
|
@@ -76,4 +76,4 @@
|
|
|
76
76
|
- **工具守界**:非实现角色(pm/architect/researcher/ui/reviewer/sec/docs)在 preset toolFilter 里不得有写业务代码/改文件工具,只读;qa/devops 只跑测试/构建/部署命令,dba 只做只读查询。越权工具已从 preset 移除;若遇通用 subagent 回退,写进 prompt 的 ROLES.md 守界条款同样适用。
|
|
77
77
|
- **实现者只动 `inScope`**:每条实现/修复任务的 `inScope` 写清合法改动范围;实现者不得改 `outOfScope` 文件;完成时回报 `changedPaths`,lead 对照 `inScope` 审计,越界不得 `completed`。
|
|
78
78
|
- **高危命令**:成员(尤其实现者)禁止执行 `rm -rf`、`sudo`、`chmod 777`、`git push --force` 等破坏性/生产命令;这些只由 lead 在**沙箱/受控终端**里跑(dsh sandbox 已启用时),且需用户确认。
|
|
79
|
-
- **审计**:lead 把关键工具/文件变更链路记入 `RUN.log`(`tool:bash <cmd>`、`fs:write <path
|
|
79
|
+
- **审计**:lead 把关键工具/文件变更链路记入 `RUN.log`(`tool:bash <cmd>`、`fs:write <path>`)——§2 口径:lead 无 `write`,内容由 lead 口述、指派的有 `write` 角色落盘——形成可追溯审计;`/team learn` 会聚合高频错误/越界。
|