dsh-tower 2.1.0-beta.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMPATIBILITY.md +6 -1
  3. package/CONTRIBUTING.md +71 -5
  4. package/PRINCIPLES.md +145 -0
  5. package/README.en.md +14 -5
  6. package/README.md +14 -5
  7. package/SECURITY.md +6 -0
  8. package/adapters/archive.js +279 -0
  9. package/adapters/caseSensitivity.js +66 -0
  10. package/adapters/default.js +51 -8
  11. package/{lib → adapters}/git.js +1 -1
  12. package/{lib → adapters}/leases.js +15 -6
  13. package/{lib → adapters}/legacy.js +20 -6
  14. package/adapters/persistence.js +543 -0
  15. package/adapters/stateFile.js +99 -0
  16. package/adapters/store-fs.js +28 -1
  17. package/adapters/store.js +204 -0
  18. package/{lib → adapters}/tests.js +15 -7
  19. package/adapters/vcs-git.js +15 -1
  20. package/{lib → adapters}/workspaceLock.js +1 -1
  21. package/{lib → adapters}/worktreeOwnership.js +18 -7
  22. package/app/archive.js +188 -0
  23. package/app/batch.js +9 -10
  24. package/app/board.js +4 -4
  25. package/app/checkCompletion.js +23 -11
  26. package/app/enrichedMissions.js +2 -4
  27. package/app/findings.js +6 -7
  28. package/app/gate.js +32 -15
  29. package/app/init.js +59 -22
  30. package/app/merge.js +64 -51
  31. package/app/messages.js +7 -8
  32. package/app/mission.js +7 -5
  33. package/app/plan.js +20 -9
  34. package/app/provision.js +12 -14
  35. package/app/read.js +65 -6
  36. package/app/rebase.js +14 -16
  37. package/app/recording.js +7 -8
  38. package/app/review.js +17 -8
  39. package/app/roster.js +14 -15
  40. package/{lib → app}/spawn-prechecks.js +4 -3
  41. package/app/spawn.js +12 -13
  42. package/app/teardown.js +28 -8
  43. package/app/tower.js +121 -39
  44. package/docs/README.md +6 -2
  45. package/docs/adr/0001-ports-adapters-as-data.md +49 -0
  46. package/docs/adr/0002-single-state-json.md +50 -0
  47. package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
  48. package/docs/adr/0004-surface-budget.md +50 -0
  49. package/docs/adr/README.md +51 -0
  50. package/docs/agents/domain.md +38 -0
  51. package/docs/agents/issue-tracker.md +46 -0
  52. package/docs/agents/triage-labels.md +15 -0
  53. package/docs/agents/workflow.md +51 -0
  54. package/docs/architecture/overview.md +170 -37
  55. package/docs/guide/hot-reload.md +1 -1
  56. package/docs/guide/install-upgrade-migration.md +18 -1
  57. package/docs/guide/release.md +1 -1
  58. package/docs/operations/incident.md +82 -0
  59. package/docs/operations/runbook.md +12 -3
  60. package/docs/reference/blockers.md +3 -3
  61. package/docs/reference/config.generated.md +5 -0
  62. package/docs/reference/config.md +4 -0
  63. package/docs/reference/doctor.md +104 -8
  64. package/docs/reference/errors.generated.md +41 -35
  65. package/docs/reference/errors.md +54 -40
  66. package/docs/reference/events.md +38 -6
  67. package/docs/reference/surface-budget.md +128 -0
  68. package/docs/reference/tools.generated.md +9 -8
  69. package/docs/reference/tools.md +9 -8
  70. package/docs/zh/guide/hot-reload.md +1 -1
  71. package/docs/zh/guide/install-upgrade-migration.md +13 -1
  72. package/docs/zh/reference/blockers.md +3 -3
  73. package/docs/zh/reference/config.md +4 -0
  74. package/docs/zh/reference/doctor.md +63 -2
  75. package/docs/zh/reference/errors.md +4 -2
  76. package/docs/zh/reference/tools.md +6 -6
  77. package/domain/batch.js +2 -2
  78. package/domain/config/schema.js +51 -4
  79. package/domain/contracts/outputs.js +33 -3
  80. package/domain/contracts/tool-inputs/missions.js +2 -1
  81. package/domain/contracts/tool-inputs/ops.js +1 -0
  82. package/domain/errors.js +297 -40
  83. package/domain/events.js +207 -0
  84. package/domain/integration/gate.js +41 -12
  85. package/domain/integration/init.js +40 -4
  86. package/domain/integration/merge.js +35 -0
  87. package/domain/lockBudget.js +30 -0
  88. package/domain/mission.js +1 -1
  89. package/domain/missions/completion.js +72 -5
  90. package/domain/missions/lifecycle.js +11 -4
  91. package/domain/missions/plan.js +14 -0
  92. package/domain/missions/review.js +90 -7
  93. package/domain/planning.js +6 -3
  94. package/domain/read/views.js +17 -1
  95. package/domain/report.js +9 -1
  96. package/domain/scope.js +83 -11
  97. package/domain/state/archive.js +257 -0
  98. package/domain/state/migrations.js +145 -0
  99. package/domain/state/schema.js +6 -0
  100. package/domain/state.js +154 -4
  101. package/domain/summary.js +2 -0
  102. package/domain/surface.js +248 -0
  103. package/host/doctor.js +555 -0
  104. package/{lib → host}/modeStore.js +1 -1
  105. package/{lib → host}/tool-registry.js +106 -31
  106. package/host/tower.js +32 -0
  107. package/index.js +12 -9
  108. package/package.json +10 -3
  109. package/ports/archive.js +18 -0
  110. package/ports/caseSensitivity.js +15 -0
  111. package/ports/clock.js +10 -2
  112. package/ports/config.js +19 -0
  113. package/ports/contract.js +38 -0
  114. package/ports/events.js +24 -0
  115. package/ports/ids.js +10 -2
  116. package/ports/index.js +54 -2
  117. package/ports/ownership.js +17 -0
  118. package/ports/state.js +16 -0
  119. package/ports/store.js +52 -3
  120. package/ports/vcs.js +25 -4
  121. package/scripts/architecture-waivers.json +19 -0
  122. package/scripts/architecture.mjs +634 -23
  123. package/scripts/check-docs.mjs +8 -35
  124. package/scripts/check-pack.mjs +68 -39
  125. package/scripts/check-philosophy.mjs +266 -0
  126. package/scripts/domain-metrics.mjs +13 -4
  127. package/scripts/generate-config-reference.mjs +14 -1
  128. package/scripts/generate-error-reference.mjs +68 -14
  129. package/scripts/generate-events-reference.mjs +83 -0
  130. package/scripts/generate-tools-reference.mjs +11 -0
  131. package/scripts/issue-audit.mjs +2 -2
  132. package/scripts/lib/surface.mjs +87 -0
  133. package/scripts/pack-manifest.txt +64 -19
  134. package/scripts/regression-audit.mjs +164 -0
  135. package/scripts/release-check.mjs +1 -1
  136. package/scripts/surface-baseline.json +21 -0
  137. package/scripts/surface-baseline.mjs +120 -0
  138. package/tools/adopt.js +2 -3
  139. package/tools/batch.js +2 -3
  140. package/tools/complete.js +3 -4
  141. package/tools/diff.js +3 -3
  142. package/tools/doctor.js +7 -10
  143. package/tools/finding.js +2 -3
  144. package/tools/findings.js +3 -3
  145. package/tools/import-legacy.js +3 -5
  146. package/tools/inbox.js +3 -3
  147. package/tools/index.js +7 -7
  148. package/tools/init.js +4 -7
  149. package/tools/interrupt.js +2 -3
  150. package/tools/legacy-map.js +3 -5
  151. package/tools/merge.js +3 -3
  152. package/tools/mission.js +2 -3
  153. package/tools/plan.js +7 -6
  154. package/tools/preflight.js +3 -3
  155. package/tools/rebase.js +3 -3
  156. package/tools/recover.js +38 -13
  157. package/tools/reload.js +3 -6
  158. package/tools/resource.js +3 -5
  159. package/tools/review.js +3 -4
  160. package/tools/roster.js +2 -3
  161. package/tools/send.js +2 -2
  162. package/tools/spawn.js +8 -5
  163. package/tools/status.js +16 -8
  164. package/tools/teardown.js +3 -4
  165. package/tools/test.js +2 -2
  166. package/types/index.d.ts +35 -1
  167. package/ui-bundle/README.md +37 -3
  168. package/ui-bundle/client.cards.js +356 -0
  169. package/ui-bundle/client.denied.js +142 -0
  170. package/ui-bundle/client.dock.js +333 -0
  171. package/ui-bundle/client.helpers.js +271 -0
  172. package/ui-bundle/client.js +79 -1747
  173. package/ui-bundle/client.locales.js +247 -0
  174. package/ui-bundle/client.mode.js +206 -0
  175. package/ui-bundle/client.overlay.js +64 -0
  176. package/ui-bundle/client.styles.js +151 -0
  177. package/ui-bundle/client.toolview.js +82 -0
  178. package/ui-bundle/package.json +1 -0
  179. package/lib/doctor.js +0 -270
  180. package/lib/persistence.js +0 -349
  181. package/lib/store.js +0 -118
  182. /package/{lib → app}/briefing.js +0 -0
  183. /package/{lib → app}/spawn-support.js +0 -0
  184. /package/{lib → host}/compatibility.js +0 -0
  185. /package/{lib → host}/config.js +0 -0
  186. /package/{lib → host}/hmr.js +0 -0
  187. /package/{lib → host}/modeState.js +0 -0
  188. /package/{lib → host}/presets.js +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,160 @@
4
4
 
5
5
  ## 未发布(Unreleased)
6
6
 
7
+ ## 2.3.0(2026-10-23)
8
+
9
+ ### 新增
10
+
11
+ - **#87 live harness 可复现**:`tests/live/package.json` 固定 `playwright`(含独立 lockfile),
12
+ 根脚本 `npm run test:live` 解析本地/`NODE_PATH` 并在缺失时打印两条确切安装命令后退出 2(无堆栈);
13
+ 七个 live 脚本统一走该解析器;`--check` 模式无需浏览器即可在 clean clone 上通过;
14
+ `tests/README.md` 以可复现路径为默认、全局安装作为 fallback。
15
+ - **#47 live 套件安全门禁 + nightly**:live 入口强制 `DSH_LIVE=1` 且 `DSH_HOME` 必须是 `os.tmpdir()` 的严格子目录,
16
+ 拒绝真实 home 及其 `profiles/*`(错误信息指向 `npm run test:live`);CI 的浏览器 live job 移出 push/PR,
17
+ 改为 nightly `schedule`/`workflow_dispatch` 且主机版本固定在 `0.2.1-rc.2`;输出首行打印解析后的
18
+ `DSH_HOME` 与 host 版本。
19
+ - **#83 工程哲学**:根目录 `PRINCIPLES.md`(10 条原则,每条带明确的 refusal 与可解析的 enforcement pointer)、
20
+ `docs/adr/` 四份带日期的 ADR(ports-as-data、单一 state.json、allowAgentControl 默认、surface budget,
21
+ 均含 rejected alternatives)、`docs/` 非目标审计、PR 模板的 principle 问题;
22
+ `scripts/check-philosophy.mjs` + `npm run docs:check:philosophy` 在 pointer 失效时失败。
23
+ - **#84 概念预算**:`tower_doctor` 打印 `config keys: 64 · sections: 15 · tools: 27 (baseline 2026-10-23)`;
24
+ 生成的 config/tools 参考头部携带同一计数与日期;`scripts/surface-baseline.json` + `npm run surface:check`
25
+ 在 surface 增长而无 ADR/等量删除时失败(fixture 覆盖);64 个 config key 的 core/tuning/niche 分类
26
+ (含 3 条 niche removal note)与 27 个工具的 core/optional 分类。
27
+ - **#85 事故实践**:`docs/operations/incident.md` 六字段模板(Symptom/Introduced in/Blast radius/Root cause/
28
+ Regression test/Released in),从 CONTRIBUTING 与 SECURITY 链接;`regression` label 语义固定为
29
+ “worked in a released version, then stopped”;`scripts/regression-audit.mjs` 离线审计已关闭 regression issue
30
+ 的 `Regression test:` 路径;#68 已按模板回填并通过审计。
31
+
32
+ ### 修复
33
+
34
+ - 发布前 CI flake:lock 心跳测试改为轮询等待一次 tick,避免高负载 Node 24 下固定 60ms 窗口偶发失败。
35
+ ## 2.2.0(2026-10-23)
36
+
37
+ ### 新增
38
+
39
+ - **#74 恢复动作到达模型**:`ERROR_CODES` 统一为 35 条目录(description/trigger/retryable/nextAction);
40
+ `renderErrorMessage()` 在消息尾部追加恰好一个 `— next: …` 片段,retryable 条目附加 `(retryable)`;
41
+ 无 nextAction 的条目保持字节不变;`TowerToolError`/`classifyError()` 均从目录解析字段;
42
+ `errors.generated.md` 与 errors.md 的 Code/Retryable/Next action 表由同一目录生成。
43
+ - **#75 领域事件**:域转换返回 `{ state, events }`;app 持久化后发出稳定事件
44
+ `tower.mission.planned|completed|reviewed|merged|abandoned`(携带 missionId/revision/at,merged 另有 commit),
45
+ `tower/<tool>` 作为弃用别名保留一个 minor;`ports/events.js` 新增 `EventPort`(no-op 默认,Cordis `ctx.emit` 绑定);
46
+ `docs/reference/events.md` 由事件表生成并纳入 `--check`。
47
+ - **#71 ui-bundle 纳入 fitness gate**:`client-no-node`/`client-size`/`client-ambient` 三条阻塞规则;
48
+ `client.js` 从 1,980 行拆为 entry + 9 个按渲染面切分的 chunk(每个 ≤400 行,总行数 +9.95%);
49
+ `domain:metrics` 打印 ui-bundle 行;`pack:check` 额外要求 ui-bundle/package.json 与 cordis.patch.yml。
50
+
51
+ ### 变更
52
+
53
+ - **#69 app 依赖 ports 而非 adapters**:9 个运行时 Port 契约 + `assertPorts`;组合根一次性构造 adapter 并注入;
54
+ `app-ports-only`/`composition-root`/`port-shape` 三条 fitness 规则;内存 adapter 全生命周期测试。
55
+ - **#70 退役 lib/**:23 个文件 `git mv` 到 adapters/host/app;新增 `no-lib-from-core`、`host-scope` 阻塞规则;
56
+ ambient 豁免降为 2 条 I/O 边界;`lib/` 不再存在。
57
+ - **#80 fitness 豁免有据**:硬编码 allow-list 改为 `scripts/architecture-waivers.json` 中带 reason/owner/ref 的豁免;
58
+ 新增阻塞 `waiver-contract`,无理由豁免/未知规则/ambient 无 match/domain 豁免一律失败;statement-aware ambient 扫描。
59
+
60
+ ### 修复
61
+
62
+ - **发布前 hygiene(F2–F5)**:根级 `*.log` 忽略规则;控制流 `)` 后正则的 scanner 加固;
63
+ `tsconfig` 移除已删除 lib 路径;COMPATIBILITY/ui-bundle 文档中的旧 lib 路径更新。
64
+ ## 2.1.2(2026-10-23)
65
+
66
+ ### 修复
67
+
68
+ - **#107 survey 依赖满足门禁**:`dependencySatisfied()` 让 `merged`/`abandoned` 继续满足任意类型依赖,
69
+ 并额外接受 `kind === 'survey' && status === 'completed'`(survey 的终态成功,没有可合并分支);
70
+ `dependencyBlockers()` 对活跃 state 与归档索引一视同仁,因此归档的已完成 survey 也满足依赖。
71
+ build→build 仍要求 `merged`,不为 survey 发明新状态。
72
+ - **#108 多路 review 同 tip 记录**:`reviewAdmission()` 允许 `active` mission 的后续 route 在同一
73
+ reviewed tip 上追加 verdict,并入同一 round(`appended:true`),不再次翻转状态;tip 已移动时以
74
+ 明确说明拒绝。`app/review.js` 与输出 schema 同步。
75
+
76
+ ### 变更
77
+
78
+ - **Tower 交付流程**:2.1.2 的 #107/#108 由 Tower mission M2 实施(worker → reviewer clean R2 →
79
+ preflight → merge);review 后按文档化路径扩了 mission scope 以覆盖根文档,`tower_review` 契约
80
+ 描述更新为「completed 起新 round / active 可追加同 tip route」。
81
+ ## 2.1.1(2026-10-23)
82
+
83
+ ### 修复
84
+
85
+ - **#92 PID 复用导致 state 锁永久不可恢复**:持有 state/git 锁期间,记录中的 `at` 现在按固定间隔
86
+ 心跳重写(默认 1000 ms,`unref` 定时器,释放时清除且任何写失败都不会抛给调用方)。回收条件
87
+ 从"PID 已死"改为"`ESRCH` 立即回收,或心跳静默超过 3 × 间隔(默认 3 s)且改名后字节校验通过",
88
+ 因此即使 PID 被无关进程复用也能安全回收,且仍在默认 5 s 的 state 锁等待预算内。修复前
89
+ `tower_recover clear-stale-lock` 会拒绝 PID 仍存活的锁;新增 `force: true`(仍需
90
+ `confirm:"CLEAR_STALE_LOCK"`)可清理"PID 存活但心跳静默"的锁,输出
91
+ `{pid, startedAt, ageMs, heartbeatAgeMs}` 并始终改名为 `.recovered-<ts>` 备份、绝不 unlink。
92
+ 自动回收一个"PID 存活但心跳过期"的记录时保留 `state.lock.reclaimed-<ts>-<id>` 审计备份;
93
+ `ESRCH` 快速路径行为不变。`tower_doctor` 新增 `stateLock` 报告(`ageMs`、`heartbeatAgeMs`、
94
+ `staleAfterMs`、`stale`),锁记录新增 `since`(记录写入时间;旧记录回退到 `at`)。
95
+ 改名 → 校验字节 → 再处置的顺序保持不变,回收中途被替换的记录会被还原而不会丢失。
96
+
97
+ - **#93 git mutex 等待按合并超时预算**:持有 git 锁的一方记录 `{kind:'git', phase}`,并让
98
+ `tower_init`/`tower_teardown`/`tower_rebase`/`tower_spawn` 按 `merge.timeoutMs`
99
+ (默认 900000)+ 60 s 边距等待,慢 pre-merge hook 不再让并发操作在 60 s 失败。超时错误携带
100
+ `{holderPid, holderPhase, heldMs, waitedMs}`(消息形如 `held by pid 1234 (merge) for 92 s;
101
+ waited 60 s`),`tower_status`/`tower_doctor` 新增 `gitLock {held,pid,phase,heldMs}` 与
102
+ `git-lock` 检查;state 锁的 5 s 预算与 merge 超时默认值保持不变。
103
+
104
+ ### 变更
105
+
106
+ - **#106 执行规范**:新增 `docs/agents/workflow.md`,把 Matt Pocock 技能流程(triage → spec →
107
+ tickets → TDD → 两轴 code-review → retro)与 Tower 交付协议(worktree mission、scope 围栏、
108
+ review/merge 门禁)合并为仓库唯一执行规范,`AGENTS.md` 指向它;明确 tag-only 与 published
109
+ 两种发布模式以及人工门禁(npm publish/2FA、保护分支变更)。
110
+ ## 2.1.0(2026-10-23)
111
+
112
+ ### 新增
113
+
114
+ - **#72 state 迁移**:`state.json` 的 `schemaVersion` 升到 3(v1→v2→v3 有序迁移链),并新增有序、纯函数式的迁移注册表
115
+ (`domain/state/migrations.js`)。旧 state 在持有 state 锁的加载中自动迁移:先写
116
+ `state.json.v<N>.bak` 备份,再原子替换、`revision` 只 +1、并向 activity 追加
117
+ `state.migrated {from,to}`;只读工具只在内存中迁移,不改写文件。由更新插件写入的 state 现在
118
+ 以 `[tower:state-version-unsupported]` 拒绝(不再误报 `state-corrupt`),且绝不会被降级重写;
119
+ 迁移抛错以 `[tower:state-migration-failed]` 报出,原文件保持字节不变。`tower_doctor` 新增
120
+ `stateSchema {currentVersion,supportedVersion,migrations,status}` 与 `state-schema` 检查。
121
+ - **#73 终态任务归档**:`merged`/`abandoned` 且 worktree 已移除的 mission 在达到可配置策略
122
+ (`archive.enabled`/`archive.afterDays`/`archive.maxMissions`)后,于下一次变更边界移入
123
+ `<git-common-dir>/dsh-tower/archive/index.json` 与 `archive/<year>.json`(各自带 `archiveVersion`
124
+ 头)。归档保留完整 mission、tasks、reviews、findings 与完成报告,state.json 只保留活跃任务,
125
+ 因此每次 mutation 不再重写全部历史;归档按 id 幂等,进程崩溃后可重试。`tower_status` 默认只列
126
+ 活跃任务,`includeArchived:true` 在活跃页后追加 `archived:true` 的压缩摘要(不含报告正文),
127
+ `missionId` 详情会从 year 文件加载;归档损坏时降级为 `archive.unreadable` 诊断而不是抛错。
128
+ `tower_doctor` 新增 `archive {missions,archivedMissions,stateBytes,archiveBytes,years,files,
129
+ unreadable,policy}` 与 `archive` 检查。`tower_preflight`/`tower_merge` 对已归档 mission 保持原行为,
130
+ 新 mission 的编号也会避开归档 id。
131
+
132
+ - **#88 Unicode mission 标题**:`validateTitle` 改为接受可打印 Unicode(NFC 归一化、120 码点上限),
133
+ 显式拒绝 bidi override(U+202A–202E、U+2066–2069)与零宽字符(U+200B–200D、U+FEFF),控制/格式/
134
+ 代理码点单独报错;mission id 仍为 ASCII。CJK/西里尔/日韩/emoji 标题可完整往返 `tower_plan` →
135
+ state → `tower_status` → `MISSIONS.md` → briefing,board 表格保持对齐。
136
+ ### 变更
137
+
138
+ - **#89 completion report 上限**:`tower_complete.report` 新增 `gates.maxReportBytes`
139
+ (默认 32768,按 **UTF-8 字节**而非字符计)硬上限;超限以 `invalid-argument` 拒绝并给出
140
+ `nextAction`(把全文写进 mission worktree 的文件并引用路径),绝不截断。存储的完成报告新增
141
+ `reportBytes`;`tower_status` 的分页摘要只返回大小,完整正文只在 `missionId` 详情中返回;
142
+ `tower_doctor` 新增 `reports` 检查并报告仓库中最大的报告。旧 state 无需迁移:缺失
143
+ `reportBytes` 时在读取时按 UTF-8 字节推导。
144
+
145
+ - **#90 scope 大小写**:`tower_init` 在检出目录上做一次性大小写探测并记录 `caseInsensitive`
146
+ (探测失败记录 `null`,比较继续使用安全的 case-sensitive 默认值);`tower_doctor` 报告
147
+ `caseInsensitive` 与生效的 `comparisonMode`。在大小写不敏感的文件系统上,`fileInScope` 与
148
+ `scopesOverlap` 折叠双方,因此 `src/Api/**` 与 `src/api/**` 视为重叠(plan 自动串行、扩 scope
149
+ 被拒绝),仅大小写不同的改动不再被 out-of-scope 门禁拒绝;在大小写敏感的检出上仍拒绝,但 blocker
150
+ detail 会指出折叠大小写后本可匹配的 scope 条目。存储的 scope 文本保持原样。state schema 升到 3,
151
+ v2 → v3 迁移回填 `caseInsensitive: null`。
152
+
153
+ - **#86 平台矩阵 CI**:`test` job 改为显式 include 矩阵——ubuntu Node 22/24 与 Windows Node 22
154
+ (`test (22)`/`test (24)`/`test (22, windows)`),required context 名称不变;`win32Lower`/`isInside`、
155
+ `shellSpec`、`renameWithRetry`、`unlinkWithRetry`、worktree `compareKey` 均改为可注入 seam,并在任意
156
+ OS 上断言两条分支。Windows leg 已加入 `main` 的 required checks。
157
+
158
+ - **#91 Windows 测试确定性**:新增 `tests/helpers/cleanup.mjs`(有界重试 + 隔离 rename + `os.tmpdir()`
159
+ 守卫),37 个测试文件、106 处直接 `rmSync` 全部迁移;导入时把 Windows CI 的 8.3 短路径 TEMP
160
+ 规范化,消除短/长路径比较失败;Windows 全量套件连续 5 次全绿。
7
161
  ## 2.1.0-beta.1(2026-10-22)
8
162
 
9
163
  **预发布**:本版本发布到 npm 的 `next` dist-tag,`latest` 继续保持 `2.0.4`,直到出现稳定版本。
package/COMPATIBILITY.md CHANGED
@@ -5,6 +5,11 @@ is shipped in the npm package; doctor reads it locally and never contacts a regi
5
5
 
6
6
  | dsh version | tag / commit | verified on | what was run |
7
7
  |---|---|---|---|
8
+ | 0.2.1-rc.2 | v2.3.0 | 2026-10-23 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (573), `npm run docs:check`, four generated docs checks, philosophy + surface gates, live `--check` |
9
+ | 0.2.1-rc.2 | v2.2.0 | 2026-10-23 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (536), `npm run docs:check`, four generated docs checks |
10
+ | 0.2.1-rc.2 | v2.1.2 | 2026-10-23 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (495), `npm run docs:check`, generated docs checks |
11
+ | 0.2.1-rc.2 | v2.1.1 | 2026-10-23 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (490), `npm run docs:check`, generated docs checks |
12
+ | 0.2.1-rc.2 | v2.1.0 | 2026-10-23 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (479), `npm run docs:check`, generated docs checks |
8
13
  | 0.2.1-rc.2 | v2.1.0-beta.1 | 2026-10-22 | mount smoke (29 tower tools, `/tower`, 3 skills, 2 presets, single bundle row), `npm test` (413), `npm run docs:check`, generated docs checks |
9
14
  | 0.2.1-rc.2 | 6ee19de | 2026-10-21 | `npm test` (229), `npm run docs:check`, offline `ui-bundle` tests |
10
15
  | 0.2.1-rc.2 | 5b0accd | 2026-10-18 | `npm test` (219), `npm run docs:check` |
@@ -24,7 +29,7 @@ Tower work. This is provenance, not a feature-detection mechanism.
24
29
 
25
30
  Add a row in the release PR, before tagging, after running the offline suite and docs check on the
26
31
  host build being shipped. Name the release tag in cell 1 and the host build in cell 0; the other
27
- columns are read positionally by `lib/compatibility.js`, so do not insert or reorder them. Do not
32
+ columns are read positionally by `host/compatibility.js`, so do not insert or reorder them. Do not
28
33
  claim a live run that was not performed. The 2.1.0-beta.1 row is a pre-release: it becomes
29
34
  installable on the `next` dist-tag only after the checklist's publish step, and `latest` stays on
30
35
  2.0.4 until a stable release. The full six-step procedure is the
package/CONTRIBUTING.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  Thanks for your interest in `dsh-tower`. This is a DeepSeek Harness plugin; the repository root is the bundle itself (`package.json` + `cordis.patch.yml`).
4
4
 
5
+ ## Engineering philosophy and decisions
6
+
7
+ Two short documents say what this project refuses to do:
8
+
9
+ - [PRINCIPLES.md](PRINCIPLES.md) — the numbered refusals. Each one names what it
10
+ refuses and the file plus test or gate that enforces it. A principle whose
11
+ pointer does not resolve fails `npm run docs:check:philosophy`.
12
+ - [docs/adr/](docs/adr/README.md) — dated decision records for choices already
13
+ made (ports as data, one `state.json`, `allowAgentControl`, the surface
14
+ budget).
15
+
16
+ Every pull request answers **"which principle does this change touch, or which new
17
+ one does it require?"** in the PR template. "None" is not an answer: if no
18
+ existing refusal covers the change, say why a new principle is needed. The
19
+ concept budget and its classification table are in
20
+ [docs/reference/surface-budget.md](docs/reference/surface-budget.md): a config key
21
+ or tool may grow only with an ADR or a provably matching deletion, checked by
22
+ `npm run surface:check`.
23
+
5
24
  ## Landing changes
6
25
 
7
26
  `main` is protected. Every change lands through a pull request; direct pushes to `main` are rejected,
@@ -18,7 +37,16 @@ The context names come from the workflow job id and matrix values. Renaming a jo
18
37
  changes the required context; update the protection setting in the same PR, or the check never reports
19
38
  and every PR stays unmergeable. The setting and its verification command are in
20
39
  [SECURITY.md](SECURITY.md#repository-protection-on-main). The `mount-fixtures` job runs the seeded
21
- smoke failures for evidence but is deliberately **not** a required context.
40
+ smoke failures for evidence but is deliberately **not** a required context. The `live` job is nightly
41
+ (and manual-dispatch) only, runs on a pinned host matrix, and is evidence as well: it is never part of
42
+ a push or a PR gate.
43
+
44
+ The offline `test` matrix also has a `test (22, windows)` leg on `windows-latest` (Node 22). It runs the
45
+ offline suite and the offline gates, not the release/coverage steps, so the platform branches are
46
+ measured on a real Windows filesystem (see `tests/platform.test.mjs`). Its name is pinned through
47
+ `matrix.name` so the two required contexts above stay byte-identical. The Windows leg is **not** a
48
+ required context yet: #86 only asks that it exists and is green, and adding it to the protection setting
49
+ is a separate human step.
22
50
 
23
51
  There are no mandatory approvals, no CODEOWNERS gate, and **merging your own PR is allowed**: there is
24
52
  one maintainer, and a mandatory self-approval would be theatre. What replaces the second reviewer is a
@@ -32,8 +60,8 @@ four-line body, in this order:
32
60
 
33
61
  - `domain/**`
34
62
  - `app/**`
35
- - `lib/store.js`, `lib/persistence.js`, `lib/*lock*.js`
36
- - `scripts/architecture.mjs`
63
+ - `adapters/store.js`, `adapters/persistence.js`, `adapters/*lock*.js`
64
+ - `scripts/architecture.mjs`, `scripts/architecture-waivers.json`
37
65
  - `cordis.patch.yml`
38
66
 
39
67
  ```markdown
@@ -65,22 +93,37 @@ npm test # no dependencies to install; Node's built-in test runner only
65
93
 
66
94
  Requirements: Node.js 22+, Git. No package installation is needed for the offline suite.
67
95
 
96
+ The opt-in live suite is the exception: install it once with
97
+ `npm ci --prefix tests/live && npx --prefix tests/live playwright install chromium`, then run it with `npm run test:live`
98
+ against a disposable `DSH_HOME` under the OS temp directory and a booted isolated profile. The guard
99
+ refuses the operator's real `DSH_HOME` and any profile under `DSH_HOME/profiles`. `npm run test:live --
100
+ --check` validates the environment and Playwright resolution without a browser. See
101
+ [tests/README.md](tests/README.md#2-real-web-runtime-isolated-profile). The live suite is not part of
102
+ the push gates; CI schedules it nightly.
103
+
68
104
  ## What to change
69
105
 
70
106
  - `index.js` — Host entry: `/tower` command, mode guard, dynamic prompt section, `tower_control`.
71
- - `lib/` — the Tower domain (`tower.js`), tool definitions (`tools.js`), persistence (`store.js`, `modeStore.js`), Git helpers (`git.js`).
107
+ - `host/` — the composition root and session/process glue (`tool-registry.js`, `config.js`, `hmr.js`, `modeStore.js`, `doctor.js`); `adapters/` — the only I/O implementations (store, git, archive, leases, locks); `app/` — use cases; `domain/` — the pure core.
72
108
  - `worker.js` — the `tower-worker` preset policy (tool surface + protocol text).
73
109
  - `tests/` — add a regression test for every behavior change. The offline suite is `tests/*.test.mjs`; live-runtime scripts are documented in `tests/README.md`.
110
+ - `scripts/architecture.mjs` — the fitness rules; `scripts/architecture-waivers.json` — the only exemptions, each with a `reason` and an `owner`/`ref`. An entry without a reason fails the blocking `waiver-contract` rule, The three layering rules that keep the core pure — `domain-pure`, `host-scope`, and `no-lib-from-core` — cannot be waived at all. The `let` scan behind `host-scope` is statement-aware: every binding of every top-level declaration is recorded, and only `host/**` may hold ambient state.
74
111
 
75
112
  ## Pull request checklist
76
113
 
77
- 1. `npm test` passes locally (416 tests today, including protocol end-to-end against a real temporary Git repository and mock spawn coverage) and `npm run docs:check` passes.
114
+ 1. `npm test` passes locally (573 tests today, including protocol end-to-end against a real temporary Git repository and mock spawn coverage) and `npm run docs:check` passes.
78
115
  2. New behavior has a test. Bug fixes should add a test that fails before the fix.
79
116
  3. No secrets, tokens, personal paths, session logs, or generated artifacts are added. `.gitignore` covers local acceptance output; do not force-add it.
80
117
  4. Keep the public API and state-file formats compatible; if a change is breaking, call it out in `CHANGELOG.md` and bump the version.
81
118
  5. Update `README.md` / `README.en.md` when the user-visible behavior changes.
82
119
  6. If you touched `cordis.patch.yml`, `index.js`, `skills.js`, or `tools/`, run `npm run smoke:mount` (see below).
83
120
  7. A commit carries `Refs #n` or `Fixes #n`; a P0/P1 closure carries `verified-by:` and `commit:`, and `npm run audit:issues` reports no new failures.
121
+ 8. A regression closure carries the six-field record from
122
+ [docs/operations/incident.md](docs/operations/incident.md) and `npm run audit:regressions`
123
+ reports no failure; a `Regression test:` path must exist.
124
+ 9. If you added a config key or tool, `scripts/surface-baseline.json` and
125
+ [docs/reference/surface-budget.md](docs/reference/surface-budget.md) are updated in the
126
+ same PR, and `npm run surface:check` passes.
84
127
 
85
128
  ## Mount smoke
86
129
 
@@ -175,6 +218,29 @@ Exit codes: `0` — every checked closure names an existing test file (a missing
175
218
  export is missing or malformed. `--strict` promotes the missing-`commit:` warning to an error.
176
219
  `tracker/` is a local snapshot and is gitignored; regenerate it instead of committing it.
177
220
 
221
+ ## Regressions and incidents
222
+
223
+ The `regression` label means **"worked in a released version, then stopped"**. It is
224
+ narrower than `bug` and mutually exclusive with it for a shipped break: a user
225
+ could rely on the old behavior, so the record must say which release broke it and
226
+ which test prevents it from happening again.
227
+
228
+ A closed `regression` issue carries the six-field record from
229
+ [docs/operations/incident.md](docs/operations/incident.md) (symptom, introduced in,
230
+ blast radius, root cause and fix, regression test, released in). The one checkable
231
+ line is `Regression test:`, which must name a test file that exists. The same
232
+ `tracker/issues.json` export from above is audited offline:
233
+
234
+ ```powershell
235
+ npm run audit:regressions
236
+ npm run audit:regressions -- --tracker path/to/export.json --json
237
+ ```
238
+
239
+ Exit codes: `0` — every closed regression names an existing test; `1` — a line is
240
+ missing or the path is nonexistent/non-test; `2` — the export is missing or
241
+ malformed. The fixture in `tests/regression-audit.test.mjs` seeds a missing path
242
+ and a valid one, so the rule cannot quietly become prose.
243
+
178
244
  ## Commit style
179
245
 
180
246
  Conventional-commit prefixes are preferred: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`. Keep the subject line in the imperative mood. Add the issue trailer (`Refs #n` / `Fixes #n`) described above.
package/PRINCIPLES.md ADDED
@@ -0,0 +1,145 @@
1
+ # Engineering principles
2
+
3
+ These are the refusals this project makes. A principle with no refusal and no
4
+ enforcement pointer is a slogan, so every entry below names what it refuses and
5
+ the file plus test or gate that makes the refusal real. `npm run
6
+ docs:check:philosophy` fails when a pointer no longer resolves.
7
+
8
+ The product philosophy — when to use Tower, and when not to — is a separate
9
+ document: [docs/guide/scenarios-and-philosophy.md](docs/guide/scenarios-and-philosophy.md).
10
+ Decisions already made are recorded in [docs/adr/README.md](docs/adr/README.md).
11
+
12
+ ## 1. Evidence before done
13
+
14
+ We do not accept "done" as an assertion. A change is done when a named test,
15
+ command, or receipt can be re-run by someone else, and the closure names that
16
+ pointer instead of describing the work.
17
+
18
+ Refused because of this: one-line closures, "implemented on main, trust me",
19
+ and completion reports that list prose without an exit code.
20
+
21
+ Enforced by: `scripts/issue-audit.mjs` and `scripts/regression-audit.mjs`, with
22
+ the fixtures in `tests/issue-audit.test.mjs` and `tests/regression-audit.test.mjs`;
23
+ gate: `npm run audit:issues`.
24
+
25
+ ## 2. Gates before prose
26
+
27
+ We do not add a rule to a document that a machine could enforce. If a sentence
28
+ says "always" or "never", the same change must either point at an existing check
29
+ or admit that the sentence is `unowned`.
30
+
31
+ Refused because of this: warnings-only lint rules, "please remember to bump the
32
+ version", and instructions that only a careful reader can follow.
33
+
34
+ Enforced by: `scripts/architecture.mjs` and `scripts/check-docs.mjs`; tests in
35
+ `tests/architecture.test.mjs`; gate: `npm run architecture:check`.
36
+
37
+ ## 3. Ports are data, not a DI container
38
+
39
+ We do not add a service locator, decorators, or a reflection-driven container.
40
+ The adapter seam is a table of frozen runtime contracts that a test asserts, so a
41
+ reader can see every effectful call without booting the app.
42
+
43
+ Refused because of this: `reflect-metadata`, string-keyed service lookup, and
44
+ "the container will wire it" as an explanation for a dependency.
45
+
46
+ Enforced by: `ports/index.js` and `scripts/architecture.mjs`; contract tests in
47
+ `tests/memory-adapters.contract.test.mjs`; ADR:
48
+ `docs/adr/0001-ports-adapters-as-data.md`.
49
+
50
+ ## 4. One state file, one writer
51
+
52
+ We do not split mission state across per-mission files for diffability. One
53
+ `state.json` with one lock and one atomic write is the persistence contract;
54
+ archived missions move to year files only after they are terminal.
55
+
56
+ Refused because of this: per-mission JSON files, append-only forks of the state
57
+ tree, and any write that does not go through the lock.
58
+
59
+ Enforced by: `adapters/store.js` and `adapters/persistence.js`; tests in
60
+ `tests/persistence-contract.test.mjs`; ADR: `docs/adr/0002-single-state-json.md`.
61
+
62
+ ## 5. The human owns the mode
63
+
64
+ The model may toggle Tower mode, but it may not acquire ownership of it, resume a
65
+ user-paused goal, or merge without the gate. The default that lets the model
66
+ enter is paired with lock, ack, and resume-authorization checks rather than with
67
+ trust.
68
+
69
+ Refused because of this: an autonomous "always on" mode, auto-resuming a paused
70
+ goal, and a model-initiated merge that skips preflight.
71
+
72
+ Enforced by: `index.js` and `host/modeState.js`; tests in `tests/plugin.test.mjs`;
73
+ ADR: `docs/adr/0003-allow-agent-control-default-true.md`.
74
+
75
+ ## 6. The surface is a budget
76
+
77
+ We do not treat "highly configurable" as a feature. Every config key and tool is
78
+ a promise, so the counts are committed in `scripts/surface-baseline.json` and a
79
+ count may grow only with an ADR or a provably matching deletion.
80
+
81
+ Refused because of this: a new config key added because it was easy, and a tool
82
+ added without deciding which tool it replaces.
83
+
84
+ Enforced by: `scripts/surface-baseline.mjs` and
85
+ `docs/reference/surface-budget.md`; tests in `tests/surface.test.mjs`; gate:
86
+ `npm run surface:check`.
87
+
88
+ ## 7. Read-only means no writes
89
+
90
+ A tool that says read-only may not create a temp file, migrate state, or probe the
91
+ filesystem. Diagnostics either report the absence or return a warning; they never
92
+ make the repository healthier as a side effect.
93
+
94
+ Refused because of this: doctor probes, lazy migrations during inspection, and
95
+ "read-only" tools that repair things on first call.
96
+
97
+ Enforced by: `host/doctor.js` and `adapters/store.js`; tests in
98
+ `tests/lock-heartbeat.test.mjs` and `tests/state-migration.test.mjs`; gate:
99
+ `npm test`.
100
+
101
+ ## 8. Offline tests are the contract
102
+
103
+ `npm test` does not need a network, a browser, credentials, or a live host. A
104
+ proof that can only run on one machine is evidence for that machine, not a gate.
105
+
106
+ Refused because of this: a test that calls GitHub, a required model run in the
107
+ offline suite, and a live-only check that blocks a merge.
108
+
109
+ Enforced by: `tests/live-harness.test.mjs` and `scripts/mount-smoke/fixtures.mjs`;
110
+ gate: `npm run smoke:fixtures`.
111
+
112
+ ## 9. Generated references are generated
113
+
114
+ We do not hand-maintain a list that the source already knows. Docs under
115
+ `docs/reference/*.generated.md` are written by a generator, their count line
116
+ comes from `scripts/surface-baseline.json`, and a mismatch is a failed check
117
+ rather than a review comment.
118
+
119
+ Refused because of this: hand-editing a generated table, and two references that
120
+ disagree about the same count.
121
+
122
+ Enforced by: `scripts/generate-config-reference.mjs` and
123
+ `scripts/generate-tools-reference.mjs`; tests in `tests/surface.test.mjs`; gate:
124
+ `npm run docs:check:generated:config`.
125
+
126
+ ## 10. A regression keeps its test
127
+
128
+ We do not close a shipped regression with a sentence. A `regression` is anything
129
+ that worked in a released version and then stopped, and its closure carries the
130
+ six-field incident record plus a test path that exists.
131
+
132
+ Refused because of this: "fixed in the next release", a regression closed
133
+ without a test path, and a postmortem that names the cause but not the proof.
134
+
135
+ Enforced by: `docs/operations/incident.md`, `scripts/regression-audit.mjs`, and
136
+ `tests/regression-audit.test.mjs`; gate: `npm run audit:regressions`.
137
+
138
+ ## Non-goals
139
+
140
+ This file is not a governance process: there is no RFC queue, no architecture
141
+ review board, and no required approval. The list stays useful only while its
142
+ pointers resolve, which is exactly what `scripts/check-philosophy.mjs` checks.
143
+
144
+ Enforced by: `scripts/check-philosophy.mjs`; tests in
145
+ `tests/philosophy.test.mjs`; gate: `npm run docs:check:philosophy`.
package/README.en.md CHANGED
@@ -16,8 +16,9 @@ The repository root **is** the DSH bundle: `package.json` declares `dsh.bundle.p
16
16
 
17
17
  - **Configurable Tower mode.** `/tower` toggles a session-level stance that narrows the tool surface and injects the Tower protocol. `/tower on|off <instruction>` also forwards your instruction to the model in the same step. By default the model may also enter/leave with `tower_control` and a concrete reason; set `mode.allowAgentControl:false` for human-only control.
18
18
  - **Missions with real isolation.** Every mission gets its own branch and Git worktree; workers never touch your checkout.
19
- - **Review-gated merges.** When `review.requireReview:true`, a reviewer bound to the current branch tip must submit a `clean` / `merge` verdict before merge; the merge gate re-checks scope, staleness, task completion, review history, and worktree ownership before a `--no-ff` merge. Independent review is optional by default (the gate is off); use `reviewPolicy:"none"` + reason plus `gates.allowReviewExemption:true` for a single mission when the global gate is on.
19
+ - **Review-gated merges.** When `review.requireReview:true`, a reviewer bound to the current branch tip must submit a `clean` / `merge` verdict before merge; the merge gate re-checks scope, staleness, task completion, review history, and worktree ownership before a `--no-ff` merge. Independent review is optional by default (the gate is off); use `reviewPolicy:"none"` + reason plus `gates.allowReviewExemption:true` for a single mission when the global gate is on. Multi-route review is recorded per round: while a non-clean round keeps the mission `active`, a later reviewer may still submit its verdict for the same reviewed tip and it appends to that round. A completed `survey` dependency satisfies the dependency gate (a build dependency still must be merged).
20
20
  - **Durable coordination.** Roster, inbox, findings, reviews, and mission state are persisted under `.git/dsh-tower/`, so a restart does not lose the plan.
21
+ - **Bounded state growth.** Terminal missions whose worktree is gone move to `.git/dsh-tower/archive/` once `archive.afterDays`/`archive.maxMissions` says so, keeping their tasks, reviews, findings and completion report; mutations then rewrite only active work. `tower_status includeArchived` lists compact archived summaries, and `tower_doctor` reports archive size, count and policy.
21
22
  - **Safe by default.** While Tower mode is active, `create_goal`, `update_goal(resume)`, `todo_write`, the Team task board, and `spawn_teammate` are refused; a paused goal is never resumed automatically.
22
23
  - **Traceable finding lifecycle.** `tower_findings` lists, reads, and moves findings through `open → triaged → fixed` / `wontfix` (and can reopen); only the tower or a worker may change state, while reviewers can only read.
23
24
  - **Repository-level config.** Any repository can override review/merge/worktree/spawn policy in `<git-common-dir>/dsh-tower/config.json`; tool arguments override the repository layer, so no profile edit is needed per repository.
@@ -201,13 +202,13 @@ Workers use the bundled `tower-worker` preset (writable worktree surface); revie
201
202
  | Path | Contents |
202
203
  |---|---|
203
204
  | `package.json`, `cordis.patch.yml` | bundle manifest and loader patch (one `tower-bundle` group containing the Host, skills, and both presets) |
204
- | `index.js`, `lib/`, `worker.js`, `reviewer.js` | Host entry, protocol/config implementation, worker and reviewer preset policies |
205
+ | `index.js`, `host/`, `adapters/`, `app/`, `domain/`, `ports/`, `tools/`, `worker.js`, `reviewer.js` | Host entry; `domain/` pure core; `ports/` contracts; `adapters/` the only I/O; `app/` use cases; `host/` composition root and session glue; `tools/` one handler per tool; worker and reviewer preset policies |
205
206
  | `skills.js`, `skills/` | bundled agent skills (worker protocol, review checklist, recovery playbook) |
206
207
  | `docs/` | guides, reference, architecture, operations, FAQ and examples |
207
208
  | `locale/`, `icon.svg` | localization and icon |
208
209
  | `tests/` | pure-function, protocol end-to-end, and plugin-level tests; optional isolated Web-runtime scripts |
209
210
  | `ui-bundle/` | optional Client widget (Tower tool cards, persistent Tower-mode chip, mode-aware denied rows, dock summary, transient overlay strip), not installed by default |
210
- | `.github/workflows/test.yml` | CI running the offline suite on Node 22/24, the release and coverage checks, and the Node 22 mount smoke and seeded fixtures |
211
+ | `.github/workflows/test.yml` | CI running the offline suite on Node 22/24 (Ubuntu) and Node 22 (Windows), the release and coverage checks, and the Node 22 mount smoke and seeded fixtures |
211
212
 
212
213
  ## Development and testing
213
214
 
@@ -216,7 +217,7 @@ npm test # node --test tests/*.test.mjs
216
217
  ```
217
218
 
218
219
  Hot-reload development: `npm run dev:hmr` adds this checkout to the current profile's HMR watch roots
219
- (one DSH restart); afterwards `/tower reload` or the automatic watcher applies `lib/` edits. See
220
+ (one DSH restart); afterwards `/tower reload` or the automatic watcher applies `host/`, `adapters/`, `app/` and `domain/` edits. See
220
221
  [docs/guide/hot-reload.md](docs/guide/hot-reload.md).
221
222
 
222
223
  The offline suite needs no DSH process and creates throwaway Git repositories in the OS temp directory. `tests/README.md` documents the isolated Web-runtime checks, model-driven acceptance, and the session-diagnostic scripts used while hardening the plugin.
@@ -227,11 +228,19 @@ Every push and PR runs the same set of jobs; each job backs one checkable claim
227
228
 
228
229
  | Job | Claim | Evidence |
229
230
  |---|---|---|
230
- | `test` (Node 22/24) | the offline suite, documentation consistency, generated references, the published file list, architecture, types, and domain purity all hold | `npm test`, `npm run docs:check`, the three `docs:check:generated*` checks, `pack:check`, `architecture:check`, `types:check`, `domain:check` |
231
+ | `test (22)`, `test (24)` (Ubuntu) | the offline suite, documentation consistency, generated references, the published file list, architecture, types, and domain purity all hold | `npm test`, `npm run docs:check`, the four `docs:check:generated*` checks, `pack:check`, `architecture:check`, `types:check`, `domain:check` |
232
+ | `test (22, windows)` (`windows-latest`, not required) | the same offline suite and offline gates hold on a real Windows filesystem, so path casing, the `pwsh` shell, and the rename/unlink retries are measured instead of assumed | `npm test` plus the same offline gates as Ubuntu (no release/coverage) |
231
233
  | `test`'s `Coverage (Node 22)` step | coverage is actually measured in CI (reported only: no threshold, no badge) | `npm run test:coverage` |
232
234
  | `test`'s `Release check` step (Node 22) | a tag is a claim: the version moved past the previous version tag, a pushed tag is `v<version>`, and the changelog carries release content | `npm run release:check` |
233
235
  | `mount` (Node 22) | the packed tarball, added through the plugin CLI to a throwaway profile, mounts in the real loader and registers the surface the bundle claims | `npm run smoke:mount` |
234
236
  | `mount-fixtures` (Node 22, not required) | the mount smoke still goes red for the right seeded reasons (a renamed tool id, a removed skills section), with one cached host install shared by all runs | `npm run smoke:fixtures -- --host-dir <cached host>` |
237
+ | `live (<pinned host build>)` (nightly/manual, not required) | the browser live suite still drives a real Web runtime on the pinned host-version matrix; push/PR runs only the browserless environment and resolution check | nightly runs `npm run test:live -- --script nightly`; push runs `npm run test:live -- --check` |
238
+
239
+ The `test` matrix pins each leg's context string through `matrix.name`: `test (22)`, `test (24)` and the new `test (22, windows)`. The first two are `main`'s required checks and keep their exact names; adding an `os` matrix key without an explicit name would rename them to `test (22, ubuntu-latest)`, and protected `main` would wait for a context that never reports. The Windows leg is Node 22 only and runs the offline suite and offline gates (no browser, no live suite, no release/coverage), in parallel with the existing jobs, so it does not extend the critical path. It is not a required context yet: making it green is the acceptance for #86, while adding it to the protection setting is a human step in #94.
240
+
241
+ The live suite downloads a browser and boots a real host, so it is out of push/PR: it runs on a nightly `schedule` (also `workflow_dispatch`) on a pinned host-version matrix, and its first output line prints the resolved `DSH_HOME` and host version. Push keeps only `--check` (environment + Playwright resolution, no browser). Playwright is pinned by `tests/live/package-lock.json`; the root `npm ci` downloads no browsers.
242
+
243
+ `tests/platform.test.mjs` injects `platform`/`rename`/`unlink` so both the win32 and POSIX branches are asserted on any OS; the only case that needs a real Windows filesystem (renaming over an open destination) is counted as `# SKIP` on Linux (`tests 11`, `skipped 1`), never silently absent. There is no macOS runner: macOS follows the same POSIX branch as Linux, which is a #86 non-goal.
235
244
 
236
245
  `mount` asserts the throwaway profile's **loaded surface**, never the text of `cordis.patch.yml`: 29 `tower_*` tools (27 `tools/*.js` modules plus `tower_mode` / `tower_control`), the 3 bundled skills, both the `tower-worker` and `tower-reviewer` presets, the `/tower` command, and exactly one top-level `tower-bundle` group row (the #66 invariant). It calls no model and opens no browser. Measured on the maintainer's Windows machine: about 100 seconds on a cold host (install included) and about 85 seconds with a cached host; the CI step allows 3 minutes (`timeout-minutes: 3`) inside the 5-minute `mount` job. A red run prints the resolved profile path, the host build, the loaded counts, and every unmet claim.
237
246
 
package/README.md CHANGED
@@ -18,6 +18,7 @@ Tower 模式与 mission/审查工作流的灵感来自 Moonshot AI 的 **Kimi Co
18
18
  - **任务级隔离。** 每个 mission 使用独立分支和 Git worktree,worker 不会碰到你的主检出。
19
19
  - **审查门禁合并。** 开启 `review.requireReview:true` 时,审查者针对当前分支 tip 给出 `clean` / `merge` 结论;合并门禁重新校验 scope、分支新鲜度、任务完成度、审查记录与 worktree 归属,通过后才执行 `--no-ff` 合并。独立审查默认可选(门禁关闭);全局开启时,单个 mission 用 `reviewPolicy:"none"` + 理由并开启 `gates.allowReviewExemption:true` 做更窄豁免。
20
20
  - **可恢复的协作状态。** roster、inbox、findings、reviews 与 mission 状态持久化在 `.git/dsh-tower/` 下,重启不会丢计划。
21
+ - **受控的状态增长。** worktree 已移除的终态任务在达到 `archive.afterDays`/`archive.maxMissions` 后移入 `.git/dsh-tower/archive/`,保留 tasks、reviews、findings 与完成报告;此后 mutation 只重写活跃工作。`tower_status includeArchived` 列出压缩后的归档摘要,`tower_doctor` 报告归档大小、数量与策略。
21
22
  - **默认安全。** Tower 模式开启期间拒绝 `create_goal`、`update_goal(resume)`、`todo_write`、Team 任务板与 `spawn_teammate`;被暂停的 goal 不会被自动恢复。
22
23
  - **可追溯的 finding 生命周期。** `tower_findings` 列表、读取并按 `open → triaged → fixed` / `wontfix` 流转,也可重新打开;只有 tower 或 worker 能改状态,reviewer 只能读。
23
24
  - **仓库级配置。** 每个仓库可用 `<git-common-dir>/dsh-tower/config.json` 覆盖 review/merge/worktree/spawn 策略,工具参数再覆盖仓库配置;无需为不同仓库改 profile。
@@ -174,7 +175,7 @@ review/merge 阶段死锁。
174
175
  ## 安全与约束
175
176
 
176
177
  - **门禁不全绿不合并。** `tower_preflight` 是只读检查,返回稳定的 blocker code;存在 blocker 时 `tower_merge` 拒绝合并。
177
- - **审查绑定提交。** 开启 `review.requireReview` 时,审查后分支 tip 变化会报 `tip-moved`,必须重新审查;reviewer 使用只读 preset,若在审查期间改动分支,提交审查会被拒绝。
178
+ - **审查绑定提交。** 开启 `review.requireReview` 时,审查后分支 tip 变化会报 `tip-moved`,必须重新审查;reviewer 使用只读 preset,若在审查期间改动分支,提交审查会被拒绝。非 clean 的一轮审查会让 mission 回到 active,此后其他 route 仍可为同一 reviewed tip 提交结论并追加到同一轮;依赖为 `completed` 的 survey 视为已满足(build 依赖仍需 merged)。
178
179
  - **scope 强制。** mission 只能修改其 scope glob 匹配的文件,越界会以 `out-of-scope` 阻止合并。
179
180
  - **worktree 归属。** 脏工作树、运行中的 worker、已关闭 mission 的分支都会阻止合并或 teardown。
180
181
  - **goal 隔离。** 进入 Tower 模式会暂停活跃 goal 并记录;退出不会自动恢复,需要你自行恢复。
@@ -199,11 +200,11 @@ worker 与 reviewer 是使用内置 `tower-worker` preset 的普通 DSH 子代
199
200
  | 路径 | 内容 |
200
201
  |---|---|
201
202
  | `package.json`、`cordis.patch.yml` | bundle 清单与 loader patch(单个 `tower-bundle` group,内含 Host、skills 与两个 preset) |
202
- | `index.js`、`lib/`、`worker.js` | Host 入口、协议实现、worker preset 策略 |
203
+ | `index.js`、`host/`、`adapters/`、`app/`、`domain/`、`ports/`、`tools/`、`worker.js` | Host 入口;`domain/` 纯核心;`ports/` 契约;`adapters/` 唯一 I/O;`app/` 用例;`host/` 组合根与会话胶水;`tools/` 每工具一个 handler;worker preset 策略 |
203
204
  | `locale/`、`icon.svg` | 本地化与图标 |
204
205
  | `tests/` | 纯函数、协议 e2e、插件级测试;可选的真实 Web 运行时脚本 |
205
206
  | `ui-bundle/` | 可选 Client 小挂件(Tower 工具卡片、Tower·on 状态 chip、模式感知的 disabled 行、dock 摘要、临时 overlay 状态条),默认不安装 |
206
- | `.github/workflows/test.yml` | CI:Node 22/24 离线套件、release 与覆盖率检查、Node 22 挂载 smoke 与种子失败 fixtures |
207
+ | `.github/workflows/test.yml` | CI:Ubuntu 上 Node 22/24 与 Windows 上 Node 22 的离线套件、release 与覆盖率检查、Node 22 挂载 smoke 与种子失败 fixtures |
207
208
 
208
209
  ## 开发与测试
209
210
 
@@ -211,7 +212,7 @@ worker 与 reviewer 是使用内置 `tower-worker` preset 的普通 DSH 子代
211
212
  npm test # node --test tests/*.test.mjs
212
213
  ```
213
214
 
214
- 热重载开发:`npm run dev:hmr` 把本仓库加入当前 profile 的 HMR watch root(需重启 DSH 一次),之后编辑 `lib/` 代码可用 `/tower reload` 或自动 watch 生效;详见 [docs/guide/hot-reload.md](docs/guide/hot-reload.md)。
215
+ 热重载开发:`npm run dev:hmr` 把本仓库加入当前 profile 的 HMR watch root(需重启 DSH 一次),之后编辑 `host/`、`adapters/`、`app/`、`domain/` 代码可用 `/tower reload` 或自动 watch 生效;详见 [docs/guide/hot-reload.md](docs/guide/hot-reload.md)。
215
216
 
216
217
  离线套件不需要 DSH 进程,只在系统临时目录创建一次性 Git 仓库。隔离 Web 运行时检查、模型驱动验收和会话诊断脚本见 `tests/README.md`。
217
218
 
@@ -221,11 +222,19 @@ npm test # node --test tests/*.test.mjs
221
222
 
222
223
  | Job | 声明 | 证据 |
223
224
  |---|---|---|
224
- | `test`(Node 22/24) | 离线套件、文档一致性、生成文档、发布文件清单、架构、类型与 domain 纯度全部成立 | `npm test`、`npm run docs:check`、三个 `docs:check:generated*`、`pack:check`、`architecture:check`、`types:check`、`domain:check` |
225
+ | `test (22)`、`test (24)`(Ubuntu) | 离线套件、文档一致性、生成文档、发布文件清单、架构、类型与 domain 纯度全部成立 | `npm test`、`npm run docs:check`、四个 `docs:check:generated*`、`pack:check`、`architecture:check`、`types:check`、`domain:check` |
226
+ | `test (22, windows)`(`windows-latest`,非必需检查) | 同一套离线套件与离线 gates 在真实 Windows 文件系统上成立;路径大小写、`pwsh` shell、rename/unlink 重试都不是只在 Linux 上被假设 | `npm test` + 与 Ubuntu 相同的离线 gates(不跑 release/coverage) |
225
227
  | `test` 的 `Coverage (Node 22)` 步骤 | 覆盖率在 CI 中真实测量(只报告,不设阈值、不加徽章) | `npm run test:coverage` |
226
228
  | `test` 的 `Release check`(Node 22)步骤 | tag 是一种声明:版本已越过上一个版本 tag、推送的 tag 是 `v<version>`、CHANGELOG 带有发布内容 | `npm run release:check` |
227
229
  | `mount`(Node 22) | 打出的 tarball 经 plugin CLI 装进一次性 profile 后,真实 loader 挂载并注册了 bundle 声称的表面 | `npm run smoke:mount` |
228
230
  | `mount-fixtures`(Node 22,非必需检查) | mount smoke 仍会因正确的种子失败而变红(改名一个工具 id、删除 skills 段),三次运行共用一次缓存的 host 安装 | `npm run smoke:fixtures -- --host-dir <缓存的 host>` |
231
+ | `live (<固定 host 版本>)`(nightly/手动,非必需检查) | 浏览器 live suite 在固定 host 版本矩阵上仍能驱动真实 Web 运行时;push/PR 只做无浏览器的环境与解析检查 | nightly 跑 `npm run test:live -- --script nightly`;push 跑 `npm run test:live -- --check` |
232
+
233
+ `test` 的三条 leg 在 job 内用 `matrix.name` 固定各自的 context 字符串:`test (22)`、`test (24)` 与新增的 `test (22, windows)`。前两条是 `main` 的必需检查,名字逐字节不变;把 `os` 直接塞进 matrix 会把它们改名成 `test (22, ubuntu-latest)`,保护规则将永远等不到该 context,所以矩阵用显式 `include` + `name`。Windows leg 只跑 Node 22 和离线套件/离线 gates(无浏览器、无 live suite、无 release/coverage),与现有 job 并行,不延长关键路径。它目前不是必需 context:让它变绿是本 issue 的验收,把它加入保护设置是 #94 的人工步骤。
234
+
235
+ live suite 需要下载浏览器并真实启动 host,因此移出 push/PR,改为 nightly `schedule`(另支持 `workflow_dispatch`),在固定 host 版本矩阵上运行;它的输出第一行打印解析后的 `DSH_HOME` 与 host 版本。push 上只保留 `--check`(环境 + Playwright 解析,无浏览器)。Playwright 由 `tests/live/package-lock.json` 固定,root 的 `npm ci` 不下载浏览器。
236
+
237
+ `tests/platform.test.mjs` 用注入的 `platform`/`rename`/`unlink` 在任意 OS 上同时断言 win32 与 POSIX 两条分支;唯一需要真实 Windows 的“rename 覆盖被打开的目标文件”用例在 Linux 上以 `# SKIP` 计入 TAP(`tests 11`、`skipped 1`),而不是静默消失。macOS 不单设 runner:它与 Linux 走同一条 POSIX 分支,这是 #86 的非目标。
229
238
 
230
239
  `mount` 断言一次性 profile 的**已加载表面**,而不是 `cordis.patch.yml` 的文本:29 个 `tower_*` 工具(27 个 `tools/*.js` 模块 + `tower_mode` / `tower_control`)、3 个内置 skill、`tower-worker` / `tower-reviewer` 两个 preset、`/tower` 命令,以及唯一一个顶层 `tower-bundle` group 行(#66)。它不调用模型、不打开浏览器。在维护者的 Windows 机器上实测:host 冷装(含安装)约 100 秒,host 已缓存约 85 秒;CI 中该步骤上限为 3 分钟(`timeout-minutes: 3`),`mount` job 上限 5 分钟。红叉会打印解析后的 profile 路径、host build、已加载计数与每一条未满足的声明。
231
240
 
package/SECURITY.md CHANGED
@@ -42,6 +42,12 @@ gh api repos/louifinn/dsh-tower/branches/main/protection \
42
42
 
43
43
  Please do **not** publish exploit details in a public issue. Open a minimal issue titled `security: request private contact` describing only the impact category (for example “merge gate bypass”), and a maintainer will arrange a private channel. Include the affected version and, if possible, a minimal reproduction in the private follow-up.
44
44
 
45
+ A security fix that shipped and then stopped working is a regression, not an
46
+ ordinary bug. Use the six-field record in
47
+ [docs/operations/incident.md](docs/operations/incident.md) (including the
48
+ `Regression test:` path) once the private report is resolved; the public closure
49
+ still carries no exploit detail.
50
+
45
51
  ## Supported versions
46
52
 
47
53
  Security fixes target the latest released `2.x` line (currently `2.0.4`). Older pre-release snapshots