dsh-tower 2.3.0 → 2.4.1
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 +44 -0
- package/COMPATIBILITY.md +2 -0
- package/CONTRIBUTING.md +20 -3
- package/PRINCIPLES.md +18 -0
- package/README.en.md +29 -16
- package/README.md +22 -15
- package/cordis.patch.yml +56 -28
- package/docs/README.md +2 -1
- package/docs/adr/0004-surface-budget.md +20 -0
- package/docs/adr/0005-component-surface.md +130 -0
- package/docs/adr/README.md +1 -0
- package/docs/agents/workflow.md +1 -1
- package/docs/architecture/overview.md +69 -7
- package/docs/guide/hot-reload.md +4 -4
- package/docs/guide/install-upgrade-migration.md +13 -0
- package/docs/operations/security.md +1 -1
- package/docs/reference/component-surface.md +135 -0
- package/docs/reference/config.generated.md +2 -10
- package/docs/reference/config.md +11 -9
- package/docs/reference/doctor.md +14 -9
- package/docs/reference/surface-budget.md +52 -23
- package/docs/reference/tools.generated.md +4 -2
- package/docs/reference/tools.md +6 -3
- package/docs/zh/README.md +1 -1
- package/docs/zh/guide/hot-reload.md +3 -3
- package/docs/zh/guide/install-upgrade-migration.md +10 -0
- package/docs/zh/operations/security.md +1 -1
- package/docs/zh/reference/config.md +9 -9
- package/docs/zh/reference/tools.md +6 -4
- package/domain/components.js +641 -0
- package/domain/config/schema.js +37 -27
- package/domain/contracts/outputs.js +10 -0
- package/domain/errors.js +11 -1
- package/domain/mission.js +12 -1
- package/domain/surface.js +154 -55
- package/host/capability-batch.js +15 -0
- package/host/capability-hmr.js +30 -0
- package/host/capability-import.js +11 -0
- package/host/capability-leases.js +11 -0
- package/host/capability-registry.js +149 -0
- package/host/capability-tests.js +16 -0
- package/host/doctor.js +41 -9
- package/host/hmr.js +2 -4
- package/host/presets.js +51 -9
- package/host/tool-registry.js +150 -34
- package/index.js +32 -20
- package/locale/batch/en.json +6 -0
- package/locale/batch/zh.json +6 -0
- package/locale/en.json +31 -11
- package/locale/hmr/en.json +6 -0
- package/locale/hmr/zh.json +6 -0
- package/locale/import/en.json +6 -0
- package/locale/import/zh.json +6 -0
- package/locale/leases/en.json +6 -0
- package/locale/leases/zh.json +6 -0
- package/locale/skills/en.json +6 -0
- package/locale/skills/zh.json +6 -0
- package/locale/tests/en.json +6 -0
- package/locale/tests/zh.json +6 -0
- package/locale/zh.json +30 -10
- package/package.json +25 -4
- package/scripts/check-components.mjs +235 -0
- package/scripts/check-pack.mjs +4 -0
- package/scripts/component-surface.json +395 -0
- package/scripts/generate-capability-locale.mjs +148 -0
- package/scripts/generate-component-surface.mjs +59 -0
- package/scripts/generate-tools-reference.mjs +7 -25
- package/scripts/generate-types.mjs +127 -0
- package/scripts/lib/tool-surface.mjs +85 -0
- package/scripts/mount-smoke/fixtures.mjs +3 -3
- package/scripts/mount-smoke/surface.mjs +96 -27
- package/scripts/mount-smoke.mjs +3 -1
- package/scripts/pack-manifest.txt +30 -0
- package/scripts/surface-baseline.json +35 -4
- package/scripts/surface-baseline.mjs +32 -34
- package/scripts/types-coverage.mjs +184 -0
- package/scripts/types-strict-manifest.txt +10 -0
- package/scripts/types-strict.mjs +141 -0
- package/skills.js +8 -1
- package/tools/index.js +2 -1
- package/tools/reload.js +34 -35
- package/tools/status.js +8 -1
- package/types/index.d.ts +28 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@
|
|
|
4
4
|
|
|
5
5
|
## 未发布(Unreleased)
|
|
6
6
|
|
|
7
|
+
## 2.4.1(2026-10-24)
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **可选组件行在 Plugins 页面只显示模块名**:每个可选能力导出现在提供
|
|
12
|
+
`<specifier>/locale/{en,zh}.json`(`meta.title` / `meta.description`),由
|
|
13
|
+
`locale/<lang>.json` 的 `components` 块经 `npm run docs:generate:locale` 生成,并由
|
|
14
|
+
`docs:check:generated:locale` + CI 步骤防漂移;描述同时说明能力用途与关闭后移除的内容,
|
|
15
|
+
开关无需离开页面即可判断。
|
|
16
|
+
|
|
17
|
+
## 2.4.0(2026-10-24)
|
|
18
|
+
|
|
19
|
+
### 新增
|
|
20
|
+
|
|
21
|
+
- **#121 可选能力回到组件**:一个 `tower-bundle` 组拆成强制 `tower` 宿主 + 可独立开关的顶层行
|
|
22
|
+
(`tower-skills`/`tower-batch`/`tower-import`/`tower-leases`/`tower-tests`/`tower-hmr` 默认关/
|
|
23
|
+
`preset-tower-reviewer` 依 `review.requireReview` 条件存在);新增 `./batch`/`./import`/`./leases`/
|
|
24
|
+
`./tests`/`./hmr` 导出,每个入口只注册自己的能力;8 个可见组件的 title/summary/disables 双语覆盖;
|
|
25
|
+
任一可选行关闭后 Tower 仍可用(模式、规划、状态、其余工具),缺失能力在 status/doctor 中以
|
|
26
|
+
“disabled by component <id>” 呈现。
|
|
27
|
+
- **#120 组件面政策载体**:`PRINCIPLES.md` 第 11 条(“optional capabilities are switches; the core is not”)、
|
|
28
|
+
ADR 0005、64 键的组件轴分类(core / optional-component / optional-config / forbidden-to-disable,由
|
|
29
|
+
`scripts/component-surface.json` 单一来源生成)、`check-components`(locale 覆盖、forbidden-to-disable
|
|
30
|
+
denylist、组件计数 baseline)与 `components` 基线;组件面 4→1→8 的变化以带日期的 decision 记录。
|
|
31
|
+
- **#115 类型面完整性**:`types:coverage` 从 `package.json files` 与 `tsc --listFiles` 派生检查程序
|
|
32
|
+
(146/146 运行时 .js 全覆盖,旧配置下可复现 19 个漏检);tsconfig 改为 glob;`types:generate` 从运行时常量
|
|
33
|
+
生成发布词汇(ErrorCode/状态/角色等)并纳入 `--check`,修掉 8 个幽灵码与 4 个缺失码;
|
|
34
|
+
`tsconfig.strict.json`(noImplicitAny)以显式文件清单起步,manifest 禁止清单收缩。
|
|
35
|
+
- **#118 surface 计数覆盖完整工具面**:`tower_doctor`/生成参考/`docs:check`/`surface:check` 统一为
|
|
36
|
+
29 个工具面(默认装配 28,tower-hmr 关闭时少 `tower_reload`),直接注册的 `tower_mode`/`tower_control`
|
|
37
|
+
也进入刹车;baseline 以 correction 记录 27→29。
|
|
38
|
+
|
|
39
|
+
### 变更
|
|
40
|
+
|
|
41
|
+
- **#122 安全网永不可关**:`tower_doctor`/`tower_recover` 在任何配置下都注册;删除
|
|
42
|
+
`tools.exposeDoctor`/`exposeRecovery`/`exposeOperational` 与整个 `tools` 组、`dev.hotReload`;
|
|
43
|
+
旧配置作为未知键被拒绝并停止挂载(升级指南 en/zh 已记录迁移)。
|
|
44
|
+
- 配置面从 64 键/15 节收缩为 56 键/14 节;生成的 config/tools/component-surface 参考同步更新。
|
|
45
|
+
|
|
46
|
+
### 修复
|
|
47
|
+
|
|
48
|
+
- **F11/F9**:`operationalRegistry` 解析容忍 trailing comma 与 `OPERATIONAL_TOOLS.set` 路由;
|
|
49
|
+
根 README 的工具计数标题修正为 29(20 core + 9 optional)并注明默认装配 28。
|
|
50
|
+
- 真实 `smoke:mount` 在 pinned host `0.2.1-rc.2` 上通过(默认 28/28),required `mount` CI 上下文保持绿。
|
|
7
51
|
## 2.3.0(2026-10-23)
|
|
8
52
|
|
|
9
53
|
### 新增
|
package/COMPATIBILITY.md
CHANGED
|
@@ -5,6 +5,8 @@ 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.4.1 | 2026-10-24 | mount smoke (28/28 default tools, 3 skills, 2 presets, 1 bundle row), `npm test` (620), `types:coverage` 146/146, docs + seven generated checks incl capability-row locales, components + philosophy + surface gates |
|
|
9
|
+
| 0.2.1-rc.2 | v2.4.0 | 2026-10-24 | mount smoke (28/28 default tools, 3 skills, 2 presets, 1 bundle row), `npm test` (612), `types:coverage` 146/146, strict ratchet 10, docs + six generated checks, components + philosophy + surface gates |
|
|
8
10
|
| 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
11
|
| 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
12
|
| 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 |
|
package/CONTRIBUTING.md
CHANGED
|
@@ -11,7 +11,7 @@ Two short documents say what this project refuses to do:
|
|
|
11
11
|
pointer does not resolve fails `npm run docs:check:philosophy`.
|
|
12
12
|
- [docs/adr/](docs/adr/README.md) — dated decision records for choices already
|
|
13
13
|
made (ports as data, one `state.json`, `allowAgentControl`, the surface
|
|
14
|
-
budget).
|
|
14
|
+
budget, the component surface).
|
|
15
15
|
|
|
16
16
|
Every pull request answers **"which principle does this change touch, or which new
|
|
17
17
|
one does it require?"** in the PR template. "None" is not an answer: if no
|
|
@@ -21,6 +21,16 @@ concept budget and its classification table are in
|
|
|
21
21
|
or tool may grow only with an ADR or a provably matching deletion, checked by
|
|
22
22
|
`npm run surface:check`.
|
|
23
23
|
|
|
24
|
+
The component axis — who may be switched off — is principle 11 and
|
|
25
|
+
[ADR 0005](docs/adr/0005-component-surface.md). It is generated from
|
|
26
|
+
[scripts/component-surface.json](scripts/component-surface.json) into
|
|
27
|
+
[docs/reference/component-surface.md](docs/reference/component-surface.md) and
|
|
28
|
+
enforced by `npm run docs:check:components`: every visible component needs
|
|
29
|
+
`title`/`summary`/`disables` in both locales, the host/health/recovery/review/
|
|
30
|
+
merge-gate capabilities may not be gated by any row or boolean key (the
|
|
31
|
+
`gateableTools` allowlist is empty since #122), and the component count — 8
|
|
32
|
+
visible rows in 2.4.0 — is the fourth baselined surface.
|
|
33
|
+
|
|
24
34
|
## Landing changes
|
|
25
35
|
|
|
26
36
|
`main` is protected. Every change lands through a pull request; direct pushes to `main` are rejected,
|
|
@@ -108,10 +118,11 @@ the push gates; CI schedules it nightly.
|
|
|
108
118
|
- `worker.js` — the `tower-worker` preset policy (tool surface + protocol text).
|
|
109
119
|
- `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
120
|
- `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.
|
|
121
|
+
- `types/` — the published declaration surface (`package.json` `types` is `types/index.d.ts`). The vocabulary block in that file is generated from the runtime constants by `npm run types:generate` and gated by `npm run docs:check:generated:types`; never edit it by hand. `tsconfig.strict.json` turns `noImplicitAny` on for an explicit file list that may grow but never silently shrink: add a file, then record it with `npm run types:strict:write` (a removal also needs `--allow-shrink`). `npm run types:coverage` fails when a shipped runtime `.js` file is outside the `tsc` program.
|
|
111
122
|
|
|
112
123
|
## Pull request checklist
|
|
113
124
|
|
|
114
|
-
1. `npm test` passes locally (
|
|
125
|
+
1. `npm test` passes locally (620 tests today, including protocol end-to-end against a real temporary Git repository and mock spawn coverage) and `npm run docs:check` passes.
|
|
115
126
|
2. New behavior has a test. Bug fixes should add a test that fails before the fix.
|
|
116
127
|
3. No secrets, tokens, personal paths, session logs, or generated artifacts are added. `.gitignore` covers local acceptance output; do not force-add it.
|
|
117
128
|
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.
|
|
@@ -123,7 +134,13 @@ the push gates; CI schedules it nightly.
|
|
|
123
134
|
reports no failure; a `Regression test:` path must exist.
|
|
124
135
|
9. If you added a config key or tool, `scripts/surface-baseline.json` and
|
|
125
136
|
[docs/reference/surface-budget.md](docs/reference/surface-budget.md) are updated in the
|
|
126
|
-
same PR, and `npm run surface:check` passes.
|
|
137
|
+
same PR, and `npm run surface:check` passes. A visible component change also updates
|
|
138
|
+
`scripts/surface-baseline.json` (`components`), `scripts/component-surface.json` and
|
|
139
|
+
`docs/reference/component-surface.md` (`npm run docs:generate:components`), and must pass
|
|
140
|
+
`npm run docs:check:components`. A new visible capability row also ships its own
|
|
141
|
+
`<specifier>/locale/*.json` row label via `npm run docs:generate:locale` and passes
|
|
142
|
+
`npm run docs:check:generated:locale` (see
|
|
143
|
+
[architecture/overview.md](docs/architecture/overview.md#row-display-metadata-121)).
|
|
127
144
|
|
|
128
145
|
## Mount smoke
|
|
129
146
|
|
package/PRINCIPLES.md
CHANGED
|
@@ -135,6 +135,24 @@ without a test path, and a postmortem that names the cause but not the proof.
|
|
|
135
135
|
Enforced by: `docs/operations/incident.md`, `scripts/regression-audit.mjs`, and
|
|
136
136
|
`tests/regression-audit.test.mjs`; gate: `npm run audit:regressions`.
|
|
137
137
|
|
|
138
|
+
## 11. Optional capabilities are switches; the core is not
|
|
139
|
+
|
|
140
|
+
A component may exist only for a capability whose off state leaves the rest of the
|
|
141
|
+
product intact. Every capability that qualifies gets a component, so the user can
|
|
142
|
+
control it. Every capability that does not — the host, the health contract, the
|
|
143
|
+
recovery escape, the review contract, the merge gates — is configuration or code,
|
|
144
|
+
never a switch: not as a row, and not as a boolean that removes it.
|
|
145
|
+
|
|
146
|
+
Refused because of this: a single all-or-nothing switch offered as "simpler"; a
|
|
147
|
+
disableable `tower_doctor`/`tower_recover`; a core feature exposed as a toggle; a
|
|
148
|
+
component whose off state is worse than uninstalling the plugin.
|
|
149
|
+
|
|
150
|
+
Enforced by: `scripts/check-components.mjs` (locale coverage, the
|
|
151
|
+
forbidden-to-disable denylist and the generated component reference),
|
|
152
|
+
`scripts/surface-baseline.mjs` (the component count is a baselined surface),
|
|
153
|
+
`docs/adr/0005-component-surface.md`, and `tests/components.test.mjs`; gate:
|
|
154
|
+
`npm run docs:check:components`.
|
|
155
|
+
|
|
138
156
|
## Non-goals
|
|
139
157
|
|
|
140
158
|
This file is not a governance process: there is no RFC queue, no architecture
|
package/README.en.md
CHANGED
|
@@ -128,20 +128,33 @@ Image and file attachments are forwarded together with the instruction as one co
|
|
|
128
128
|
|
|
129
129
|
## Component model
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
`cordis
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
The bundle ships a **mandatory host anchor plus individually switchable optional
|
|
132
|
+
rows** (#121): `cordis.patch.yml` inserts a top-level `tower` group that mounts
|
|
133
|
+
the Host plugin and the mandatory `preset-tower-worker`, then the optional
|
|
134
|
+
components `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`,
|
|
135
|
+
`tower-tests`, `tower-hmr` (shipped off) and `preset-tower-reviewer`. Turn a row
|
|
136
|
+
off from the Plugins page and exactly that capability goes away; the mode, the
|
|
137
|
+
planning tools, `tower_status`, and the remaining tools keep working.
|
|
138
|
+
|
|
139
|
+
Mode entry and `tower_plan` validate the required presets before work starts. The
|
|
140
|
+
worker preset is mandatory; the reviewer preset is required only when
|
|
141
|
+
`review.requireReview` is true, so the reviewer row may be off while review is
|
|
142
|
+
optional and is locked with an explanatory `preset-missing` error when the gate
|
|
143
|
+
is on. `tower-tests` off with `gates.requireTests:true` is refused the same way.
|
|
144
|
+
`tower_doctor` and `tower_recover` are not components and have no config key: they
|
|
145
|
+
are registered for every configuration (#122).
|
|
146
|
+
|
|
147
|
+
[Principle 11](PRINCIPLES.md) and
|
|
148
|
+
[ADR 0005](docs/adr/0005-component-surface.md) say a component may exist only for
|
|
149
|
+
a capability whose off state leaves the rest intact; the host, health, recovery,
|
|
150
|
+
review and merge-gate capabilities are never switches. The per-key answer is the
|
|
151
|
+
generated [component surface](docs/reference/component-surface.md), enforced by
|
|
152
|
+
`npm run docs:check:components`.
|
|
140
153
|
|
|
141
154
|
## Configuration
|
|
142
155
|
|
|
143
156
|
```yaml
|
|
144
|
-
- id: tower
|
|
157
|
+
- id: tower-host
|
|
145
158
|
name: 'dsh-tower'
|
|
146
159
|
config:
|
|
147
160
|
section: 'Optional legacy alias replacing the Tower-mode prompt text'
|
|
@@ -166,13 +179,13 @@ or spawning first and dead-ending at review/merge.
|
|
|
166
179
|
- `mode.allowAgentControl` defaults to `true` in code; the bundled `cordis.patch.yml` also sets it explicitly to `true` so the model may enter/leave when the user intent or task state warrants it, always stating a reason. Set it to `false` for human-only control. The bundled patch also enables `mode.workspaceLock` (single-active tower), `send.interruptDefault`, `init.autoGitInit`, and sets `spawn.staleMs` to 15s.
|
|
167
180
|
- `/tower base <branch>` records a session-level init base preference; `/tower on` clears an active plan mode (`mode.planModePolicy:"refuse"` instead refuses entry), keeping plan and Tower mutually exclusive.
|
|
168
181
|
- Repository-level config: put the same JSON shape in `<git-common-dir>/dsh-tower/config.json` (usually `.git/dsh-tower/config.json`) to override `review`/`merge`/`worktrees`/`spawn`. Precedence is code defaults < profile config < repository config < tool arguments. `mode.*` is session-scoped and only the profile layer controls it.
|
|
169
|
-
- Every key and default is in [docs/reference/config.md](docs/reference/config.md); unknown top-level
|
|
182
|
+
- Every key and default is in [docs/reference/config.md](docs/reference/config.md); unknown keys are rejected (top-level and inside a group) so typos cannot silently disable security-relevant settings. Tool error codes are in [docs/reference/errors.md](docs/reference/errors.md).
|
|
170
183
|
|
|
171
|
-
## Tools (
|
|
184
|
+
## Tools (29: 22 core + 7 optional)
|
|
172
185
|
|
|
173
186
|
`tower_init` · `tower_plan` · `tower_mission` · `tower_status` · `tower_spawn` · `tower_roster` · `tower_adopt` · `tower_diff` · `tower_send` · `tower_inbox` · `tower_complete` · `tower_interrupt` · `tower_finding` · `tower_findings` · `tower_review` · `tower_preflight` · `tower_merge` · `tower_rebase` · `tower_teardown` · `tower_mode` · `tower_doctor` · `tower_recover` · `tower_test` · `tower_batch` · `tower_resource` · `tower_import_legacy` · `tower_legacy_map`
|
|
174
187
|
|
|
175
|
-
`tower_control` is registered by default (`allowAgentControl:true`); set it to `false` to disable it. `tower_reload` is registered while `
|
|
188
|
+
The list above is the 22 core tools plus 5 of the component-owned optional tools. `tower_control` is the sixth optional tool, registered by default (`allowAgentControl:true`); set it to `false` to disable it. `tower_reload` is the seventh, registered only while the `tower-hmr` component row is on (shipped off) and returning `reload-unavailable` without an HMR service (so up to 29 tools: 22 core + 7 optional). `tower_doctor` and `tower_recover` are core: they are registered for every configuration (#122). Mission-scoped tools also accept `id` as an alias for `missionId`; snake/camel spellings such as `task_done`/`taskDone` are both accepted.
|
|
176
189
|
|
|
177
190
|
## Safety and guardrails
|
|
178
191
|
|
|
@@ -201,7 +214,7 @@ Workers use the bundled `tower-worker` preset (writable worktree surface); revie
|
|
|
201
214
|
|
|
202
215
|
| Path | Contents |
|
|
203
216
|
|---|---|
|
|
204
|
-
| `package.json`, `cordis.patch.yml` | bundle manifest and loader patch (
|
|
217
|
+
| `package.json`, `cordis.patch.yml` | bundle manifest and loader patch (a mandatory `tower` host group plus the optional capability rows) |
|
|
205
218
|
| `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 |
|
|
206
219
|
| `skills.js`, `skills/` | bundled agent skills (worker protocol, review checklist, recovery playbook) |
|
|
207
220
|
| `docs/` | guides, reference, architecture, operations, FAQ and examples |
|
|
@@ -228,7 +241,7 @@ Every push and PR runs the same set of jobs; each job backs one checkable claim
|
|
|
228
241
|
|
|
229
242
|
| Job | Claim | Evidence |
|
|
230
243
|
|---|---|---|
|
|
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
|
|
244
|
+
| `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 five `docs:check:generated*` checks (including the generated type vocabulary), `pack:check`, `architecture:check`, `types:coverage`, `types:strict:check`, `types:check`, `domain:check` |
|
|
232
245
|
| `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
246
|
| `test`'s `Coverage (Node 22)` step | coverage is actually measured in CI (reported only: no threshold, no badge) | `npm run test:coverage` |
|
|
234
247
|
| `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` |
|
|
@@ -242,7 +255,7 @@ The live suite downloads a browser and boots a real host, so it is out of push/P
|
|
|
242
255
|
|
|
243
256
|
`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.
|
|
244
257
|
|
|
245
|
-
`mount` asserts the throwaway profile's **loaded surface**, never the text of `cordis.patch.yml`:
|
|
258
|
+
`mount` asserts the throwaway profile's **loaded surface**, never the text of `cordis.patch.yml`: 28 `tower_*` tools in the shipped profile (27 `tools/*.js` modules plus `tower_mode` / `tower_control`, minus `tower_reload` because `tower-hmr` ships off; 29 with every optional capability row on), the 3 bundled skills, both the `tower-worker` and `tower-reviewer` presets, the `/tower` command, one top-level `tower` host group, and the optional top-level rows (`tower-hmr` is shipped off). 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.
|
|
246
259
|
|
|
247
260
|
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).
|
|
248
261
|
|
package/README.md
CHANGED
|
@@ -127,19 +127,26 @@ tower_teardown repoRoot=D:\work\repo
|
|
|
127
127
|
|
|
128
128
|
## 组件模型
|
|
129
129
|
|
|
130
|
-
本 bundle 采用
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
review
|
|
130
|
+
本 bundle 采用 **强制 host 锚点 + 可单独启停的可选行**(#121):`cordis.patch.yml` 插入顶层
|
|
131
|
+
`tower` group(挂载 Host 插件与强制的 `preset-tower-worker`),随后是可选组件 `tower-skills`、
|
|
132
|
+
`tower-batch`、`tower-import`、`tower-leases`、`tower-tests`、`tower-hmr`(随包关闭)与
|
|
133
|
+
`preset-tower-reviewer`。在插件页关闭某一行,只会移除该能力;模式、规划工具、`tower_status`
|
|
134
|
+
与其余工具继续工作。
|
|
135
|
+
|
|
136
|
+
进入 Tower 模式与 `tower_plan` 前会校验必需的 preset。worker preset 是强制依赖;reviewer
|
|
137
|
+
preset 只在 `review.requireReview` 为 true 时必需,因此审查可选时允许关闭 reviewer 行,强制审查
|
|
138
|
+
开启时会以带解释的 `preset-missing` 锁定。`tower-tests` 关闭且 `gates.requireTests:true` 时同样
|
|
139
|
+
拒绝。`tower_doctor` 与 `tower_recover` 不是组件、也没有配置键:任何配置都会注册它们(#122)。
|
|
140
|
+
|
|
141
|
+
原则 11([PRINCIPLES.md](PRINCIPLES.md))与
|
|
142
|
+
[ADR 0005](docs/adr/0005-component-surface.md) 要求:只有关闭后仍不破坏其余能力的 capability
|
|
143
|
+
才能成为组件;host、健康检查、恢复通道、审查契约与合并门禁永远不是开关。逐键答案见生成的
|
|
144
|
+
[组件表面](docs/reference/component-surface.md),由 `npm run docs:check:components` 强制。
|
|
138
145
|
|
|
139
146
|
## 配置
|
|
140
147
|
|
|
141
148
|
```yaml
|
|
142
|
-
- id: tower
|
|
149
|
+
- id: tower-host
|
|
143
150
|
name: 'dsh-tower'
|
|
144
151
|
config:
|
|
145
152
|
section: '(可选,兼容别名)覆盖 Tower 模式系统提示文案'
|
|
@@ -164,13 +171,13 @@ review/merge 阶段死锁。
|
|
|
164
171
|
- 代码中 `mode.allowAgentControl` 默认 `true`;随包 `cordis.patch.yml` 也显式设为 `true`:模型可在用户意图/任务状态明确时自行进出,但必须给出理由;设为 `false` 时只有人类能切换。随包 patch 还默认开启 `mode.workspaceLock`(单活塔)、`send.interruptDefault`、`init.autoGitInit`,并把 `spawn.staleMs` 设为 15s。
|
|
165
172
|
- `/tower base <branch>` 可设置会话级 init base 偏好;`/tower on` 会清除活跃 plan mode(`mode.planModePolicy:"refuse"` 则拒绝进入),保证 plan/tower 互斥。
|
|
166
173
|
- 仓库级配置:在 `<git-common-dir>/dsh-tower/config.json`(通常是 `.git/dsh-tower/config.json`)放入同样的 JSON 结构,可覆盖 `review`/`merge`/`worktrees`/`spawn`;优先级为代码默认 < profile < 仓库配置 < 工具参数。`mode.*` 属于会话级,只在 profile 生效。
|
|
167
|
-
- 所有键与默认值见 [docs/reference/config.md](docs/reference/config.md)
|
|
174
|
+
- 所有键与默认值见 [docs/reference/config.md](docs/reference/config.md);未知键(顶层与分组内)都会被拒绝,防止拼写错误静默失效。工具错误码见 [docs/reference/errors.md](docs/reference/errors.md)。
|
|
168
175
|
|
|
169
|
-
## 工具(
|
|
176
|
+
## 工具(29 个:22 个核心 + 7 个可选)
|
|
170
177
|
|
|
171
178
|
`tower_init` · `tower_plan` · `tower_mission` · `tower_status` · `tower_spawn` · `tower_roster` · `tower_adopt` · `tower_diff` · `tower_send` · `tower_inbox` · `tower_complete` · `tower_interrupt` · `tower_finding` · `tower_findings` · `tower_review` · `tower_preflight` · `tower_merge` · `tower_rebase` · `tower_teardown` · `tower_mode` · `tower_doctor` · `tower_recover` · `tower_test` · `tower_batch` · `tower_resource` · `tower_import_legacy` · `tower_legacy_map`
|
|
172
179
|
|
|
173
|
-
|
|
180
|
+
上面列出的是 22 个核心工具加 5 个由组件行拥有的可选工具。`tower_control` 是第 6 个可选工具,默认注册(`allowAgentControl:true`);设为 `false` 可禁用。`tower_reload` 是第 7 个,仅在 `tower-hmr` 组件行开启时注册(随包关闭),无 HMR 服务调用会返回 `reload-unavailable`(因此最多 29 个工具:22 个核心 + 7 个可选)。`tower_doctor` 与 `tower_recover` 属于核心:任何配置都会注册它们(#122)。mission 级工具同时接受 `id` 作为 `missionId` 的别名;`task_done`/`taskDone` 等 snake/camel 写法均可。
|
|
174
181
|
|
|
175
182
|
## 安全与约束
|
|
176
183
|
|
|
@@ -199,7 +206,7 @@ worker 与 reviewer 是使用内置 `tower-worker` preset 的普通 DSH 子代
|
|
|
199
206
|
|
|
200
207
|
| 路径 | 内容 |
|
|
201
208
|
|---|---|
|
|
202
|
-
| `package.json`、`cordis.patch.yml` | bundle 清单与 loader patch
|
|
209
|
+
| `package.json`、`cordis.patch.yml` | bundle 清单与 loader patch(强制 `tower` host group + 可选能力行) |
|
|
203
210
|
| `index.js`、`host/`、`adapters/`、`app/`、`domain/`、`ports/`、`tools/`、`worker.js` | Host 入口;`domain/` 纯核心;`ports/` 契约;`adapters/` 唯一 I/O;`app/` 用例;`host/` 组合根与会话胶水;`tools/` 每工具一个 handler;worker preset 策略 |
|
|
204
211
|
| `locale/`、`icon.svg` | 本地化与图标 |
|
|
205
212
|
| `tests/` | 纯函数、协议 e2e、插件级测试;可选的真实 Web 运行时脚本 |
|
|
@@ -222,7 +229,7 @@ npm test # node --test tests/*.test.mjs
|
|
|
222
229
|
|
|
223
230
|
| Job | 声明 | 证据 |
|
|
224
231
|
|---|---|---|
|
|
225
|
-
| `test (22)`、`test (24)`(Ubuntu) | 离线套件、文档一致性、生成文档、发布文件清单、架构、类型与 domain 纯度全部成立 | `npm test`、`npm run docs:check
|
|
232
|
+
| `test (22)`、`test (24)`(Ubuntu) | 离线套件、文档一致性、生成文档、发布文件清单、架构、类型与 domain 纯度全部成立 | `npm test`、`npm run docs:check`、五个 `docs:check:generated*`(含生成的类型词汇表)、`pack:check`、`architecture:check`、`types:coverage`、`types:strict:check`、`types:check`、`domain:check` |
|
|
226
233
|
| `test (22, windows)`(`windows-latest`,非必需检查) | 同一套离线套件与离线 gates 在真实 Windows 文件系统上成立;路径大小写、`pwsh` shell、rename/unlink 重试都不是只在 Linux 上被假设 | `npm test` + 与 Ubuntu 相同的离线 gates(不跑 release/coverage) |
|
|
227
234
|
| `test` 的 `Coverage (Node 22)` 步骤 | 覆盖率在 CI 中真实测量(只报告,不设阈值、不加徽章) | `npm run test:coverage` |
|
|
228
235
|
| `test` 的 `Release check`(Node 22)步骤 | tag 是一种声明:版本已越过上一个版本 tag、推送的 tag 是 `v<version>`、CHANGELOG 带有发布内容 | `npm run release:check` |
|
|
@@ -236,7 +243,7 @@ live suite 需要下载浏览器并真实启动 host,因此移出 push/PR,
|
|
|
236
243
|
|
|
237
244
|
`tests/platform.test.mjs` 用注入的 `platform`/`rename`/`unlink` 在任意 OS 上同时断言 win32 与 POSIX 两条分支;唯一需要真实 Windows 的“rename 覆盖被打开的目标文件”用例在 Linux 上以 `# SKIP` 计入 TAP(`tests 11`、`skipped 1`),而不是静默消失。macOS 不单设 runner:它与 Linux 走同一条 POSIX 分支,这是 #86 的非目标。
|
|
238
245
|
|
|
239
|
-
`mount` 断言一次性 profile 的**已加载表面**,而不是 `cordis.patch.yml`
|
|
246
|
+
`mount` 断言一次性 profile 的**已加载表面**,而不是 `cordis.patch.yml` 的文本:随包 profile 加载 28 个 `tower_*` 工具(27 个 `tools/*.js` 模块 + `tower_mode` / `tower_control`,减去因 `tower-hmr` 随包关闭而不注册的 `tower_reload`;所有可选能力行全开时为 29 个)、3 个内置 skill、`tower-worker` / `tower-reviewer` 两个 preset、`/tower` 命令、一个顶层 `tower` host group 以及可选的顶层能力行(`tower-hmr` 随包关闭)。它不调用模型、不打开浏览器。在维护者的 Windows 机器上实测:host 冷装(含安装)约 100 秒,host 已缓存约 85 秒;CI 中该步骤上限为 3 分钟(`timeout-minutes: 3`),`mount` job 上限 5 分钟。红叉会打印解析后的 profile 路径、host build、已加载计数与每一条未满足的声明。
|
|
240
247
|
|
|
241
248
|
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 分钟)。
|
|
242
249
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
# dsh-tower
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
1
|
+
# dsh-tower component rows (#121).
|
|
2
|
+
#
|
|
3
|
+
# The first top-level row is the mandatory host group: the Host plugin and its
|
|
4
|
+
# worker-preset dependency. Turning it off equals uninstalling Tower (no tools,
|
|
5
|
+
# no presets, no skills), which is the accepted all-off boundary; every other
|
|
6
|
+
# top-level row is an individually switchable optional capability whose off
|
|
7
|
+
# state leaves the rest of Tower working.
|
|
8
|
+
#
|
|
9
|
+
# Policy: principle 11 in PRINCIPLES.md + docs/adr/0005-component-surface.md.
|
|
10
|
+
# The per-key answer is docs/reference/component-surface.md, generated from
|
|
11
|
+
# scripts/component-surface.json. `npm run docs:check:components` refuses a new
|
|
12
|
+
# switch, a missing locale label, or a moved component count without a written
|
|
13
|
+
# decision. `tower_doctor` and `tower_recover` are part of the host and are never
|
|
14
|
+
# switches (#122); a capability row owns exactly one optional capability, and
|
|
15
|
+
# `tower_status` / `tower_doctor` name any row that is off.
|
|
5
16
|
- insert:
|
|
6
|
-
|
|
17
|
+
# Mandatory host anchor. Not an optional capability: off == uninstall.
|
|
18
|
+
- id: tower
|
|
7
19
|
name: cordis:group
|
|
8
20
|
group: true
|
|
9
21
|
config:
|
|
10
|
-
- id: tower
|
|
22
|
+
- id: tower-host
|
|
11
23
|
name: 'dsh-tower'
|
|
12
24
|
config:
|
|
13
25
|
mode:
|
|
@@ -21,8 +33,6 @@
|
|
|
21
33
|
interruptDefault: true
|
|
22
34
|
spawn:
|
|
23
35
|
staleMs: 15000
|
|
24
|
-
- id: tower-skills
|
|
25
|
-
name: 'dsh-tower/skills'
|
|
26
36
|
- id: preset-tower-worker
|
|
27
37
|
name: '@deepseek-ai/dsh-agent-preset'
|
|
28
38
|
config:
|
|
@@ -41,23 +51,41 @@
|
|
|
41
51
|
sampleOverCapGlobResults: false
|
|
42
52
|
- id: tower-worker-pwsh
|
|
43
53
|
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
# Optional capabilities: each row is the switch for exactly one capability.
|
|
55
|
+
- id: tower-skills
|
|
56
|
+
name: 'dsh-tower/skills'
|
|
57
|
+
- id: tower-batch
|
|
58
|
+
name: 'dsh-tower/batch'
|
|
59
|
+
- id: tower-import
|
|
60
|
+
name: 'dsh-tower/import'
|
|
61
|
+
- id: tower-leases
|
|
62
|
+
name: 'dsh-tower/leases'
|
|
63
|
+
- id: tower-tests
|
|
64
|
+
name: 'dsh-tower/tests'
|
|
65
|
+
# dev hot reload: shipped off. Enable the row for a linked dev profile.
|
|
66
|
+
- id: tower-hmr
|
|
67
|
+
name: 'dsh-tower/hmr'
|
|
68
|
+
disabled: true
|
|
69
|
+
# Conditional optional component: off is supported only while
|
|
70
|
+
# review.requireReview is false; with the gate on planning/mode entry refuse
|
|
71
|
+
# the combination with an explanatory error.
|
|
72
|
+
- id: preset-tower-reviewer
|
|
73
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
74
|
+
config:
|
|
75
|
+
id: tower-reviewer
|
|
76
|
+
name: Tower Reviewer
|
|
77
|
+
description: Read-only independent reviewer for a completed Tower mission.
|
|
78
|
+
order: 61
|
|
79
|
+
plugins:
|
|
80
|
+
- id: tower-reviewer-policy
|
|
81
|
+
name: 'dsh-tower/reviewer'
|
|
82
|
+
- id: tower-reviewer-fs
|
|
83
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
84
|
+
config:
|
|
85
|
+
write: false
|
|
86
|
+
edit: false
|
|
87
|
+
readImage: false
|
|
88
|
+
- id: tower-reviewer-fs-search
|
|
89
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
90
|
+
config:
|
|
91
|
+
sampleOverCapGlobResults: false
|
package/docs/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Choose a path:
|
|
|
9
9
|
| New user | [When to use Tower](guide/scenarios-and-philosophy.md) → [Quick start](guide/quickstart.md) → [Tools](reference/tools.md) → [Example](examples/first-mission.md) |
|
|
10
10
|
| Choosing a workflow | [When to use Tower, and how it differs from Agent Teams, Ralph, Workflow and parallel subagents](guide/scenarios-and-philosophy.md) |
|
|
11
11
|
| Operator | [Runbook](operations/runbook.md) → [Blockers](reference/blockers.md) → [Error codes](reference/errors.md) → [Events](reference/events.md) → [Doctor](reference/doctor.md) → [Testing](reference/testing.md) → [Batches/leases/legacy](reference/operations.md) → [Security](operations/security.md) → [Incidents and regressions](operations/incident.md) |
|
|
12
|
-
| Maintainer | [Engineering principles](../PRINCIPLES.md) → [ADRs](adr/README.md) → [Architecture](architecture/overview.md) → [Surface budget](reference/surface-budget.md) → [Config](reference/config.md) → [Hot reload](guide/hot-reload.md) → [Release checklist](guide/release.md) → [Tracker board](board.md) → [Contributing](../CONTRIBUTING.md) |
|
|
12
|
+
| Maintainer | [Engineering principles](../PRINCIPLES.md) → [ADRs](adr/README.md) → [Architecture](architecture/overview.md) → [Surface budget](reference/surface-budget.md) → [Component surface](reference/component-surface.md) → [Config](reference/config.md) → [Hot reload](guide/hot-reload.md) → [Release checklist](guide/release.md) → [Tracker board](board.md) → [Contributing](../CONTRIBUTING.md) |
|
|
13
13
|
|
|
14
14
|
Reference pages are the source of truth for the current release; the [tools reference](reference/tools.md),
|
|
15
15
|
[blocker list](reference/blockers.md) and [error codes](reference/errors.md) are checked against the
|
|
@@ -37,6 +37,7 @@ Other useful pages: [When to use Tower](guide/scenarios-and-philosophy.md), [Ins
|
|
|
37
37
|
- [Batches, leases and legacy import](reference/operations.md)
|
|
38
38
|
- [Doctor](reference/doctor.md)
|
|
39
39
|
- [Surface budget and classification](reference/surface-budget.md)
|
|
40
|
+
- [Component surface: who may be switched off](reference/component-surface.md)
|
|
40
41
|
|
|
41
42
|
**Operations and background**
|
|
42
43
|
|
|
@@ -36,6 +36,26 @@ note for each niche key.
|
|
|
36
36
|
- The numbers are three lines in `tower_doctor` output, not a new dashboard.
|
|
37
37
|
- This does not remove any key in this release; it is a brake, not a clean-up.
|
|
38
38
|
|
|
39
|
+
## Measurement correction (2026-10-23)
|
|
40
|
+
|
|
41
|
+
The first baseline counted 27 tools because it recorded only the `define()`
|
|
42
|
+
registration path. `tower_mode` and `tower_control` are registered directly by
|
|
43
|
+
`index.js`, so the shipped surface was always 29 (#118). The baseline now carries
|
|
44
|
+
an explicit correction decision (27 → 29) with a reason, a date and a reference
|
|
45
|
+
to this ADR, and the recorder in `host/tool-registry.js` covers both registration
|
|
46
|
+
paths. This is a measurement correction, not surface growth: a later count change
|
|
47
|
+
still needs its own ADR or a provably matching deletion.
|
|
48
|
+
|
|
49
|
+
## Amended by ADR 0005 (2026-10-23)
|
|
50
|
+
|
|
51
|
+
The decision above counts three surfaces. [ADR 0005](0005-component-surface.md)
|
|
52
|
+
extends the budget with a fourth — the number of user-visible component rows in
|
|
53
|
+
`cordis.patch.yml` — and records it as its own `components` baseline entry with a
|
|
54
|
+
two-way decision rule (a contraction needs a decision just like growth). The
|
|
55
|
+
three original counts are still measured by `npm run surface:check`; the
|
|
56
|
+
component count is validated by `npm run docs:check:components`. The historical
|
|
57
|
+
decision above is unchanged: "three surfaces" describes the 2.3.0 rule.
|
|
58
|
+
|
|
39
59
|
## Rejected alternatives
|
|
40
60
|
|
|
41
61
|
- **A line-count or file-size rule** — the project already has those and the
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 0005 — The component surface: mandatory host, switchable optional rows
|
|
2
|
+
|
|
3
|
+
- Date: 2026-10-23
|
|
4
|
+
- Status: Accepted
|
|
5
|
+
- Deciders: maintainer
|
|
6
|
+
- Supersedes: none
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The plugin had no durable statement of what may and may not be a switch. The
|
|
11
|
+
policy that #65 was resolved against lived only in the issue body, so when the
|
|
12
|
+
issue closed the policy evaporated: the 2.0.4 surface (four top-level rows:
|
|
13
|
+
`tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`) became
|
|
14
|
+
one all-or-nothing `tower-bundle` group row plus seven `tools.expose*` config
|
|
15
|
+
keys. The user-controllable component count went 4 → 1 while nothing recorded
|
|
16
|
+
that decision or refused the next one.
|
|
17
|
+
|
|
18
|
+
Two failure modes are already visible in the 2.3.0 tree:
|
|
19
|
+
|
|
20
|
+
1. Optional capabilities have no component. Skills, the reviewer preset, the
|
|
21
|
+
batch/import/leases/test tools and hot reload can only be changed by editing
|
|
22
|
+
YAML under `tools:`, which is the discoverability problem components exist to
|
|
23
|
+
solve. A config key is still a switch, just a worse-labelled one.
|
|
24
|
+
2. The safety nets are switchable. `tools.exposeOperational:false` removes all
|
|
25
|
+
seven operational tools including `tower_recover` and `tower_doctor`, so the
|
|
26
|
+
equipment that diagnoses and repairs a stale `state.lock` can itself be
|
|
27
|
+
turned off, while the plugin stays mounted. That off state is worse than
|
|
28
|
+
uninstalling the plugin, because uninstalling would remove the lock problem
|
|
29
|
+
too.
|
|
30
|
+
|
|
31
|
+
`PRINCIPLES.md` already says an enforcement pointer that does not resolve is a
|
|
32
|
+
slogan; by that standard the component policy was a slogan.
|
|
33
|
+
|
|
34
|
+
## Decision
|
|
35
|
+
|
|
36
|
+
Adopt this shape and record it as principle 11:
|
|
37
|
+
|
|
38
|
+
- **The host is mandatory.** `tower` (and its worker preset dependency) is never
|
|
39
|
+
a switch, not as a row and not as a boolean that removes it.
|
|
40
|
+
- **Each optional capability is its own component row**, individually toggleable
|
|
41
|
+
from the plugin page, with `title`, `summary` and `disables` in both locales and
|
|
42
|
+
a documented fallback stating what still works while it is off. A capability
|
|
43
|
+
qualifies only when its off state leaves the rest of Tower intact and is not
|
|
44
|
+
worse than uninstalling.
|
|
45
|
+
- **The safety nets are never switches.** The health contract, the recovery
|
|
46
|
+
escape, the review contract and the merge gates are configuration or code; no
|
|
47
|
+
group, row or boolean key may gate them. Enforcement covers both routes: a
|
|
48
|
+
`cordis.patch.yml` row that defaults any key classified `forbidden-to-disable`
|
|
49
|
+
to `false` is refused, and every `tools.expose*` route in
|
|
50
|
+
`host/tool-registry.js` must match the `gateableTools` allowlist in the source
|
|
51
|
+
of truth, so a new route to a review, merge or host tool fails even when that
|
|
52
|
+
capability lists no tools of its own.
|
|
53
|
+
- **Config keys stay on the component axis.** Every config key is classified
|
|
54
|
+
`core`, `optional-component`, `optional-config` or `forbidden-to-disable` in
|
|
55
|
+
`docs/reference/component-surface.md`, generated from the single source of
|
|
56
|
+
truth `scripts/component-surface.json`, so "should this be a component?" is a
|
|
57
|
+
table lookup rather than a judgement call.
|
|
58
|
+
- **The component count is a baselined surface.** `scripts/surface-baseline.json`
|
|
59
|
+
gains `components` (seed 4, the 2.0.4 row count; value 1, today's group row) and
|
|
60
|
+
the 4 → 1 contraction is itself a dated decision. Unlike the other surfaces, the
|
|
61
|
+
component count is two-way: a contraction needs a decision record just like
|
|
62
|
+
growth does. `npm run surface:check` and `npm run docs:check:components` refuse a
|
|
63
|
+
count that moved without one. That is what makes the next contraction — or the
|
|
64
|
+
coming growth — visible in a diff.
|
|
65
|
+
|
|
66
|
+
This ADR records the policy. The individual instance fixes follow it: #121 turns
|
|
67
|
+
the qualifying capabilities into optional rows, and #122 deletes
|
|
68
|
+
`tools.exposeDoctor` / `tools.exposeRecovery`, removes the `exposeOperational`
|
|
69
|
+
master switch and the whole `tools` config group. While that migration was in
|
|
70
|
+
flight the two `tools.expose*` keys that gated the health and recovery contracts
|
|
71
|
+
were recorded as dated debt in `scripts/component-surface.json`, so a *new*
|
|
72
|
+
switch failed the gate immediately even before #122 landed.
|
|
73
|
+
|
|
74
|
+
## Migration (2.4.0): the #65 policy is restored from the carrier
|
|
75
|
+
|
|
76
|
+
- Date: 2026-10-23
|
|
77
|
+
- Status: implemented by #121 and #122
|
|
78
|
+
|
|
79
|
+
The one `tower-bundle` group is gone. `cordis.patch.yml` now ships the mandatory
|
|
80
|
+
`tower` host anchor (the Host plugin and the worker preset are its internal
|
|
81
|
+
sections) plus the individually switchable top-level rows `tower-skills`,
|
|
82
|
+
`tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-hmr`
|
|
83
|
+
(shipped off) and `preset-tower-reviewer` (conditional on
|
|
84
|
+
`review.requireReview`). The visible component count moved 1 → 8 and is recorded
|
|
85
|
+
as a dated decision in `scripts/surface-baseline.json`.
|
|
86
|
+
|
|
87
|
+
The capability switches became rows: the seven `tools.expose*` keys and
|
|
88
|
+
`dev.hotReload` were deleted from the schema and are now rejected as unknown
|
|
89
|
+
keys. `tower_doctor` and `tower_recover` are registered for every configuration
|
|
90
|
+
and have no switch; the `gateableTools` allowlist is empty. This is the
|
|
91
|
+
carrier's promised instance fix, not a new policy: #120 wrote the rule down, and
|
|
92
|
+
this wave makes the shipped surface obey it instead of carrying debt.
|
|
93
|
+
|
|
94
|
+
## Consequences
|
|
95
|
+
|
|
96
|
+
- The user can control every capability that legitimately has an off state, from
|
|
97
|
+
the place components are controlled, instead of editing YAML.
|
|
98
|
+
- The host, the diagnostics and the lock escape are always registered; a failure
|
|
99
|
+
message may safely point at `tower_recover`.
|
|
100
|
+
- A component count change now needs a written decision, so the 2.3.0 4 → 1
|
|
101
|
+
contraction can never recur silently; the 1 → 8 restoration is recorded the
|
|
102
|
+
same way.
|
|
103
|
+
- The 2.3.0 debt is paid: `tools.exposeDoctor`, `tools.exposeRecovery` and the
|
|
104
|
+
`exposeOperational` master switch are gone, so no configuration can remove the
|
|
105
|
+
health contract or the lock escape. The `gateableTools` allowlist stays in the
|
|
106
|
+
source of truth, empty, so the next `tools.expose*` route is refused rather
|
|
107
|
+
than legalised by default.
|
|
108
|
+
- Adding a component costs a locale row in two languages and a fallback sentence
|
|
109
|
+
in the source of truth; that is the intended marginal cost.
|
|
110
|
+
|
|
111
|
+
## Rejected alternatives
|
|
112
|
+
|
|
113
|
+
- **One all-or-nothing group row (the shipped 2.3.0 shape).** Simple, and it
|
|
114
|
+
removes the orphan-mount hazard, but it removes every user control as well:
|
|
115
|
+
the qualifying capabilities become unreachable from the plugin page. It also
|
|
116
|
+
makes the "off state" question vacuous, because the only off state is
|
|
117
|
+
"uninstalled".
|
|
118
|
+
- **Config-key switches instead of components.** `tools.expose*` and
|
|
119
|
+
`dev.hotReload` carry the same capabilities with none of the discoverability,
|
|
120
|
+
and they make safety nets disableable in a place where no label warns about it.
|
|
121
|
+
A config key is a switch with worse labelling, not an absence of a switch.
|
|
122
|
+
- **Per-tool components.** 29 rows is a worse surface than 29 tools, and it turns
|
|
123
|
+
every implementation detail of the tool registry into a user decision.
|
|
124
|
+
- **A denylist-only rule with no component count.** It would catch a forbidden
|
|
125
|
+
switch but not the 4 → 1 contraction, which is the failure that actually
|
|
126
|
+
happened; the count baseline is the part that keeps the policy alive across the
|
|
127
|
+
next refactor.
|
|
128
|
+
- **A GitHub issue label or a docs-only policy.** The brake must run offline in
|
|
129
|
+
`npm test` and in CI without a token, and the policy must survive the closure
|
|
130
|
+
of the issue that introduced it.
|
package/docs/adr/README.md
CHANGED
|
@@ -15,6 +15,7 @@ alternatives, Date and Status.
|
|
|
15
15
|
| [0002](0002-single-state-json.md) | One `state.json` is the mission persistence contract | 2026-10-23 |
|
|
16
16
|
| [0003](0003-allow-agent-control-default-true.md) | `allowAgentControl` defaults to `true` | 2026-10-23 |
|
|
17
17
|
| [0004](0004-surface-budget.md) | The concept surface is frozen by a committed baseline | 2026-10-23 |
|
|
18
|
+
| [0005](0005-component-surface.md) | Mandatory host plus individually toggleable optional component rows | 2026-10-23 |
|
|
18
19
|
|
|
19
20
|
## Template
|
|
20
21
|
|