sillyspec 3.26.10 → 3.26.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.
Files changed (101) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +85 -85
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  3. package/.claude/skills/sillyspec-commit/SKILL.md +109 -109
  4. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  5. package/.claude/skills/sillyspec-doctor/SKILL.md +76 -76
  6. package/.claude/skills/sillyspec-execute/SKILL.md +200 -200
  7. package/.claude/skills/sillyspec-explore/SKILL.md +95 -95
  8. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  9. package/.claude/skills/sillyspec-plan/SKILL.md +148 -148
  10. package/.claude/skills/sillyspec-quick/SKILL.md +115 -111
  11. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  12. package/.claude/skills/sillyspec-scan/SKILL.md +79 -79
  13. package/.claude/skills/sillyspec-state/SKILL.md +64 -64
  14. package/.claude/skills/sillyspec-status/SKILL.md +54 -54
  15. package/.claude/skills/sillyspec-verify/SKILL.md +83 -83
  16. package/.claude/skills/sillyspec-workspace/SKILL.md +169 -169
  17. package/.gitattributes +2 -0
  18. package/.husky/pre-push +20 -20
  19. package/README.md +203 -201
  20. package/SKILL.md +91 -90
  21. package/package.json +39 -39
  22. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  23. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  24. package/packages/dashboard/dist/index.html +16 -16
  25. package/packages/dashboard/package.json +25 -25
  26. package/packages/dashboard/server/executor.js +95 -86
  27. package/packages/dashboard/server/index.js +663 -601
  28. package/packages/dashboard/server/parser.js +526 -526
  29. package/src/change-risk-profile.js +254 -254
  30. package/src/classify-change.js +109 -109
  31. package/src/config-schema.js +313 -310
  32. package/src/contract-matrix.js +388 -379
  33. package/src/db.js +370 -350
  34. package/src/dispatch/backends/local-agent.js +155 -155
  35. package/src/dispatch/backends/sillyhub-mcp.js +245 -245
  36. package/src/dispatch/probe.js +246 -210
  37. package/src/dispatch/strategy.js +121 -121
  38. package/src/docs-check.js +188 -16
  39. package/src/docs-debt.js +6 -2
  40. package/src/docs-gate.js +1 -0
  41. package/src/doctor-diagnostics.js +898 -711
  42. package/src/endpoint-extractor.js +401 -401
  43. package/src/fs-atomic.js +71 -69
  44. package/src/git-helper.js +82 -79
  45. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  46. package/src/hooks/worktree-guard.js +854 -848
  47. package/src/index.js +251 -20
  48. package/src/init.js +624 -623
  49. package/src/knowledge-match.js +130 -130
  50. package/src/local-detect.js +128 -120
  51. package/src/machine-interface.js +543 -543
  52. package/src/migrate.js +127 -127
  53. package/src/modules.js +7 -5
  54. package/src/progress/change-registry.js +297 -260
  55. package/src/progress/consistency-doctor.js +355 -355
  56. package/src/progress/shared.js +36 -36
  57. package/src/progress/stage-machine.js +589 -550
  58. package/src/progress/step-store.js +261 -261
  59. package/src/progress.js +1285 -1127
  60. package/src/quicklog.js +724 -693
  61. package/src/review-tier.js +77 -77
  62. package/src/run/command.js +1408 -1364
  63. package/src/run/complete-handlers.js +1450 -1399
  64. package/src/run/complete.js +1181 -920
  65. package/src/run/gates.js +878 -755
  66. package/src/run/prompt.js +765 -761
  67. package/src/run/quick-audit.js +155 -146
  68. package/src/run/shared.js +1348 -1215
  69. package/src/run/stage.js +527 -479
  70. package/src/scan-postcheck.js +371 -371
  71. package/src/setup.js +414 -416
  72. package/src/sillyhub-mcp/client.js +597 -529
  73. package/src/spec-dir-typo.js +3 -24
  74. package/src/spec-sync.js +358 -319
  75. package/src/stage-contract.js +871 -897
  76. package/src/stage-review.js +549 -549
  77. package/src/stages/archive.js +156 -155
  78. package/src/stages/brainstorm-auto.js +236 -236
  79. package/src/stages/brainstorm.js +597 -597
  80. package/src/stages/cmd-existence.js +145 -145
  81. package/src/stages/doctor.js +386 -371
  82. package/src/stages/execute.js +1014 -1003
  83. package/src/stages/explore.js +34 -34
  84. package/src/stages/index.js +29 -29
  85. package/src/stages/knowledge.js +498 -498
  86. package/src/stages/plan-postcheck.js +1323 -1302
  87. package/src/stages/plan.js +642 -623
  88. package/src/stages/quick.js +153 -151
  89. package/src/stages/scan.js +607 -607
  90. package/src/stages/status.js +64 -64
  91. package/src/stages/verify.js +264 -264
  92. package/src/sync.js +1325 -1135
  93. package/src/task-review.js +1204 -1019
  94. package/src/taskcard.js +178 -0
  95. package/src/verify-postcheck.js +1199 -1186
  96. package/src/workflow.js +600 -600
  97. package/src/worktree-apply.js +1237 -1136
  98. package/src/worktree-deps.js +453 -353
  99. package/src/worktree.js +1527 -1481
  100. package/templates/workflows/archive-impact.yaml +79 -79
  101. package/templates/workflows/scan-docs.yaml +132 -132
@@ -1,245 +1,245 @@
1
- /**
2
- * SillyHub MCP 后端派发指令模板(task-dispatcher 抽象层)— task-04
3
- *
4
- * 定位(D-007):dispatcher **不是 JS 执行体**。SillyHub 的 MCP tool(create_mission /
5
- * dispatch_worker / list_workers / report_progress)只有 agent 能调,CLI(Node)进程调不了。
6
- * 所以本文件 = **派发指令模板生成器**:生成注入 execute prompt 的「派发指令文本」,告诉
7
- * agent 如何经 SillyHub MCP tool 创建 mission / 派 worker / 轮询终态 / kill lease / 回收;
8
- * 实际 MCP tool 调用由 agent 执行,本模块不调任何 tool,也不直接 import client.js。
9
- *
10
- * 路径 A 探测翻真(task-11,D-005 + R-04):`isPathASupported()` 读 probe.js 预热的
11
- * tools/list 探测缓存——`dispatch_worker.inputSchema.properties` 含 `worktree_path` 与
12
- * `worker_prompt` → true;任一缺失 / 未预热 / 探测失败 → false(保守回退 Local,**绝不硬试路径A**)。
13
- * env 备选(spike-01 不通过时):`process.env.SILLYHUB_PATH_A==='1'` → true(手动标记 daemon 侧
14
- * 已落地)。探测预热住 probe.js `probeSillyHub`(daemon 可达后 best-effort 调 `client.listTools()`),
15
- * 本模块只缓存探测结果 + 同步读——保持 `isPathASupported()` sync 签名不改 strategy.js /
16
- * execute.js `getDispatchMode` 调用点(探测 = async,读缓存 = sync,解耦)。
17
- *
18
- * 铁律:
19
- * - converge_mission 不出现在任何指令文本(D-004,SillySpec 自己 apply);JSDoc 仅作开发者锚点。
20
- * - worker 不 git commit,改动留 worktree 工作区(D-003@v2 第三处 UB-1,worker_prompt 覆写)。
21
- * - 不直接调 client,只生成指令由 agent 执行 MCP tool 调用(探测由 probe.js 调 client,本模块仅读缓存)。
22
- * - 路径 A 检测保守:不支持即回退,不硬试。
23
- * - 兼容 Win/Linux/macOS:本文件无路径拼接 / 无平台分支,纯模板生成。
24
- */
25
-
26
- /**
27
- * 路径 A 未落地时 strategy.js 拼到降级提示里的原因说明。
28
- *
29
- * 属指令文本(会注入 prompt),故不含 converge_mission 字面(D-004)。strategy 在探测到
30
- * isPathASupported()=false 时把此串拼进降级提示,引导 agent 回退 Local。
31
- */
32
- export const PATH_A_DOWNGRADE_REASON =
33
- '路径A 跨仓未落地:dispatch_worker 不支持 worktree_path / render_worker_prompt 仍硬编码 commit,本次派发回退 Local'
34
-
35
- /**
36
- * SillyHub 后端结果回收约定(worker 终态 → review.json,D-004 + UB-1)。
37
- *
38
- * 与 Local 后端回收同构(R-07 屏蔽后端差异):worker 终态后 SillySpec 主体对 worktree
39
- * **工作区**做 git diff 写 review.json。差异在 worker 侧产出形态——SillyHub worker **绝不
40
- * git commit**(路径A 第三处 UB-1,worker_prompt 已覆写覆盖 daemon 默认 commit 行为),改动
41
- * 全留工作区;合并回主干由 SillySpec 自己 apply(worktree-apply 全门控保留),**不调 SillyHub
42
- * 合并 tool**(D-004)。
43
- *
44
- * 抽成独立常量供 strategy.js 在拼接降级 / 回收段时复用,避免 Local 与 SillyHub 两份回收文案
45
- * 重复维护。含 `<runId>` 占位符,使用方按实际 contract.runId 替换(见 renderSillyHubInstruction)。
46
- */
47
- export const SILLYHUB_RECYCLE_RULE = `### 回收约定(worker 终态 → review.json,SillySpec 自己 apply)
48
-
49
- worker 终态后,**回收不依赖 worker git commit,也不调 SillyHub 合并 tool**:SillySpec 主体
50
- (你,调度 agent)对 worktree **工作区**做 git diff,按既有 task-review 契约写 review.json;
51
- 合并回主干由 SillySpec 自己 apply(worktree-apply / Review Gate / assess 全门控保留)。
52
-
53
- 1. worker 终态(completed / failed / killed)后,读取该 task 在 worktree 工作区的 git diff
54
- (task 派发 → 终态的工作区变更;worker 未 commit 故全在工作区,不依赖 worker 自报)
55
- 2. 对照 plan.md 与 tasks/task-XX.md 检查实现(不信任 worker 自报,只看当前 task 的 diff)
56
- 3. 写入 review.json:\`.runtime/execute-runs/<runId>/tasks/task-XX/review.json\`
57
- 4. review.json 写入成功后,该 task 的派发回收完成;合并走 SillySpec apply,不调 SillyHub 合并 tool
58
-
59
- review.json 复用既有 task-review 契约(必填字段:schemaVersion / task / base / head /
60
- changedFiles / specVerdict / qualityVerdict / reviewerNotes / requiredEvidence),不发明
61
- 新契约。回收屏蔽后端差异:无论 Local 还是 SillyHub worker,SillySpec 都自己 diff worktree
62
- 工作区写 review.json,再自己 apply(D-004)。`
63
-
64
- /**
65
- * 路径A 探测结果缓存(task-11):probe.js `probeSillyHub` 预热 → `isPathASupported` 同步读。
66
- *
67
- * 保持 `isPathASupported` sync 签名(strategy.js / execute.js `getDispatchMode` 同步调,不改
68
- * 调用点):探测是 async(probe.js 调 client.listTools),读缓存是 sync。未预热(probe 未跑 /
69
- * 探测失败)→ 默认 false(保守 R-04,不硬试路径A)。模块级单例,进程内有效;测试用
70
- * `clearPathAProbeCache` 隔离。
71
- */
72
- let _pathAProbe = { probed: false, supported: false };
73
-
74
- /**
75
- * 写路径A 探测结果(probe.js `probeSillyHub` 在 daemon 可达后预热调用)。
76
- * @param {boolean} supported - dispatch_worker schema 含 worktree_path + worker_prompt
77
- */
78
- export function setPathAProbeResult(supported) {
79
- _pathAProbe = { probed: true, supported: !!supported };
80
- }
81
-
82
- /**
83
- * 清空路径A 探测缓存(测试隔离用;生产中进程重启 / 不需要 TTL,probe 每次可达都重预热带新值)。
84
- */
85
- export function clearPathAProbeCache() {
86
- _pathAProbe = { probed: false, supported: false };
87
- }
88
-
89
- /**
90
- * 路径 A 是否已落地(dispatch_worker 支持 caller worktree + worker_prompt 覆写)。
91
- *
92
- * task-11 翻真(D-005 + R-04):读 probe.js 预热的探测结果(probeSillyHub 经 `tools/list` 查
93
- * `dispatch_worker.inputSchema.properties` 含 `worktree_path` 与 `worker_prompt`)。保持 sync
94
- * 签名(strategy.js / execute.js `getDispatchMode` 同步调),探测预热在 probe 流程。
95
- *
96
- * 判定顺序(保守,**绝不硬试路径A**):
97
- * 1. **env 强制开启**:`process.env.SILLYHUB_PATH_A==='1'` → true(spike-01 不通过 / tools/list
98
- * 不可达时的备选,手动标记 daemon 侧路径A 已落地;优先级最高,让探测不可信时也能启用)
99
- * 2. 否则读探测缓存:probe 预热且 dispatch_worker schema 全命中(worktree_path + worker_prompt)→ true
100
- * 3. 未预热 / 探测失败 / schema 缺字段 → false(保守回退 Local,R-04)
101
- *
102
- * @returns {boolean} 路径A 可用 → true;否则 false(保守不硬试,R-04)
103
- */
104
- export function isPathASupported() {
105
- // 1. env 强制开启(spike-01 备选):优先级最高,探测不可信 / 不可达时手动启用
106
- if (process.env.SILLYHUB_PATH_A === '1') return true;
107
- // 2. 读 probe 预热的 schema 探测缓存(未预热 → supported=false,保守 R-04 不硬试)
108
- return _pathAProbe.supported;
109
- }
110
-
111
- /**
112
- * @typedef {import('./local-agent.js').DispatchContract} DispatchContract
113
- * @see design.md「接口定义」DispatchContract:brief / worktreePath / branch / allowedPaths /
114
- * readOnly / modelHint / agentProfileHint / runId / missionId(与 task-03 Local 后端同构)
115
- */
116
-
117
- /**
118
- * 渲染 SillyHub 后端派发指令文本(路径 A 落地后由 strategy.js 注入 execute prompt)。
119
- *
120
- * 纯模板生成器:不校验 contract 完整性、不调任何 MCP tool / client,只把 contract 字段拼成
121
- * 可注入 execute prompt 的中文指令字符串。contract 缺字段时回退到醒目占位符(`<未提供 ...>`),
122
- * 让 agent 在 prompt 里一眼看到派发前必须补齐的参数,而非静默产出错位指令。
123
- *
124
- * 指令含四要点(D-008 / D-003@v2 / UB-6 / D-004):
125
- * 1. **一 Wave 一 mission**:agent 先调 create_mission(objective/change_id/budget_usd per Wave),
126
- * Wave 内 task→worker 并行 dispatch,Wave 间 mission 串行
127
- * 2. **dispatch_worker 参数**:mission_id/objective/worktree_path/branch/read_only/model/
128
- * agent_profile_id/worker_prompt(覆写:worker 不 git commit)
129
- * 3. **终态轮询**:agent 轮询 list_workers,per-worker 超时 → report_progress 标记 + kill lease
130
- * 防双写 + fallback Local 重派
131
- * 4. **回收约定**:worker 不 commit,SillySpec 自己 diff worktree 写 review.json,不调合并 tool
132
- *
133
- * @param {DispatchContract} [contract] - 派发契约(见 design.md DispatchContract 接口定义)
134
- * @returns {string} 注入 execute prompt 的 SillyHub 后端派发指令文本
135
- */
136
- export function renderSillyHubInstruction(contract) {
137
- const c = contract || {}
138
- const worktreePath = c.worktreePath
139
- const branch = c.branch
140
- const allowedPaths = Array.isArray(c.allowedPaths) ? c.allowedPaths : null
141
- const readOnly = !!c.readOnly
142
- const brief = c.brief
143
- const runId = c.runId
144
- const modelHint = c.modelHint
145
- const agentProfileHint = c.agentProfileHint
146
- const missionId = c.missionId
147
-
148
- // 回收约定:替换 <runId> 占位符;runId 缺失保留占位符让 agent 警觉
149
- const recycleRule = SILLYHUB_RECYCLE_RULE.replaceAll(
150
- '<runId>',
151
- runId ? String(runId) : '<未提供 runId>'
152
- )
153
-
154
- // 各字段缺省醒目占位(派发前必须补齐的参数一眼可见)
155
- const worktreeDisplay = worktreePath
156
- ? String(worktreePath)
157
- : '<未提供 contract.worktreePath — 派发前必须补齐,否则 worker cwd 错位>'
158
- const branchDisplay = branch
159
- ? String(branch)
160
- : '<未提供 contract.branch — 派发前必须补齐 worktree 当前分支>'
161
- const briefDisplay = brief
162
- ? String(brief)
163
- : '<未提供 contract.brief — 派发前必须补齐任务目标与蓝图路径>'
164
- const missionDisplay = missionId
165
- ? String(missionId)
166
- : '<本 Wave mission_id — 先调 create_mission 拿到后填入>'
167
-
168
- const allowedLine = allowedPaths && allowedPaths.length
169
- ? `- 本次任务边界(allowedPaths):${allowedPaths.map(p => `\`${p}\``).join(' / ')}(SillySpec 侧 assess/apply 校验,SillyHub 不物化)`
170
- : '- 本次任务边界(allowedPaths):未提供,按蓝图 tasks/task-XX.md 的 allowed_paths 为准(SillySpec 侧校验)'
171
- const modelLine = modelHint
172
- ? `- \`model\`:\`${modelHint}\`(来自 tasks.md [model:xxx],映射到 SillyHub model)`
173
- : '- `model`:未提供(contract.modelHint 缺),沿用 daemon 默认模型'
174
- const profileLine = agentProfileHint
175
- ? `- \`agent_profile_id\`:\`${agentProfileHint}\`(来自 contract.agentProfileHint)`
176
- : '- `agent_profile_id`:未提供(contract.agentProfileHint 缺),沿用 daemon 默认 profile'
177
-
178
- const modeLine = readOnly
179
- ? '**只读模式(read_only=true)**:worker 只读 / 审查 / 写 review.json,不改源码(注:第一波 execute task 用写模式,acceptance 强制 tier=self,R-03)'
180
- : '**写模式(read_only=false)**:worker 按蓝图实现源码(TDD),但**绝不 git commit**(见下方 worker_prompt 覆写)'
181
-
182
- return `### 派发后端:SillyHub MCP — 路径 A 落地后启用
183
-
184
- 本指令用 **SillyHub MCP tool** 派 worker 执行任务(路径 A 落地后由 strategy.js 注入;当前
185
- \`isPathASupported()\` 返回 false,strategy 不会调本指令,而是降级提示 + 回退 Local)。
186
-
187
- **你的角色是调度者 + 审查者,不要自己写代码。** 实际 MCP tool 调用由你(调度 agent)执行:
188
- 创建 mission → 派 worker → 轮询终态 → kill 异常 lease → 回收 diff。
189
-
190
- ### 一 Wave 一 mission(D-008)
191
-
192
- 1. **Wave 开始先建 mission**:调 \`create_mission\` tool 为本 Wave 创建独立 mission
193
- - 参数:\`objective\`(本 Wave 目标)/ \`change_id\`(SillySpec change id)/ \`budget_usd\`(per Wave 预算)
194
- - 返回 \`mission_id\`,记下供本 Wave 内所有 task→worker 复用:${missionDisplay}
195
- 2. **Wave 内 task → worker 并行 dispatch**:同 Wave 多 task 各调一次 \`dispatch_worker\`,共用本 Wave 的 mission_id(保 Wave 内并行)
196
- 3. **Wave 间 mission 串行**:上一 Wave 全 worker 终态 + 回收完成后,下一 Wave 起新 mission(不把全 Wave 塞一个 mission,否则失串行)
197
-
198
- ### dispatch_worker 参数(每个 task 调一次)
199
-
200
- 调 \`dispatch_worker\` tool,参数(snake_case 对齐 SillyHub schema;对照 client.js \`dispatchWorker\` 签名):
201
- - \`mission_id\`:${missionDisplay}
202
- - \`objective\`:${briefDisplay}
203
- - \`worktree_path\`:\`${worktreeDisplay}\`(路径A:worker cwd = SillySpec worktree,D-002)
204
- - \`branch\`:\`${branchDisplay}\`(路径A:worktree 当前分支)
205
- - \`read_only\`:\`${readOnly}\`
206
- ${modelLine}
207
- ${profileLine}
208
- - \`worker_prompt\`:**必须覆写**(见下方,覆盖 daemon 默认 render_worker_prompt 的 git commit 行为)
209
-
210
- ${modeLine}
211
- ${allowedLine}
212
-
213
- ### worker_prompt 覆写(D-003@v2 第三处,UB-1 关键)
214
-
215
- dispatch_worker 的 \`worker_prompt\` 参数必须传以下覆写文本(路径A 第三处:daemon 默认
216
- \`render_worker_prompt\` 硬编码 \`git add -A && git commit\`,会污染 sillyspec/<change> 分支历史
217
- + 撞 D-004;用 worker_prompt 覆写让 caller 控制 commit 行为):
218
-
219
- \`\`\`
220
- 你是 SillySpec execute 的 worker,在指定 worktree 干活。铁律:
221
- 1. 按 objective + 蓝图(tasks/task-XX.md / design.md)实现任务(TDD:先读后写 → 写测试 → 写实现)
222
- 2. **绝不执行 git add / git commit / git push**:改动留在工作区(staged 或 unstaged 都行),
223
- 交 SillySpec 主体 git diff 回收(D-004)
224
- 3. 不超出 allowedPaths,不碰其他 task 的文件
225
- 4. 完成后输出简短自报(改了哪些文件 / 跑了哪些测试 / 是否存疑),不自报 review.json(SillySpec 自己写)
226
- 5. 写测试时遵循下方「测试用例设计」规则:
227
-
228
- {{include: testcase-design}}
229
- \`\`\`
230
-
231
- ### 终态轮询 + 超时 kill lease(UB-6)
232
-
233
- dispatch 后在当前 step 内主动轮询,不阻塞等待:
234
-
235
- 1. **轮询 \`list_workers\`**:间隔默认 15s,可配 local.yaml \`dispatch.poll_interval_ms\`
236
- - 入参 \`mission_id\`;关注每个 worker 的 status(pending → running → completed/failed)
237
- 2. **per-worker 超时**:默认可配 local.yaml \`dispatch.worker_timeout_ms\`;超时 → 三步兜底:
238
- - a. **标记**:调 \`report_progress\` tool(worker_id + 超时说明)记录该 worker 超时
239
- - b. **kill lease 防双写**:调 \`report_progress\` tool(worker_id + \`kill: true\` + marker)终止 lease
240
- (等价 client.js \`killLease(workerId)\` 路径;SillyHub 当前无专用 kill tool,路径A 落地后升级为专用 kill)
241
- - c. **fallback Local 重派**:该 task 改用本机 Agent tool 重派(调 \`sillyspec dispatch hint\` 走 Local 模板)
242
- 3. **worker 终态(completed / failed / killed)** → 进入下方回收约定
243
-
244
- ${recycleRule}`
245
- }
1
+ /**
2
+ * SillyHub MCP 后端派发指令模板(task-dispatcher 抽象层)— task-04
3
+ *
4
+ * 定位(D-007):dispatcher **不是 JS 执行体**。SillyHub 的 MCP tool(create_mission /
5
+ * dispatch_worker / list_workers / report_progress)只有 agent 能调,CLI(Node)进程调不了。
6
+ * 所以本文件 = **派发指令模板生成器**:生成注入 execute prompt 的「派发指令文本」,告诉
7
+ * agent 如何经 SillyHub MCP tool 创建 mission / 派 worker / 轮询终态 / kill lease / 回收;
8
+ * 实际 MCP tool 调用由 agent 执行,本模块不调任何 tool,也不直接 import client.js。
9
+ *
10
+ * 路径 A 探测翻真(task-11,D-005 + R-04):`isPathASupported()` 读 probe.js 预热的
11
+ * tools/list 探测缓存——`dispatch_worker.inputSchema.properties` 含 `worktree_path` 与
12
+ * `worker_prompt` → true;任一缺失 / 未预热 / 探测失败 → false(保守回退 Local,**绝不硬试路径A**)。
13
+ * env 备选(spike-01 不通过时):`process.env.SILLYHUB_PATH_A==='1'` → true(手动标记 daemon 侧
14
+ * 已落地)。探测预热住 probe.js `probeSillyHub`(daemon 可达后 best-effort 调 `client.listTools()`),
15
+ * 本模块只缓存探测结果 + 同步读——保持 `isPathASupported()` sync 签名不改 strategy.js /
16
+ * execute.js `getDispatchMode` 调用点(探测 = async,读缓存 = sync,解耦)。
17
+ *
18
+ * 铁律:
19
+ * - converge_mission 不出现在任何指令文本(D-004,SillySpec 自己 apply);JSDoc 仅作开发者锚点。
20
+ * - worker 不 git commit,改动留 worktree 工作区(D-003@v2 第三处 UB-1,worker_prompt 覆写)。
21
+ * - 不直接调 client,只生成指令由 agent 执行 MCP tool 调用(探测由 probe.js 调 client,本模块仅读缓存)。
22
+ * - 路径 A 检测保守:不支持即回退,不硬试。
23
+ * - 兼容 Win/Linux/macOS:本文件无路径拼接 / 无平台分支,纯模板生成。
24
+ */
25
+
26
+ /**
27
+ * 路径 A 未落地时 strategy.js 拼到降级提示里的原因说明。
28
+ *
29
+ * 属指令文本(会注入 prompt),故不含 converge_mission 字面(D-004)。strategy 在探测到
30
+ * isPathASupported()=false 时把此串拼进降级提示,引导 agent 回退 Local。
31
+ */
32
+ export const PATH_A_DOWNGRADE_REASON =
33
+ '路径A 跨仓未落地:dispatch_worker 不支持 worktree_path / render_worker_prompt 仍硬编码 commit,本次派发回退 Local'
34
+
35
+ /**
36
+ * SillyHub 后端结果回收约定(worker 终态 → review.json,D-004 + UB-1)。
37
+ *
38
+ * 与 Local 后端回收同构(R-07 屏蔽后端差异):worker 终态后 SillySpec 主体对 worktree
39
+ * **工作区**做 git diff 写 review.json。差异在 worker 侧产出形态——SillyHub worker **绝不
40
+ * git commit**(路径A 第三处 UB-1,worker_prompt 已覆写覆盖 daemon 默认 commit 行为),改动
41
+ * 全留工作区;合并回主干由 SillySpec 自己 apply(worktree-apply 全门控保留),**不调 SillyHub
42
+ * 合并 tool**(D-004)。
43
+ *
44
+ * 抽成独立常量供 strategy.js 在拼接降级 / 回收段时复用,避免 Local 与 SillyHub 两份回收文案
45
+ * 重复维护。含 `<runId>` 占位符,使用方按实际 contract.runId 替换(见 renderSillyHubInstruction)。
46
+ */
47
+ export const SILLYHUB_RECYCLE_RULE = `### 回收约定(worker 终态 → review.json,SillySpec 自己 apply)
48
+
49
+ worker 终态后,**回收不依赖 worker git commit,也不调 SillyHub 合并 tool**:SillySpec 主体
50
+ (你,调度 agent)对 worktree **工作区**做 git diff,按既有 task-review 契约写 review.json;
51
+ 合并回主干由 SillySpec 自己 apply(worktree-apply / Review Gate / assess 全门控保留)。
52
+
53
+ 1. worker 终态(completed / failed / killed)后,读取该 task 在 worktree 工作区的 git diff
54
+ (task 派发 → 终态的工作区变更;worker 未 commit 故全在工作区,不依赖 worker 自报)
55
+ 2. 对照 plan.md 与 tasks/task-XX.md 检查实现(不信任 worker 自报,只看当前 task 的 diff)
56
+ 3. 写入 review.json:\`.runtime/execute-runs/<runId>/tasks/task-XX/review.json\`
57
+ 4. review.json 写入成功后,该 task 的派发回收完成;合并走 SillySpec apply,不调 SillyHub 合并 tool
58
+
59
+ review.json 复用既有 task-review 契约(必填字段:schemaVersion / task / base / head /
60
+ changedFiles / specVerdict / qualityVerdict / reviewerNotes / requiredEvidence),不发明
61
+ 新契约。回收屏蔽后端差异:无论 Local 还是 SillyHub worker,SillySpec 都自己 diff worktree
62
+ 工作区写 review.json,再自己 apply(D-004)。`
63
+
64
+ /**
65
+ * 路径A 探测结果缓存(task-11):probe.js `probeSillyHub` 预热 → `isPathASupported` 同步读。
66
+ *
67
+ * 保持 `isPathASupported` sync 签名(strategy.js / execute.js `getDispatchMode` 同步调,不改
68
+ * 调用点):探测是 async(probe.js 调 client.listTools),读缓存是 sync。未预热(probe 未跑 /
69
+ * 探测失败)→ 默认 false(保守 R-04,不硬试路径A)。模块级单例,进程内有效;测试用
70
+ * `clearPathAProbeCache` 隔离。
71
+ */
72
+ let _pathAProbe = { probed: false, supported: false };
73
+
74
+ /**
75
+ * 写路径A 探测结果(probe.js `probeSillyHub` 在 daemon 可达后预热调用)。
76
+ * @param {boolean} supported - dispatch_worker schema 含 worktree_path + worker_prompt
77
+ */
78
+ export function setPathAProbeResult(supported) {
79
+ _pathAProbe = { probed: true, supported: !!supported };
80
+ }
81
+
82
+ /**
83
+ * 清空路径A 探测缓存(测试隔离用;生产中进程重启 / 不需要 TTL,probe 每次可达都重预热带新值)。
84
+ */
85
+ export function clearPathAProbeCache() {
86
+ _pathAProbe = { probed: false, supported: false };
87
+ }
88
+
89
+ /**
90
+ * 路径 A 是否已落地(dispatch_worker 支持 caller worktree + worker_prompt 覆写)。
91
+ *
92
+ * task-11 翻真(D-005 + R-04):读 probe.js 预热的探测结果(probeSillyHub 经 `tools/list` 查
93
+ * `dispatch_worker.inputSchema.properties` 含 `worktree_path` 与 `worker_prompt`)。保持 sync
94
+ * 签名(strategy.js / execute.js `getDispatchMode` 同步调),探测预热在 probe 流程。
95
+ *
96
+ * 判定顺序(保守,**绝不硬试路径A**):
97
+ * 1. **env 强制开启**:`process.env.SILLYHUB_PATH_A==='1'` → true(spike-01 不通过 / tools/list
98
+ * 不可达时的备选,手动标记 daemon 侧路径A 已落地;优先级最高,让探测不可信时也能启用)
99
+ * 2. 否则读探测缓存:probe 预热且 dispatch_worker schema 全命中(worktree_path + worker_prompt)→ true
100
+ * 3. 未预热 / 探测失败 / schema 缺字段 → false(保守回退 Local,R-04)
101
+ *
102
+ * @returns {boolean} 路径A 可用 → true;否则 false(保守不硬试,R-04)
103
+ */
104
+ export function isPathASupported() {
105
+ // 1. env 强制开启(spike-01 备选):优先级最高,探测不可信 / 不可达时手动启用
106
+ if (process.env.SILLYHUB_PATH_A === '1') return true;
107
+ // 2. 读 probe 预热的 schema 探测缓存(未预热 → supported=false,保守 R-04 不硬试)
108
+ return _pathAProbe.supported;
109
+ }
110
+
111
+ /**
112
+ * @typedef {import('./local-agent.js').DispatchContract} DispatchContract
113
+ * @see design.md「接口定义」DispatchContract:brief / worktreePath / branch / allowedPaths /
114
+ * readOnly / modelHint / agentProfileHint / runId / missionId(与 task-03 Local 后端同构)
115
+ */
116
+
117
+ /**
118
+ * 渲染 SillyHub 后端派发指令文本(路径 A 落地后由 strategy.js 注入 execute prompt)。
119
+ *
120
+ * 纯模板生成器:不校验 contract 完整性、不调任何 MCP tool / client,只把 contract 字段拼成
121
+ * 可注入 execute prompt 的中文指令字符串。contract 缺字段时回退到醒目占位符(`<未提供 ...>`),
122
+ * 让 agent 在 prompt 里一眼看到派发前必须补齐的参数,而非静默产出错位指令。
123
+ *
124
+ * 指令含四要点(D-008 / D-003@v2 / UB-6 / D-004):
125
+ * 1. **一 Wave 一 mission**:agent 先调 create_mission(objective/change_id/budget_usd per Wave),
126
+ * Wave 内 task→worker 并行 dispatch,Wave 间 mission 串行
127
+ * 2. **dispatch_worker 参数**:mission_id/objective/worktree_path/branch/read_only/model/
128
+ * agent_profile_id/worker_prompt(覆写:worker 不 git commit)
129
+ * 3. **终态轮询**:agent 轮询 list_workers,per-worker 超时 → report_progress 标记 + kill lease
130
+ * 防双写 + fallback Local 重派
131
+ * 4. **回收约定**:worker 不 commit,SillySpec 自己 diff worktree 写 review.json,不调合并 tool
132
+ *
133
+ * @param {DispatchContract} [contract] - 派发契约(见 design.md DispatchContract 接口定义)
134
+ * @returns {string} 注入 execute prompt 的 SillyHub 后端派发指令文本
135
+ */
136
+ export function renderSillyHubInstruction(contract) {
137
+ const c = contract || {}
138
+ const worktreePath = c.worktreePath
139
+ const branch = c.branch
140
+ const allowedPaths = Array.isArray(c.allowedPaths) ? c.allowedPaths : null
141
+ const readOnly = !!c.readOnly
142
+ const brief = c.brief
143
+ const runId = c.runId
144
+ const modelHint = c.modelHint
145
+ const agentProfileHint = c.agentProfileHint
146
+ const missionId = c.missionId
147
+
148
+ // 回收约定:替换 <runId> 占位符;runId 缺失保留占位符让 agent 警觉
149
+ const recycleRule = SILLYHUB_RECYCLE_RULE.replaceAll(
150
+ '<runId>',
151
+ runId ? String(runId) : '<未提供 runId>'
152
+ )
153
+
154
+ // 各字段缺省醒目占位(派发前必须补齐的参数一眼可见)
155
+ const worktreeDisplay = worktreePath
156
+ ? String(worktreePath)
157
+ : '<未提供 contract.worktreePath — 派发前必须补齐,否则 worker cwd 错位>'
158
+ const branchDisplay = branch
159
+ ? String(branch)
160
+ : '<未提供 contract.branch — 派发前必须补齐 worktree 当前分支>'
161
+ const briefDisplay = brief
162
+ ? String(brief)
163
+ : '<未提供 contract.brief — 派发前必须补齐任务目标与蓝图路径>'
164
+ const missionDisplay = missionId
165
+ ? String(missionId)
166
+ : '<本 Wave mission_id — 先调 create_mission 拿到后填入>'
167
+
168
+ const allowedLine = allowedPaths && allowedPaths.length
169
+ ? `- 本次任务边界(allowedPaths):${allowedPaths.map(p => `\`${p}\``).join(' / ')}(SillySpec 侧 assess/apply 校验,SillyHub 不物化)`
170
+ : '- 本次任务边界(allowedPaths):未提供,按蓝图 tasks/task-XX.md 的 allowed_paths 为准(SillySpec 侧校验)'
171
+ const modelLine = modelHint
172
+ ? `- \`model\`:\`${modelHint}\`(来自 tasks.md [model:xxx],映射到 SillyHub model)`
173
+ : '- `model`:未提供(contract.modelHint 缺),沿用 daemon 默认模型'
174
+ const profileLine = agentProfileHint
175
+ ? `- \`agent_profile_id\`:\`${agentProfileHint}\`(来自 contract.agentProfileHint)`
176
+ : '- `agent_profile_id`:未提供(contract.agentProfileHint 缺),沿用 daemon 默认 profile'
177
+
178
+ const modeLine = readOnly
179
+ ? '**只读模式(read_only=true)**:worker 只读 / 审查 / 写 review.json,不改源码(注:第一波 execute task 用写模式,acceptance 强制 tier=self,R-03)'
180
+ : '**写模式(read_only=false)**:worker 按蓝图实现源码(TDD),但**绝不 git commit**(见下方 worker_prompt 覆写)'
181
+
182
+ return `### 派发后端:SillyHub MCP — 路径 A 落地后启用
183
+
184
+ 本指令用 **SillyHub MCP tool** 派 worker 执行任务(路径 A 落地后由 strategy.js 注入;当前
185
+ \`isPathASupported()\` 返回 false,strategy 不会调本指令,而是降级提示 + 回退 Local)。
186
+
187
+ **你的角色是调度者 + 审查者,不要自己写代码。** 实际 MCP tool 调用由你(调度 agent)执行:
188
+ 创建 mission → 派 worker → 轮询终态 → kill 异常 lease → 回收 diff。
189
+
190
+ ### 一 Wave 一 mission(D-008)
191
+
192
+ 1. **Wave 开始先建 mission**:调 \`create_mission\` tool 为本 Wave 创建独立 mission
193
+ - 参数:\`objective\`(本 Wave 目标)/ \`change_id\`(SillySpec change id)/ \`budget_usd\`(per Wave 预算)
194
+ - 返回 \`mission_id\`,记下供本 Wave 内所有 task→worker 复用:${missionDisplay}
195
+ 2. **Wave 内 task → worker 并行 dispatch**:同 Wave 多 task 各调一次 \`dispatch_worker\`,共用本 Wave 的 mission_id(保 Wave 内并行)
196
+ 3. **Wave 间 mission 串行**:上一 Wave 全 worker 终态 + 回收完成后,下一 Wave 起新 mission(不把全 Wave 塞一个 mission,否则失串行)
197
+
198
+ ### dispatch_worker 参数(每个 task 调一次)
199
+
200
+ 调 \`dispatch_worker\` tool,参数(snake_case 对齐 SillyHub schema;对照 client.js \`dispatchWorker\` 签名):
201
+ - \`mission_id\`:${missionDisplay}
202
+ - \`objective\`:${briefDisplay}
203
+ - \`worktree_path\`:\`${worktreeDisplay}\`(路径A:worker cwd = SillySpec worktree,D-002)
204
+ - \`branch\`:\`${branchDisplay}\`(路径A:worktree 当前分支)
205
+ - \`read_only\`:\`${readOnly}\`
206
+ ${modelLine}
207
+ ${profileLine}
208
+ - \`worker_prompt\`:**必须覆写**(见下方,覆盖 daemon 默认 render_worker_prompt 的 git commit 行为)
209
+
210
+ ${modeLine}
211
+ ${allowedLine}
212
+
213
+ ### worker_prompt 覆写(D-003@v2 第三处,UB-1 关键)
214
+
215
+ dispatch_worker 的 \`worker_prompt\` 参数必须传以下覆写文本(路径A 第三处:daemon 默认
216
+ \`render_worker_prompt\` 硬编码 \`git add -A && git commit\`,会污染 sillyspec/<change> 分支历史
217
+ + 撞 D-004;用 worker_prompt 覆写让 caller 控制 commit 行为):
218
+
219
+ \`\`\`
220
+ 你是 SillySpec execute 的 worker,在指定 worktree 干活。铁律:
221
+ 1. 按 objective + 蓝图(tasks/task-XX.md / design.md)实现任务(TDD:先读后写 → 写测试 → 写实现)
222
+ 2. **绝不执行 git add / git commit / git push**:改动留在工作区(staged 或 unstaged 都行),
223
+ 交 SillySpec 主体 git diff 回收(D-004)
224
+ 3. 不超出 allowedPaths,不碰其他 task 的文件
225
+ 4. 完成后输出简短自报(改了哪些文件 / 跑了哪些测试 / 是否存疑),不自报 review.json(SillySpec 自己写)
226
+ 5. 写测试时遵循下方「测试用例设计」规则:
227
+
228
+ {{include: testcase-design}}
229
+ \`\`\`
230
+
231
+ ### 终态轮询 + 超时 kill lease(UB-6)
232
+
233
+ dispatch 后在当前 step 内主动轮询,不阻塞等待:
234
+
235
+ 1. **轮询 \`list_workers\`**:间隔默认 15s,可配 local.yaml \`dispatch.poll_interval_ms\`
236
+ - 入参 \`mission_id\`;关注每个 worker 的 status(pending → running → completed/failed)
237
+ 2. **per-worker 超时**:默认可配 local.yaml \`dispatch.worker_timeout_ms\`;超时 → 三步兜底:
238
+ - a. **标记**:调 \`report_progress\` tool(worker_id + 超时说明)记录该 worker 超时
239
+ - b. **kill lease 防双写**:调 \`report_progress\` tool(worker_id + \`kill: true\` + marker)终止 lease
240
+ (等价 client.js \`killLease(workerId)\` 路径;SillyHub 当前无专用 kill tool,路径A 落地后升级为专用 kill)
241
+ - c. **fallback Local 重派**:该 task 改用本机 Agent tool 重派(调 \`sillyspec dispatch hint\` 走 Local 模板)
242
+ 3. **worker 终态(completed / failed / killed)** → 进入下方回收约定
243
+
244
+ ${recycleRule}`
245
+ }