dsh-lead-mode 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cordis.patch.yml +1 -0
- package/lib/index.js +99 -40
- package/package.json +1 -1
- package/skills/lead-mode/SKILL.md +66 -0
- package/skills/team-digest/SKILL.md +200 -0
package/cordis.patch.yml
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
- 派活自包含。teammate 看不到你的上下文,把目标、约束、验收标准、已知失败、绝对路径都写进 prompt。
|
|
53
53
|
- 要求它收尾那条消息写清交付物。它收工时那条消息会随通知回到你手上。
|
|
54
|
+
- 派活时要求它第一步建工作日志(.scratch/<effort>/worklog-<mate>.md),收尾前补完最后一轮。
|
|
54
55
|
- 写作用域不重叠。一人一块文件。分不开的活排成依赖。
|
|
55
56
|
- 共享任务走 list、get、claim(带 revision)、干、complete。task 只是账本,不是唤醒通道。
|
|
56
57
|
- 等活就结束回合等通知:teammate 收工、或给你发消息,都会唤醒你。task 变更只唤醒正在 wait_agent 的调用方,不会唤醒空闲的你。
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-lead-mode host half.
|
|
3
3
|
*
|
|
4
|
-
* Three jobs: register
|
|
5
|
-
*
|
|
4
|
+
* Three jobs: register every bundled runtime skill (`skills/<name>/SKILL.md`,
|
|
5
|
+
* discovered at apply time), register the `/lead-mode` slash command, and warn
|
|
6
|
+
* at startup when the Team runtime is absent.
|
|
6
7
|
*
|
|
7
8
|
* The plugin declares no `inject` of its own. Each surface waits for its own
|
|
8
9
|
* service through `ctx.inject()`, so a host missing `dsh-skill` still mounts the
|
|
@@ -12,7 +13,8 @@
|
|
|
12
13
|
*
|
|
13
14
|
* @module dsh-lead-mode
|
|
14
15
|
*/
|
|
15
|
-
import { readFileSync } from 'node:fs'
|
|
16
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
16
18
|
import { fileURLToPath } from 'node:url'
|
|
17
19
|
|
|
18
20
|
/** Cordis plugin name. */
|
|
@@ -27,11 +29,8 @@ const ENABLE_COMMAND = `dsh plugin --profile <你的 profile 名> add ${TEAM_PRO
|
|
|
27
29
|
/** Why the version pin matters, repeated wherever the command appears. */
|
|
28
30
|
const VERSION_NOTE = '不带版本号会装到 0.1.5-alpha.2,和 0.2.1 宿主对不上。'
|
|
29
31
|
|
|
30
|
-
/**
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
/** Skill name shared by the file on disk, the runtime registration, and the naming manifest. */
|
|
34
|
-
const SKILL_NAME = 'lead-mode'
|
|
32
|
+
/** Directory holding one subdirectory per bundled skill; the directory name is the skill name. */
|
|
33
|
+
const SKILLS_DIR = new URL('../skills/', import.meta.url)
|
|
35
34
|
|
|
36
35
|
/** Command name shared by the registration and the naming manifest. */
|
|
37
36
|
const COMMAND_NAME = 'lead-mode'
|
|
@@ -40,25 +39,104 @@ const COMMAND_NAME = 'lead-mode'
|
|
|
40
39
|
const PRESET_ID = 'lead-mode'
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
* Read
|
|
42
|
+
* Read one bundled skill and split off its YAML frontmatter.
|
|
44
43
|
*
|
|
45
44
|
* The runtime skill registry wants `description` as its own field, so the body
|
|
46
45
|
* handed to it must not repeat the frontmatter block. Reading synchronously
|
|
47
|
-
* keeps `apply` synchronous;
|
|
46
|
+
* keeps `apply` synchronous; each file is a few kilobytes shipped inside this
|
|
48
47
|
* package, so there is no I/O worth deferring.
|
|
49
48
|
*
|
|
50
|
-
*
|
|
49
|
+
* A skill directory whose file is unreadable, has no frontmatter, declares no
|
|
50
|
+
* `name`, mismatches its directory name, declares no `description`, or has an
|
|
51
|
+
* empty body is reported through `warn` and dropped. One broken skill must not
|
|
52
|
+
* take the whole injection down with it.
|
|
53
|
+
*
|
|
54
|
+
* @param skillsRoot - absolute path of the `skills/` directory.
|
|
55
|
+
* @param dirName - the skill directory name, which is also the expected skill name.
|
|
56
|
+
* @param warn - receives one Chinese diagnostic per skipped skill.
|
|
57
|
+
* @returns the registry payload, or `undefined` when this skill was skipped.
|
|
51
58
|
*/
|
|
52
|
-
function readBundledSkill() {
|
|
53
|
-
const path =
|
|
54
|
-
|
|
59
|
+
function readBundledSkill(skillsRoot, dirName, warn) {
|
|
60
|
+
const path = join(skillsRoot, dirName, 'SKILL.md')
|
|
61
|
+
let raw
|
|
62
|
+
try {
|
|
63
|
+
raw = readFileSync(path, 'utf8')
|
|
64
|
+
} catch (error) {
|
|
65
|
+
warn(`[lead-mode] 读不到 ${path},${dirName} 这个 skill 跳过:${String(error)}`)
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
55
68
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw)
|
|
56
|
-
if (match === null)
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
if (match === null) {
|
|
70
|
+
warn(`[lead-mode] ${path} 没有 YAML frontmatter,${dirName} 这个 skill 跳过`)
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
const declared = /^name:\s*(.+)$/m.exec(match[1])?.[1]?.trim()
|
|
74
|
+
if (declared !== dirName) {
|
|
75
|
+
warn(`[lead-mode] ${path} 的 frontmatter name 是「${declared ?? '缺'}」,与目录名「${dirName}」不一致,跳过这一个`)
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
const description = /^description:\s*(.+)$/m.exec(match[1])?.[1]?.trim()
|
|
79
|
+
if (description === undefined) {
|
|
80
|
+
warn(`[lead-mode] ${path} 的 frontmatter 没有 description,${dirName} 这个 skill 跳过`)
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
59
83
|
const content = raw.slice(match[0].length).trim()
|
|
60
|
-
if (content.length === 0)
|
|
61
|
-
|
|
84
|
+
if (content.length === 0) {
|
|
85
|
+
warn(`[lead-mode] ${path} 正文是空的,${dirName} 这个 skill 跳过`)
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
name: declared,
|
|
90
|
+
description,
|
|
91
|
+
content,
|
|
92
|
+
// `source` is required by the registry's load-time validation but is NOT
|
|
93
|
+
// defaulted by `register()`: `validateRuntimeSkill` (register) checks only
|
|
94
|
+
// name, description and invocation, while `validateDefinition` (load)
|
|
95
|
+
// throws unless `source` is a string. Omitting it registers fine and then
|
|
96
|
+
// fails every `skill` tool call with `source must be a string`.
|
|
97
|
+
source: `dsh-lead-mode/skills/${declared}/SKILL.md`,
|
|
98
|
+
path,
|
|
99
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Scan `skillsRoot` and register every valid bundled skill.
|
|
105
|
+
*
|
|
106
|
+
* Directory order is sorted so the registration order is stable across hosts.
|
|
107
|
+
* A missing or unreadable root, an unreadable skill file, and a registry that
|
|
108
|
+
* throws on one skill all degrade to a warning instead of failing the callback.
|
|
109
|
+
*
|
|
110
|
+
* @param skillsRoot - absolute path of the `skills/` directory.
|
|
111
|
+
* @param registry - the `skills` service (`register(definition)`).
|
|
112
|
+
* @param warn - receives one Chinese diagnostic per skipped skill.
|
|
113
|
+
* @returns the names that were registered, in registration order.
|
|
114
|
+
*/
|
|
115
|
+
export function syncBundledSkills(skillsRoot, registry, warn) {
|
|
116
|
+
let entries
|
|
117
|
+
try {
|
|
118
|
+
entries = readdirSync(skillsRoot, { withFileTypes: true })
|
|
119
|
+
} catch (error) {
|
|
120
|
+
warn(`[lead-mode] 扫描 ${skillsRoot} 失败,本插件的 skill 都没注册:${String(error)}`)
|
|
121
|
+
return []
|
|
122
|
+
}
|
|
123
|
+
const dirNames = entries
|
|
124
|
+
.filter((entry) => entry.isDirectory())
|
|
125
|
+
.map((entry) => entry.name)
|
|
126
|
+
.sort()
|
|
127
|
+
const registered = []
|
|
128
|
+
for (const dirName of dirNames) {
|
|
129
|
+
const skill = readBundledSkill(skillsRoot, dirName, warn)
|
|
130
|
+
if (skill === undefined) continue
|
|
131
|
+
try {
|
|
132
|
+
registry.register(skill)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
warn(`[lead-mode] 注册 skill ${skill.name} 失败:${String(error)}`)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
registered.push(skill.name)
|
|
138
|
+
}
|
|
139
|
+
return registered
|
|
62
140
|
}
|
|
63
141
|
|
|
64
142
|
/**
|
|
@@ -155,26 +233,7 @@ export function apply(ctx) {
|
|
|
155
233
|
const warn = (message) => ctx.logger.warn(message)
|
|
156
234
|
|
|
157
235
|
ctx.inject(['skills'], (skillsCtx) => {
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
skill = readBundledSkill()
|
|
161
|
-
} catch (error) {
|
|
162
|
-
warn(`[lead-mode] 读不到 skills/lead-mode/SKILL.md,lead-mode skill 没注册:${String(error)}`)
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
skillsCtx.skills.register({
|
|
166
|
-
name: SKILL_NAME,
|
|
167
|
-
description: skill.description,
|
|
168
|
-
content: skill.content,
|
|
169
|
-
// `source` is required by the registry's load-time validation but is NOT
|
|
170
|
-
// defaulted by `register()`: `validateRuntimeSkill` (register) checks only
|
|
171
|
-
// name, description and invocation, while `validateDefinition` (load)
|
|
172
|
-
// throws unless `source` is a string. Omitting it registers fine and then
|
|
173
|
-
// fails every `skill` tool call with `source must be a string`.
|
|
174
|
-
source: 'dsh-lead-mode/skills/lead-mode/SKILL.md',
|
|
175
|
-
path: skill.path,
|
|
176
|
-
invocation: { modelInvocable: true, userInvocable: true },
|
|
177
|
-
})
|
|
236
|
+
syncBundledSkills(fileURLToPath(SKILLS_DIR), skillsCtx.skills, warn)
|
|
178
237
|
})
|
|
179
238
|
|
|
180
239
|
ctx.inject(['commands'], (commandsCtx) => {
|
|
@@ -186,7 +245,7 @@ export function apply(ctx) {
|
|
|
186
245
|
})
|
|
187
246
|
|
|
188
247
|
if (ctx.get('skills') === undefined) {
|
|
189
|
-
warn('[lead-mode] skills
|
|
248
|
+
warn('[lead-mode] skills 服务不在,本插件的 skill 暂时都没注册。它会等 dsh-skill 挂载后再注册;一直不出现就检查 dsh-skill 是否装好。')
|
|
190
249
|
}
|
|
191
250
|
if (ctx.get('commands') === undefined) {
|
|
192
251
|
warn('[lead-mode] commands 服务不在,/lead-mode 命令暂时没注册。它会等 dsh-commands 挂载后再注册。')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lead-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Lead mode for DSH: an agent preset, a working-method skill, a dependency self-check, and two reminders that tell you to enable the official Agent Teams. | 给 DSH 加 Lead 模式:预设、skill、依赖自检、两条前置提醒。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -99,12 +99,74 @@ teammate 收工时,它**最后那条消息**会随通知回到你手上,格
|
|
|
99
99
|
|
|
100
100
|
需要**中途**进度(干到一半问一句、或者要它先确认再继续)时,才另外要求它 `send_message` 给你。
|
|
101
101
|
|
|
102
|
+
还有一条硬要求,写进每张单里:开建工作日志是成员的第一件事,收尾前补完最后一轮。它是你的账本,成员被 `interrupt_agent` 打断也不丢账。格式见下面的「上下文变长怎么办」那节。
|
|
103
|
+
|
|
102
104
|
### 收尾消息可能被省掉
|
|
103
105
|
|
|
104
106
|
如果 teammate 在收工前刚给你发过 `send_message`,通知里不会重复它的收尾正文,而是写「It sent you a message just before finishing, so its closing message is omitted.」。
|
|
105
107
|
|
|
106
108
|
也就是说:你要么拿到收尾消息,要么拿到它发的那条,不会两个都没。
|
|
107
109
|
|
|
110
|
+
## 上下文变长怎么办:压缩、复用、交接、换人
|
|
111
|
+
|
|
112
|
+
### 为什么会丢
|
|
113
|
+
|
|
114
|
+
宿主对每个 agent 都自动压缩会话。压力到阈值就触发,会话 id 不变,早期节点被一条摘要替换。这就是**上下文降级**:细节只剩摘要,不再完整可见。
|
|
115
|
+
|
|
116
|
+
团队层没有关掉压缩的开关。Lead 也没有「压缩指定成员会话」的通道,只能等它自己触发。
|
|
117
|
+
|
|
118
|
+
成员活干长了,前面的细节会变薄。你不查,就当它还记得。
|
|
119
|
+
|
|
120
|
+
### 怎么检测
|
|
121
|
+
|
|
122
|
+
查这个成员的会话被压缩过几次:
|
|
123
|
+
|
|
124
|
+
1. `list_agents` 取成员的 `id`。
|
|
125
|
+
2. `session_event_search`,`session_id` 填那个 id,`event_types: ["compaction/start"]`,`surfaces: ["log-only"]`。
|
|
126
|
+
3. 命中 ≥1 次,就是该成员已被压缩,细节开始丢。
|
|
127
|
+
|
|
128
|
+
不要在 `wait_agent` 里套这个检查,也不要反复轮询。一次派活、验收之前查一次就够。
|
|
129
|
+
|
|
130
|
+
阈值不写死。各宿主不一样,写死会过期,你只需要知道怎么查。
|
|
131
|
+
|
|
132
|
+
### 交接阶梯
|
|
133
|
+
|
|
134
|
+
按代价从低到高排,能不换人就不换人。
|
|
135
|
+
|
|
136
|
+
1. 继续跑:命中 0–1 次、活快收尾,交给自动压缩兜住。
|
|
137
|
+
2. 重发窄单:用 `send_message` 给同一个成员一张更窄的单,只做剩下的部分。成员常驻,别急着换人。
|
|
138
|
+
3. 写交接文档:要求它先补完工作日志,再写 handoff,然后 `interrupt_agent`。
|
|
139
|
+
4. 换新成员:`spawn_teammate`,`context` 用 `fresh`。原因写死:`fork` 拷的是你 Lead 自己的历史,不是上一个成员的历史,所以拿不到前任的细节。
|
|
140
|
+
|
|
141
|
+
### 工作日志与交接文档放哪
|
|
142
|
+
|
|
143
|
+
位置固定在 `.scratch/<effort>/`。一人一个 `worklog-<mate>.md`,换人时写 `handoff-<mate>-<n>.md`。
|
|
144
|
+
|
|
145
|
+
`.scratch` 存在就用(通常是 `obsidian-docs/scratch/` 的软链,笔记库能看见,不进 git);不存在就先建目录,别假设它一定有。
|
|
146
|
+
|
|
147
|
+
工作日志(worklog)分两段:
|
|
148
|
+
|
|
149
|
+
| 段 | 写什么 |
|
|
150
|
+
|---|---|
|
|
151
|
+
| 状态摘要(头部,一屏内) | 任务 id 与标题、所属成员、写作用域、验收判据、当前状态 |
|
|
152
|
+
| 轮次记录(下面,只追加) | 一轮一条:做了什么、改了哪些文件、下一步、踩到的坑 |
|
|
153
|
+
|
|
154
|
+
交接文档(handoff)六块,全部用指针,不复制正文:
|
|
155
|
+
|
|
156
|
+
1. 接手关系:谁交给谁、交接原因、时间。
|
|
157
|
+
2. 任务与账本:task id、revision、owner、验收判据。
|
|
158
|
+
3. 文件账:已改、未改、只读过,全部绝对路径,加工作日志指针。
|
|
159
|
+
4. 验收:能复跑的命令,加最近一次输出摘要。
|
|
160
|
+
5. 死路与决策:试过什么不行、为什么选现在这条。
|
|
161
|
+
6. 下一步:接手第一件事、明确不做什么。
|
|
162
|
+
|
|
163
|
+
### 换人操作清单
|
|
164
|
+
|
|
165
|
+
- 先查名额:`/lead-mode` 报 `maxMembers`。换一次吃一个名额,创建失败的也占位,名字永不复用。
|
|
166
|
+
- 旧 task 你先 `team_task_update` 执行 `release`,续作成员再 `claim`。revision 会变。
|
|
167
|
+
- 续作成员名字加 `-r2` 后缀,与被接手的成员并存,不要复用旧名字。
|
|
168
|
+
- `interrupt_agent` 不清空它排队的消息,打断前确认工作日志已写完。
|
|
169
|
+
|
|
108
170
|
## 写作用域不重叠
|
|
109
171
|
|
|
110
172
|
两个 teammate 同时改一个文件,改动会互相覆盖,而且谁都发现不了。
|
|
@@ -181,6 +243,10 @@ teammate 继承你(Lead)在 spawn 那一刻的 provider 和 model。想换
|
|
|
181
243
|
|
|
182
244
|
派活时可以直接告诉用户去这儿看进度,比自己复述一遍更清楚。
|
|
183
245
|
|
|
246
|
+
## 收尾要写团队汇总
|
|
247
|
+
|
|
248
|
+
本 effort 有 2 个以上成员或跨 2 轮以上时,收尾要写一份团队汇总。加载 `team-digest` skill:触发阈值、落盘路径、六块骨架、原料取法都在那里,这里不展开格式。
|
|
249
|
+
|
|
184
250
|
## 给最终答复前
|
|
185
251
|
|
|
186
252
|
- 必需的 teammate 都等齐了再答。没等齐就说「还在跑」,别把半成品当结论。
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: team-digest
|
|
3
|
+
description: 收尾、结项、写报告时用的团队汇总。把多个成员的产出合并成一份 markdown,本 effort 有 2 个以上成员或跨 2 轮以上才写。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 团队汇总
|
|
7
|
+
|
|
8
|
+
成员各交一段收尾消息,各写一份工作日志。收尾时没人合并,用户得自己翻 N 条通知,过一周想回看还得重新调查一遍。这份 skill 产出那份合并物。
|
|
9
|
+
|
|
10
|
+
主要用途是日后回看。当场看懂是它的副产物,不要为了「当场好看」多写。
|
|
11
|
+
|
|
12
|
+
## 什么时候写
|
|
13
|
+
|
|
14
|
+
给最终答复之前判断,满足一条就写:
|
|
15
|
+
|
|
16
|
+
| 条件 | 说明 |
|
|
17
|
+
|---|---|
|
|
18
|
+
| 本 effort 有 2 个以上成员 | 一人一段就要合并,否则用户得自己拼 |
|
|
19
|
+
| 跨 2 轮以上 | 同一成员多轮、或换过续作成员,过程散在多轮里 |
|
|
20
|
+
|
|
21
|
+
不写的情况:只有一个成员一轮干完、只查了资料没改动、纯问答。别为一次调用产出一份报告。
|
|
22
|
+
|
|
23
|
+
## 写到哪
|
|
24
|
+
|
|
25
|
+
`reports/<YYYY-MM-DD>-<effort>.md`,相对仓库根,单一路径。
|
|
26
|
+
|
|
27
|
+
- `<effort>` 用 `.scratch/` 下那个目录名,和工作日志同源,别另起一套命名。
|
|
28
|
+
- 不新增目录层级,不加 `-v2`、`-final` 这类后缀。
|
|
29
|
+
- 同一 effort 同一天只写一份。收尾时一次成型,不追加、不做增量、不设「晋升」步骤。
|
|
30
|
+
- 想进笔记库,自己给 `reports` 建软链,不要改路径。
|
|
31
|
+
- 目录不存在就建。建不了(只读挂载等)就在最终答复里说清楚,别悄悄丢。
|
|
32
|
+
|
|
33
|
+
## 六块骨架
|
|
34
|
+
|
|
35
|
+
六块标题写死:不改名、不合并、不跳块。某块没内容就写「无」。
|
|
36
|
+
|
|
37
|
+
| 块 | 标题 | 写什么 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| 1 | 结论 | 做完了什么、能不能用,一到两句。不放清单 |
|
|
40
|
+
| 2 | 谁干了什么 | 表格:成员 / 交付物 / 状态 / 过程产物指针 |
|
|
41
|
+
| 3 | 验收证据 | 一条一行:可粘贴的命令 + 输出摘要 + 是不是 Lead 自己复跑过 |
|
|
42
|
+
| 4 | 偏离与未验证 | 偏离规格的、只口头声称没证据的 |
|
|
43
|
+
| 5 | 死路与决策 | 结论级死路一行一条;不可逆决策一条一记录 |
|
|
44
|
+
| 6 | 未决与下一步 | 还没定的、下一轮第一件事、明确不做什么 |
|
|
45
|
+
|
|
46
|
+
照这个骨架填,标题一个字都别改:
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
# 团队汇总 · <effort>
|
|
50
|
+
|
|
51
|
+
## 结论
|
|
52
|
+
|
|
53
|
+
## 谁干了什么
|
|
54
|
+
|
|
55
|
+
| 成员 | 交付物 | 状态 | 过程产物指针 |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
|
|
58
|
+
## 验收证据
|
|
59
|
+
|
|
60
|
+
## 偏离与未验证
|
|
61
|
+
|
|
62
|
+
### 未验证清单
|
|
63
|
+
|
|
64
|
+
## 死路与决策
|
|
65
|
+
|
|
66
|
+
## 未决与下一步
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
第 4 块是隔离区。「已通过」但拿不出证据的东西只能写在这里,不许混进第 1、2、3 块。
|
|
70
|
+
|
|
71
|
+
### 第 2 块的状态列
|
|
72
|
+
|
|
73
|
+
只有五个取值,别扩展:
|
|
74
|
+
|
|
75
|
+
| 取值 | 什么时候用 |
|
|
76
|
+
|---|---|
|
|
77
|
+
| 完成 | 交付物在,验收证据也核过 |
|
|
78
|
+
| 未验证 | 交付物在,但只有口头声称,没有可复跑的证据 |
|
|
79
|
+
| 卡住 | 没干完,阻塞点还在 |
|
|
80
|
+
| 被打断 | 被 `interrupt_agent` 或用户中途停掉 |
|
|
81
|
+
| 上下文已降级 | 会话被自动压缩过,细节只剩摘要 |
|
|
82
|
+
|
|
83
|
+
除「完成」外,其余四个都要在末尾「未验证」清单里点名。
|
|
84
|
+
|
|
85
|
+
### 第 3 块怎么写
|
|
86
|
+
|
|
87
|
+
每条证据占一行,三项齐:命令、输出摘要、是否复跑。
|
|
88
|
+
|
|
89
|
+
- 命令要能直接粘贴,带工作目录或绝对路径。
|
|
90
|
+
- 输出摘要写判定结果,比如 `31 pass / 0 fail`、`diff 为空`,不贴整段日志。
|
|
91
|
+
- 复跑分三种:Lead 自己跑过、只有成员跑过、谁都没跑。第三种不算证据。
|
|
92
|
+
|
|
93
|
+
### 第 5 块怎么写
|
|
94
|
+
|
|
95
|
+
死路按结论级写,一行一条,形状是「试过 X,不行,因为 Y」。不写探索过程。
|
|
96
|
+
|
|
97
|
+
不可逆决策一条一记录,五个字段:
|
|
98
|
+
|
|
99
|
+
1. 背景:当时卡在什么上。
|
|
100
|
+
2. 备选:还考虑过什么。
|
|
101
|
+
3. 选的:最后选了哪个。
|
|
102
|
+
4. 依据:为什么是它。
|
|
103
|
+
5. 什么情况下推翻:什么信号出现就该翻案。
|
|
104
|
+
|
|
105
|
+
## 原料怎么取
|
|
106
|
+
|
|
107
|
+
三层,按顺序取,别跳级。上层够用就不下钻。
|
|
108
|
+
|
|
109
|
+
| 层 | 来源 | 取什么 |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| 1 | 收尾消息 | 结论、文件清单、验收输出、偏离规格四块,做骨架 |
|
|
112
|
+
| 2 | 工作日志 | `.scratch/<effort>/worklog-<mate>.md`,过程细节、死路、决策 |
|
|
113
|
+
| 3 | 会话日志 | 兜底,只在收尾消息缺字段或工作日志不存在时 |
|
|
114
|
+
|
|
115
|
+
### 第 1 层:收尾消息
|
|
116
|
+
|
|
117
|
+
成员收工时,它最后那条消息随通知进你的上下文。大多数情况这一层就够写第 1、2、3 块。
|
|
118
|
+
|
|
119
|
+
有两种情况会缺:
|
|
120
|
+
|
|
121
|
+
- 上下文被压缩,早期成员的通知只剩摘要。
|
|
122
|
+
- 成员收工前刚发过 `send_message`,收尾正文会被省掉。
|
|
123
|
+
|
|
124
|
+
捞回来用 `session_event_search`,`session_id` 填自己的会话 id,`surfaces` 同时带被遮蔽和只进日志两面:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
session_event_search({
|
|
128
|
+
session_id: '<你自己的会话 id>',
|
|
129
|
+
query: '<成员名或交付物关键词>',
|
|
130
|
+
surfaces: ['shadowed', 'log-only'],
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`query` 是必填的全文关键词,做的是字符串匹配,不是语义检索。写成员名或文件名最准。
|
|
135
|
+
|
|
136
|
+
### 第 2 层:工作日志
|
|
137
|
+
|
|
138
|
+
路径固定 `.scratch/<effort>/worklog-<mate>.md`。头部状态摘要给第 2 块,下面的轮次记录给第 4、5 块。
|
|
139
|
+
|
|
140
|
+
文件不存在不算异常:说明该成员没按派活要求写日志,第 2 块状态照实写,末尾清单点名。
|
|
141
|
+
|
|
142
|
+
### 第 3 层:会话日志下钻
|
|
143
|
+
|
|
144
|
+
只在「收尾消息缺字段」或「工作日志不存在」时用,**一个成员最多下钻一次**。
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
session_event_search({
|
|
148
|
+
session_id: '<成员 id>',
|
|
149
|
+
event_types: ['assistant/message'],
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
两条限制:
|
|
154
|
+
|
|
155
|
+
- 跨 cwd 读不到。成员会话的工作目录必须和 Lead 相同,否则这条查不到东西。
|
|
156
|
+
- 下钻一次还不齐就写「未验证」,不要反复翻。
|
|
157
|
+
|
|
158
|
+
## 证据指针怎么挂
|
|
159
|
+
|
|
160
|
+
按证据类型挂,挂错类型等于没挂。
|
|
161
|
+
|
|
162
|
+
| 证据类型 | 挂什么 | 例子 |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| 改动 | `路径:行号` | `skills/team-digest/SKILL.md:38` |
|
|
165
|
+
| 验收 | 可粘贴命令 | `node --test test/*.test.mjs` |
|
|
166
|
+
| 决策 | 工作日志段名,或 `session-<id>#seq` | `worklog-skill-author.md 第 5 块` |
|
|
167
|
+
|
|
168
|
+
指针指向已有产物,不复制正文。复制两份,改一份就有一份在骗人。
|
|
169
|
+
|
|
170
|
+
## 异常成员怎么处理
|
|
171
|
+
|
|
172
|
+
| 情况 | 第 2 块状态 | 额外动作 |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| 没交收尾消息 | 被打断 | 末尾清单列出它负责的交付物 |
|
|
175
|
+
| 只有「已通过」「已修复」这类话 | 未验证 | 能复跑就复跑,跑不了就照实写缺什么 |
|
|
176
|
+
| 工作日志没写 | 按实际交付判 | 末尾清单点名「无工作日志」 |
|
|
177
|
+
| 被压缩过 | 上下文已降级 | 需要细节时下钻一次,结论降一档置信度 |
|
|
178
|
+
| 创建失败或始终没起来 | 卡住 | 单列一行,写清失败现象 |
|
|
179
|
+
|
|
180
|
+
「未验证」清单不是第 4 块的替代品:第 4 块写偏离规格和无证据的结论,清单写「谁、什么没验、缺什么证据」。
|
|
181
|
+
|
|
182
|
+
## 宿主事实
|
|
183
|
+
|
|
184
|
+
这些是宿主当前行为,别按直觉推:
|
|
185
|
+
|
|
186
|
+
| 事实 | 后果 |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `team_task_update` 的 `complete` 不能带结果载荷 | 任务板不是结果记录本,结论只能写进汇总 |
|
|
189
|
+
| `wait_agent` 只返回 `{timedOut, noProgress}` | 它不带任何内容,别指望从返回值里拿到成员说了什么 |
|
|
190
|
+
| 宿主没有报告类工具 | 汇总只能自己组织,没有现成模板命令可调 |
|
|
191
|
+
| 压缩事件(`compaction/start` 等)都是 log-only | 检索必须显式带 `surfaces: ['log-only']`,否则查不到 |
|
|
192
|
+
| 成员名额 `maxMembers` 由 profile 层设为 8 | 名字永不释放,创建失败也占名额 |
|
|
193
|
+
|
|
194
|
+
## 写作口径
|
|
195
|
+
|
|
196
|
+
- 中文,短句,结论在前。
|
|
197
|
+
- 没实测的数字不写精确值,写「未验证」。
|
|
198
|
+
- 六块标题写死,不许改名、不许合并、不许跳块。
|
|
199
|
+
- 纯 markdown,零外部依赖。Obsidian、answer-me-with-html、dsh-context 一律「装了才用」,没装也要能写完。
|
|
200
|
+
- 术语固定四个:团队汇总、死路、过程产物、证据指针。不另造近义词。
|