dsh-punky-swarm 0.3.6

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/CHANGELOG.md +120 -0
  2. package/LICENSE +661 -0
  3. package/README.en.md +281 -0
  4. package/README.md +281 -0
  5. package/cordis.patch.yml +59 -0
  6. package/lib/acps/certs.js +310 -0
  7. package/lib/acps/discovery-client.js +283 -0
  8. package/lib/acps/registry-client.js +527 -0
  9. package/lib/acps/server.js +302 -0
  10. package/lib/aip/agent-descriptor.js +170 -0
  11. package/lib/aip/identity.js +469 -0
  12. package/lib/aip/tool-descriptor.js +93 -0
  13. package/lib/api.js +266 -0
  14. package/lib/artifact-types.js +48 -0
  15. package/lib/assembly/audit-blind-review.js +170 -0
  16. package/lib/assembly/schema.js +253 -0
  17. package/lib/assembly.js +41 -0
  18. package/lib/assets.js +103 -0
  19. package/lib/bridge/trajectory.js +202 -0
  20. package/lib/client.js +704 -0
  21. package/lib/comms/acps-bridge.js +219 -0
  22. package/lib/comms/aip-format.js +141 -0
  23. package/lib/comms/budget.js +124 -0
  24. package/lib/comms/mailbox.js +221 -0
  25. package/lib/comms/topic-runtime.js +76 -0
  26. package/lib/comms/topic.js +115 -0
  27. package/lib/discovery/filter.js +199 -0
  28. package/lib/discovery/schema.js +157 -0
  29. package/lib/discovery/service.js +251 -0
  30. package/lib/hot/config-watch.js +252 -0
  31. package/lib/index.js +433 -0
  32. package/lib/lock.js +69 -0
  33. package/lib/panel/batch-detail.js +163 -0
  34. package/lib/panel/batch-list.js +84 -0
  35. package/lib/panel/locales.js +93 -0
  36. package/lib/panel/main.js +270 -0
  37. package/lib/panel/stream.js +348 -0
  38. package/lib/panel/theme.js +103 -0
  39. package/lib/panel/widgets.js +63 -0
  40. package/lib/schema.d.ts +192 -0
  41. package/lib/schema.js +259 -0
  42. package/lib/schema.ts +398 -0
  43. package/lib/state/archive.js +174 -0
  44. package/lib/state/command-exec.js +148 -0
  45. package/lib/state/constants.js +37 -0
  46. package/lib/state/corrupt-registry.js +103 -0
  47. package/lib/state/event-types.js +65 -0
  48. package/lib/state/gates.d.ts +226 -0
  49. package/lib/state/gates.js +428 -0
  50. package/lib/state/gates.ts +348 -0
  51. package/lib/state/machine-rules.d.ts +14 -0
  52. package/lib/state/machine-rules.js +76 -0
  53. package/lib/state/machine-rules.ts +88 -0
  54. package/lib/state/machine.js +68 -0
  55. package/lib/state/resume.js +201 -0
  56. package/lib/state/schema-v3.d.ts +10 -0
  57. package/lib/state/schema-v3.js +57 -0
  58. package/lib/state/schema-v3.ts +80 -0
  59. package/lib/state/store.js +607 -0
  60. package/lib/state/task-utils.js +40 -0
  61. package/lib/tools/core.js +323 -0
  62. package/lib/tools/git-utils.js +44 -0
  63. package/lib/tools/lane-tools.js +455 -0
  64. package/lib/tools/log-tools.js +173 -0
  65. package/lib/tools/mailbox-tools.js +121 -0
  66. package/lib/tools/merge-agent.js +127 -0
  67. package/lib/tools/register.js +85 -0
  68. package/lib/tools/shared.js +32 -0
  69. package/lib/types/contracts.d.ts +277 -0
  70. package/lib/types/contracts.js +17 -0
  71. package/lib/types/contracts.ts +279 -0
  72. package/lib/verify/evidence.js +249 -0
  73. package/lib/verify/gate.js +161 -0
  74. package/lib/verify/mount.js +48 -0
  75. package/lib/verify/selector.js +101 -0
  76. package/lib/watch/lane-heartbeat.js +333 -0
  77. package/lib/wave-plan.d.ts +48 -0
  78. package/lib/wave-plan.js +473 -0
  79. package/lib/wave-plan.ts +465 -0
  80. package/package.json +71 -0
  81. package/presets/jiufeng/NOTICE +5 -0
  82. package/presets/jiufeng/agent.cordis.yml +275 -0
  83. package/presets/jiufeng/preset.yml +3 -0
  84. package/skills/jiufeng-team/SKILL.md +214 -0
  85. package/skills/jiufeng-team/references/constitution.md +62 -0
  86. package/skills/jiufeng-team/references/roles/coder.md +17 -0
  87. package/skills/jiufeng-team/references/roles/coordinator.md +13 -0
  88. package/skills/jiufeng-team/references/roles/designer.md +14 -0
  89. package/skills/jiufeng-team/references/roles/doc-manager.md +13 -0
  90. package/skills/jiufeng-team/references/roles/manager.md +22 -0
  91. package/skills/jiufeng-team/references/roles/reviewer.md +19 -0
  92. package/skills/jiufeng-team/references/roles/supervisor.md +13 -0
  93. package/skills/jiufeng-team/references/roles/tester.md +13 -0
  94. package/skills/jiufeng-team/references/templates/call-chain-matrix-template.md +121 -0
  95. package/skills/jiufeng-team/references/templates/endpoint-behavior-template.md +60 -0
  96. package/skills/jiufeng-team/references/templates/gap-list.json +33 -0
  97. package/skills/jiufeng-team/references/templates/leader-decision-pack.md +92 -0
  98. package/skills/jiufeng-team/references/templates/plan-template.md +61 -0
  99. package/skills/jiufeng-team/references/templates/success-pattern-seeds.md +126 -0
  100. package/skills/jiufeng-team/references/workflow.md +98 -0
  101. package/skills/jiufeng-team/scripts/check-v3-density.mjs +98 -0
package/lib/api.js ADDED
@@ -0,0 +1,266 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // dsh-punky-swarm 只读治理 API:batches / batch / mailbox / locks / stream(全部按 session 隔离)
19
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import * as mailbox from './comms/mailbox.js';
22
+ import { createStreamHub } from './panel/stream.js';
23
+ // P1 批 R-07 排除项(承接归 panel 批):事件读端字面量收敛——EVT 常量源 lib/state/event-types.js(P2-07 单点)
24
+ import * as EVT from './state/event-types.js';
25
+
26
+ function sendJson(res, status, data) {
27
+ const body = JSON.stringify(data);
28
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
29
+ res.end(body);
30
+ }
31
+
32
+ function q(url) {
33
+ return Object.fromEntries(new URL(url, 'http://localhost').searchParams.entries());
34
+ }
35
+
36
+ export function createApi(ctx, deps) {
37
+ const { store, root, catalog, agentCatalog, aipFormat, discovery } = deps;
38
+ const disposers = [];
39
+ const register = (route) => disposers.push(ctx.webServer.register(route));
40
+
41
+ // R3 SSE hub(设计 §3.3.3):随 webServer 挂载(ADR-4:无独立配置键;降级回轮询即运行时自适配开关)。
42
+ // 装配点(index.js 层,exec-a 批②)预留时经 deps.panelStream 注入复用(含 topic 触发源 attachTopic 接线);
43
+ // 缺省自建(fs.watch 单通道先行交付——契约移交点语义),自建者负责 dispose。
44
+ const panelStream = deps.panelStream || createStreamHub({ root, logger: ctx.logger });
45
+ if (!deps.panelStream) disposers.push(() => { try { panelStream.dispose(); } catch {} });
46
+
47
+ register({
48
+ kind: 'prefix',
49
+ path: '/api/dsh-punky-swarm',
50
+ handler(req, res) { sendJson(res, 404, { error: 'not-found' }); },
51
+ });
52
+
53
+ register({
54
+ kind: 'exact',
55
+ path: '/api/dsh-punky-swarm/batches',
56
+ handler(req, res) {
57
+ try {
58
+ const { session } = q(req.url);
59
+ if (!session) return sendJson(res, 400, { error: 'session required' });
60
+ const batches = store.listBatches(session).map((id) => {
61
+ const b = store.readBatch(session, id);
62
+ return { batchId: id, session, phase: b.phase, lanes: b.lanes, settled: store.batchSettled(b), eventCount: b.events.length };
63
+ });
64
+ sendJson(res, 200, { session, batches });
65
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
66
+ },
67
+ });
68
+
69
+ register({
70
+ kind: 'exact',
71
+ path: '/api/dsh-punky-swarm/sessions',
72
+ handler(req, res) {
73
+ try {
74
+ // 全部有批次的 session 概览(含每 session 批次计数)
75
+ const sessions = store.listSessions().map((s) => ({ sessionId: s, batchCount: store.listBatches(s).length }));
76
+ sendJson(res, 200, { sessions });
77
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
78
+ },
79
+ });
80
+
81
+ register({
82
+ kind: 'exact',
83
+ path: '/api/dsh-punky-swarm/batch',
84
+ handler(req, res) {
85
+ try {
86
+ const { batchId, session } = q(req.url);
87
+ if (!batchId) return sendJson(res, 400, { error: 'batchId required' });
88
+ if (!session) return sendJson(res, 400, { error: 'session required' });
89
+ const b = store.readBatch(session, batchId);
90
+ if (!b) return sendJson(res, 404, { error: 'batch-not-found' });
91
+ // 每 lane attempt(review->running 返工计数)与升级标记(>=3)由事件推导
92
+ const laneAttempts = {};
93
+ const upgrades = {};
94
+ for (const e of b.events) {
95
+ if (e.type === EVT.EVT_MEMBER_SETTLED && e.from === 'review' && e.to === 'running') {
96
+ laneAttempts[e.lane] = (laneAttempts[e.lane] ?? 0) + 1;
97
+ }
98
+ }
99
+ for (const lane of Object.keys(b.lanes)) {
100
+ upgrades[lane] = (laneAttempts[lane] ?? 0) >= 3 && (b.lanes[lane] === 'review' || b.lanes[lane] === 'failed');
101
+ }
102
+ sendJson(res, 200, {
103
+ batchId: b.batchId, session, phase: b.phase, concurrency: b.concurrency,
104
+ lanes: b.lanes, wavePlan: b.wavePlan,
105
+ eventCount: b.events.length, recentEvents: b.events.slice(-20),
106
+ settled: store.batchSettled(b),
107
+ autoReleaseable: store.batchAutoReleaseable(b),
108
+ viewSettled: (b.phase === 'complete' || b.phase === 'aborted') ? true : store.batchSettled(b),
109
+ laneAttempts,
110
+ upgrades,
111
+ lanesGate: Object.fromEntries(Object.keys(b.lanes).map((l) => [l, store.gateStatus(session, batchId, l)])),
112
+ // P6 接线(exec-format-wire):装配注入 aipFormat 时附 ACPs Session 投影(纯函数,不改存储;缺省不附 → 既有响应不变)
113
+ ...(aipFormat ? { aipSession: aipFormat.toAipSession(b) } : {}),
114
+ });
115
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
116
+ },
117
+ });
118
+
119
+ register({
120
+ kind: 'exact',
121
+ path: '/api/dsh-punky-swarm/mailbox',
122
+ handler(req, res) {
123
+ try {
124
+ const { batchId, box, lane, session } = q(req.url);
125
+ if (!batchId || !box) return sendJson(res, 400, { error: 'batchId+box required' });
126
+ if (!session) return sendJson(res, 400, { error: 'session required' });
127
+ // P2-08:box 枚举校验——非法 box 返回 400(含枚举提示)而非透传进 mailbox.readUnacked 抛错兜成 500
128
+ const BOXES = ['inbox', 'outbox', 'broadcast'];
129
+ if (!BOXES.includes(box)) {
130
+ return sendJson(res, 400, { error: 'invalid box: ' + box + ' (allowed: ' + BOXES.join(' | ') + ')' });
131
+ }
132
+ // 可选加固:outbox 分支 lane 必填(readUnacked 按 lane 读 outbox,缺 lane 属调用错误)
133
+ if (box === 'outbox' && !lane) return sendJson(res, 400, { error: 'lane required for outbox' });
134
+ const b = box === 'outbox' ? { type: 'outbox', lane } : { type: box };
135
+ const items = mailbox.readUnacked(join(root, 'sessions', session, 'mailbox', batchId), b);
136
+ // P6 接线(exec-format-wire):装配注入 aipFormat 时逐条附 ACPs Message 投影(纯函数投影,不改 mailbox 存储与 ack 语义)
137
+ sendJson(res, 200, aipFormat ? { items: items.map((it) => ({ ...it, aip: aipFormat.toAipMessage(it) })) } : { items });
138
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
139
+ },
140
+ });
141
+
142
+ register({
143
+ kind: 'exact',
144
+ path: '/api/dsh-punky-swarm/locks',
145
+ handler(req, res) {
146
+ try {
147
+ const { session } = q(req.url);
148
+ if (!session) return sendJson(res, 400, { error: 'session required' });
149
+ const locksDir = join(root, 'sessions', session, '.locks');
150
+ const locks = existsSync(locksDir) ? readdirSync(locksDir).map((f) => {
151
+ try { return { lock: f, ...JSON.parse(readFileSync(join(locksDir, f), 'utf8')) }; }
152
+ catch { return { lock: f }; }
153
+ }) : [];
154
+ sendJson(res, 200, { session, locks });
155
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
156
+ },
157
+ });
158
+
159
+ // 国标 AIP 工具列表同步(方案 A:只读 API 端点):catalog 非空(缺省默认开启)时注册 /tools;
160
+ // 仅显式 aip.enabled=false 时 catalog 为 null,不注册该路由(保持既有 6 路由契约)。
161
+ // catalog 由 register.js 经 readCapability 默认合并口径提供——本条件与 enabled 联动,此处零逻辑改动。
162
+ if (catalog) {
163
+ register({
164
+ kind: 'exact',
165
+ path: '/api/dsh-punky-swarm/tools',
166
+ handler(req, res) {
167
+ try {
168
+ const { name } = q(req.url);
169
+ const tools = name ? catalog.list().filter((d) => d.name === name) : catalog.list();
170
+ sendJson(res, 200, { count: tools.length, tools, generatedAt: catalog.generatedAt });
171
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
172
+ },
173
+ });
174
+ }
175
+
176
+ // P4 ACS 智能体描述目录:enabled=true 时 agentCatalog 非空,注册 /agents;
177
+ // 端点输出 ACS 字段集(AgentCapabilitySpec,逐字字段见 lib/aip/agent-descriptor.js);只读、无参。
178
+ // enabled=false(默认)时 agentCatalog 为 null,不注册该路由(既有路由契约不变)。
179
+ if (agentCatalog) {
180
+ register({
181
+ kind: 'exact',
182
+ path: '/api/dsh-punky-swarm/agents',
183
+ handler(req, res) {
184
+ try {
185
+ const agents = agentCatalog.list();
186
+ sendJson(res, 200, { count: agents.length, agents, generatedAt: agentCatalog.generatedAt });
187
+ } catch (e) { sendJson(res, 500, { error: String(e.message) }); }
188
+ },
189
+ });
190
+ }
191
+ // 国标 P5 发现服务(ADP 语义):discovery 服务实例注入时注册
192
+ // POST /api/dsh-punky-swarm/discover — 统一发现查询(DiscoveryRequest → DiscoveryResponse)
193
+ // GET /.well-known/aip — 发现服务预置信息(地址/协议版本/能力概要)
194
+ if (discovery) {
195
+ register({
196
+ kind: 'exact',
197
+ path: '/api/dsh-punky-swarm/discover',
198
+ handler(req, res) {
199
+ return readJsonBody(req).then((body) => {
200
+ const resp = discovery.discover(body);
201
+ // 响应码映射:ADP 错误(result 缺省、error 存在)→ 400(客户端 4xxxx)/ 500(服务端 5xxxx);成功 → 200
202
+ const status = resp.error
203
+ ? (String(resp.error.code).startsWith('5') ? 500 : 400)
204
+ : 200;
205
+ sendJson(res, status, resp);
206
+ }).catch((e) => sendJson(res, 500, { error: { code: 50001, message: 'InternalError', data: String(e.message) } }));
207
+ },
208
+ });
209
+ register({
210
+ kind: 'exact',
211
+ path: '/.well-known/aip',
212
+ handler(req, res) {
213
+ try {
214
+ sendJson(res, 200, discovery.wellKnown());
215
+ } catch (e) { sendJson(res, 500, { error: { code: 50001, message: 'InternalError', data: String(e.message) } }); }
216
+ },
217
+ });
218
+ }
219
+
220
+ // R3 SSE 端点(设计 §3.3.3,纯新增路由——既有 /api 路由一字不动):
221
+ // GET /api/dsh-punky-swarm/stream?session=<sid>[&batchId=<bid>]
222
+ // SSE 帧协议:event: batch|mailbox|heartbeat + data:<JSON>;注释心跳帧每 10s(hub 内维护)。
223
+ // 推送只发轻量摘要 {sessionId,batchId,eventCount,updatedAt},客户端回拉既有只读 API 取全量(ADR-5)。
224
+ register({
225
+ kind: 'exact',
226
+ path: '/api/dsh-punky-swarm/stream',
227
+ handler(req, res) {
228
+ try {
229
+ const { session, batchId } = q(req.url);
230
+ if (!session) return sendJson(res, 400, { error: 'session required' });
231
+ const st = panelStream.subscribe(session, batchId || null, res);
232
+ if (!st.ok) {
233
+ // 会话级连接数上限(≤8)→ 503;其余(缺 session/handshake 失败)→ 400
234
+ const status = st.reason === 'limit' ? 503 : 400;
235
+ try { sendJson(res, status, { error: st.reason === 'limit' ? 'too many stream connections for session' : st.reason }); } catch {}
236
+ return;
237
+ }
238
+ } catch (e) {
239
+ try { res.end(); } catch {}
240
+ }
241
+ },
242
+ });
243
+
244
+ return { dispose() { for (const d of disposers) { try { d?.(); } catch {} } } };
245
+ }
246
+
247
+ // 读取请求体(POST JSON):兼容 webServer 注入的 node req(流式)与测试直调形态(req.body 预置)
248
+ function readJsonBody(req) {
249
+ if (req && typeof req.body === 'string') {
250
+ return Promise.resolve(req.body ? JSON.parse(req.body) : {});
251
+ }
252
+ if (req && req.body && typeof req.body === 'object') {
253
+ return Promise.resolve(req.body);
254
+ }
255
+ if (req && typeof req.on === 'function') {
256
+ return new Promise((resolve, reject) => {
257
+ let data = '';
258
+ req.on('data', (chunk) => { data += chunk; });
259
+ req.on('end', () => {
260
+ try { resolve(data ? JSON.parse(data) : {}); } catch (e) { reject(e); }
261
+ });
262
+ req.on('error', reject);
263
+ });
264
+ }
265
+ return Promise.resolve({});
266
+ }
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 通用产物类型注册表(2026-08-19,Tier3 增量)
19
+ // 定位:通用任务治理模式——登记产物类型 → 层/目录前缀的约定,供校验与查询;
20
+ // 不绑定任何团队模板(jiufeng 四件套只是使用者,产物内部格式归模板层)。
21
+ // 三层目录约定:plan/(任务层)、exec/(执行层)、audit/(审计层),与 wave-plan 路径契约一致。
22
+
23
+ export const ARTIFACT_TYPES = [
24
+ { type: 'plan', dir: 'plan/', layer: 'plan', desc: '任务层产物(排期/模块清单/摸底)' },
25
+ { type: 'spec', dir: 'plan/', layer: 'plan', desc: '执行规范(模板层定义内部结构)' },
26
+ { type: 'taskTree', dir: 'plan/', layer: 'plan', desc: '任务树(细拆)' },
27
+ { type: 'survey', dir: 'plan/', layer: 'plan', desc: '代码摸底报告(现状/入口/依赖/风险)' },
28
+ { type: 'code', dir: 'exec/', layer: 'exec', desc: '代码/实现产物' },
29
+ { type: 'testReport', dir: 'exec/', layer: 'exec', desc: '测试报告' },
30
+ { type: 'review', dir: 'audit/', layer: 'audit', desc: '审查证据' },
31
+ { type: 'gapList', dir: 'audit/', layer: 'audit', desc: '差距清单' },
32
+ { type: 'acceptance', dir: 'audit/', layer: 'audit', desc: '验收报告' },
33
+ { type: 'retrospective', dir: 'audit/', layer: 'audit', desc: '复盘报告(记忆沉淀输入,记忆工具开放语义)' },
34
+ ];
35
+
36
+ // 相对产物路径 → 类型名(按目录前缀匹配;绝对路径返回 null)
37
+ export function artifactTypeOf(relPath) {
38
+ if (typeof relPath !== 'string') return null;
39
+ for (const t of ARTIFACT_TYPES) {
40
+ if (relPath.startsWith(t.dir)) return t.type;
41
+ }
42
+ return null;
43
+ }
44
+
45
+ // 层 → 该层注册的产物类型
46
+ export function typesOfLayer(layer) {
47
+ return ARTIFACT_TYPES.filter((t) => t.layer === layer).map((t) => t.type);
48
+ }
@@ -0,0 +1,170 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 盲审编排装配数据(review-workflow 借鉴)
19
+ // 纯数据 + 辅助函数,零依赖(被 assembly-schema.js 断言扩展视图引用,独立可加载):
20
+ // - BLIND_REVIEW_ROLES / BLIND_REVIEW_SKILLS:audit-panelist/audit-aggregate/audit-critic 三角色映射
21
+ // - BLIND_REVIEW_TEMPLATES:六任务契约模板(bundle/panelist/aggregate/critic/checklist/config,键名固定供断言)
22
+ // - applyAssemblyExtensions(assembly, extensions):装配扩展合并(默认关——blindReview 未启用时原样返回,base 一字不改)
23
+ // - buildAuditLaneSpec({panelistCount, deps}):Leader/Manager 建批用——生成盲审子 lane 任务包骨架
24
+ // (consume/produce 契约 + 模板引用;不预写实现,0e 不越权;可直接喂 buildWavePlan 走 wavePlan 固定 DAG)
25
+ // 范围红线:零引擎改动(wave-plan.js / gates.js / store.js 等一律不碰)——盲审 = 装配数据 + 既有工具编排。
26
+
27
+ // ── 三角色映射(layer=audit,skills 手册前缀;Leader 显式声明或经 applyAssemblyExtensions 合并补全)──
28
+ export const BLIND_REVIEW_ROLES = ['audit-panelist', 'audit-aggregate', 'audit-critic'];
29
+
30
+ export const BLIND_REVIEW_SKILLS = {
31
+ 'audit-panelist': ['report-blind-audit', 'code-review-guideline'], // 事实核验 + 对抗式打分(rubric/checklist)
32
+ 'audit-aggregate': ['report-blind-audit'], // 锚定一致性 + Δ 分级裁决 + 加权聚合
33
+ 'audit-critic': ['report-blind-audit'], // 独立复核 + 三态决议(endorse/challenge/block)
34
+ };
35
+
36
+ // ── 叠加顺序(verify 自动证据 → 盲审可选 → needHuman 人工闸;verify blocked 直接升级人工不进入盲审)──
37
+ // 文档化常量:Leader/Manager 建批参照;三原则 = ① verify 内容证据底座(先跑、自动)② 盲审质量评审编排(后跑、可选)
38
+ // ③ needHuman 人工闸(最后、引擎强制 gates.js checkNeedHumanGate)。任一层的 produce 都进 Tier3 门禁(缺则拒 merged)。
39
+ export const BLIND_REVIEW_ORDER = Object.freeze({
40
+ steps: ['verify 自动证据裁决(先:advisory 记录 / enforce 拦截;产物 audit/verify-verdict.md)',
41
+ '盲审编排(可选:blindReview.enabled=true 时 panelist×N → aggregate → critic)',
42
+ 'needHuman 人工闸(后:引擎强制,merged 需 note 含 human:<裁决人>:<时间>:<结论> 证据)'],
43
+ verifyBlocked: 'verify blocked(EVIDENCE_UNREADABLE/TRUNCATED)→ 直接升级人工(conflict/needHuman),不进入盲审——不可信证据不做评审',
44
+ needHumanOwner: '盲审 lane 的 needHuman 声明落 aggregate 产物——人工闸裁决对象=聚合结论,非单 panelist',
45
+ });
46
+
47
+ // ── 六任务契约模板(键名固定,供 assertAssemblyCompleteness 扩展视图与 Leader/Manager 建批取用)──
48
+ const T_BUNDLE = `材料包准备(bundle,防评价污染 M6):
49
+ - 事实摘录段:仅摘录 exec 产物事实,每条标注出处(file:line);锚定对照声明前置(对照同 X 分锚定);
50
+ - 禁评价词(好/差/优雅/低劣等主观词一律禁止——事实层与评价层分离是盲审有效性关键);
51
+ - 材料完整性按 checklist 模板分级:阻塞缺失→中止;非阻塞→备注 + 对应维度扣分;用户确认降级为可选条款;
52
+ - 叠加顺序:先消费 verify 裁决报告(audit/verify-verdict.md)——verify 自动证据先行,盲审在其上做质量评审;
53
+ verify blocked(EVIDENCE_UNREADABLE/TRUNCATED)→ 直接升级人工,不进入盲审(不可信证据不做评审)。`;
54
+
55
+ const T_PANELIST = `独立盲审打分(panelist):
56
+ - 独立 subagent 互不可见(隔离盲审,各评各的);按 rubric 逐维度评分 + 理由 + 低分修改建议;
57
+ - 锚定对照声明必填(对照同 X 分锚定——aggregate 阶段 1 依赖);
58
+ - 失败重试 ≤3 次,仍失败标 DEGRADED(映射 skipped/低权重,不单评顶替);
59
+ - 产物落盘 audit/panelist-<i>.md(Tier3 门禁:缺 produce 拒 merged,替代文本 checkpoint)。`;
60
+
61
+ const T_AGGREGATE = `聚合裁决(aggregate,锚定 + Δ 两阶段,M2):
62
+ - 阶段 1 锚定一致性:各 panelist 锚定对照匹配同 X 分 → 进阶段 2;锚定分歧 → Chair 裁决(引用事实依据);
63
+ - 阶段 2 Δ 分级:Δ=0 直接取;Δ=1 默认取低(可依理据采纳高);Δ=2 Chair 裁决;Δ≥3 补理据重评,仍 ≥3 升级人工;
64
+ - 加权总分手算验证(Checkpoint 4);聚合报告落盘 audit/aggregate.md;
65
+ - needHuman 声明落本产物(独立行 needHuman: true)——人工闸裁决对象=聚合结论,非单 panelist。`;
66
+
67
+ const T_CRITIC = `独立复核(critic,三态决议 M3):
68
+ - 独立 session(不复用聚合上下文,避免偏见);输入 bundle + 全部 panelist 评分 + 聚合报告 + rubric;
69
+ - 流程合规检查 + 逐维度理据挑战 + 跨维度一致性检查;
70
+ - 三态决议:endorse(通过→merged)/ challenge(Chair 逐条响应→conflict/重审)/ block(退回聚合,不允许跳过);
71
+ - 产物落盘 audit/critic.md(audit produce,Tier3 缺则拒 merged);顺序约束:critic 必须等 aggregate 完成后启动。`;
72
+
73
+ const T_CHECKLIST = `材料完整性分级(checklist,M4):
74
+ - 阻塞项缺失 → 中止(Tier3 硬门禁为默认,引擎强制不后退到自觉);
75
+ - 非阻塞项缺失 → 备注 + 对应维度扣分;
76
+ - 用户确认降级为可选条款(audit 层检查通过但标注降级)。`;
77
+
78
+ const T_CONFIG = {
79
+ panelistCount: 2, // 评委数默认 2(关键 lane 才启用、默认单审,防流程膨胀)
80
+ threshold: 3.5, // 评分阈值(如 rubric 均分 ≥3.5 为通过线,Leader/Manager 可按项目覆写)
81
+ criticRequired: true, // critic 默认必跑(三态决议兜底;显式 false 可跳过,不建议)
82
+ note: 'config 为建批默认值;Leader/Manager 经 buildAuditLaneSpec 覆写(panelistCount/threshold/criticRequired)',
83
+ };
84
+
85
+ export const BLIND_REVIEW_TEMPLATES = Object.freeze({
86
+ bundle: T_BUNDLE,
87
+ panelist: T_PANELIST,
88
+ aggregate: T_AGGREGATE,
89
+ critic: T_CRITIC,
90
+ checklist: T_CHECKLIST,
91
+ config: T_CONFIG,
92
+ });
93
+
94
+ // ── 装配扩展合并(extensions.blindReview 默认关)──
95
+ // blindReview.enabled !== true → 原样返回(base DEFAULT_ASSEMBLY 一字不改 → buildAgentDescriptors 输出不变,兼容);
96
+ // enabled === true → 返回合并副本(三角色入 layers.audit.roles + skills,其他层/角色共享引用,base 不被 mutate)。
97
+ export function applyAssemblyExtensions(assembly, extensions) {
98
+ const br = (extensions && extensions.blindReview) || (assembly && assembly.extensions && assembly.extensions.blindReview);
99
+ if (!br || br.enabled !== true) return assembly; // 默认关:零变化
100
+ if (!assembly || typeof assembly !== 'object' || !assembly.layers || !assembly.layers.audit) return assembly;
101
+ const out = {
102
+ ...assembly,
103
+ layers: {
104
+ ...assembly.layers,
105
+ audit: {
106
+ ...assembly.layers.audit,
107
+ roles: [...(assembly.layers.audit.roles ?? [])],
108
+ skills: { ...(assembly.layers.audit.skills ?? {}) },
109
+ },
110
+ },
111
+ };
112
+ for (const role of BLIND_REVIEW_ROLES) {
113
+ if (!out.layers.audit.roles.includes(role)) out.layers.audit.roles.push(role);
114
+ out.layers.audit.skills[role] = BLIND_REVIEW_SKILLS[role];
115
+ }
116
+ return out;
117
+ }
118
+
119
+ // ── 盲审子 lane 任务包骨架(Leader/Manager 建批用)──
120
+ // 返回 { config, templates, tasks }:tasks 可直接喂 buildWavePlan(固定 DAG:panelist×N → aggregate → critic;
121
+ // aggregate deps=全部 panelist、critic deps=aggregate);consume/produce 契约 + 模板引用齐备,不预写实现(0e)。
122
+ // deps = 外部依赖路径数组(如 exec 产物 + audit/verify-verdict.md),作为 panelist/aggregate/critic 的共享 consume。
123
+ export function buildAuditLaneSpec({ panelistCount, deps = [], threshold, criticRequired } = {}) {
124
+ const n = Number.isInteger(panelistCount) && panelistCount >= 1 ? panelistCount : T_CONFIG.panelistCount;
125
+ const depList = Array.isArray(deps) ? deps : [];
126
+ const panelists = [];
127
+ for (let i = 1; i <= n; i++) {
128
+ panelists.push({
129
+ id: 'audit-panelist-' + i,
130
+ layer: 'audit',
131
+ role: 'audit-panelist',
132
+ deps: [],
133
+ consume: [...depList],
134
+ produce: ['audit/panelist-' + i + '.md'],
135
+ template: 'panelist',
136
+ cmd: '盲审打分(模板 panelist):rubric 逐维度评分 + 理由 + 低分建议 + 锚定对照声明;产物 audit/panelist-' + i + '.md',
137
+ });
138
+ }
139
+ const panelistIds = panelists.map((p) => p.id);
140
+ const panelistProduces = panelists.map((p) => p.produce[0]);
141
+ const aggregate = {
142
+ id: 'audit-aggregate',
143
+ layer: 'audit',
144
+ role: 'audit-aggregate',
145
+ deps: [...panelistIds],
146
+ consume: [...depList, ...panelistProduces],
147
+ produce: ['audit/aggregate.md'],
148
+ template: 'aggregate',
149
+ cmd: '聚合裁决(模板 aggregate):锚定一致性 + Δ 分级裁决 + 加权聚合 + needHuman 声明;产物 audit/aggregate.md',
150
+ };
151
+ const critic = {
152
+ id: 'audit-critic',
153
+ layer: 'audit',
154
+ role: 'audit-critic',
155
+ deps: ['audit-aggregate'],
156
+ consume: [...depList, ...panelistProduces, 'audit/aggregate.md'],
157
+ produce: ['audit/critic.md'],
158
+ template: 'critic',
159
+ cmd: '独立复核(模板 critic):流程合规 + 逐维度理据挑战 + 三态决议 endorse/challenge/block;产物 audit/critic.md',
160
+ };
161
+ return {
162
+ config: {
163
+ panelistCount: n,
164
+ threshold: threshold ?? T_CONFIG.threshold,
165
+ criticRequired: criticRequired ?? T_CONFIG.criticRequired,
166
+ },
167
+ templates: BLIND_REVIEW_TEMPLATES,
168
+ tasks: [...panelists, aggregate, critic],
169
+ };
170
+ }