dsh-tower 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +152 -0
- package/COMPATIBILITY.md +39 -0
- package/CONTRIBUTING.md +157 -2
- package/README.en.md +46 -3
- package/README.md +44 -3
- package/SECURITY.md +27 -0
- package/adapters/default.js +12 -0
- package/adapters/store-fs.js +6 -0
- package/adapters/vcs-git.js +4 -0
- package/app/archive.js +190 -0
- package/app/batch.js +79 -0
- package/app/board.js +27 -0
- package/app/checkCompletion.js +89 -0
- package/app/enrichedMissions.js +26 -0
- package/app/findings.js +78 -0
- package/app/gate.js +126 -0
- package/app/init.js +291 -0
- package/app/merge.js +573 -0
- package/app/messages.js +66 -0
- package/app/mission.js +37 -0
- package/app/missionContext.js +14 -0
- package/app/plan.js +50 -0
- package/app/provision.js +193 -0
- package/app/read.js +113 -0
- package/app/rebase.js +302 -0
- package/app/recording.js +72 -0
- package/app/review.js +147 -0
- package/app/roster.js +123 -0
- package/app/spawn.js +125 -0
- package/app/teardown.js +205 -0
- package/app/tower.js +552 -0
- package/cordis.patch.yml +61 -53
- package/docs/README.md +3 -1
- package/docs/agents/domain.md +38 -0
- package/docs/agents/issue-tracker.md +46 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/architecture/overview.md +114 -13
- package/docs/board.md +72 -0
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/install-upgrade-migration.md +25 -1
- package/docs/guide/release.md +125 -0
- package/docs/operations/security.md +47 -2
- package/docs/reference/blockers.md +1 -1
- package/docs/reference/config.generated.md +70 -0
- package/docs/reference/config.md +8 -1
- package/docs/reference/doctor.md +58 -8
- package/docs/reference/errors.generated.md +41 -0
- package/docs/reference/errors.md +7 -3
- package/docs/reference/tools.generated.md +33 -0
- package/docs/reference/tools.md +7 -6
- package/docs/zh/README.md +3 -2
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/guide/install-upgrade-migration.md +18 -1
- package/docs/zh/operations/security.md +30 -1
- package/docs/zh/reference/blockers.md +1 -1
- package/docs/zh/reference/config.md +8 -1
- package/docs/zh/reference/doctor.md +35 -2
- package/docs/zh/reference/errors.md +6 -2
- package/docs/zh/reference/tools.md +3 -3
- package/domain/collab/batch.js +112 -0
- package/domain/collab/messages.js +127 -0
- package/domain/collab/roster.js +158 -0
- package/domain/collab/spawn.js +188 -0
- package/domain/config/schema.js +427 -0
- package/domain/contracts/outputs.js +305 -0
- package/domain/contracts/tool-inputs/collaboration.js +58 -0
- package/domain/contracts/tool-inputs/findings.js +29 -0
- package/domain/contracts/tool-inputs/index.js +94 -0
- package/domain/contracts/tool-inputs/integration.js +37 -0
- package/domain/contracts/tool-inputs/missions.js +95 -0
- package/domain/contracts/tool-inputs/ops.js +75 -0
- package/domain/contracts/tool-inputs/shared.js +25 -0
- package/domain/errors.js +98 -0
- package/domain/gate.js +125 -0
- package/domain/integration/gate.js +190 -0
- package/domain/integration/init.js +261 -0
- package/domain/integration/liveness.js +31 -0
- package/domain/integration/merge.js +377 -0
- package/domain/integration/provision.js +124 -0
- package/domain/integration/rebase.js +236 -0
- package/domain/integration/review.js +52 -0
- package/domain/integration/teardown.js +118 -0
- package/domain/mission.js +68 -0
- package/domain/missions/completion.js +167 -0
- package/domain/missions/findings.js +201 -0
- package/domain/missions/lifecycle.js +186 -0
- package/domain/missions/plan.js +149 -0
- package/domain/missions/review.js +179 -0
- package/domain/planning.js +121 -0
- package/domain/read/enrichedMissions.js +58 -0
- package/domain/read/missionContext.js +53 -0
- package/domain/read/views.js +141 -0
- package/domain/report.js +67 -0
- package/domain/roster.js +40 -0
- package/domain/scope.js +177 -0
- package/domain/state/archive.js +257 -0
- package/domain/state/migrations.js +145 -0
- package/domain/state/schema.js +239 -0
- package/domain/state/semantics.js +117 -0
- package/domain/state.js +183 -0
- package/domain/summary.js +116 -0
- package/domain/vocabulary.js +179 -0
- package/index.js +63 -20
- package/lib/archive.js +279 -0
- package/lib/briefing.js +176 -0
- package/lib/caseSensitivity.js +66 -0
- package/lib/compatibility.js +108 -0
- package/lib/config.js +33 -375
- package/lib/doctor.js +275 -4
- package/lib/git.js +60 -16
- package/lib/hmr.js +4 -3
- package/lib/leases.js +17 -7
- package/lib/legacy.js +39 -14
- package/lib/modeState.js +39 -0
- package/lib/modeStore.js +11 -43
- package/lib/persistence.js +356 -0
- package/lib/presets.js +35 -0
- package/lib/spawn-prechecks.js +168 -0
- package/lib/spawn-support.js +79 -0
- package/lib/stateFile.js +99 -0
- package/lib/store.js +67 -135
- package/lib/tests.js +17 -12
- package/lib/tool-registry.js +544 -0
- package/lib/workspaceLock.js +2 -2
- package/lib/worktreeOwnership.js +22 -10
- package/locale/en.json +23 -1
- package/locale/zh.json +22 -0
- package/package.json +32 -4
- package/ports/clock.js +6 -0
- package/ports/ids.js +7 -0
- package/ports/index.js +2 -0
- package/ports/store.js +11 -0
- package/ports/vcs.js +11 -0
- package/scripts/architecture.mjs +94 -0
- package/scripts/check-docs.mjs +9 -3
- package/scripts/check-pack.mjs +49 -0
- package/scripts/domain-metrics.mjs +48 -0
- package/scripts/generate-config-reference.mjs +46 -0
- package/scripts/generate-error-reference.mjs +29 -0
- package/scripts/generate-tools-reference.mjs +56 -0
- package/scripts/issue-audit.mjs +259 -0
- package/scripts/lib/cli.mjs +76 -0
- package/scripts/lib/tmp.mjs +20 -0
- package/scripts/mount-smoke/fixtures.mjs +162 -0
- package/scripts/mount-smoke/host.json +4 -0
- package/scripts/mount-smoke/probe.mjs +125 -0
- package/scripts/mount-smoke/surface.mjs +141 -0
- package/scripts/mount-smoke.mjs +270 -0
- package/scripts/pack-manifest.txt +212 -0
- package/scripts/release-check.mjs +303 -0
- package/tools/adopt.js +72 -0
- package/tools/batch.js +78 -0
- package/tools/complete.js +53 -0
- package/tools/diff.js +39 -0
- package/tools/doctor.js +50 -0
- package/tools/finding.js +40 -0
- package/tools/findings.js +39 -0
- package/tools/import-legacy.js +42 -0
- package/tools/inbox.js +39 -0
- package/tools/index.js +39 -0
- package/tools/init.js +63 -0
- package/tools/interrupt.js +42 -0
- package/tools/legacy-map.js +44 -0
- package/tools/merge.js +39 -0
- package/tools/mission.js +41 -0
- package/tools/plan.js +39 -0
- package/tools/preflight.js +45 -0
- package/tools/rebase.js +39 -0
- package/tools/recover.js +45 -0
- package/tools/reload.js +52 -0
- package/tools/resource.js +54 -0
- package/tools/review.js +45 -0
- package/tools/roster.js +60 -0
- package/tools/send.js +147 -0
- package/tools/spawn.js +196 -0
- package/tools/status.js +102 -0
- package/tools/teardown.js +42 -0
- package/tools/test.js +61 -0
- package/types/cordis.d.ts +6 -0
- package/types/index.d.ts +373 -0
- package/lib/errors.js +0 -123
- package/lib/tools.js +0 -2460
- package/lib/tower.js +0 -2912
- /package/{lib → domain}/batch.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,158 @@
|
|
|
2
2
|
|
|
3
3
|
> 自 2.0.0 起,每个版本按 **新增 / 变更 / 修复** 三类组织;更早的版本保留原始流水式记录。
|
|
4
4
|
|
|
5
|
+
## 未发布(Unreleased)
|
|
6
|
+
|
|
7
|
+
## 2.1.0(2026-10-23)
|
|
8
|
+
|
|
9
|
+
### 新增
|
|
10
|
+
|
|
11
|
+
- **#72 state 迁移**:`state.json` 的 `schemaVersion` 升到 3(v1→v2→v3 有序迁移链),并新增有序、纯函数式的迁移注册表
|
|
12
|
+
(`domain/state/migrations.js`)。旧 state 在持有 state 锁的加载中自动迁移:先写
|
|
13
|
+
`state.json.v<N>.bak` 备份,再原子替换、`revision` 只 +1、并向 activity 追加
|
|
14
|
+
`state.migrated {from,to}`;只读工具只在内存中迁移,不改写文件。由更新插件写入的 state 现在
|
|
15
|
+
以 `[tower:state-version-unsupported]` 拒绝(不再误报 `state-corrupt`),且绝不会被降级重写;
|
|
16
|
+
迁移抛错以 `[tower:state-migration-failed]` 报出,原文件保持字节不变。`tower_doctor` 新增
|
|
17
|
+
`stateSchema {currentVersion,supportedVersion,migrations,status}` 与 `state-schema` 检查。
|
|
18
|
+
- **#73 终态任务归档**:`merged`/`abandoned` 且 worktree 已移除的 mission 在达到可配置策略
|
|
19
|
+
(`archive.enabled`/`archive.afterDays`/`archive.maxMissions`)后,于下一次变更边界移入
|
|
20
|
+
`<git-common-dir>/dsh-tower/archive/index.json` 与 `archive/<year>.json`(各自带 `archiveVersion`
|
|
21
|
+
头)。归档保留完整 mission、tasks、reviews、findings 与完成报告,state.json 只保留活跃任务,
|
|
22
|
+
因此每次 mutation 不再重写全部历史;归档按 id 幂等,进程崩溃后可重试。`tower_status` 默认只列
|
|
23
|
+
活跃任务,`includeArchived:true` 在活跃页后追加 `archived:true` 的压缩摘要(不含报告正文),
|
|
24
|
+
`missionId` 详情会从 year 文件加载;归档损坏时降级为 `archive.unreadable` 诊断而不是抛错。
|
|
25
|
+
`tower_doctor` 新增 `archive {missions,archivedMissions,stateBytes,archiveBytes,years,files,
|
|
26
|
+
unreadable,policy}` 与 `archive` 检查。`tower_preflight`/`tower_merge` 对已归档 mission 保持原行为,
|
|
27
|
+
新 mission 的编号也会避开归档 id。
|
|
28
|
+
|
|
29
|
+
- **#88 Unicode mission 标题**:`validateTitle` 改为接受可打印 Unicode(NFC 归一化、120 码点上限),
|
|
30
|
+
显式拒绝 bidi override(U+202A–202E、U+2066–2069)与零宽字符(U+200B–200D、U+FEFF),控制/格式/
|
|
31
|
+
代理码点单独报错;mission id 仍为 ASCII。CJK/西里尔/日韩/emoji 标题可完整往返 `tower_plan` →
|
|
32
|
+
state → `tower_status` → `MISSIONS.md` → briefing,board 表格保持对齐。
|
|
33
|
+
### 变更
|
|
34
|
+
|
|
35
|
+
- **#89 completion report 上限**:`tower_complete.report` 新增 `gates.maxReportBytes`
|
|
36
|
+
(默认 32768,按 **UTF-8 字节**而非字符计)硬上限;超限以 `invalid-argument` 拒绝并给出
|
|
37
|
+
`nextAction`(把全文写进 mission worktree 的文件并引用路径),绝不截断。存储的完成报告新增
|
|
38
|
+
`reportBytes`;`tower_status` 的分页摘要只返回大小,完整正文只在 `missionId` 详情中返回;
|
|
39
|
+
`tower_doctor` 新增 `reports` 检查并报告仓库中最大的报告。旧 state 无需迁移:缺失
|
|
40
|
+
`reportBytes` 时在读取时按 UTF-8 字节推导。
|
|
41
|
+
|
|
42
|
+
- **#90 scope 大小写**:`tower_init` 在检出目录上做一次性大小写探测并记录 `caseInsensitive`
|
|
43
|
+
(探测失败记录 `null`,比较继续使用安全的 case-sensitive 默认值);`tower_doctor` 报告
|
|
44
|
+
`caseInsensitive` 与生效的 `comparisonMode`。在大小写不敏感的文件系统上,`fileInScope` 与
|
|
45
|
+
`scopesOverlap` 折叠双方,因此 `src/Api/**` 与 `src/api/**` 视为重叠(plan 自动串行、扩 scope
|
|
46
|
+
被拒绝),仅大小写不同的改动不再被 out-of-scope 门禁拒绝;在大小写敏感的检出上仍拒绝,但 blocker
|
|
47
|
+
detail 会指出折叠大小写后本可匹配的 scope 条目。存储的 scope 文本保持原样。state schema 升到 3,
|
|
48
|
+
v2 → v3 迁移回填 `caseInsensitive: null`。
|
|
49
|
+
|
|
50
|
+
- **#86 平台矩阵 CI**:`test` job 改为显式 include 矩阵——ubuntu Node 22/24 与 Windows Node 22
|
|
51
|
+
(`test (22)`/`test (24)`/`test (22, windows)`),required context 名称不变;`win32Lower`/`isInside`、
|
|
52
|
+
`shellSpec`、`renameWithRetry`、`unlinkWithRetry`、worktree `compareKey` 均改为可注入 seam,并在任意
|
|
53
|
+
OS 上断言两条分支。Windows leg 已加入 `main` 的 required checks。
|
|
54
|
+
|
|
55
|
+
- **#91 Windows 测试确定性**:新增 `tests/helpers/cleanup.mjs`(有界重试 + 隔离 rename + `os.tmpdir()`
|
|
56
|
+
守卫),37 个测试文件、106 处直接 `rmSync` 全部迁移;导入时把 Windows CI 的 8.3 短路径 TEMP
|
|
57
|
+
规范化,消除短/长路径比较失败;Windows 全量套件连续 5 次全绿。
|
|
58
|
+
## 2.1.0-beta.1(2026-10-22)
|
|
59
|
+
|
|
60
|
+
**预发布**:本版本发布到 npm 的 `next` dist-tag,`latest` 继续保持 `2.0.4`,直到出现稳定版本。
|
|
61
|
+
从 2.0.4 直接跳到 2.1.0-beta.1,是为了让 #94 的交付闸门(mount smoke、发布闸门、证据约定)有一个
|
|
62
|
+
可验证的预发布载体;从 2.0.4 升级不需要改动配置。
|
|
63
|
+
|
|
64
|
+
### 新增
|
|
65
|
+
|
|
66
|
+
- **#95 mount smoke**:新增 `npm run smoke:mount`——打包本包、把 `scripts/mount-smoke/host.json` 中
|
|
67
|
+
唯一 pin 的 host 装进一次性目录、创建一次性 `DSH_HOME` 与 profile、用真实 loader 启动,并断言
|
|
68
|
+
**加载后的真实 surface**(29 个 `tower_*` 工具 id、3 个 skill、2 个 preset、`/tower` 命令,以及恰好
|
|
69
|
+
一个顶层 `tower-bundle` 行,#66 不变量);CI 增加 `mount` job(Node 22,5 分钟预算),
|
|
70
|
+
`tests/mount-smoke.test.mjs` 锁定 oracle,并保持 host pin 等于 `COMPATIBILITY.md` 最新行。
|
|
71
|
+
- **#95 覆盖率可见化**:CI 每次 push 运行 `npm run test:coverage` 并输出覆盖率表(只接线:无阈值、
|
|
72
|
+
无 baseline、无徽章);`CONTRIBUTING.md` 写明 CI 能证明什么、不能证明什么。
|
|
73
|
+
- **#97 发布闸门**:新增 `npm run release:check`(`scripts/release-check.mjs`)。每次运行都打印上一个
|
|
74
|
+
tag、`git describe`、自该 tag 起的提交数与 Unreleased 是否有内容;PR/分支 push 只警告(exit 0),
|
|
75
|
+
tag push 在版本没有前进、tag 不是 `v<version>`、或 CHANGELOG 没有该版本内容时失败(exit 1)。
|
|
76
|
+
CI 在每次 push 与 tag push 上运行它。
|
|
77
|
+
- **#97 发布清单**:`docs/guide/release.md` 把发布收敛为六步(版本 → CHANGELOG → COMPATIBILITY →
|
|
78
|
+
`npm publish --tag next` → 注释 tag → `gh release create`),含里程碑 P0/P1 规则、CHANGELOG 段落的
|
|
79
|
+
bash/PowerShell 提取命令,以及 `--verify-tag`、`--prerelease --latest=false` 与“先发布后打 tag”的
|
|
80
|
+
原因。
|
|
81
|
+
- **#96 离线 issue 审计**:新增 `npm run audit:issues`(`scripts/issue-audit.mjs`),读取本地
|
|
82
|
+
`tracker/issues.json` 导出,检查已关闭 P0/P1 的 `verified-by:`/`commit:` 两行契约,全程不访问
|
|
83
|
+
GitHub;`--since` 只看约定日期之后的关闭,`tracker/` 是 gitignored 的本地快照。
|
|
84
|
+
- **#97 追踪看板**:新增 `docs/board.md`,把 owner 每周要问的三个问题写成可执行的 `gh` 命令,并记录
|
|
85
|
+
标签/搜索语义(`--label P0,P1` 匹配单个字面标签,`--search 'label:P0,P1'` 才是 OR)。
|
|
86
|
+
|
|
87
|
+
### 变更
|
|
88
|
+
|
|
89
|
+
- **#96 受保护 `main` 与证据约定**:`main` 只能经 PR 合入(管理员同样受限),必需检查与“two-key
|
|
90
|
+
路径 + 四行 PR body”写进 `CONTRIBUTING.md`;P0/P1 关闭必须带 `verified-by:` 与 `commit:` 两行。
|
|
91
|
+
“某些路径允许直推”的例外被删除,因为 GitHub 的分支保护无法表达它——留一条服务器不会执行的规则
|
|
92
|
+
比没有规则更糟。
|
|
93
|
+
- **#94/#97 追踪策略**:优先级只存在于 `P0`–`P3` 标签、领域在 `area/*`,issue 标题不再带 `[P*]`
|
|
94
|
+
前缀;milestone 仍然只表示发布范围(一个 release 一个 milestone)。
|
|
95
|
+
- **README 徽章**:中英文 README 增加 “Listed on dsh-plugin.org” 徽章,链接到插件市场条目。
|
|
96
|
+
- **#66 all-or-nothing bundle**:`cordis.patch.yml` 改为单个 `tower-bundle` `cordis:group`,Host、
|
|
97
|
+
skills 与 worker/reviewer preset 成为内部 section;子行不再可独立启停,避免 host 关闭后的 orphan。
|
|
98
|
+
- **#64 preset 前置校验**:模式进入与 `tower_plan` 前解析 worker/reviewer preset;缺失时以稳定
|
|
99
|
+
`preset-missing` 拒绝并点名 `preset-tower-worker` / `preset-tower-reviewer`,不再先规划后死锁。
|
|
100
|
+
- **#65 可选能力开关**:`tools.exposeBatch/exposeImport/exposeLeases/exposeTests/exposeRecovery/exposeDoctor`
|
|
101
|
+
可独立关闭;`gates.requireTests:true` 与 tests/tools 关闭的组合在配置解析时直接拒绝。
|
|
102
|
+
- **#67 组件文案**:host locale 增加 `components.{tower-bundle,tower-skills,preset-*}` 的
|
|
103
|
+
title/summary/disables 中英文案;预设仍为内部 section,不再作为独立开关暴露。
|
|
104
|
+
- **#52 继续拆分 tools**:review/preflight/merge-rebase-teardown 拆为 per-tool 模块;
|
|
105
|
+
`lib/tools.js` 降至约 503 行。
|
|
106
|
+
- **#51 two-phase 试点**:`tower_complete` 改为锁外 prepare + 锁内 revision 复核;`submitReview`
|
|
107
|
+
同样拆为锁外准备(actor/worktree/tip/round)与锁内 revision 复核;新增
|
|
108
|
+
`lock-holds.jsonl` 遥测与 doctor p50/p95 检查(>1s warn)。
|
|
109
|
+
- **#51 git 互斥基础**:新增 `withGitLock` / `git.lock`,用与 state lock 相同的 token/PID-dead
|
|
110
|
+
回收协议串行化 git 操作,为把 git 移出 state lock 做准备。
|
|
111
|
+
- **#51 merge/rebase/teardown**:三者改为 prepare/act/commit;长 git 操作在 `git.lock` 下进行,
|
|
112
|
+
state lock 只做 revision 复核与写回;rebase 增加可对账的 `rebaseIntent`。
|
|
113
|
+
- **#51 init/provision/merge reconcile**:init/provision 同样锁外 prepare + `git.lock` act +
|
|
114
|
+
短 state lock 复核;merge 的 intent/landed 对账改为锁外 git 观察 + revision CAS,任何
|
|
115
|
+
state lock 内不再启动 git。
|
|
116
|
+
- **#51 init/provision**:`tower_init` 与 worktree provision 同样改为锁外 prepare + `git.lock`
|
|
117
|
+
下 act + 短 state lock 复核;provision 失败/并发变更时回滚 worktree 与 branch。
|
|
118
|
+
- **#68 owner 恢复提示**:`towerAgentId` 缺失时写工具现在返回可操作错误
|
|
119
|
+
“run tower_init to re-claim”,`tower_status` 暴露 `towerAgentId/ownerMissing`,doctor 增加
|
|
120
|
+
`tower-owner` 检查;`tower_init` 重新绑定后恢复写权限。
|
|
121
|
+
- **#52 Tower 拆分**:`lib/tower.js` 从约 2.9k 行降到 **535** 行;plan/mission/checkCompletion/
|
|
122
|
+
missionContext/enrichedMissions/board/spawn/recording 拆为 `lib/tower/*.js` use-case 模块,
|
|
123
|
+
`Tower` 方法仅作委托,行为、错误码与锁顺序不变。
|
|
124
|
+
- **#50 I/O 边界**:`node:fs` 仅保留在 `lib/adapters/store-fs.js`,`node:child_process` 仅保留在
|
|
125
|
+
`lib/adapters/vcs-git.js`;其余 lib 模块经 adapter 导入,`package.json files` 补齐 adapters;
|
|
126
|
+
新增架构回归锁定该不变量。
|
|
127
|
+
- **#50 依赖注入**:新增 `lib/adapters/default.js` 组合根;`Tower` 构造器接受
|
|
128
|
+
`deps.store/vcs/clock/ids`,类内不再直接调用 git/store 模块;回归证明注入 adapter 生效。
|
|
129
|
+
- **#56 前置工作**:架构 fitness 规则按实际 `lib/adapters` 边界修正并全部升级为 error
|
|
130
|
+
(domain-pure / child-process / fs / ambient-state / aggregate-size / tools-thin / tools-vocabulary),
|
|
131
|
+
baseline 零违规;`docs/architecture/overview.md` 更新为实际分层、state/modes 契约和剩余差距。
|
|
132
|
+
- **#45 路线 A:TypeBox 单一 schema 源**:经调研 Schemastery 的 `toJSON()` 只序列化自身结构、不输出 JSON Schema,因此选用 TypeBox。29 个工具输入 schema 改为 `Type.Object(...)`;`define` 泛型从 schema 推导 `execute(args)` 类型;`tsc` 覆盖全部 tool 模块并加入 required/enum 负向 fixture。运行时注册前 JSON 化,wire schema 与迁移前逐项一致。
|
|
133
|
+
- **#50 test doubles**:`tests/support/memory-store.js` 与 `scripted-vcs.js` 提供无磁盘/无 git 的
|
|
134
|
+
Store/Vcs 契约;新增完整 scripted lifecycle 与 10 项 adapter contract 测试。
|
|
135
|
+
- **#45 domain 类型**:`lib/domain/*.js` 纳入 checkJs,新增 `tests/type-contracts.ts` 正/负类型
|
|
136
|
+
fixture;types 契约与 domain 签名对齐。
|
|
137
|
+
- **packaging**:`package.json files` 补齐 `lib/tower/*.js`,避免发布包遗漏 use-case 模块。
|
|
138
|
+
- **#45 错误结构化继续**:纯 domain、review/findings/read/batch/rebase、`lib/tower.js` 与 infra
|
|
139
|
+
(store/config/git/tests)均改为抛出带 code 的 `TowerToolError`;`classifyError` 的 26 条
|
|
140
|
+
prose `RULES` 表已删除,纯文本错误统一落到 `tool-internal`。
|
|
141
|
+
- **#54 mode 锁等待**:`mode.lockTimeoutMs`(默认 5000ms)统一 modes.lock 的有界等待与死锁回收语义。
|
|
142
|
+
- **#59 兼容性记录**:新增随包发布 `COMPATIBILITY.md`,doctor 增加 `host-version` 检查(verified /
|
|
143
|
+
untested-newer / untested-older / unknown,仅警告)。
|
|
144
|
+
- **#61 共存冲突**:README 增加 “Known conflicts”,doctor 增加 `loader-conflicts` 检查。
|
|
145
|
+
- **#58 仓库产物排除**:`docs.boardLocation:"repo"` 与仓库内 `worktrees.root` 自动写入
|
|
146
|
+
`.git/info/exclude`(幂等,绝不改 `.gitignore`);新增 `init.autoExclude`。
|
|
147
|
+
- **#44 模式进入审计**:`tower_status` 增加 `mode.lastChange`/`mode.history`;新增
|
|
148
|
+
`mode.requireHumanAckOnFirstEntry` 与 `/tower ack`;安全文档补充模型主动进入的威胁模型。
|
|
149
|
+
- **#43 默认值提醒**:doctor 的 `default-reversals` 检查提示仍依赖旧默认值的 profile。
|
|
150
|
+
- **#60 提供商冷却**:spawn 在连续可重试失败后进入进程内冷却并收缩预算;
|
|
151
|
+
`tower_status.spawn` 暴露 `{inflight, budget, blockedUntil}`。
|
|
152
|
+
- **#48 发布文件守卫**:新增 `scripts/pack-manifest.txt`、`npm run pack:check` 与 CI 步骤。
|
|
153
|
+
- **#62 工具面预算**:`tools.exposeOperational:false` 可跳过 batch/resource/import/legacy/recover/test/doctor 工具。
|
|
154
|
+
- **#47 live 守卫**:`tests/live` 需要显式 `DSH_LIVE=1` 与隔离的 `DSH_HOME`,不再可能误跑真实 profile。
|
|
155
|
+
- **#63**:doctor 不再把正常 live worker 视为 warn。
|
|
156
|
+
|
|
5
157
|
## 2.0.4(2026-10-21)
|
|
6
158
|
|
|
7
159
|
### 变更
|
package/COMPATIBILITY.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
Tower records the DeepSeek Harness host build each release was actually verified against. This file
|
|
4
|
+
is shipped in the npm package; doctor reads it locally and never contacts a registry.
|
|
5
|
+
|
|
6
|
+
| dsh version | tag / commit | verified on | what was run |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
| 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 |
|
|
9
|
+
| 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 |
|
|
10
|
+
| 0.2.1-rc.2 | 6ee19de | 2026-10-21 | `npm test` (229), `npm run docs:check`, offline `ui-bundle` tests |
|
|
11
|
+
| 0.2.1-rc.2 | 5b0accd | 2026-10-18 | `npm test` (219), `npm run docs:check` |
|
|
12
|
+
|
|
13
|
+
## How to read this
|
|
14
|
+
|
|
15
|
+
- **verified**: exact host version match; the row names the date and what ran.
|
|
16
|
+
- **untested-newer**: newer than every row. Normally safe to try; include the host version and the
|
|
17
|
+
latest row in a bug report.
|
|
18
|
+
- **untested-older**: older than every row; upgrade the host or expect missing APIs.
|
|
19
|
+
- **unknown**: the host did not expose a parseable version.
|
|
20
|
+
|
|
21
|
+
`tower_doctor` reports this as a warning only: a newer host is the normal case and must not block
|
|
22
|
+
Tower work. This is provenance, not a feature-detection mechanism.
|
|
23
|
+
|
|
24
|
+
## Release process
|
|
25
|
+
|
|
26
|
+
Add a row in the release PR, before tagging, after running the offline suite and docs check on the
|
|
27
|
+
host build being shipped. Name the release tag in cell 1 and the host build in cell 0; the other
|
|
28
|
+
columns are read positionally by `lib/compatibility.js`, so do not insert or reorder them. Do not
|
|
29
|
+
claim a live run that was not performed. The 2.1.0-beta.1 row is a pre-release: it becomes
|
|
30
|
+
installable on the `next` dist-tag only after the checklist's publish step, and `latest` stays on
|
|
31
|
+
2.0.4 until a stable release. The full six-step procedure is the
|
|
32
|
+
[release checklist](docs/guide/release.md).
|
|
33
|
+
|
|
34
|
+
## Pack provenance
|
|
35
|
+
|
|
36
|
+
Releases are currently published locally with npm 2FA. `publishConfig.provenance` is intentionally
|
|
37
|
+
not enabled because provenance requires a CI OIDC publisher; enabling it locally would fail the
|
|
38
|
+
release. The reviewed `scripts/pack-manifest.txt` and CI `pack:check` guard the published file list.
|
|
39
|
+
Move publishing to GitHub Actions before enabling provenance.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -2,6 +2,66 @@
|
|
|
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
|
+
## Landing changes
|
|
6
|
+
|
|
7
|
+
`main` is protected. Every change lands through a pull request; direct pushes to `main` are rejected,
|
|
8
|
+
including the maintainer's, because protection is enforced for admins. To merge, a PR must pass these
|
|
9
|
+
required checks:
|
|
10
|
+
|
|
11
|
+
| Required context | What it proves |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `test (22)` | offline suite, docs/pack/architecture/types/domain checks on Node 22 |
|
|
14
|
+
| `test (24)` | the same jobs on Node 24 |
|
|
15
|
+
| `mount` | the packed tarball mounts in a throwaway profile and registers its surface (`npm run smoke:mount`) |
|
|
16
|
+
|
|
17
|
+
The context names come from the workflow job id and matrix values. Renaming a job or a matrix value
|
|
18
|
+
changes the required context; update the protection setting in the same PR, or the check never reports
|
|
19
|
+
and every PR stays unmergeable. The setting and its verification command are in
|
|
20
|
+
[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.
|
|
22
|
+
|
|
23
|
+
The offline `test` matrix also has a `test (22, windows)` leg on `windows-latest` (Node 22). It runs the
|
|
24
|
+
offline suite and the offline gates, not the release/coverage steps, so the platform branches are
|
|
25
|
+
measured on a real Windows filesystem (see `tests/platform.test.mjs`). Its name is pinned through
|
|
26
|
+
`matrix.name` so the two required contexts above stay byte-identical. The Windows leg is **not** a
|
|
27
|
+
required context yet: #86 only asks that it exists and is green, and adding it to the protection setting
|
|
28
|
+
is a separate human step.
|
|
29
|
+
|
|
30
|
+
There are no mandatory approvals, no CODEOWNERS gate, and **merging your own PR is allowed**: there is
|
|
31
|
+
one maintainer, and a mandatory self-approval would be theatre. What replaces the second reviewer is a
|
|
32
|
+
written contract.
|
|
33
|
+
|
|
34
|
+
### Two-key paths and the four-line PR body
|
|
35
|
+
|
|
36
|
+
"Two-key" here means two independent keys of evidence — the written failure mode and the named test,
|
|
37
|
+
on top of the green required checks — not two approvals. A change touching any of these paths carries a
|
|
38
|
+
four-line body, in this order:
|
|
39
|
+
|
|
40
|
+
- `domain/**`
|
|
41
|
+
- `app/**`
|
|
42
|
+
- `lib/store.js`, `lib/persistence.js`, `lib/*lock*.js`
|
|
43
|
+
- `scripts/architecture.mjs`
|
|
44
|
+
- `cordis.patch.yml`
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
Failure mode: <what breaks for a user if this is wrong>
|
|
48
|
+
Change: <what changed, one sentence>
|
|
49
|
+
Test: <test file and case that fails without this change>
|
|
50
|
+
Blast radius: <repositories, state files or tools that can be affected>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Those paths decide whether a user's repository is modified or a gate can be bypassed. Every other path
|
|
54
|
+
still lands as a PR, but a one-line description is enough. The four lines are evidence, not ceremony:
|
|
55
|
+
if the "Test" line cannot name a file, the change needs one first.
|
|
56
|
+
|
|
57
|
+
### Direct push
|
|
58
|
+
|
|
59
|
+
There is none. Before the delivery gate (#94 / #96), `main` accepted direct pushes, and #77 proposed
|
|
60
|
+
the two-key rule as a path-scoped exception. GitHub branch protection cannot express "direct push
|
|
61
|
+
allowed for these paths only", so the exception was dropped instead of documented as a rule the server
|
|
62
|
+
would not enforce. An emergency bypass is not a workflow: repairing `main` means changing the setting
|
|
63
|
+
deliberately and recording why, not silently pushing.
|
|
64
|
+
|
|
5
65
|
## Development setup
|
|
6
66
|
|
|
7
67
|
```bash
|
|
@@ -21,15 +81,110 @@ Requirements: Node.js 22+, Git. No package installation is needed for the offlin
|
|
|
21
81
|
|
|
22
82
|
## Pull request checklist
|
|
23
83
|
|
|
24
|
-
1. `npm test` passes locally (
|
|
84
|
+
1. `npm test` passes locally (479 tests today, including protocol end-to-end against a real temporary Git repository and mock spawn coverage) and `npm run docs:check` passes.
|
|
25
85
|
2. New behavior has a test. Bug fixes should add a test that fails before the fix.
|
|
26
86
|
3. No secrets, tokens, personal paths, session logs, or generated artifacts are added. `.gitignore` covers local acceptance output; do not force-add it.
|
|
27
87
|
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.
|
|
28
88
|
5. Update `README.md` / `README.en.md` when the user-visible behavior changes.
|
|
89
|
+
6. If you touched `cordis.patch.yml`, `index.js`, `skills.js`, or `tools/`, run `npm run smoke:mount` (see below).
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
## Mount smoke
|
|
93
|
+
|
|
94
|
+
The offline suite never loads a host, so it cannot see a bundle that fails to mount. The smoke
|
|
95
|
+
packs this package, installs the pinned host into a temp directory, adds the tarball through
|
|
96
|
+
`dsh plugin`, and boots the throwaway profile through the real loader:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run smoke:mount # asserts the loaded surface; under two minutes
|
|
100
|
+
npm run smoke:fixtures # proves the smoke still fails for the right seeded reasons
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
It needs network access the first time (the host install) and `pnpm` on `PATH` (the plugin CLI's
|
|
104
|
+
package manager). `scripts/mount-smoke/host.json` is the single host build pin: bump it and the
|
|
105
|
+
newest `COMPATIBILITY.md` row together — a unit test fails when the two disagree. The fixtures
|
|
106
|
+
rename one tool id and remove the `tower-skills` row to prove the red run names the cause rather
|
|
107
|
+
than only counting rows; CI runs them in the `mount-fixtures` job, sharing one cached host install
|
|
108
|
+
through `--host-dir`.
|
|
109
|
+
|
|
110
|
+
Coverage is reported by `npm run test:coverage` in CI. It is a signal, not a gate: no threshold,
|
|
111
|
+
no baseline, no badge.
|
|
112
|
+
|
|
113
|
+
## Releasing
|
|
114
|
+
|
|
115
|
+
A release is six steps: bump → changelog → COMPATIBILITY row → publish → annotated tag → GitHub
|
|
116
|
+
Release. The one-page procedure with exact commands is [docs/guide/release.md](docs/guide/release.md).
|
|
117
|
+
It carries the two rules that are easy to get wrong: **publish before tagging** (or delete the tag if
|
|
118
|
+
publish fails), and a release **may not be tagged while its milestone holds an open P0/P1** unless the
|
|
119
|
+
release notes say why. `npm publish` is the only human-only step (2FA).
|
|
120
|
+
|
|
121
|
+
`npm run release:check` prints `git describe`, the previous tag, the commits since it and whether
|
|
122
|
+
Unreleased is non-empty on every run. It warns on a PR and fails on a tag push whose
|
|
123
|
+
`package.json` version did not move past the last tag or whose tag is not `v<version>`.
|
|
124
|
+
|
|
125
|
+
## Tracker board
|
|
126
|
+
|
|
127
|
+
Priority lives in the `P0`–`P3` labels; open titles carry no `[P*]` prefix. The saved queries — open
|
|
128
|
+
P1, unmilestoned P0/P1, closed this week by area, and the milestone check that blocks a release — are
|
|
129
|
+
in [docs/board.md](docs/board.md).
|
|
130
|
+
|
|
131
|
+
## Commits, closures and evidence
|
|
132
|
+
|
|
133
|
+
### Trailers
|
|
134
|
+
|
|
135
|
+
Put the issue trailer on its own line at the end of the commit message:
|
|
136
|
+
|
|
137
|
+
- `Refs #n` — the commit touches issue n but does not complete it.
|
|
138
|
+
- `Fixes #n` — the commit completes the issue; GitHub closes it when the change reaches `main`.
|
|
139
|
+
|
|
140
|
+
One trailer per issue, and exactly one of the two per commit. `Fixes` is not decoration for work that a
|
|
141
|
+
later commit still has to finish.
|
|
142
|
+
|
|
143
|
+
### The two-line closure contract
|
|
144
|
+
|
|
145
|
+
A closure on a P0/P1 is a pointer, not prose. The closing comment starts with these two lines:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
verified-by: tests/<file>.test.mjs
|
|
149
|
+
commit: <sha>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `verified-by:` names a test file that exists in the repository (under `tests/`, or named `*.test.*` /
|
|
153
|
+
`*.spec.*`). A script or a doc is not a test.
|
|
154
|
+
- `commit:` names the commit that landed the change.
|
|
155
|
+
|
|
156
|
+
Prose may follow. A closure without the two lines is treated as unverified, and the audit below fails
|
|
157
|
+
on it.
|
|
158
|
+
|
|
159
|
+
### Checking closures offline
|
|
160
|
+
|
|
161
|
+
The audit reads a local export; it never calls GitHub, so it cannot pass because of a network outage or
|
|
162
|
+
a token. Produce the export once (network), then audit it offline:
|
|
163
|
+
|
|
164
|
+
```powershell
|
|
165
|
+
# 1) Export (PowerShell; no jq needed)
|
|
166
|
+
New-Item -ItemType Directory -Force tracker | Out-Null
|
|
167
|
+
$all = foreach ($n in (gh issue list --repo louifinn/dsh-tower --state all --limit 500 --json number --jq '.[].number')) {
|
|
168
|
+
gh issue view $n --repo louifinn/dsh-tower --json number,title,state,labels,closedAt,body,comments | ConvertFrom-Json
|
|
169
|
+
}
|
|
170
|
+
@{ issues = @($all) } | ConvertTo-Json -Depth 8 | Set-Content tracker/issues.json -Encoding utf8
|
|
171
|
+
|
|
172
|
+
# 2) Audit (offline)
|
|
173
|
+
npm run audit:issues # every closed P0/P1 in tracker/issues.json
|
|
174
|
+
npm run audit:issues -- --since 2026-10-22 # only closures on/after the convention date
|
|
175
|
+
npm run audit:issues -- --tracker path/to/export.json --json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The bash equivalent of step 1 is the same `gh` calls piped through `jq -s '{issues: .}'`.
|
|
179
|
+
|
|
180
|
+
Exit codes: `0` — every checked closure names an existing test file (a missing `commit:` is a warning);
|
|
181
|
+
`1` — at least one pointer is missing, nonexistent, not a test, or outside the repository; `2` — the
|
|
182
|
+
export is missing or malformed. `--strict` promotes the missing-`commit:` warning to an error.
|
|
183
|
+
`tracker/` is a local snapshot and is gitignored; regenerate it instead of committing it.
|
|
29
184
|
|
|
30
185
|
## Commit style
|
|
31
186
|
|
|
32
|
-
Conventional-commit prefixes are preferred: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`. Keep the subject line in the imperative mood.
|
|
187
|
+
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.
|
|
33
188
|
|
|
34
189
|
## Reporting issues
|
|
35
190
|
|
package/README.en.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/louifinn/dsh-tower/actions/workflows/test.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/dsh-tower)
|
|
5
|
+
[](https://dsh-plugin.org/plugins/louifinn/dsh-tower)
|
|
5
6
|
[](LICENSE)
|
|
6
7
|
[中文说明](README.md)
|
|
7
8
|
|
|
@@ -17,6 +18,7 @@ The repository root **is** the DSH bundle: `package.json` declares `dsh.bundle.p
|
|
|
17
18
|
- **Missions with real isolation.** Every mission gets its own branch and Git worktree; workers never touch your checkout.
|
|
18
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
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.
|
|
20
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.
|
|
21
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.
|
|
22
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.
|
|
@@ -51,7 +53,7 @@ Tower mode refuses Team/Workflow/Ralph/ordinary subagent tools and clears plan m
|
|
|
51
53
|
|
|
52
54
|
## Requirements
|
|
53
55
|
|
|
54
|
-
- DeepSeek Harness `0.2.1-rc.2` (
|
|
56
|
+
- DeepSeek Harness `0.2.1-rc.2` (see [COMPATIBILITY.md](COMPATIBILITY.md) for the verified host builds)
|
|
55
57
|
- Node.js 22 or newer
|
|
56
58
|
- Git (worktrees are created locally; no network access is required)
|
|
57
59
|
|
|
@@ -82,6 +84,13 @@ The optional Web UI ships inside the npm package at `ui-bundle/`: for an npm ins
|
|
|
82
84
|
`<profile>/node_modules/dsh-tower/ui-bundle`, or `ui-bundle/` in a source checkout. Install it with
|
|
83
85
|
`plugin_manager({action:"install_bundle", target:"<that directory>"})`, restart DSH, then refresh the page.
|
|
84
86
|
|
|
87
|
+
### Known conflicts
|
|
88
|
+
|
|
89
|
+
Do not install `dsh-plugins-tower` (third-party) alongside this package. Its loader row also uses
|
|
90
|
+
`name: dsh-tower`, so it resolves to this package: this package may load twice or the other package
|
|
91
|
+
never runs. `tower_doctor` reports this as `loader-conflicts`; uninstall the conflicting package and
|
|
92
|
+
restart DSH.
|
|
93
|
+
|
|
85
94
|
## Quick start
|
|
86
95
|
|
|
87
96
|
```text
|
|
@@ -112,10 +121,23 @@ A small change usually takes one worker mission; add one reviewer round when ind
|
|
|
112
121
|
| `/tower on <instruction>` | Enter/stay **and** forward `<instruction>` as the next user message |
|
|
113
122
|
| `/tower off` | Leave Tower mode |
|
|
114
123
|
| `/tower off <instruction>` | Leave **and** forward `<instruction>` |
|
|
124
|
+
| `/tower ack` | Record human acknowledgement for `mode.requireHumanAckOnFirstEntry` |
|
|
115
125
|
| `/tower <instruction>` | Ensure on, then forward `<instruction>` |
|
|
116
126
|
|
|
117
127
|
Image and file attachments are forwarded together with the instruction as one complete `UserMessage`.
|
|
118
128
|
|
|
129
|
+
## Component model
|
|
130
|
+
|
|
131
|
+
This bundle is **all-or-nothing**: `cordis.patch.yml` inserts one `tower-bundle`
|
|
132
|
+
`cordis:group` row, and the Host plugin, skills, and worker/reviewer presets are internal sections
|
|
133
|
+
of it. The plugin page should treat that group as one component; disabling it disables all sections
|
|
134
|
+
together, so no orphan preset or skill points at missing `tower_*` tools. Do not enable/disable the
|
|
135
|
+
child rows independently.
|
|
136
|
+
|
|
137
|
+
Mode entry and `tower_plan` validate both presets before work starts. A missing preset is refused
|
|
138
|
+
with `preset-missing` and names `preset-tower-worker` / `preset-tower-reviewer`, instead of planning
|
|
139
|
+
or spawning first and dead-ending at review/merge.
|
|
140
|
+
|
|
119
141
|
## Configuration
|
|
120
142
|
|
|
121
143
|
```yaml
|
|
@@ -179,14 +201,14 @@ Workers use the bundled `tower-worker` preset (writable worktree surface); revie
|
|
|
179
201
|
|
|
180
202
|
| Path | Contents |
|
|
181
203
|
|---|---|
|
|
182
|
-
| `package.json`, `cordis.patch.yml` | bundle manifest and loader patch (
|
|
204
|
+
| `package.json`, `cordis.patch.yml` | bundle manifest and loader patch (one `tower-bundle` group containing the Host, skills, and both presets) |
|
|
183
205
|
| `index.js`, `lib/`, `worker.js`, `reviewer.js` | Host entry, protocol/config implementation, worker and reviewer preset policies |
|
|
184
206
|
| `skills.js`, `skills/` | bundled agent skills (worker protocol, review checklist, recovery playbook) |
|
|
185
207
|
| `docs/` | guides, reference, architecture, operations, FAQ and examples |
|
|
186
208
|
| `locale/`, `icon.svg` | localization and icon |
|
|
187
209
|
| `tests/` | pure-function, protocol end-to-end, and plugin-level tests; optional isolated Web-runtime scripts |
|
|
188
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 |
|
|
189
|
-
| `.github/workflows/test.yml` | CI running the offline suite on Node 22/24 |
|
|
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 |
|
|
190
212
|
|
|
191
213
|
## Development and testing
|
|
192
214
|
|
|
@@ -200,6 +222,27 @@ Hot-reload development: `npm run dev:hmr` adds this checkout to the current prof
|
|
|
200
222
|
|
|
201
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.
|
|
202
224
|
|
|
225
|
+
## What CI proves
|
|
226
|
+
|
|
227
|
+
Every push and PR runs the same set of jobs; each job backs one checkable claim instead of a README promise.
|
|
228
|
+
|
|
229
|
+
| Job | Claim | Evidence |
|
|
230
|
+
|---|---|---|
|
|
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 three `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) |
|
|
233
|
+
| `test`'s `Coverage (Node 22)` step | coverage is actually measured in CI (reported only: no threshold, no badge) | `npm run test:coverage` |
|
|
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` |
|
|
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` |
|
|
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
|
+
|
|
238
|
+
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.
|
|
239
|
+
|
|
240
|
+
`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.
|
|
241
|
+
|
|
242
|
+
`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.
|
|
243
|
+
|
|
244
|
+
The host build is pinned in exactly one place, `scripts/mount-smoke/host.json`; a unit test keeps the newest `COMPATIBILITY.md` row equal to it, so a release never retypes it. `npm run smoke:fixtures` proves the smoke still fails for the right seeded reasons (a renamed tool id, a removed skills section). CI runs it as the non-required `mount-fixtures` job, where the control and both seeds share one cached host install through `--host-dir` (about 2 minutes warm here).
|
|
245
|
+
|
|
203
246
|
## Documentation
|
|
204
247
|
|
|
205
248
|
- [docs/README.md](docs/README.md) — documentation index (new user / operator / maintainer paths)
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/louifinn/dsh-tower/actions/workflows/test.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/dsh-tower)
|
|
5
|
+
[](https://dsh-plugin.org/plugins/louifinn/dsh-tower)
|
|
5
6
|
[](LICENSE)
|
|
6
7
|
[English](README.en.md)
|
|
7
8
|
|
|
@@ -17,6 +18,7 @@ Tower 模式与 mission/审查工作流的灵感来自 Moonshot AI 的 **Kimi Co
|
|
|
17
18
|
- **任务级隔离。** 每个 mission 使用独立分支和 Git worktree,worker 不会碰到你的主检出。
|
|
18
19
|
- **审查门禁合并。** 开启 `review.requireReview:true` 时,审查者针对当前分支 tip 给出 `clean` / `merge` 结论;合并门禁重新校验 scope、分支新鲜度、任务完成度、审查记录与 worktree 归属,通过后才执行 `--no-ff` 合并。独立审查默认可选(门禁关闭);全局开启时,单个 mission 用 `reviewPolicy:"none"` + 理由并开启 `gates.allowReviewExemption:true` 做更窄豁免。
|
|
19
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` 报告归档大小、数量与策略。
|
|
20
22
|
- **默认安全。** Tower 模式开启期间拒绝 `create_goal`、`update_goal(resume)`、`todo_write`、Team 任务板与 `spawn_teammate`;被暂停的 goal 不会被自动恢复。
|
|
21
23
|
- **可追溯的 finding 生命周期。** `tower_findings` 列表、读取并按 `open → triaged → fixed` / `wontfix` 流转,也可重新打开;只有 tower 或 worker 能改状态,reviewer 只能读。
|
|
22
24
|
- **仓库级配置。** 每个仓库可用 `<git-common-dir>/dsh-tower/config.json` 覆盖 review/merge/worktree/spawn 策略,工具参数再覆盖仓库配置;无需为不同仓库改 profile。
|
|
@@ -51,7 +53,7 @@ Tower 模式会拒绝 Team/Workflow/Ralph/普通 subagent 工具并清除 plan m
|
|
|
51
53
|
|
|
52
54
|
## 环境要求
|
|
53
55
|
|
|
54
|
-
- DeepSeek Harness `0.2.1-rc.2
|
|
56
|
+
- DeepSeek Harness `0.2.1-rc.2`(已验证的 host 构建见 [COMPATIBILITY.md](COMPATIBILITY.md))
|
|
55
57
|
- Node.js 22+
|
|
56
58
|
- Git(worktree 在本地创建,不需要联网)
|
|
57
59
|
|
|
@@ -82,6 +84,12 @@ Host 插件在 DSH 进程启动时加载,**安装或更新后需重启 DSH**
|
|
|
82
84
|
`<profile>/node_modules/dsh-tower/ui-bundle`,源码安装时为目标仓库的 `ui-bundle/` 目录。用
|
|
83
85
|
`plugin_manager({action:"install_bundle", target:"<该目录>"})` 安装并重启 DSH,然后刷新页面。
|
|
84
86
|
|
|
87
|
+
### 已知冲突
|
|
88
|
+
|
|
89
|
+
不要同时安装 `dsh-plugins-tower`(第三方包)。它的 loader row 也使用 `name: dsh-tower`,会解析到
|
|
90
|
+
本包,导致本包被加载两次或对方代码不生效。`tower_doctor` 的 `loader-conflicts` 检查会报告该情况;
|
|
91
|
+
卸载冲突包并重启 DSH。
|
|
92
|
+
|
|
85
93
|
## 快速开始
|
|
86
94
|
|
|
87
95
|
```text
|
|
@@ -112,10 +120,22 @@ tower_teardown repoRoot=D:\work\repo
|
|
|
112
120
|
| `/tower on <指令>` | 开启/保持,并把 `<指令>` 作为下一条用户消息转发 |
|
|
113
121
|
| `/tower off` | 退出 Tower 模式 |
|
|
114
122
|
| `/tower off <指令>` | 退出并转发 `<指令>` |
|
|
123
|
+
| `/tower ack` | 记录人类确认,供 `mode.requireHumanAckOnFirstEntry` 使用 |
|
|
115
124
|
| `/tower <指令>` | 确保开启,然后转发 `<指令>` |
|
|
116
125
|
|
|
117
126
|
图片与文件附件会随指令一起作为一条完整的 `UserMessage` 转发。
|
|
118
127
|
|
|
128
|
+
## 组件模型
|
|
129
|
+
|
|
130
|
+
本 bundle 采用 **all-or-nothing**:`cordis.patch.yml` 只插入一个 `tower-bundle`
|
|
131
|
+
`cordis:group` 行,Host 插件、skills、worker/reviewer preset 都是它的内部 section。插件页只应把
|
|
132
|
+
这个 group 当作一个组件;关闭它时四个 section 一起关闭,不会留下指向已消失 `tower_*` 工具的
|
|
133
|
+
orphan preset/skill。不要再把子行单独启停。
|
|
134
|
+
|
|
135
|
+
进入 Tower 模式与 `tower_plan` 前会校验两个 preset 是否已注册;缺失时直接以 `preset-missing`
|
|
136
|
+
拒绝(并在消息中点名 `preset-tower-worker` / `preset-tower-reviewer`),不会先完成规划/派发再在
|
|
137
|
+
review/merge 阶段死锁。
|
|
138
|
+
|
|
119
139
|
## 配置
|
|
120
140
|
|
|
121
141
|
```yaml
|
|
@@ -179,12 +199,12 @@ worker 与 reviewer 是使用内置 `tower-worker` preset 的普通 DSH 子代
|
|
|
179
199
|
|
|
180
200
|
| 路径 | 内容 |
|
|
181
201
|
|---|---|
|
|
182
|
-
| `package.json`、`cordis.patch.yml` | bundle 清单与 loader patch
|
|
202
|
+
| `package.json`、`cordis.patch.yml` | bundle 清单与 loader patch(单个 `tower-bundle` group,内含 Host、skills 与两个 preset) |
|
|
183
203
|
| `index.js`、`lib/`、`worker.js` | Host 入口、协议实现、worker preset 策略 |
|
|
184
204
|
| `locale/`、`icon.svg` | 本地化与图标 |
|
|
185
205
|
| `tests/` | 纯函数、协议 e2e、插件级测试;可选的真实 Web 运行时脚本 |
|
|
186
206
|
| `ui-bundle/` | 可选 Client 小挂件(Tower 工具卡片、Tower·on 状态 chip、模式感知的 disabled 行、dock 摘要、临时 overlay 状态条),默认不安装 |
|
|
187
|
-
| `.github/workflows/test.yml` | CI:Node 22/24
|
|
207
|
+
| `.github/workflows/test.yml` | CI:Ubuntu 上 Node 22/24 与 Windows 上 Node 22 的离线套件、release 与覆盖率检查、Node 22 挂载 smoke 与种子失败 fixtures |
|
|
188
208
|
|
|
189
209
|
## 开发与测试
|
|
190
210
|
|
|
@@ -196,6 +216,27 @@ npm test # node --test tests/*.test.mjs
|
|
|
196
216
|
|
|
197
217
|
离线套件不需要 DSH 进程,只在系统临时目录创建一次性 Git 仓库。隔离 Web 运行时检查、模型驱动验收和会话诊断脚本见 `tests/README.md`。
|
|
198
218
|
|
|
219
|
+
## CI 证明了什么
|
|
220
|
+
|
|
221
|
+
每次 push 与 PR 都运行同一组 job;每个 job 对应一条可检查的声明,而不是一句 README 承诺。
|
|
222
|
+
|
|
223
|
+
| Job | 声明 | 证据 |
|
|
224
|
+
|---|---|---|
|
|
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) |
|
|
227
|
+
| `test` 的 `Coverage (Node 22)` 步骤 | 覆盖率在 CI 中真实测量(只报告,不设阈值、不加徽章) | `npm run test:coverage` |
|
|
228
|
+
| `test` 的 `Release check`(Node 22)步骤 | tag 是一种声明:版本已越过上一个版本 tag、推送的 tag 是 `v<version>`、CHANGELOG 带有发布内容 | `npm run release:check` |
|
|
229
|
+
| `mount`(Node 22) | 打出的 tarball 经 plugin CLI 装进一次性 profile 后,真实 loader 挂载并注册了 bundle 声称的表面 | `npm run smoke:mount` |
|
|
230
|
+
| `mount-fixtures`(Node 22,非必需检查) | mount smoke 仍会因正确的种子失败而变红(改名一个工具 id、删除 skills 段),三次运行共用一次缓存的 host 安装 | `npm run smoke:fixtures -- --host-dir <缓存的 host>` |
|
|
231
|
+
|
|
232
|
+
`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 的人工步骤。
|
|
233
|
+
|
|
234
|
+
`tests/platform.test.mjs` 用注入的 `platform`/`rename`/`unlink` 在任意 OS 上同时断言 win32 与 POSIX 两条分支;唯一需要真实 Windows 的“rename 覆盖被打开的目标文件”用例在 Linux 上以 `# SKIP` 计入 TAP(`tests 11`、`skipped 1`),而不是静默消失。macOS 不单设 runner:它与 Linux 走同一条 POSIX 分支,这是 #86 的非目标。
|
|
235
|
+
|
|
236
|
+
`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、已加载计数与每一条未满足的声明。
|
|
237
|
+
|
|
238
|
+
host build 只固定在一个地方:`scripts/mount-smoke/host.json`,`COMPATIBILITY.md` 最新一行由单元测试强制与它一致,发布时不再各写一遍。`npm run smoke:fixtures` 用两种种子失败(改名一个工具 id、删除 skills 段)证明 smoke 会以正确原因变红;CI 中它作为非必需的 `mount-fixtures` job 运行,control 与两个种子通过 `--host-dir` 共用一次缓存的 host 安装(本机热跑约 2 分钟)。
|
|
239
|
+
|
|
199
240
|
## 文档
|
|
200
241
|
|
|
201
242
|
- [docs/README.md](docs/README.md) — 文档索引(新用户 / 运维 / 维护者三条阅读路径)
|
package/SECURITY.md
CHANGED
|
@@ -11,6 +11,33 @@ Areas that matter for security reports:
|
|
|
11
11
|
- Command forwarding: injecting content into the model history that the user did not send.
|
|
12
12
|
- Writes outside the declared scope, the mission worktree, or the DSH home directory.
|
|
13
13
|
|
|
14
|
+
## Repository protection on `main`
|
|
15
|
+
|
|
16
|
+
The repository's own landing path is part of the security surface, not separate from it: the merge
|
|
17
|
+
gate, state lock, persistence and scope code decide whether a user's repository is modified, so a bad
|
|
18
|
+
change there is exploitable for every repository that installs the plugin. `main` is therefore
|
|
19
|
+
protected with:
|
|
20
|
+
|
|
21
|
+
- a pull request required for every change, with **admins enforced** — a direct push is rejected even
|
|
22
|
+
for the maintainer;
|
|
23
|
+
- required checks `test (22)`, `test (24)` and `mount` — the packed bundle must mount and register its
|
|
24
|
+
surface before a change can merge, and a red or missing check blocks the merge;
|
|
25
|
+
- force-pushes and branch deletion blocked, so a released tag's history cannot be rewritten;
|
|
26
|
+
- no mandatory approvals and no CODEOWNERS gate. With one maintainer, self-merge is the workflow;
|
|
27
|
+
what replaces the second approval is the four-line PR body for the two-key paths in
|
|
28
|
+
[CONTRIBUTING.md](CONTRIBUTING.md#landing-changes).
|
|
29
|
+
|
|
30
|
+
Rationale, the enforcement boundary (server-side protection is not a plugin feature) and the list of
|
|
31
|
+
what the setting does **not** buy are in
|
|
32
|
+
[docs/operations/security.md](docs/operations/security.md#repository-protection). Verify the setting:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gh api repos/louifinn/dsh-tower/branches/main/protection \
|
|
36
|
+
--jq '{checks: .required_status_checks.contexts, strict: .required_status_checks.strict,
|
|
37
|
+
admins: .enforce_admins.enabled, force_push: .allow_force_pushes.enabled,
|
|
38
|
+
deletions: .allow_deletions.enabled, reviews: .required_pull_request_reviews}'
|
|
39
|
+
```
|
|
40
|
+
|
|
14
41
|
## Reporting
|
|
15
42
|
|
|
16
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.
|