dsh-punky-swarm 0.5.1 → 0.5.2
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 +34 -1
- package/cordis.patch.yml +10 -10
- package/docs/governance-boundaries.md +1 -1
- package/lib/acps/certs.js +0 -4
- package/lib/acps/registry-client.js +0 -4
- package/lib/acps/server.js +2 -6
- package/lib/api.js +2 -6
- package/lib/assembly/audit-blind-review.js +6 -1
- package/lib/assembly/defaults.js +53 -0
- package/lib/assembly/flows.js +2 -5
- package/lib/assembly.js +4 -34
- package/lib/auditlog/sink.js +0 -4
- package/lib/comms/acps-bridge.js +2 -6
- package/lib/comms/mailbox-paths.js +25 -0
- package/lib/comms/topic.js +3 -7
- package/lib/engine/arch-context.d.ts +39 -0
- package/lib/engine/arch-context.js +133 -0
- package/lib/engine/arch-context.ts +149 -0
- package/lib/engine/dispatch.js +23 -4
- package/lib/engine/replay-verify.js +1 -12
- package/lib/governance/tool-ban.js +5 -2
- package/lib/governance/tool-ban.ts +5 -2
- package/lib/index.js +307 -195
- package/lib/json-response.js +25 -0
- package/lib/shell-text.js +33 -0
- package/lib/state/archive.js +9 -15
- package/lib/state/batch-paths.js +55 -0
- package/lib/state/command-exec.js +4 -5
- package/lib/state/constants.js +10 -0
- package/lib/state/dangling.js +1 -1
- package/lib/state/gates/detect.js +31 -6
- package/lib/state/gates/detect.ts +11 -6
- package/lib/state/gates/entry.js +3 -1
- package/lib/state/gates/entry.ts +3 -2
- package/lib/state/gates/exit.js +3 -1
- package/lib/state/gates/exit.ts +3 -2
- package/lib/state/gates/handoff.js +3 -1
- package/lib/state/gates/handoff.ts +3 -1
- package/lib/state/gates/helpers.js +8 -14
- package/lib/state/gates/helpers.ts +8 -12
- package/lib/state/gates/plan-contract.js +9 -1
- package/lib/state/gates/plan-contract.ts +4 -2
- package/lib/state/gates/reason-vocabulary.js +8 -3
- package/lib/state/gates/reason-vocabulary.ts +3 -2
- package/lib/state/lane-state.js +37 -0
- package/lib/state/store/lifecycle.js +4 -2
- package/lib/state/store/member-gates.js +3 -1
- package/lib/state/store/paths.js +9 -14
- package/lib/tools/core.js +0 -1
- package/lib/tools/handoff-tools.js +12 -4
- package/lib/tools/host-surface.js +1 -8
- package/lib/tools/log-tools.js +3 -1
- package/lib/tools/mailbox-tools.js +4 -6
- package/lib/tools/readonly.js +5 -6
- package/lib/watch/heartbeat/context.js +5 -3
- package/lib/watch/heartbeat/primitives.js +2 -8
- package/lib/watch/lane-heartbeat.js +6 -10
- package/package.json +4 -4
- package/presets/punky-preset/agent.cordis.yml +17 -11
- package/presets/punky-preset/agent.cordis.yml.bak-2026-10-19T12-05-11 +285 -0
- package/presets/punky-preset/asset-manifest.json +1 -1
- package/presets/punky-preset/references/discipline.md +12 -0
- package/puncat.jpg +0 -0
- package/skills/writing-team/SKILL.md +34 -2
- package/skills/writing-team/references/roles/evidence-auditor.md +57 -0
- package/skills/writing-team/references/roles/logic-auditor.md +57 -0
- package/skills/writing-team/references/roles/structure-auditor.md +59 -0
- package/skills/writing-team/references/roles/theory-mapper.md +55 -0
- package/icon.svg +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
## 0.5.
|
|
1
|
+
## 0.5.2(2026-10-21)
|
|
2
|
+
|
|
3
|
+
### 结构优化:`apply` 873 → 51 行 + 八项单点化收敛(41 笔提交 / 83 文件 / `+3925 −487`)
|
|
4
|
+
|
|
5
|
+
本次以**结构**为主、行为零变更:把长期堆在单一入口里的逻辑拆成可独立验证的单元,并把重复实现收敛到单点。**所有改动均以「行为等价」为验收前提**(块 sha 等价 / 逐文件回归 / 架构棘轮不破)。
|
|
6
|
+
|
|
7
|
+
- **入口瘦身 —— `apply` 873 → 51 行(−94%)**:
|
|
8
|
+
- `W7a`–`W7d` 四波把内联块函数化(4 + 4 + 7 + 5 块),`apply` 依次 873 → 739 → 633 → 493 → 339;**零逻辑变更**,逐波以块 sha 等价证明。
|
|
9
|
+
- `W7e` 把 `governance` 装载与两个 remount 下沉为顶层工厂,`apply` 339 → 261。
|
|
10
|
+
- `W7f` 把运行期真源收拢为 `createRuntimeState`(**20 字段**)并把六个区间外移为 8 个工厂,`apply` 261 → 51。**顶层工厂数 0 → 30**(v0.5.1 时全部内联在 `apply` 内)。
|
|
11
|
+
- **八项单点化收敛**(`W1a`/`W1b`/`W2`/`W3a`/`W3b`/`W4`/`W5`):
|
|
12
|
+
- `sendJson` / `packageRoot` / `stripStreamMerges` 三处重复实现合一(`W1a`);`BLIND_REVIEW_ROLES` / `boxRoot` 单点化(`W1b`)。
|
|
13
|
+
- **产物路径函数四处收敛**到 `lib/state/batch-paths.js`(`W2`);`DEFAULT_ASSEMBLY` 迁出到 `assembly/defaults.js`(保形再导出,`W5`)。
|
|
14
|
+
- 删除 5 个零引用符号与 `core` 再导出(`W3a`)、三个 `gates/*.ts` 的未使用 `node:path` 导入(`W3b`);拆掉 `state -> watch` 回指边,三谓词下沉 `state/lane-state.js`(`W4`)。
|
|
15
|
+
- **命令分段器真单点化**:5 处完全相同正则(`command-exec` / `readonly`×2 / `tool-ban.ts`×2)收敛为 `lib/shell-text.js#segmentCommands`,**生产代码正则字面量物理只剩 1 处**;`detect.ts` 的分词正则未动(分词 ≠ 分段)。
|
|
16
|
+
- **架构真源与越界门**:新增 `.punky/arch.yml` 作为架构真源、`scripts/arch-check.mjs` 为**棘轮门**(禁止新增跨域依赖)、`arch-context` 在派发期注入架构上下文。本次为分段器单点化**授权 +1 对** `governance -> (lib-root)`(`allowedPairs` 62 → 63),理由写入提交正文。
|
|
17
|
+
- **安全面收口到 fail-closed 单点(4 处)**:产物声明路径解析(`cc62ffd`)、产物读面 5 点(`8f7bbd2`)、人工闸 `human:` 证据判定(`e6427e2`)、`P6` watch stat 与 `P7` handoff 在场/可读(`57a28d8`)。所有收口均配**根内正例对照**,防「一刀切把功能打死」。
|
|
18
|
+
- **测试债补齐 —— 166 → 171 个测试文件**:此前安全修复**零仓内测试**,本次补 `gate-artifact-containment` / `gates-needhuman-evidence` / `w7e-remount-surface` / `w7f-rt-write-surface` / `shell-text` 五件;**每条「返回空 / 被拒 / 不抛」断言均配同输入阳性对照**,防不可证伪的绿灯。
|
|
19
|
+
- **写序列可观测化**:`createRuntimeState(onWrite)` 在注入 `ctx.rtWriteLog` 时返回 Proxy 记录字段写入序列,未注入时返回纯对象(**生产路径零开销**)。使「`rt` 单写 vs 双写」这类**终态同构、仅写序列不同**的缺陷首次可被判据抓住。
|
|
20
|
+
- **指引层去重与同源**:`persona` 按「**工具能力归工具定义、调度指引与操作规范归 preset**」原则去重,注入面 **6442 → 5858 字符**;`cordis.patch.yml` 与 `presets/punky-preset/agent.cordis.yml` 两份 persona 定义**对齐为 27 条、逐条文本差异 0**(此前 patch 缺 `0i`、§0 为旧短版);补**模板变量白名单**约束(本内核只注册 `provider`/`model`,其他双花括号变量在会话启动时即抛错)。
|
|
21
|
+
- **技术债跨批总账**:新增 `reports/tech-debt-ledger-20261021.md`,登记跨批未闭合项(含**取证后的「不做」结论**与不可消除的原因),使「已修 / 有意保留 / 已知不阻断」三类可追溯。
|
|
22
|
+
- **读数**:`83 files changed, 3925 insertions(+), 487 deletions(-)`;`apply 873 → 51`、顶层工厂 `0 → 30`、测试文件 `166 → 171`;`guard` A=B=171(1896 tests / 1889 pass / **0 fail** / 4 todo)GREEN;`arch-check` `181 / 667 / 214 / 63` `[ok]`;`check-guidance-sync` GREEN(repo ↔ live 五件同 sha)。
|
|
23
|
+
|
|
2
24
|
## 0.5.1(2026-10-14)
|
|
3
25
|
|
|
26
|
+
### 发布面落版与判据尺子固化(批 `punky-release-0.5.1-20261014`)
|
|
27
|
+
|
|
28
|
+
- **`R-1`(唯一发布阻塞)已解 —— 版本史与版本面结构性对齐**:`package.json:3` 早已是 `0.5.0`,而版本史顶部仍是「未发布(Unreleased)」、首版本标题停在 `## 0.4.4`。本次**真落**:原「未发布」段体**落版为 `## 0.5.0(2026-10-12)`**(**段体一字未改**,sha16 `d6eeb75b36ba38e7`),并在其上新建本段;版本行 → `0.5.1`。
|
|
29
|
+
- **注释订正(`F-1`)**:`lib/sig-fingerprint.js:35` 原注「不进 `GateErrorCode` union(**拒码面恒 29 保持不变**)」**已为假**(实测 union = **37**)⇒ 改为**指向 `lib/types/contracts.ts` 的 union 为唯一真源、不写数字**(规模随授权批演进,写死即漂移源)。改动面 = **恰 1 减 1 加、两行均注释行**,`node --check` 双态 exit 0。
|
|
30
|
+
- **判据尺子固化(`D-04`)**:新增 `scripts/audit/tool-face-count.mjs`(**按块解析 + 四口径阶梯 + 三态退出码**)。原命令只覆盖双引号 ⇒ **26 ≠ 32**;实測病根是**两条叠加**:**引号**(+4,全在 `lib/tools/lane-tools.js` 的单引号 `name`)与**扫描面**(+2,`lib/watch/lane-heartbeat.js` 的 2 件 `defineTool` —— **不在 `lib/tools/*.js` 内**)⇒「只补引号」天花板为 **30**。
|
|
31
|
+
- **锚点类文档入库(`PA-1`)**:`docs/` **54 件**逐件 `git add -f`(禁通配)。根因 = `.gitignore:50` 忽略整个 `docs/**`,且 `:49` 记录 2026-10-10 已 `rm --cached` 49 件 ⇒ 属「**在库 → 只在工作树**」的退化。入库后三读数 **37/37** 符合预期(`check-ignore` 仍报 `:50` = **未改规则**)。
|
|
32
|
+
- **新增技能**:`skills/video-team/`(三层 7 角色 team skill + 宪法 6 章 24 条 + 模板 6 件 + `check-video-team-density.mjs` S1–S7 机检)。
|
|
33
|
+
- **读数**:`58 files changed, 13054 insertions(+), 3 deletions(-)`(发布面)+ `17 files changed, 1869 insertions(+)`(video-team)。
|
|
34
|
+
|
|
35
|
+
## 0.5.0(2026-10-12)
|
|
36
|
+
|
|
4
37
|
### 抄规格体系 P0/P1/P2 三波次 + 三个已验收外部批(`a4a2779`,91 件 / +9407 −236)
|
|
5
38
|
|
|
6
39
|
- **范围**:一次收口提交,含三批主交付(P0 抄规格 L1 事件重放校验腿 + L2 上下文注入预算;P1 抄规格 L3 测试证据自证 / L5 隔离可判定 / L6 宿主兼容第三维;P2 抄规格 L7 写路径原子性 / L4 规则变更传播 / L8 判据脚本信任模型 / L9 记忆溯源 / C-5 坏配置 fail-closed)与**三个已验收外部批**(各自 `verdict = approve`、`blocking = 0`)。
|
package/cordis.patch.yml
CHANGED
|
@@ -151,33 +151,33 @@
|
|
|
151
151
|
name: '@deepseek-ai/dsh-persona'
|
|
152
152
|
config:
|
|
153
153
|
prefix: >-
|
|
154
|
-
你是蟛蜞模式(集群治理 on dsh)的 Leader Agent,运行于 {{model}}
|
|
154
|
+
你是蟛蜞模式(集群治理 on dsh)的 Leader Agent,运行于 {{model}},工作目录以系统提示中的 Working Directory 段为准。
|
|
155
155
|
|
|
156
156
|
职责面 = 分级 / 三层拆解(plan/exec/audit)/ wavePlan 派发 / 成员治理 / 门禁裁决;被派发的角色活由 worker 承担,简单任务由 Leader 自主完成。工具面 = assign_check(委派形态判定)+ gate_status(门禁状态查询)。纪律索引(27 条):评估派发 0/0a/0b/1/2/3/9/10 | 批与交接 0c/0e/4/5/6/0i | 运行模式 11 | 审计验收 0k/12 | 结算 0t | 恢复监管 7/0d | 输出·能力·指引 8/13/14/15 | 两方向·编号 0p/0s | 执行前检索 0u。细则面见 `references/discipline.md`。
|
|
157
|
-
0. 难度路由门禁(每轮必评,**无默认档**):先做只读侦察,再调 `assign_check
|
|
158
|
-
0a. 会话显式化:评估 / 建批 / 结算三件套传同一 session(会话 ID 或命名黑板 `<task>-<date
|
|
159
|
-
0b. 三层门禁:C 必先 `wave_plan`
|
|
157
|
+
0. 难度路由门禁(每轮必评,**无默认档**):先做只读侦察,再调 `assign_check` 写入 difficulty 与 rationale(`scope:"full"`);**单线程一律不建批**;判 C/D 而未建批前**禁执行型动作**;声明 D 而通道未落实 ⇒ 须重评或显式降级为 C,**禁静默降级**。档位取值域、双轴判定与偏离三要素见工具定义。细则见 #§0。
|
|
158
|
+
0a. 会话显式化:评估 / 建批 / 结算三件套传同一 session(会话 ID 或命名黑板 `<task>-<date>`);**隐式 session 与执行会话不一致时以显式为准**。镜像语义见工具定义。细则见 #§0a。
|
|
159
|
+
0b. 三层门禁:C 必先 `wave_plan` 建批,装配声明 `auditLane` 必填;`team` 为可选自由标签。门禁判定见工具定义。细则见 #§0b。
|
|
160
160
|
0c. 任务粗拆:拆到可派发粒度即可(角色 / 目标 / 契约 / 验收 / 产物格式)。细则见 #§0c。
|
|
161
161
|
0e. 执行 lane 任务包只含(角色 / 目标 / 关键契约 / 验收标准 / 产物契约 / 期望输出格式);实现由被指派 worker 完成。细则见 #§0e。
|
|
162
|
-
1. 任务指派:`wave_plan` 按依赖 DAG
|
|
163
|
-
2.
|
|
162
|
+
1. 任务指派:`wave_plan` 按依赖 DAG 分层;`member_status` 派发。分层语义见工具定义。细则见 #§1。
|
|
163
|
+
2. 状态:以状态文件为唯一事实源,**勿凭回执或口述推断**。细则见 #§2。
|
|
164
164
|
3. 并发与锁:写同一 lane 前 `lane_claim` 取单写者锁;同 wave 同物理资源域先取域级隔离单元并把路径注入任务包。细则见 #§3。
|
|
165
165
|
4. 黑板:治理真源 = 批次黑板(`wavePlan.tasks[]` + `handoffs` + 事件流);跨上下文只写元数据、不复制正文。细则见 #§4。
|
|
166
166
|
5. 结算与终门禁:完成 / 失败用 `member_settle`;终门禁先消重、标注冲突、核证据再下结论。细则见 #§5。
|
|
167
167
|
6. 门禁:review 阶段按门禁语义裁决 merged / conflict;批次终态后不再写。细则见 #§6。
|
|
168
|
-
7. 恢复:`idle`
|
|
168
|
+
7. 恢复:`idle` 是空闲态,`idle→running` 即返工 / 续跑入口;重启后须先 `batch_status` 核对再重派。细则见 #§7。
|
|
169
169
|
8. 输出偏好:总结用「对比表 + 清单 + 摘要」三层;对用户只报治理结论与关键状态。细则见 #§8。
|
|
170
170
|
9. 消费方契约摸底先行:实现前先摸底被消费方约束(摸底归 plan 层 produce、实现 lane 只 consume,缺则拒派);产物 `survey/<target>-contract.md`。细则见 #§9。
|
|
171
171
|
10. 宿主级自验证 DoD:产出运行于真实宿主 / 消费路径时,完成判据必含宿主级加载 / 启动冒烟(RED→GREEN 作中间 checkpoint)。细则见 #§10。
|
|
172
|
-
11. 运行模式:星型直驱(`leader-direct
|
|
173
|
-
12. **audit 非盲审**:audit 审核 exec 层必须**消费 plan
|
|
172
|
+
11. 运行模式:星型直驱(`leader-direct`)——结算即派、复核并行、单点派发、真源 = `lanes[].deps` + `handoffs`、监管二源、模式可退出。细则见 #§11。
|
|
173
|
+
12. **audit 非盲审**:audit 审核 exec 层必须**消费 plan 层产物做对照**(`consume` 缺 `plan/` 锚点即拒派,锚点正文须含裸标题 `## 验收标准`)。**`consume` 与 `deps` 须逐条对齐**,且**上游结算后缺边永久不可补** ⇒ 写 `consume` 时即核 `deps`。拒建批码、三档分流与豁免条件见工具定义(`wave_plan`)。细则见 #§12。
|
|
174
174
|
13. **指引维护**:改注入面或正文后必须跑 `node scripts/check-guidance-sync.mjs`(五件同 sha + 双向指针);**GREEN(exit 0)才算收口**。细则见 #§13。
|
|
175
175
|
14. **任务包预算**:单 lane `W` ∈ 4M–10M(`W ≈ 轮次 × 232K`);派发前必带预算行(`预算:W≈…M;切法:…`)。细则见 #§14。
|
|
176
176
|
15. 判据 / 基线 / 并行读数:判据带限定量词;改动前落 sha256 快照;`baselines/**` 单写者 = Leader;并行窗口 lane 自证写域子集;新增校验须给可达构造;批边界归属须留痕。细则见 #§15。
|
|
177
177
|
0d. 记忆语义:复盘 / 归档产物落盘即可,记忆沉淀优先 dsh-mneme。细则见 #§0d。
|
|
178
178
|
0i. 执行形态:C 类批一律走 wavePlan lane 派发;每完成一子步骤即落 `<lane>/progress/NN-<slug>.md` 进度快照,禁攒批。
|
|
179
179
|
0k. audit 收敛口径:audit 层 = 验收,按 plan 验收标准逐条核对;未决项进 `gap-list`(标 blocking / followup)。细则见 #§0k。
|
|
180
|
-
0p. 两方向规格:dispatch
|
|
180
|
+
0p. 两方向规格:dispatch = 写黑板后引擎自动派发;team = `spawn_teammate` 拉起并写黑板、由事件队列交接;**一批一方向,不混用**。细则见 #§0p 与 #§0o。
|
|
181
181
|
0s. 编号规范:跨批引用带批次短码(如 `AG-04@realign`);`AG-xx` 权威定义面 = 批 A `audit/gap-list.md`。
|
|
182
182
|
0t. 完成发现与结算:audit lane 不自动结算 ⇒ 显式 `member_status(review)` → `member_settle(merged|conflict)`;完成发现靠三查(`batch_status` / `log_export` / 产物根 glob);abort 批的 `danglingLanes` 属预期残留。
|
|
183
183
|
0u. 执行前经验检索:每任务第一步 = `weknora_search` 查知识库经验;命中即按其「正向做法」执行并登记来源,无命中照常推进;库不可达记一行后继续。细则见 #§0u。
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
- **能力结论**:`toolBan` 是**启发式、非沙箱**的写通道路由判定——只收敛「无意识地用 shell(pwsh)写盘代替 edit/write」这一主路径;别名(`sc` / `ni` / `ri` / `mi`)、脚本文件(`pwsh -File x.ps1`)、转义参数、编码方式均可绕过;「内联解释器写」(`node -e "fs.writeFileSync(...)"` / `python -c "open(...,'w')"`)**首批不覆盖**(followup)。**不承诺**等价于 OS 级隔离,也**不承诺**编码安全——真正的编码保证来自 edit/write 工具链本身(UTF-8 无 BOM)。
|
|
51
51
|
- **拦截面收窄**(用户裁决 Q3/Q6):只判「修改或写文件」这一类动作;执行 / 构建 / 测试 / 包管理 / 只读命令一律放行(`npm run build`、`npm test`、`node x.mjs`、`git status`、`Get-ChildItem` 均不命中)——构建与测试本身要写盘,拦它们会断裂常规开发闭环。
|
|
52
|
-
- **与执行引擎无关**(用户裁决 Q4):判定实现在护栏内(`lib/governance/tool-ban.ts`),**不 import / 不修改** `lib/tools/readonly.js
|
|
52
|
+
- **与执行引擎无关**(用户裁决 Q4):判定实现在护栏内(`lib/governance/tool-ban.ts`),**不 import / 不修改** `lib/tools/readonly.js`(任务难度门禁「只读侦察面」在执行侧使用的共享判定,服务的是另一个问题:评估前能否跑侦察命令);两处**判定逻辑**各自独立、互不影响。**唯一共享面 = shell 文本预处理单点** `lib/shell-text.js`(`stripStreamMerges` + `segmentCommands`,DEBT-3 真单点化):护栏**不再自抄第三份分段器**,`.punky/arch.yml` 已登记授权对 `governance -> (lib-root)`(理由见该次提交信息)。
|
|
53
53
|
- **生效通道**:① 热加载(`<root>/config/runtime.json` 的 `governance.hook.toolBan`——进 resolved 快照 ⇒ 既有 remount 比较器零改动感知变化 → dispose + 重挂,**免重启**);② 随包 preset `l3-tool-ban`(引用键,注册 id 枚举需 boot 装载一次,即**重启宿主一次**)。
|
|
54
54
|
- **出厂默认**:`toolBan: []` = 零拦截(与 `rules: []` 同口径,装上即用、不改变既有行为)。
|
|
55
55
|
- **证据位点**:判定链与条目结构见 [guardrails-hook.md](guardrails-hook.md) §2「第三类判定面」与 §3 配置;逐条清单见 `presets/hook-rules/README.md`「L3 工具黑名单」表。
|
package/lib/acps/certs.js
CHANGED
|
@@ -190,10 +190,6 @@ function b64url(buf) {
|
|
|
190
190
|
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
function unb64url(str) {
|
|
194
|
-
return Buffer.from(str.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
193
|
// 加密 EAB 凭据(keyId/macKey 不落明文)→ 信封对象 { v, alg, aic, expiresAt, iv, tag, ct }
|
|
198
194
|
export function encryptEabCredential(credential, keyInput) {
|
|
199
195
|
if (!credential || typeof credential !== 'object') {
|
package/lib/acps/server.js
CHANGED
|
@@ -39,6 +39,8 @@ import { DEFAULT_ASSEMBLY } from '../assembly.js';
|
|
|
39
39
|
import { engineVersion } from '../aip/tool-descriptor.js';
|
|
40
40
|
import { ensureAcpsCerts } from './certs.js';
|
|
41
41
|
import { AIP_COMMAND_TYPES as TASK_COMMAND_TYPES, TASK_STATES } from '../comms/aip-format.js';
|
|
42
|
+
// JSON 响应写端单点((lib-root) 中立件;与 lib/api.js 同源)
|
|
43
|
+
import { sendJson } from '../json-response.js';
|
|
42
44
|
|
|
43
45
|
// ── AIP RPC 常量(对齐参考实现 aip_base_model.py:23-49 / aip_rpc_model.py:15-57)──
|
|
44
46
|
export const JSONRPC_VERSION = '2.0';
|
|
@@ -169,12 +171,6 @@ function readBody(req) {
|
|
|
169
171
|
return Promise.resolve({});
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
function sendJson(res, status, data) {
|
|
173
|
-
const body = JSON.stringify(data);
|
|
174
|
-
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
175
|
-
res.end(body);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
174
|
/**
|
|
179
175
|
* 创建 ACPs 对外 mTLS 服务端点(纯工厂,不自动 listen)
|
|
180
176
|
* @param {object} opts { config, logger?, acsProvider?, rpcHandler?, healthProvider?, certDir? }
|
package/lib/api.js
CHANGED
|
@@ -31,12 +31,8 @@ import * as EVT from './state/event-types.js';
|
|
|
31
31
|
// WebUI 治理配置写通道:/config 端点 trusted 判定(自复刻宿主 /api
|
|
32
32
|
// 护栏语义,宿主未导出故复刻——插件 exact 路由不经宿主护栏,写端点须自理,见 config-trust.js)
|
|
33
33
|
import { isTrustedConfigRequest } from './webui/config-trust.js';
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const body = JSON.stringify(data);
|
|
37
|
-
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
38
|
-
res.end(body);
|
|
39
|
-
}
|
|
34
|
+
// JSON 响应写端单点((lib-root) 中立件;与 lib/acps/server.js 同源)
|
|
35
|
+
import { sendJson } from './json-response.js';
|
|
40
36
|
|
|
41
37
|
function q(url) {
|
|
42
38
|
return Object.fromEntries(new URL(url, 'http://localhost').searchParams.entries());
|
|
@@ -33,7 +33,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
33
33
|
// 范围红线:零引擎改动(wave-plan.js / gates.js / store.js 等一律不碰)——盲审 = 装配数据 + 既有工具编排。
|
|
34
34
|
|
|
35
35
|
// ── 三角色映射(layer=audit,skills 手册前缀;Leader 显式声明或经 applyAssemblyExtensions 合并补全)──
|
|
36
|
-
|
|
36
|
+
// 单点 = `lib/assembly/schema.js:BLIND_REVIEW_ROLES`(W1b/A-2 收敛);本文件**保留同名 re-export**
|
|
37
|
+
// (`test/audit-blind-review.test.js` 从本文件 import 该符号,导入面不得断)。
|
|
38
|
+
// 两条语句各有职责:`import` 供本文件内部消费(:applyAssemblyExtensions 遍历),
|
|
39
|
+
// `export … from` 保导出面 —— 纯 re-export **不创建本地绑定**,故不可省 import。
|
|
40
|
+
import { BLIND_REVIEW_ROLES } from './schema.js';
|
|
41
|
+
export { BLIND_REVIEW_ROLES } from './schema.js';
|
|
37
42
|
|
|
38
43
|
export const BLIND_REVIEW_SKILLS = {
|
|
39
44
|
'audit-panelist': ['report-blind-audit', 'code-review-guideline'], // 事实核验 + 对抗式打分(rubric/checklist)
|
|
@@ -0,0 +1,53 @@
|
|
|
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-09-27 · team-asset 全量退役】已无资产读端)与包根解析
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// 现语义:**装配数据唯一真源 = `config.assembly`**(外部覆盖层,整份优先);无覆盖层 ⇒ `null`
|
|
21
|
+
// (调用方自行回落内置常量,如 `lib/tools/register.js` / `lib/acps/server.js` 用 `DEFAULT_ASSEMBLY`)。
|
|
22
|
+
// 「包内 `presets/<team>/team-asset.yml` 的 `layers` 段」这一权威源**已随资产面整体删除**
|
|
23
|
+
// (A-5 判据:本文件对 `team-asset` 的 import 面归零)。
|
|
24
|
+
// **零新默认值**:不得在此代造任何 `team → 装配` 映射(`team` 参数自 2026-09-27 起 = 纯归类标签)。
|
|
25
|
+
// punky-preset 团队装配**已弃用**(同上);`DEFAULT_ASSEMBLY` = **活路径**:它是 `lib/tools/register.js:64` 与 `lib/acps/server.js:134` 的**缺省回落值**,并供 `lib/index.js:321` 派生 agent descriptors;内容 = software-team 装配。
|
|
26
|
+
// 历史(保留可读):原解析顺序 = ① `config.assembly` → ② 包内团队资产 → ③ 无资产即 `null`。
|
|
27
|
+
// 【W5/R-9 · 2026-10-18】本常量自 `lib/assembly.js` **原样迁出**(内容与注释逐字未改)——
|
|
28
|
+
// 单点 = 本文件;包级根 `lib/assembly.js` 保形再导出,消费方一行不改。
|
|
29
|
+
export const DEFAULT_ASSEMBLY = {
|
|
30
|
+
team: 'software-team',
|
|
31
|
+
layers: {
|
|
32
|
+
plan: {
|
|
33
|
+
roles: ['coordinator', 'designer'],
|
|
34
|
+
skills: { coordinator: ['brainstorming', 'writing-plans'], designer: ['brainstorming', 'writing-plans', 'spec-writing'] },
|
|
35
|
+
},
|
|
36
|
+
exec: {
|
|
37
|
+
roles: ['coder', 'tester', 'reviewer'],
|
|
38
|
+
skills: {
|
|
39
|
+
coder: ['test-driven-development', 'codebase-design', 'receiving-code-review', 'requesting-code-review'],
|
|
40
|
+
tester: ['verification-before-completion', 'systematic-debugging'],
|
|
41
|
+
reviewer: ['review-execution'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
audit: {
|
|
45
|
+
roles: ['supervisor', 'doc-manager'],
|
|
46
|
+
skills: {
|
|
47
|
+
supervisor: ['acceptance-gate', 'verification-before-completion'],
|
|
48
|
+
'doc-manager': ['doc-generator', 'retro-and-memory'],
|
|
49
|
+
reviewer: ['review-execution'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
package/lib/assembly/flows.js
CHANGED
|
@@ -43,15 +43,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
43
43
|
// 零读端声明 → declarationSummaryOf(flows) 汇总 `unwired`(state_machine / progress_contract /
|
|
44
44
|
// rework 的**未接线标注**,来源 team-asset.js 的台账;`consume_field` 已于 S19① 接线 ⇒ 不在台账)
|
|
45
45
|
|
|
46
|
-
import { fileURLToPath } from 'node:url';
|
|
47
46
|
// 【2026-09-27 · team-asset 全量退役】原 import 面(资产装载器 / 资产签名 / 码族常量表 /
|
|
48
47
|
// 严重级常量表 / blocking 判据 / 未接线台账读函数 —— 字面量见上游 team-asset.js)
|
|
49
48
|
// 随资产读端**整体删除**(A-5 / A-6 判据:本文件对 `team-asset` 的 import 面与码面归零)。
|
|
50
49
|
|
|
51
|
-
/**
|
|
52
|
-
export
|
|
53
|
-
return fileURLToPath(new URL('../../', import.meta.url));
|
|
54
|
-
}
|
|
50
|
+
/** 包根目录:**单点** = `lib/assets.js`(W1a/A-4 收敛;本文件不再自算 `import.meta.url` 深度)。 */
|
|
51
|
+
export { packageRoot } from '../assets.js';
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
* 引擎基线:**验收标准(criteria)章节名** —— 判据「全中文」的**单一真源**(2026-09-21 用户裁定)。
|
package/lib/assembly.js
CHANGED
|
@@ -15,40 +15,10 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
15
15
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
// 「包内 `presets/<team>/team-asset.yml` 的 `layers` 段」这一权威源**已随资产面整体删除**
|
|
23
|
-
// (A-5 判据:本文件对 `team-asset` 的 import 面归零)。
|
|
24
|
-
// **零新默认值**:不得在此代造任何 `team → 装配` 映射(`team` 参数自 2026-09-27 起 = 纯归类标签)。
|
|
25
|
-
// punky-preset 团队装配**已弃用**(同上);`DEFAULT_ASSEMBLY` = **活路径**:它是 `lib/tools/register.js:64` 与 `lib/acps/server.js:134` 的**缺省回落值**,并供 `lib/index.js:321` 派生 agent descriptors;内容 = software-team 装配。
|
|
26
|
-
// 历史(保留可读):原解析顺序 = ① `config.assembly` → ② 包内团队资产 → ③ 无资产即 `null`。
|
|
27
|
-
export const DEFAULT_ASSEMBLY = {
|
|
28
|
-
team: 'software-team',
|
|
29
|
-
layers: {
|
|
30
|
-
plan: {
|
|
31
|
-
roles: ['coordinator', 'designer'],
|
|
32
|
-
skills: { coordinator: ['brainstorming', 'writing-plans'], designer: ['brainstorming', 'writing-plans', 'spec-writing'] },
|
|
33
|
-
},
|
|
34
|
-
exec: {
|
|
35
|
-
roles: ['coder', 'tester', 'reviewer'],
|
|
36
|
-
skills: {
|
|
37
|
-
coder: ['test-driven-development', 'codebase-design', 'receiving-code-review', 'requesting-code-review'],
|
|
38
|
-
tester: ['verification-before-completion', 'systematic-debugging'],
|
|
39
|
-
reviewer: ['review-execution'],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
audit: {
|
|
43
|
-
roles: ['supervisor', 'doc-manager'],
|
|
44
|
-
skills: {
|
|
45
|
-
supervisor: ['acceptance-gate', 'verification-before-completion'],
|
|
46
|
-
'doc-manager': ['doc-generator', 'retro-and-memory'],
|
|
47
|
-
reviewer: ['review-execution'],
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
};
|
|
18
|
+
// 缺省装配数据单点已迁出(W5/R-9):`lib/assembly/defaults.js`;本文件**保形再导出**,
|
|
19
|
+
// 消费方(`lib/acps/server.js` / `lib/tools/register.js` / `lib/index.js`)一行不改。
|
|
20
|
+
// 语义注释与常量内容随迁(逐字未改)。
|
|
21
|
+
export { DEFAULT_ASSEMBLY } from './assembly/defaults.js';
|
|
52
22
|
|
|
53
23
|
/**
|
|
54
24
|
* 装配解析(**唯一真源 = `config.assembly`**)。
|
package/lib/auditlog/sink.js
CHANGED
|
@@ -440,10 +440,6 @@ function segmentPath(day, segment) {
|
|
|
440
440
|
: path.join(state.sinkDir, 'audit-' + day + '.' + segment + '.jsonl');
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
function mainSegmentPath() {
|
|
444
|
-
return segmentPath(localDateKey(new Date()), 0);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
443
|
function currentSegmentPath() {
|
|
448
444
|
return segmentPath(localDateKey(new Date()), state.segment);
|
|
449
445
|
}
|
package/lib/comms/acps-bridge.js
CHANGED
|
@@ -27,8 +27,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
27
27
|
// 桥不自带监听——/rpc 监听归 endpoint 侧(衔接点见 handleInbound/toTaskResult 签名)。
|
|
28
28
|
// - 生命周期:start/stop/dispose 幂等(开→关销毁、关→开重建,秒级无迁移)。
|
|
29
29
|
|
|
30
|
-
import { join } from 'node:path';
|
|
31
30
|
import * as mailbox from './mailbox.js';
|
|
31
|
+
import { mailboxRootOf } from './mailbox-paths.js';
|
|
32
32
|
import {
|
|
33
33
|
toAipMessage,
|
|
34
34
|
toAipTask,
|
|
@@ -42,10 +42,6 @@ import { resolveBridgeConfig } from '../schema.js';
|
|
|
42
42
|
// lane 合法性(与 mailbox.js sanitizeLane 同正则,fail-soft:外部输入不合法 → null 而非 throw)
|
|
43
43
|
const LANE_RE = /^[a-zA-Z0-9._-]+$/;
|
|
44
44
|
|
|
45
|
-
function boxRoot(root, sessionId, batchId) {
|
|
46
|
-
return join(root, 'sessions', sessionId, 'mailbox', batchId);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
45
|
// ── inbound:ACPs TaskCommand → mailbox 消息(纯转换,不写存储)──
|
|
50
46
|
// lane 推导:mentions 单元素数组/非 'all' 字符串 → 该值;否则 groupId;均非法 → null(inbox 无 lane 概念,
|
|
51
47
|
// lane 仅为路由元数据)。mentions='all'(广播语义)外部投递不支持 → null(外部不可写 broadcast,红线)。
|
|
@@ -86,7 +82,7 @@ export function deliverInbound(root, sessionId, batchId, taskCommand, { inboundE
|
|
|
86
82
|
return { ok: false, code: 'INBOUND_DISABLED', detail: 'acps.bridge.inbound is false: external writes to mailbox disabled' };
|
|
87
83
|
}
|
|
88
84
|
const { message, meta } = taskCommandToInbound(taskCommand);
|
|
89
|
-
const res = mailbox.send(
|
|
85
|
+
const res = mailbox.send(mailboxRootOf(root, sessionId, batchId), { type: 'inbox' }, message, meta);
|
|
90
86
|
return { ok: res.ok, ackId: res.ackId, lane: meta.lane };
|
|
91
87
|
}
|
|
92
88
|
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
// mailbox 落盘根**单点**(`comms` 域):ACPs 桥接(`acps-bridge.js`)、topic 分发(`topic.js`)、
|
|
19
|
+
// mailbox 工具面(`tools/mailbox-tools.js`)原先各持一份逐字同体的路径拼接 ⇒ 收敛于此(W1b / A-5)。
|
|
20
|
+
// 语义红线:root/sessions/<sessionId>/mailbox/<batchId> 四级路径逐字不变,调用方负责 root 与 id 的取值。
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
|
|
23
|
+
export function mailboxRootOf(root, sessionId, batchId) {
|
|
24
|
+
return join(root, 'sessions', sessionId, 'mailbox', batchId);
|
|
25
|
+
}
|
package/lib/comms/topic.js
CHANGED
|
@@ -23,8 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
23
23
|
// - subscribeTopicPrefix(前缀订阅,SSE hub 订阅 `swarm.` 用,签名零改动);
|
|
24
24
|
// - 装配开关 capabilities.topic.enabled(CAPABILITY_REGISTRY 注册表默认关,显式开启才接线);
|
|
25
25
|
// - 消费点不得绕过 mailbox 公共接口(与既有广播语义同源:mailbox.send/readUnacked)。
|
|
26
|
-
import { join } from 'node:path';
|
|
27
26
|
import { send as mailboxSend, readUnacked } from './mailbox.js';
|
|
27
|
+
import { mailboxRootOf } from './mailbox-paths.js';
|
|
28
28
|
|
|
29
29
|
const registry = new Map(); // Map<topic, Set<handler>>
|
|
30
30
|
const prefixRegistry = new Map(); // Map<prefix, Set<handler>>(前缀订阅,emitTopic 按 startsWith 分发)
|
|
@@ -35,10 +35,6 @@ function assertTopic(topic) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function boxRoot(root, sessionId, batchId) {
|
|
39
|
-
return join(root, 'sessions', sessionId, 'mailbox', batchId);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
export function subscribeTopic(topic, handler) {
|
|
43
39
|
assertTopic(topic);
|
|
44
40
|
if (typeof handler !== 'function') throw new Error('invalid handler: ' + String(handler));
|
|
@@ -100,7 +96,7 @@ export function emitTopic(topic, payload, ctx = {}) {
|
|
|
100
96
|
const { root, sessionId, batchId } = ctx;
|
|
101
97
|
if (root && sessionId && batchId) {
|
|
102
98
|
const msg = { topic, payload, ts: new Date().toISOString() };
|
|
103
|
-
const res = mailboxSend(
|
|
99
|
+
const res = mailboxSend(mailboxRootOf(root, sessionId, batchId), { type: 'broadcast' }, msg);
|
|
104
100
|
sent = res.ok ? 1 : 0;
|
|
105
101
|
ackId = res.ackId ?? null;
|
|
106
102
|
}
|
|
@@ -108,7 +104,7 @@ export function emitTopic(topic, payload, ctx = {}) {
|
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
export function readTopic(root, { sessionId, batchId, topic, sinceTs = 0 } = {}) {
|
|
111
|
-
const items = readUnacked(
|
|
107
|
+
const items = readUnacked(mailboxRootOf(root, sessionId, batchId), { type: 'broadcast' }, { sinceTs });
|
|
112
108
|
return items
|
|
113
109
|
.filter((it) => it.message?.topic === topic)
|
|
114
110
|
.map((it) => ({ ackId: it.ackId, topic: it.message.topic, payload: it.message.payload, ts: it.message.ts }));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* arch-context —— 架构上下文注入件(P2)
|
|
3
|
+
*
|
|
4
|
+
* 定位:把 `.punky/arch.yml`(架构真源)裁剪成**一段任务包文本**,让 worker 在开工时就读到
|
|
5
|
+
* 「我改的文件属于哪个域、可依赖谁、白名单之外一律违规」。这是"执行期 Agent 可读的架构"的落点。
|
|
6
|
+
*
|
|
7
|
+
* 设计约束(与引擎既有惯例一致):
|
|
8
|
+
* · **独立成件** —— `dispatch.ts` 只加一行调用,逻辑不塞进已逾千行的 `dispatch.ts`(大文件解耦)。
|
|
9
|
+
* · **零回归** —— 拿不到 arch.yml / 拿不到 targets / 域集合为空 ⇒ 返回 `null`,调用方不注入任何文本,
|
|
10
|
+
* 任务包与改动前**逐字节相同**(与 `injectBudget` 缺省 `null` 的同款口径)。
|
|
11
|
+
* · **纯函数** —— 只读文件、只返回字符串;不写盘、不发事件、不改状态。
|
|
12
|
+
* · **零第三方依赖** —— 只 import `node:*`;`arch.yml` 用针对性行扫描解析本文件子集(与
|
|
13
|
+
* `scripts/arch-check.mjs` 同口径,但各自独立实现:脚本侧是门,本件是注入面)。
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_MAX_DOMAINS = 4;
|
|
16
|
+
/** 本模块所在**包根**(`lib/engine/` → 上两级)。编译产物与源同置 `lib/engine/`,故源码态与运行态一致。 */
|
|
17
|
+
export declare function pkgRootOfThisModule(): string;
|
|
18
|
+
/** 自 `start` 起向上查找 `.punky/arch.yml`(最多 5 层),兼容"真源在仓库根"的布局。 */
|
|
19
|
+
export declare function findArchFile(start: string): string | null;
|
|
20
|
+
export interface ArchRules {
|
|
21
|
+
/** 允许的 `<from> -> <to>` 域对。 */
|
|
22
|
+
allowed: Set<string>;
|
|
23
|
+
/** 域 → 简述。 */
|
|
24
|
+
domains: Map<string, string>;
|
|
25
|
+
}
|
|
26
|
+
/** 解析 `arch.yml` 的本件子集(`mode` / `domains` / `rules.allowed`);无法解析 ⇒ 空集合(调用方据此不注入)。 */
|
|
27
|
+
export declare function parseArchRules(text: string): ArchRules;
|
|
28
|
+
/** 把目标文件(绝对路径)映射到 `lib/<域>`;不在 `lib/` 下的一律忽略。 */
|
|
29
|
+
export declare function domainsOfTargets(libRoot: string, targets: readonly string[]): string[];
|
|
30
|
+
export interface RenderArchContextOptions {
|
|
31
|
+
/** 包根(缺省 = 本模块所在包)。 */
|
|
32
|
+
pkgRoot?: string;
|
|
33
|
+
/** lane 的目标文件(绝对路径);缺省/空 ⇒ 不注入。 */
|
|
34
|
+
targets?: readonly string[] | null;
|
|
35
|
+
/** 单条 lane 最多注入几个域的约束(防上下文膨胀)。 */
|
|
36
|
+
maxDomains?: number;
|
|
37
|
+
}
|
|
38
|
+
/** 渲染该 lane 的架构约束段;不可判定 ⇒ `null`(调用方零动作)。 */
|
|
39
|
+
export declare function renderArchContext(opts: RenderArchContextOptions): string | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* arch-context —— 架构上下文注入件(P2)
|
|
3
|
+
*
|
|
4
|
+
* 定位:把 `.punky/arch.yml`(架构真源)裁剪成**一段任务包文本**,让 worker 在开工时就读到
|
|
5
|
+
* 「我改的文件属于哪个域、可依赖谁、白名单之外一律违规」。这是"执行期 Agent 可读的架构"的落点。
|
|
6
|
+
*
|
|
7
|
+
* 设计约束(与引擎既有惯例一致):
|
|
8
|
+
* · **独立成件** —— `dispatch.ts` 只加一行调用,逻辑不塞进已逾千行的 `dispatch.ts`(大文件解耦)。
|
|
9
|
+
* · **零回归** —— 拿不到 arch.yml / 拿不到 targets / 域集合为空 ⇒ 返回 `null`,调用方不注入任何文本,
|
|
10
|
+
* 任务包与改动前**逐字节相同**(与 `injectBudget` 缺省 `null` 的同款口径)。
|
|
11
|
+
* · **纯函数** —— 只读文件、只返回字符串;不写盘、不发事件、不改状态。
|
|
12
|
+
* · **零第三方依赖** —— 只 import `node:*`;`arch.yml` 用针对性行扫描解析本文件子集(与
|
|
13
|
+
* `scripts/arch-check.mjs` 同口径,但各自独立实现:脚本侧是门,本件是注入面)。
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
export const DEFAULT_MAX_DOMAINS = 4;
|
|
19
|
+
/** 本模块所在**包根**(`lib/engine/` → 上两级)。编译产物与源同置 `lib/engine/`,故源码态与运行态一致。 */
|
|
20
|
+
export function pkgRootOfThisModule() {
|
|
21
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
22
|
+
}
|
|
23
|
+
/** 自 `start` 起向上查找 `.punky/arch.yml`(最多 5 层),兼容"真源在仓库根"的布局。 */
|
|
24
|
+
export function findArchFile(start) {
|
|
25
|
+
let d = resolve(start);
|
|
26
|
+
for (let i = 0; i < 5; i += 1) {
|
|
27
|
+
const candidate = join(d, '.punky', 'arch.yml');
|
|
28
|
+
if (existsSync(candidate))
|
|
29
|
+
return candidate;
|
|
30
|
+
const up = dirname(d);
|
|
31
|
+
if (up === d)
|
|
32
|
+
break;
|
|
33
|
+
d = up;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/** 解析 `arch.yml` 的本件子集(`mode` / `domains` / `rules.allowed`);无法解析 ⇒ 空集合(调用方据此不注入)。 */
|
|
38
|
+
export function parseArchRules(text) {
|
|
39
|
+
const allowed = new Set();
|
|
40
|
+
const domains = new Map();
|
|
41
|
+
let section = null;
|
|
42
|
+
let sub = null;
|
|
43
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
44
|
+
const line = raw.replace(/(^|\s)#.*$/, '');
|
|
45
|
+
if (line.trim().length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const indent = line.length - line.trimStart().length;
|
|
48
|
+
const t = line.trim();
|
|
49
|
+
if (indent === 0) {
|
|
50
|
+
const top = /^([A-Za-z_][\w-]*):/.exec(t);
|
|
51
|
+
if (top && top[1]) {
|
|
52
|
+
section = top[1];
|
|
53
|
+
sub = null;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (section === 'rules' && !t.startsWith('-')) {
|
|
58
|
+
const m = /^([A-Za-z_][\w-]*):/.exec(t);
|
|
59
|
+
if (m && m[1]) {
|
|
60
|
+
sub = m[1];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (section === 'rules' && sub === 'allowed' && t.startsWith('-')) {
|
|
65
|
+
const v = t.replace(/^-\s*/, '').replace(/^["']|["']$/g, '').trim();
|
|
66
|
+
if (v.length > 0)
|
|
67
|
+
allowed.add(v);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (section === 'domains' && !t.startsWith('-')) {
|
|
71
|
+
const m = /^("?)([^":]+)\1:\s*(.*)$/.exec(t);
|
|
72
|
+
if (m && m[2]) {
|
|
73
|
+
domains.set(m[2], (m[3] ?? '').replace(/^["']|["']$/g, ''));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { allowed, domains };
|
|
78
|
+
}
|
|
79
|
+
/** 把目标文件(绝对路径)映射到 `lib/<域>`;不在 `lib/` 下的一律忽略。 */
|
|
80
|
+
export function domainsOfTargets(libRoot, targets) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const t of targets) {
|
|
83
|
+
if (typeof t !== 'string' || t.length === 0)
|
|
84
|
+
continue;
|
|
85
|
+
const abs = resolve(t);
|
|
86
|
+
if (!abs.startsWith(libRoot + sep))
|
|
87
|
+
continue;
|
|
88
|
+
const rel = abs.slice(libRoot.length + 1);
|
|
89
|
+
const seg = rel.split(sep)[0];
|
|
90
|
+
if (!seg)
|
|
91
|
+
continue;
|
|
92
|
+
const domain = seg.includes('.') ? '(lib-root)' : seg;
|
|
93
|
+
if (!out.includes(domain))
|
|
94
|
+
out.push(domain);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/** 渲染该 lane 的架构约束段;不可判定 ⇒ `null`(调用方零动作)。 */
|
|
99
|
+
export function renderArchContext(opts) {
|
|
100
|
+
const pkgRoot = opts.pkgRoot ?? pkgRootOfThisModule();
|
|
101
|
+
const archFile = findArchFile(pkgRoot);
|
|
102
|
+
if (!archFile)
|
|
103
|
+
return null;
|
|
104
|
+
let rules;
|
|
105
|
+
try {
|
|
106
|
+
rules = parseArchRules(readFileSync(archFile, 'utf8'));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (rules.allowed.size === 0)
|
|
112
|
+
return null;
|
|
113
|
+
const domains = domainsOfTargets(join(pkgRoot, 'lib'), opts.targets ?? []);
|
|
114
|
+
if (domains.length === 0)
|
|
115
|
+
return null;
|
|
116
|
+
const max = opts.maxDomains ?? DEFAULT_MAX_DOMAINS;
|
|
117
|
+
const picked = domains.slice(0, max);
|
|
118
|
+
const L = [];
|
|
119
|
+
L.push('## 架构约束(本 lane 涉域)');
|
|
120
|
+
L.push('- 本 lane 目标文件所属域:' + picked.map((d) => '`' + d + '`').join('、')
|
|
121
|
+
+ (domains.length > picked.length ? '(共 ' + domains.length + ' 个,此处只列前 ' + max + ' 个)' : ''));
|
|
122
|
+
for (const d of picked) {
|
|
123
|
+
const from = [...rules.allowed].filter((p) => p.startsWith(d + ' -> ')).map((p) => p.split(' -> ')[1] ?? '');
|
|
124
|
+
const to = [...rules.allowed].filter((p) => p.endsWith(' -> ' + d)).map((p) => p.split(' -> ')[0] ?? '');
|
|
125
|
+
const desc = rules.domains.get(d);
|
|
126
|
+
L.push('- `' + d + '`' + (desc ? '(' + desc + ')' : '') + ' 允许依赖:'
|
|
127
|
+
+ (from.length > 0 ? from.map((x) => '`' + x + '`').join('、') : '(无)'));
|
|
128
|
+
L.push(' —— 可被这些域依赖:' + (to.length > 0 ? to.map((x) => '`' + x + '`').join('、') : '(无)'));
|
|
129
|
+
}
|
|
130
|
+
L.push('- **白名单之外的新增跨域依赖 = 违规**:门 `scripts/arch-check.mjs` 以非 0 退出码拒绝 merged。');
|
|
131
|
+
L.push('- 真源 `.punky/arch.yml`;确需新增域对时,须在产物中显式说明理由。');
|
|
132
|
+
return L.join('\n');
|
|
133
|
+
}
|