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.
Files changed (83) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/COMPATIBILITY.md +2 -0
  3. package/CONTRIBUTING.md +20 -3
  4. package/PRINCIPLES.md +18 -0
  5. package/README.en.md +29 -16
  6. package/README.md +22 -15
  7. package/cordis.patch.yml +56 -28
  8. package/docs/README.md +2 -1
  9. package/docs/adr/0004-surface-budget.md +20 -0
  10. package/docs/adr/0005-component-surface.md +130 -0
  11. package/docs/adr/README.md +1 -0
  12. package/docs/agents/workflow.md +1 -1
  13. package/docs/architecture/overview.md +69 -7
  14. package/docs/guide/hot-reload.md +4 -4
  15. package/docs/guide/install-upgrade-migration.md +13 -0
  16. package/docs/operations/security.md +1 -1
  17. package/docs/reference/component-surface.md +135 -0
  18. package/docs/reference/config.generated.md +2 -10
  19. package/docs/reference/config.md +11 -9
  20. package/docs/reference/doctor.md +14 -9
  21. package/docs/reference/surface-budget.md +52 -23
  22. package/docs/reference/tools.generated.md +4 -2
  23. package/docs/reference/tools.md +6 -3
  24. package/docs/zh/README.md +1 -1
  25. package/docs/zh/guide/hot-reload.md +3 -3
  26. package/docs/zh/guide/install-upgrade-migration.md +10 -0
  27. package/docs/zh/operations/security.md +1 -1
  28. package/docs/zh/reference/config.md +9 -9
  29. package/docs/zh/reference/tools.md +6 -4
  30. package/domain/components.js +641 -0
  31. package/domain/config/schema.js +37 -27
  32. package/domain/contracts/outputs.js +10 -0
  33. package/domain/errors.js +11 -1
  34. package/domain/mission.js +12 -1
  35. package/domain/surface.js +154 -55
  36. package/host/capability-batch.js +15 -0
  37. package/host/capability-hmr.js +30 -0
  38. package/host/capability-import.js +11 -0
  39. package/host/capability-leases.js +11 -0
  40. package/host/capability-registry.js +149 -0
  41. package/host/capability-tests.js +16 -0
  42. package/host/doctor.js +41 -9
  43. package/host/hmr.js +2 -4
  44. package/host/presets.js +51 -9
  45. package/host/tool-registry.js +150 -34
  46. package/index.js +32 -20
  47. package/locale/batch/en.json +6 -0
  48. package/locale/batch/zh.json +6 -0
  49. package/locale/en.json +31 -11
  50. package/locale/hmr/en.json +6 -0
  51. package/locale/hmr/zh.json +6 -0
  52. package/locale/import/en.json +6 -0
  53. package/locale/import/zh.json +6 -0
  54. package/locale/leases/en.json +6 -0
  55. package/locale/leases/zh.json +6 -0
  56. package/locale/skills/en.json +6 -0
  57. package/locale/skills/zh.json +6 -0
  58. package/locale/tests/en.json +6 -0
  59. package/locale/tests/zh.json +6 -0
  60. package/locale/zh.json +30 -10
  61. package/package.json +25 -4
  62. package/scripts/check-components.mjs +235 -0
  63. package/scripts/check-pack.mjs +4 -0
  64. package/scripts/component-surface.json +395 -0
  65. package/scripts/generate-capability-locale.mjs +148 -0
  66. package/scripts/generate-component-surface.mjs +59 -0
  67. package/scripts/generate-tools-reference.mjs +7 -25
  68. package/scripts/generate-types.mjs +127 -0
  69. package/scripts/lib/tool-surface.mjs +85 -0
  70. package/scripts/mount-smoke/fixtures.mjs +3 -3
  71. package/scripts/mount-smoke/surface.mjs +96 -27
  72. package/scripts/mount-smoke.mjs +3 -1
  73. package/scripts/pack-manifest.txt +30 -0
  74. package/scripts/surface-baseline.json +35 -4
  75. package/scripts/surface-baseline.mjs +32 -34
  76. package/scripts/types-coverage.mjs +184 -0
  77. package/scripts/types-strict-manifest.txt +10 -0
  78. package/scripts/types-strict.mjs +141 -0
  79. package/skills.js +8 -1
  80. package/tools/index.js +2 -1
  81. package/tools/reload.js +34 -35
  82. package/tools/status.js +8 -1
  83. package/types/index.d.ts +28 -12
@@ -24,7 +24,7 @@ This repository uses two workflows together. They are not alternatives: the **Ma
24
24
  - **Parallel or long-lived wave (architecture epics, >3 tickets, review needed):** **Tower**. `tower_init` → `tower_plan` with one mission per ticket, `scope` = the ticket's write fence, `deps` = the ticket's blocking edges → `tower_spawn` a worker per mission → optional reviewer (`tower-reviewer`) → `tower_merge` (gate checks scope, branch freshness, review, tasks) → `tower_teardown`. Tower's persistent state makes the wave resumable after a restart.
25
25
  - Never run both orchestrators over the same files at once.
26
26
  4. **Build.** Each unit starts with `/tdd`: one failing test, then the implementation, then the next slice. Commit messages carry `Refs #n`; the completing commit uses `Fixes #n`. Workers do not merge; they hand off a branch.
27
- 5. **Verify before landing.** Run `npm test`, `npm run docs:check`, all four `docs:check:generated*`, `npm run pack:check`, `npm run architecture:check`, `npm run types:check`, `npm run domain:check`. Bundle changes also run `npm run smoke:mount`. CI must be green on the required checks (`test (22)`, `test (24)`, `test (22, windows)`, `mount`). Then `/code-review` runs both axes on the diff (Standards against CONTRIBUTING/architecture docs, Spec against the ticket); every finding is fixed in the same PR or explicitly deferred with a reason.
27
+ 5. **Verify before landing.** Run `npm test`, `npm run docs:check`, all five `docs:check:generated*` (including the generated type vocabulary), `npm run pack:check`, `npm run architecture:check`, `npm run types:coverage`, `npm run types:strict:check`, `npm run types:check` (both tsconfigs), `npm run domain:check`. Bundle changes also run `npm run smoke:mount`. CI must be green on the required checks (`test (22)`, `test (24)`, `test (22, windows)`, `mount`). Then `/code-review` runs both axes on the diff (Standards against CONTRIBUTING/architecture docs, Spec against the ticket); every finding is fixed in the same PR or explicitly deferred with a reason.
28
28
  6. **Land.** PR body follows the four-line contract (failure mode → change → test that fails without it → blast radius). `main` is protected: PR + required checks, admins included, no force-push. A closure comment carries `verified-by: <test file>` and `commit: <sha>`; `npm run audit:issues` flags closures whose evidence is missing.
29
29
  7. **Release.** `release:check` compares the version, the previous tag and the CHANGELOG; a tag push fails when the version did not move or the tag does not match. Releases are either **tag-only** (version + changelog + compatibility row + annotated tag; no npm publish, no GitHub Release) or **published** (adds the human `npm publish --tag latest` with 2FA, `dist-tags` verification, then the GitHub Release). The mode is recorded in the milestone/plan.
30
30
  8. **Learn.** After a release (or a wave that went sideways) run `/retro`: mechanical mistakes become deterministic checks (scripts, CI jobs, preflight), judgement calls become written standards. Periodically run `/improve-codebase-architecture` for deepening opportunities and `/domain-modeling` to keep the vocabulary and ADRs (2.3.0) honest.
@@ -2,17 +2,78 @@
2
2
 
3
3
  ## Components
4
4
 
5
- The bundle is all-or-nothing: `cordis.patch.yml` inserts one `tower-bundle` `cordis:group` row whose
6
- internal sections are the Host plugin, the skills provider, and the worker/reviewer presets. The
7
- group is the only user-facing component; disabling it disables every section together, so no preset
8
- or skill can remain mounted while the `tower_*` tools are gone.
5
+ Since #121 the bundle ships a mandatory host anchor plus individually switchable
6
+ optional rows: `cordis.patch.yml` declares top-level `tower` (a `cordis:group`
7
+ that mounts the Host plugin and the mandatory worker preset), then
8
+ `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`,
9
+ `tower-hmr` (shipped off) and `preset-tower-reviewer`. Turning one optional row
10
+ off removes exactly that capability; the host, the diagnostics and the recovery
11
+ escape keep working.
12
+
13
+ [Principle 11](../../PRINCIPLES.md) and
14
+ [ADR 0005](../adr/0005-component-surface.md) say a component may exist only for
15
+ a capability whose off state leaves the rest intact, and that the host, health,
16
+ recovery, review and merge-gate capabilities are never switches. Which keys are
17
+ allowed to be switches is a table lookup in
18
+ [component-surface.md](../reference/component-surface.md), generated from
19
+ `scripts/component-surface.json`; the component count is baselined and checked by
20
+ `npm run docs:check:components`. That gate also refuses a `tools.expose*` route
21
+ to a tool that is not on the recorded allowlist (empty since #122) and any
22
+ `cordis.patch.yml` row that defaults a forbidden key off.
23
+
24
+ ### Row display metadata (#121)
25
+
26
+ The Plugins page renders an optional row as `row.meta.title ?? row.moduleName`
27
+ plus `row.meta.description`. The Host fills `row.meta` by resolving
28
+ `<specifier>/locale/en.json` and then every sibling `<lang>.json` in that
29
+ directory, reading `meta.title` / `meta.description`; title falls back to the full
30
+ module specifier and description to empty when no resource resolves. That is why
31
+ the mandatory `tower` card explains itself (the package root ships
32
+ `locale/*.json`) while a bare subpath row would show only `dsh-tower/batch`.
33
+
34
+ Each optional capability therefore ships its own resolvable subpath resource:
35
+ `dsh-tower/skills/locale/{en,zh}.json` and the same for `batch`, `import`,
36
+ `leases`, `tests` and `hmr`. `package.json` `exports` maps every
37
+ `./<capability>/locale/*.json` to `./locale/<capability>/*.json`, and
38
+ `files: ["locale/"]` ships the nested tree, so the lookup matches the installed
39
+ package. The root `./locale/*.json` export stays so the Tower group keeps its own
40
+ `meta`.
41
+
42
+ The capability files are generated, never hand-written. `npm run
43
+ docs:generate:locale` renders them from the `components` block of
44
+ `locale/en.json` and `locale/zh.json` (the same source `docs:check:components`
45
+ audits): `meta.title` is the component `title`, and `meta.description` joins the
46
+ component `summary` (what the capability enables) with its `disables` text (what
47
+ turning it off removes) behind a per-language "When off / 关闭后" label. The
48
+ row is therefore decidable from the page alone. `npm run
49
+ docs:check:generated:locale` fails on drift, runs in `npm run check`, and is its
50
+ own step in the `test` CI job (`.github/workflows/test.yml`).
51
+
52
+ To label a new capability row:
53
+
54
+ 1. Classify it in `scripts/component-surface.json`, ship it in
55
+ `cordis.patch.yml`, and add `title` / `summary` / `disables` to the
56
+ `components` block of both `locale/en.json` and `locale/zh.json`.
57
+ 2. Add `{ specifier, component }` to `CAPABILITY_LOCALES` in
58
+ `scripts/generate-capability-locale.mjs`, the matching
59
+ `./<specifier>/locale/*.json` export in `package.json`, and the row to the
60
+ coverage map in `tests/capability-locales.test.mjs`.
61
+ 3. Run `npm run docs:generate:locale`, then regenerate
62
+ `scripts/pack-manifest.txt` with `npm run pack:check -- --write`.
63
+
64
+ The reviewer preset row is the one visible component that is not a package
65
+ specifier: its label comes from the `cordis.patch.yml` preset `config.name` /
66
+ `description`. The generated capability files never touch the root locale `meta`.
9
67
 
10
68
  ```
11
69
  Host plugin (index.js)
12
- ├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control, /tower reload)
70
+ ├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control)
13
71
  ├─ host/tool-registry.js -> registration aggregator, actor guards, shared helpers
14
- │ (+ 27 registered repository tools)
15
- │ (+ tower_reload while dev.hotReload is on; fails reload-unavailable without HMR)
72
+ │ (+ 21 define()-registered host tools)
73
+ │ (+ tower_mode/tower_control registered by the Host entry, 29 total
74
+ │ once every optional capability row is on)
75
+ │ (tower_doctor/tower_recover are host tools, never switches)
76
+ ├─ host/capability-*.js -> optional capability entry points (batch/import/leases/tests/hmr)
16
77
  ├─ tools/index.js -> thin public wiring (re-exports the registry + briefings)
17
78
  └─ app/tower.js -> orchestration surface; delegates to use-case modules
18
79
  ├─ app/init.js, provision.js, merge.js, rebase.js, teardown.js
@@ -24,6 +85,7 @@ Host plugin (index.js)
24
85
  host/tool-registry.js registration aggregator + shared guards/helpers
25
86
  host/config.js profile/repo config layers over the pure schema
26
87
  host/doctor.js read-only environment contract and health checks
88
+ host/capability-registry.js mounted-component report for status/doctor
27
89
  host/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
28
90
  host/modeStore.js per-agent Tower mode persistence
29
91
  host/modeState.js owned in-memory mode state
@@ -26,8 +26,8 @@ This page covers the development loop. It is not required to use Tower.
26
26
  `plugin_manager`); it compares already-loaded modules with disk and replaces the changed ones even
27
27
  with `root: []`. Watching only makes that happen automatically after a save.
28
28
  - HMR is only mounted for profile boots (`disabled` when there is no `profileContext`). A process
29
- booted without a profile reports reload as unavailable (`tower_reload` stays registered while
30
- `dev.hotReload` is on, so the tool returns `reload-unavailable` instead of disappearing).
29
+ booted without a profile reports reload as unavailable (`tower_reload` stays registered while the
30
+ `tower-hmr` component row is on, so the tool returns `reload-unavailable` instead of disappearing).
31
31
  - The profile patch layer is loaded when DSH boots. Editing it — in practice the hmr `root` list —
32
32
  takes effect after **one DSH restart**. Nothing in the running process re-reads the HMR row's own
33
33
  configuration.
@@ -99,9 +99,9 @@ The bridge behind the tool follows the `dev` config group:
99
99
 
100
100
  | Config key | Meaning |
101
101
  |---|---|
102
- | `dev.hotReload` | Register `tower_reload`; `/tower reload` is disabled when off (default `true`). Without the HMR service the tool is still registered so it can return `reload-unavailable`. |
102
+ | `tower-hmr` (component row) | The switch: on registers `tower_reload` and `/tower reload`, off reports "disabled by component tower-hmr". The row ships off; enable it from the plugin page for a linked dev profile. `dev.hotReload` was removed in 2.4.0 and is now an unknown key. |
103
103
  | `dev.reloadDelayMs` | Delay before the scheduled reconciliation runs, in milliseconds (default `50`), so the tool/command result reaches the model before the plugin is replaced. |
104
- | `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower-bundle`, `tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`). An inactive required entry is reported as a reload failure even if it was already inactive. |
104
+ | `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower`, `tower-host`, `preset-tower-worker`). An inactive required entry is reported as a reload failure even if it was already inactive. Optional capability rows are deliberately not required, so a row switched off is not a reload failure. |
105
105
 
106
106
  A reload is **scheduled, not awaited**: `/tower reload` and `tower_reload` return immediately with
107
107
  `scheduled`, `delayMs`, `requiredIds` (and the current `lastError` when one is pending), then HMR
@@ -45,6 +45,19 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
45
45
  overlapping and case-only mismatches are no longer refused by the out-of-scope gate. Stored scope
46
46
  strings are never rewritten: the board keeps showing what the operator typed.
47
47
 
48
+ - **2.3.0 → 2.4.0 (unreleased)**: the single `tower-bundle` group row is gone. `cordis.patch.yml`
49
+ now ships the mandatory `tower` host anchor (the Host plugin and the worker preset are its internal
50
+ sections) plus individually switchable top-level rows: `tower-skills`, `tower-batch`, `tower-import`,
51
+ `tower-leases`, `tower-tests`, `tower-hmr` (shipped off) and `preset-tower-reviewer` (conditional on
52
+ `review.requireReview`). The `tools` config group and `dev.hotReload` were removed: a profile that
53
+ still carries them is now rejected as an unknown key (`config-unknown-key`) and the host stops
54
+ mounting until they are deleted, so no silent no-op switch remains. Move each capability to its row
55
+ in the plugin page instead; `tower-hmr` ships off, so `tower_reload` and `/tower reload` are reported
56
+ as "disabled by component tower-hmr" until you enable it for a linked dev profile. `tower_doctor` and
57
+ `tower_recover` are registered for every configuration and need no key. The reviewer row may be off
58
+ only while `review.requireReview` is false; with the gate on, planning and mode entry refuse the
59
+ combination with an explanatory error. No state migration is needed.
60
+
48
61
  - **2.0.4 → 2.1.0-beta.1**: no config migration; the state file migrates automatically (v1/v2 → v3) as
49
62
  described above. This is a **pre-release** published on the
50
63
  `next` dist-tag, so `latest` still resolves to 2.0.4 — install it with
@@ -104,7 +104,7 @@ Tower work, and run the host under OS-level isolation.
104
104
  cannot make those safe; review hooks before running Tower on an untrusted repository.
105
105
  - **Package test commands.** Declared discovery commands have the same trust as repository hooks; a
106
106
  malicious `package.json` can run code. Disable `tests.allowPackageDiscovery` and review the file.
107
- - **HMR reload.** With `dev.hotReload`, `/tower reload`/`tower_reload` asks DSH to re-import the plugin
107
+ - **HMR reload.** With the `tower-hmr` component row on, `/tower reload`/`tower_reload` asks DSH to re-import the plugin
108
108
  from disk. Only enable HMR watch roots for checkouts you trust; a reload executes the current files.
109
109
  - **Prompt injection.** The reviewer reads untrusted branch content (diff/comments). The read-only preset
110
110
  limits impact, but no model-side protection is absolute.
@@ -0,0 +1,135 @@
1
+ # Component surface (generated)
2
+
3
+ > Generated by `npm run docs:generate:components` from `scripts/component-surface.json` and
4
+ > `scripts/surface-baseline.json`. Do not edit by hand.
5
+
6
+ Decision: [ADR 0005](../adr/0005-component-surface.md) · decision date: 2026-10-23 · baseline 2026-10-23 · visible components: 8 (`tower`, `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-hmr`, `preset-tower-reviewer`)
7
+
8
+ The component axis answers one question for every config key: may a user turn it
9
+ off, and if so, must that switch be a component? Four classes cover every key, so
10
+ "should this be a component?" is a table lookup rather than a judgement call.
11
+
12
+ | Class | Meaning |
13
+ |---|---|
14
+ | `core` | Removing it breaks the Tower contract. It is never a switch. |
15
+ | `optional-component` | A capability whose off state leaves the rest of Tower intact. It must be a user-visible switch with a documented fallback. |
16
+ | `optional-config` | A tuning knob, not a capability. It stays in the config form and never hides a component. |
17
+ | `forbidden-to-disable` | The host, the health contract, the recovery escape, the review contract and the merge gates. No group, row or boolean key may turn it off. |
18
+
19
+ ## Components
20
+
21
+ The shipped `cordis.patch.yml` rows, with the fallback contract each off state
22
+ must satisfy. A visible row is a component the plugin page may switch; an internal
23
+ row is a section of its parent and is not independently switchable.
24
+
25
+ | Component | Scope | Kind | Off state and fallback |
26
+ |---|---|---|---|
27
+ | `tower` | visible | group | Off equals uninstalling Tower: no tools, presets or skills remain. That is the accepted all-off boundary of the mandatory host anchor, not an optional capability. |
28
+ | `tower-host` | internal | row | The Host plugin itself, nested under the mandatory tower anchor. A mandatory dependency, never its own switch. |
29
+ | `tower-skills` | visible | row | The 3 skills leave the catalog; the worker/reviewer PROTOCOL sections still ship through systemPrompt.section, so only the tower-recovery guidance is lost. |
30
+ | `tower-batch` | visible | row | tower_batch is not registered; missions, gates and merges are unaffected because no state depends on a batch. |
31
+ | `tower-import` | visible | row | tower_import_legacy and tower_legacy_map are not registered; new repository state is unaffected. |
32
+ | `tower-leases` | visible | row | tower_resource is not registered; advisory leases are coordination only, so nothing else depends on them. |
33
+ | `tower-tests` | visible | row | tower_test is not registered and no test receipt can be recorded. With gates.requireTests:true planning/mode entry is rejected with an explanation, and the merge gate still reports tests-missing. |
34
+ | `tower-hmr` | visible | row | tower_reload and /tower reload are unavailable; a profile without HMR behaves exactly as before. Shipped disabled: enable the row for a linked dev profile. |
35
+ | `preset-tower-worker` | internal | row | tower_spawn fails without the worker preset. A mandatory dependency, never a switch. |
36
+ | `preset-tower-reviewer` | visible | row | The reviewer cannot be dispatched. Supported while review.requireReview is false (merge is unaffected); with the gate on planning/mode entry is locked with an explanatory error. |
37
+
38
+ ## Forbidden to disable
39
+
40
+ The host, the health contract, the recovery escape, the review contract and the
41
+ merge gates are configuration or code, never switches. `npm run
42
+ docs:check:components` derives the live disablers from `host/tool-registry.js`
43
+ and `cordis.patch.yml`; a disabler that is not recorded here fails the gate.
44
+
45
+ | Capability | Why it may not be disabled | Recorded disablers | Decision |
46
+ |---|---|---|---|
47
+ | the Tower host | Without the host the plugin registers nothing; there is no off state that leaves the rest working, and no config boolean may remove it. The visible `tower` anchor is the all-off boundary, and the host section nested under it may never become its own switch. | none | none may exist |
48
+ | tower_doctor (read-only health contract) | Diagnosis must always exist so a failure message can point at it. #122 removed exposeDoctor and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
49
+ | tower_recover (stale state-lock escape) | The escape hatch must exist whenever the lock it repairs can be left behind. #122 removed exposeRecovery and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
50
+ | the review contract | review.requireReview is a merge blocker, not a component; a row may not switch it off. The reviewer row is conditional, not a way to weaken the gate. | none | none may exist |
51
+ | the merge gates | gates.* decides delivery; a component may not bypass it. | none | none may exist |
52
+
53
+ ## Gateable tools
54
+
55
+ The allowlist of boolean routes in `host/tool-registry.js`. Every shipped
56
+ `tools.expose*` route must match this table. A route to a tool that is not listed
57
+ here is refused, so no boolean key can quietly remove a review, merge or host
58
+ tool. Since #122 the shipped table is empty: `tower_doctor` and `tower_recover`
59
+ are registered for every configuration and no other capability may become a
60
+ boolean switch.
61
+
62
+ | Tool | Switch key |
63
+ |---|---|
64
+ | _(none)_ | — |
65
+
66
+ ## Config keys
67
+
68
+ All keys from `domain/config/schema.js`, classified on the component axis.
69
+
70
+ | Key | Component class | Decision note |
71
+ |---|---|---|
72
+ | `section` | `core` | Legacy alias for mode.promptSection: the Tower-mode prompt contract, not a capability. |
73
+ | `allowAgentControl` | `core` | Legacy alias for mode.allowAgentControl: the human-owned mode rule (ADR 0003), not a component. |
74
+ | `init.autoGitInit` | `optional-config` | Bootstrap convenience; off only means tower_init will not run git init. |
75
+ | `init.snapshotDirty` | `optional-config` | Dirty-work baseline convenience; no capability is removed. |
76
+ | `init.autoExclude` | `optional-config` | Keeps Tower artifacts out of .git/info/exclude; safe either way. |
77
+ | `mode.promptSection` | `core` | Replaces the whole Tower-mode prompt text; contract text, not a capability. |
78
+ | `mode.sectionAppend` | `optional-config` | Appends team conventions to the prompt. |
79
+ | `mode.allowAgentControl` | `core` | Decides whether the model may toggle the mode (ADR 0003). A governance rule, never a component. |
80
+ | `mode.autoPauseGoal` | `core` | Entering the mode pauses an active goal; exit never resumes it. |
81
+ | `mode.requireResumeAuthorization` | `core` | Guards resuming a user-paused mission. |
82
+ | `mode.requireHumanAckOnFirstEntry` | `core` | First entry can require an explicit human ack. |
83
+ | `mode.workspaceLock` | `core` | One Tower session per repository is a safety contract; a row may not turn it off. |
84
+ | `mode.workspaceLockScope` | `optional-config` | Chooses whether read-only tools take the workspace lock. |
85
+ | `mode.planModePolicy` | `core` | Plan mode and Tower mode stay mutually exclusive. |
86
+ | `mode.workspaceStaleMs` | `optional-config` | Stale window for the advisory workspace lock. |
87
+ | `mode.heartbeatMs` | `optional-config` | Heartbeat interval while the mode is on. |
88
+ | `mode.denyTools` | `core` | The denied-tool list is part of the mode guard. |
89
+ | `mode.storePath` | `optional-config` | Custom mode-store location; no capability is removed. |
90
+ | `mode.maxEntries` | `optional-config` | Bounds the persisted mode store. |
91
+ | `mode.lockTimeoutMs` | `optional-config` | Bounded wait for the cross-process mode lock. |
92
+ | `review.requireReview` | `forbidden-to-disable` | Turns independent review into a merge blocker; no component may switch it off. With it on, the preset-tower-reviewer row may not be off (planning/mode entry refuse the combination). |
93
+ | `review.maxRoundsPerReviewer` | `forbidden-to-disable` | Caps review rounds; part of the review contract. |
94
+ | `merge.timeoutMs` | `optional-config` | Default merge timeout; a per-call value wins. |
95
+ | `merge.maxBufferBytes` | `optional-config` | Captured merge output buffer. |
96
+ | `git.timeoutMs` | `optional-config` | Default Git command timeout outside merge. |
97
+ | `worktrees.root` | `optional-config` | Default worktree placement. |
98
+ | `spawn.workerPreset` | `core` | Chooses the worker tool surface; a dependency, never a switch. |
99
+ | `spawn.reviewerPreset` | `core` | Chooses the read-only reviewer surface; a dependency, never a switch. |
100
+ | `spawn.maxDepth` | `optional-config` | Subagent depth requested at spawn. |
101
+ | `spawn.provider` | `optional-config` | Provider route for spawned agents. |
102
+ | `spawn.workerModel` | `optional-config` | Optional model route for workers. |
103
+ | `spawn.reviewerModel` | `optional-config` | Optional independent model route for reviewers. |
104
+ | `spawn.maxLiveWorkers` | `core` | Resource-safety cap on live workers. |
105
+ | `spawn.staleMs` | `optional-config` | Roster staleness window before merge is blocked. |
106
+ | `spawn.maxWaitMs` | `optional-config` | Upper bound for a blocking spawn wait. |
107
+ | `spawn.requireLease` | `optional-config` | Advisory-lease gate before a worker starts. |
108
+ | `send.maxPerMinute` | `optional-config` | Message rate limit; zero disables it. |
109
+ | `send.interruptDefault` | `optional-config` | Default for the interrupt-before-deliver flag. |
110
+ | `tests.enabled` | `optional-component` | Tuning inside the optional tower-tests component: it decides whether discovery runs when the row is on. Off with gates.requireTests:true is rejected at config resolution. |
111
+ | `tests.admission` | `optional-config` | Chooses when discovery runs inside the optional tests capability. |
112
+ | `tests.timeoutMs` | `optional-config` | Per-command discovery timeout. |
113
+ | `tests.maxBytes` | `optional-config` | Captured discovery output cap. |
114
+ | `tests.autoChecks` | `optional-config` | Attaches a tests check to completion when discovery runs. |
115
+ | `tests.allowPackageDiscovery` | `core` | Allows repository code to execute; a security contract, not a component. |
116
+ | `gates.requireChecks` | `forbidden-to-disable` | Requires completion evidence before merge; a merge gate. |
117
+ | `gates.requireTests` | `forbidden-to-disable` | Requires a test receipt before merge; a merge gate. With the tower-tests row off, planning/mode entry refuse the combination. |
118
+ | `gates.allowReviewExemption` | `forbidden-to-disable` | The review-exemption path is part of the review and merge contract. |
119
+ | `gates.maxReportBytes` | `forbidden-to-disable` | The report cap is part of the completion contract; the refusal itself is never configurable away. |
120
+ | `docs.board` | `optional-config` | Writes the human-readable board; no capability is removed. |
121
+ | `docs.boardLocation` | `optional-config` | Chooses the board location. |
122
+ | `archive.enabled` | `optional-config` | Archival is maintenance, not a tool capability; off only leaves terminal missions hot. |
123
+ | `archive.afterDays` | `optional-config` | Archive age threshold. |
124
+ | `archive.maxMissions` | `optional-config` | Hot terminal-mission cap. |
125
+ | `leases.ttlMs` | `optional-config` | Tuning inside the optional tower-leases component. |
126
+ | `dev.reloadDelayMs` | `optional-config` | Delay so a tool result is delivered before a scheduled reload inside the optional tower-hmr component. |
127
+ | `dev.reloadRequiredIds` | `optional-config` | Loader ids passed to reconcileProfile as required by the tower-hmr component. |
128
+
129
+ ## Non-goals
130
+
131
+ This page does not decide whether a key is useful, and it is not an approval
132
+ process. It records the class so the gate can refuse an unclassified or
133
+ forbidden surface.
134
+
135
+ Enforced by: `scripts/check-components.mjs`; tests in `tests/components.test.mjs`; gate: `npm run docs:check:components`.
@@ -1,7 +1,7 @@
1
1
  # Config reference (generated)
2
2
 
3
3
  > Generated by `npm run docs:generate:config` from `domain/config/schema.js`. Values are defaults before repo config.
4
- > Surface: config keys: 64 · sections: 15 · tools: 27 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
4
+ > Surface: config keys: 56 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
5
5
 
6
6
  | Key | Default | Description |
7
7
  |---|---|---|
@@ -58,14 +58,6 @@
58
58
  | `archive.enabled` | `true` | Move terminal (merged/abandoned) missions whose worktree is gone into <git-common-dir>/dsh-tower/archive/ at the next mutation, so state.json stops carrying their full history. |
59
59
  | `archive.afterDays` | `30` | Terminal missions untouched for at least this many days are archived on the next mutation (0 archives them immediately). |
60
60
  | `archive.maxMissions` | `100` | Maximum terminal missions kept hot; once exceeded, the oldest eligible terminal missions are archived until the cap holds (0 archives every eligible terminal mission). |
61
- | `tools.exposeOperational` | `true` | Master switch for batch/resource/import/legacy/recover/test/doctor tools. |
62
- | `tools.exposeBatch` | `true` | Register tower_batch. |
63
- | `tools.exposeImport` | `true` | Register tower_import_legacy/tower_legacy_map. |
64
- | `tools.exposeLeases` | `true` | Register tower_resource advisory leases. |
65
- | `tools.exposeTests` | `true` | Register tower_test; gates.requireTests requires it. |
66
- | `tools.exposeRecovery` | `true` | Register tower_recover (stale state-lock escape). |
67
- | `tools.exposeDoctor` | `true` | Register tower_doctor (read-only health contract). |
68
61
  | `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
69
- | `dev.hotReload` | `true` | Register tower_reload and /tower reload when the DSH HMR service is available. |
70
62
  | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload, so the tool result is delivered first. |
71
- | `dev.reloadRequiredIds` | `["tower-bundle","tower","tower-skills","preset-tower-worker","preset-tower-reviewer"]` | Loader entry ids passed to reconcileProfile as required. |
63
+ | `dev.reloadRequiredIds` | `["tower","tower-host","preset-tower-worker"]` | Loader entry ids passed to reconcileProfile as required. Optional component rows are deliberately not required, so a row switched off is not a reload failure. |
@@ -74,14 +74,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
74
74
  | `spawn.requireLease` | `''` | Optional lease name that must be held before a worker can spawn. |
75
75
  | `send.maxPerMinute` | `120` | Per-repository `tower_send` rate limit (0 disables); exceeding it returns `rate-limited`. |
76
76
  | `send.interruptDefault` | `false` (bundled patch: `true`) | Default for `tower_send.interrupt` (interrupt the running turn before delivering). |
77
- | `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. |
77
+ | `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. It only tunes the optional `tower-tests` component: with that row off there is no `tower_test` tool and no receipt, and `gates.requireTests:true` is refused rather than silently unmet. |
78
78
  | `tests.admission` | `off` | `spawn` runs discovery before starting a worker, `complete` at `tower_complete`, `off` only on explicit `tower_test` (default; repository code is not executed automatically). |
79
79
  | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
80
80
  | `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
81
81
  | `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
82
82
  | `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
83
83
  | `gates.requireChecks` | `false` | Require at least one `tower_complete.checks` entry before merge (`no-check-evidence`). |
84
- | `gates.requireTests` | `false` | Require a passing discovery receipt for the current mission branch tip (`tests-missing`); independent of the review gate. |
84
+ | `gates.requireTests` | `false` | Require a passing discovery receipt for the current mission branch tip (`tests-missing`); independent of the review gate. With the `tower-tests` component row off, planning/mode entry refuse this combination with an explanation. |
85
85
  | `gates.allowReviewExemption` | `false` | With `review.requireReview:true`, allow `reviewPolicy:"none"` + a non-empty reason to skip review blockers. Off deliberately; irrelevant while the global review gate is off. |
86
86
  | `gates.maxReportBytes` | `32768` (32 KiB) | Hard cap in UTF-8 bytes for `tower_complete.report`. A larger report is rejected with `invalid-argument` and an actionable `nextAction` (file the full text in the mission worktree and reference its path); it is never truncated. Existing larger reports still load. |
87
87
  | `docs.board` | `true` | Write `MISSIONS.md` after mutations. |
@@ -89,16 +89,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
89
89
  | `archive.enabled` | `true` | Move terminal (`merged`/`abandoned`) missions whose worktree is gone into `<git-common-dir>/dsh-tower/archive/` at a mutation boundary. Archived missions keep tasks, reviews, findings and the completion report; they are moved, never deleted. |
90
90
  | `archive.afterDays` | `30` | Age trigger: a terminal mission untouched for at least this many days is archived on the next mutation (`0` archives immediately). |
91
91
  | `archive.maxMissions` | `100` | Cap trigger: once more than this many terminal missions are hot, the oldest eligible ones are archived until the cap holds (`0` archives every eligible terminal mission). A mission with a live worktree is never archived. |
92
- | `tools.exposeOperational` | `true` | Register batch/resource/import/legacy/recover/test/doctor tools; `false` shrinks the fixed tool-schema context. |
93
92
  | `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
94
- | `dev.hotReload` | `true` | Register `tower_reload`; `/tower reload` is disabled when off. It returns `reload-unavailable` when the profile has no HMR service. |
95
- | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. |
96
- | `dev.reloadRequiredIds` | five Tower loader ids | Entry ids passed to `reconcileProfile` as required. |
93
+ | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. Tuning inside the optional `tower-hmr` component. |
94
+ | `dev.reloadRequiredIds` | three Tower loader ids | Entry ids passed to `reconcileProfile` as required by the `tower-hmr` component. Optional component rows are deliberately not required. |
97
95
 
98
96
  ## Example
99
97
 
100
98
  ```yaml
101
- - id: tower
99
+ - id: tower-host
102
100
  name: 'dsh-tower'
103
101
  config:
104
102
  mode:
@@ -113,10 +111,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
113
111
  reviewerModel:
114
112
  provider: deepseek-account
115
113
  model: deepseek-flash
116
- dev:
117
- hotReload: true
118
114
  ```
119
115
 
116
+ The optional capabilities (`tower-skills`, `tower-batch`, `tower-import`,
117
+ `tower-leases`, `tower-tests`, `tower-hmr`, `preset-tower-reviewer`) are switched
118
+ from the plugin page, not from a config key. The keys that used to switch them
119
+ (`tools.expose*`, `dev.hotReload`) were removed in 2.4.0 and are rejected as
120
+ unknown keys.
121
+
120
122
  ## Looking up the effective schema
121
123
 
122
124
  `cordis_inspect_query` with `Config.listConfigs({ name: "dsh-tower" })` returns the projected JSON
@@ -8,17 +8,17 @@ It returns:
8
8
  "ok": true,
9
9
  "checks": [
10
10
  { "id": "git", "status": "pass", "detail": "git version 2.55.0" },
11
- { "id": "surface", "status": "pass", "detail": "config keys: 64 · sections: 15 · tools: 27 (baseline 2026-10-23)" },
11
+ { "id": "surface", "status": "pass", "detail": "config keys: 56 · sections: 14 · tools: 29 (baseline 2026-10-23)" },
12
12
  { "id": "state", "status": "pass", "detail": ".../state.json revision 7" },
13
13
  { "id": "state-schema", "status": "pass", "detail": "schemaVersion 3 is current" },
14
14
  { "id": "case-mode", "status": "pass", "detail": "caseInsensitive: true; scope matching uses case-insensitive comparison" }
15
15
  ],
16
16
  "surface": {
17
- "configKeys": 64,
18
- "sections": 15,
19
- "tools": 27,
17
+ "configKeys": 56,
18
+ "sections": 14,
19
+ "tools": 29,
20
20
  "baselineDate": "2026-10-23",
21
- "line": "config keys: 64 · sections: 15 · tools: 27"
21
+ "line": "config keys: 56 · sections: 14 · tools: 29"
22
22
  },
23
23
  "stateLock": {
24
24
  "held": true,
@@ -67,10 +67,15 @@ It returns:
67
67
 
68
68
  `surface` (and the `surface` check) reports the concept budget in the same format
69
69
  as the generated references: `config keys: N · sections: N · tools: N`, plus the
70
- committed baseline date from `scripts/surface-baseline.json`. The counts are live
71
- (the config schema and the defined tool surface, independent of the operational
72
- switches); `npm run surface:check` is what fails when a count grew without an ADR
73
- or a matching deletion. See
70
+ committed baseline date from `scripts/surface-baseline.json`. The counts are live:
71
+ the config schema and every tool the build ships — `define()`-registered tools,
72
+ the `tower_mode`/`tower_control` pair the Host entry registers directly (#118),
73
+ and the capability tools whose optional component row may be off (#121) —
74
+ independent of the operational switches. The `tools` count is therefore the full
75
+ build surface (29 in 2.4.0), even though the shipped profile mounts 28 because
76
+ `tower-hmr` ships off; the `components` check names the mounted subset and every
77
+ disabled row. `npm run surface:check` is what fails when a count grew without an
78
+ ADR, a matching deletion or a recorded measurement correction. See
74
79
  [surface-budget.md](surface-budget.md) for the classification table.
75
80
 
76
81
  `stateSchema` describes the file **on disk**, not the in-memory document: `currentVersion` is the
@@ -1,10 +1,12 @@
1
1
  # Surface budget
2
2
 
3
- The plugin measures its user-facing surface in three numbers: **config leaf keys**,
4
- **config sections**, and **registered tools**. The committed baseline is
5
- [`scripts/surface-baseline.json`](../../scripts/surface-baseline.json); the brake
6
- is `npm run surface:check`; the decision record is
7
- [ADR 0004](../adr/0004-surface-budget.md).
3
+ The plugin measures its user-facing surface in four numbers: **config leaf keys**,
4
+ **config sections**, **registered tools**, and **visible component rows**. The
5
+ committed baseline is [`scripts/surface-baseline.json`](../../scripts/surface-baseline.json);
6
+ the brakes are `npm run surface:check` for the first three and
7
+ `npm run docs:check:components` for the component count; the decision records are
8
+ [ADR 0004](../adr/0004-surface-budget.md) and
9
+ [ADR 0005](../adr/0005-component-surface.md).
8
10
 
9
11
  Decision date: 2026-10-23
10
12
 
@@ -14,6 +16,37 @@ default a user may want to change, and `niche` was added for one workflow and
14
16
  must carry a removal note for the next minor. A live key or tool missing from
15
17
  these tables fails `npm run surface:check`.
16
18
 
19
+ A second axis asks who may turn a key off. It is generated from
20
+ [`scripts/component-surface.json`](../../scripts/component-surface.json) into
21
+ [component-surface.md](component-surface.md): every key is `core`,
22
+ `optional-component`, `optional-config` or `forbidden-to-disable`, and the
23
+ component count is the fourth baselined surface, two-way: a contraction needs a
24
+ dated decision just like growth does (`npm run docs:check:components`,
25
+ [ADR 0005](../adr/0005-component-surface.md)). The same source of truth records
26
+ the `gateableTools` allowlist; it is empty since #122, so a future
27
+ `tools.expose*` route to any tool is refused before it can remove a review,
28
+ merge or host tool.
29
+
30
+ The 2.3.0 baseline counted 27 tools because it recorded only the `define()` path.
31
+ `tower_mode` and `tower_control` are registered directly by `index.js`, so the
32
+ shipped surface was always 29. The 27 → 29 step is an explicit
33
+ *measurement correction* with a reason and date in
34
+ [`scripts/surface-baseline.json`](../../scripts/surface-baseline.json) (see
35
+ [ADR 0004](../adr/0004-surface-budget.md)), not surface growth; a later count
36
+ change still needs an ADR or a provably matching deletion.
37
+
38
+ The tool count is the **build** surface, not one profile's mounted subset (#121):
39
+ the recorder includes every tool this build can register, so a component row that
40
+ ships off (`tower-hmr`, whose `tower_reload` keeps the count at 29) does not
41
+ shrink the budget. `tower_doctor` reports the same 29 and names the mounted
42
+ subset through its `components` check; the mount smoke asserts the shipped
43
+ profile's 28 loaded tools and pins 29 for the all-rows-on surface.
44
+
45
+ #121 also shrank the config surface: the seven capability `tools.expose*` keys
46
+ and `dev.hotReload` became component rows, so config keys went 64 → 56 and
47
+ sections 15 → 14. Those are recorded shrank values in the committed baseline,
48
+ not free growth: a later increase still needs its own decision.
49
+
17
50
  ## Config keys
18
51
 
19
52
  | Key | Class | Note |
@@ -71,53 +104,49 @@ these tables fails `npm run surface:check`.
71
104
  | `archive.enabled` | core | Controls whether terminal missions leave the hot state. |
72
105
  | `archive.afterDays` | tuning | Archive age threshold. |
73
106
  | `archive.maxMissions` | tuning | Hot terminal-mission cap. |
74
- | `tools.exposeOperational` | core | Master switch for the operational tool surface. |
75
- | `tools.exposeBatch` | core | Registers the evidence-batch tool. |
76
- | `tools.exposeImport` | core | Registers the legacy import tools. |
77
- | `tools.exposeLeases` | core | Registers advisory leases. |
78
- | `tools.exposeTests` | core | Registers test discovery; required when the test gate is on. |
79
- | `tools.exposeRecovery` | core | Registers the stale-lock escape hatch. |
80
- | `tools.exposeDoctor` | core | Registers the read-only health contract. |
81
107
  | `leases.ttlMs` | tuning | Default advisory lease lifetime. |
82
- | `dev.hotReload` | tuning | Registers the dev reload tool when HMR exists. |
83
108
  | `dev.reloadDelayMs` | tuning | Delay so a tool result is delivered before reload. |
84
109
  | `dev.reloadRequiredIds` | niche | Loader ids added for one linked-profile development setup. Removal note: derive the required ids from the bundle manifest in the next minor. |
85
110
 
86
111
  ## Tools
87
112
 
88
113
  Tools are `core` when the product does not work without them and `optional` when
89
- an operational switch (`tools.expose*`) or the dev mode can legitimately remove
90
- them from the surface.
114
+ a component row (`tower-batch`, `tower-import`, `tower-leases`, `tower-tests`,
115
+ `tower-hmr`), `mode.allowAgentControl`, or the dev mode can legitimately remove
116
+ them from the surface. `tower_doctor` and `tower_recover` are core: #122 made
117
+ them mandatory for every configuration.
91
118
 
92
119
  | Tool | Class | Note |
93
120
  |---|---|---|
94
121
  | `tower_adopt` | core | Reconciles a restored roster with the durable catalog. |
95
- | `tower_batch` | optional | Evidence-batch workflow; `tools.exposeBatch`. |
122
+ | `tower_batch` | optional | Evidence-batch workflow; component `tower-batch`. |
96
123
  | `tower_complete` | core | The only completion path for a worker. |
124
+ | `tower_control` | optional | Model-initiated mode change; `mode.allowAgentControl:false` removes it (ADR 0003). |
97
125
  | `tower_diff` | core | The read-only review view. |
98
- | `tower_doctor` | optional | Read-only health contract; `tools.exposeDoctor`. |
126
+ | `tower_doctor` | core | Read-only health contract; registered for every configuration (#122). |
99
127
  | `tower_finding` | core | Out-of-scope issue recording. |
100
128
  | `tower_findings` | core | Finding lifecycle. |
101
- | `tower_import_legacy` | optional | Legacy migration; `tools.exposeImport`. |
129
+ | `tower_import_legacy` | optional | Legacy migration; component `tower-import`. |
102
130
  | `tower_inbox` | core | Message delivery and acknowledgement. |
103
131
  | `tower_init` | core | Creates the repository state. |
104
132
  | `tower_interrupt` | core | Stops a runaway worker. |
105
- | `tower_legacy_map` | optional | Legacy audit mapping; `tools.exposeImport`. |
133
+ | `tower_legacy_map` | optional | Legacy audit mapping; component `tower-import`. |
106
134
  | `tower_merge` | core | The only merge path. |
107
135
  | `tower_mission` | core | Mission progress and blockers. |
136
+ | `tower_mode` | core | Reports the session stance, workspace lock, HMR and plugin version; always registered. |
108
137
  | `tower_plan` | core | Mission planning. |
109
138
  | `tower_preflight` | core | The read-only merge gate. |
110
139
  | `tower_rebase` | core | Keeps a mission branch current. |
111
- | `tower_recover` | optional | Stale-lock recovery; `tools.exposeRecovery`. |
112
- | `tower_reload` | optional | Dev-only reload; registered while the HMR/dev mode is on. |
113
- | `tower_resource` | optional | Advisory leases; `tools.exposeLeases`. |
140
+ | `tower_recover` | core | Stale-lock recovery; registered for every configuration (#122). |
141
+ | `tower_reload` | optional | Dev-only reload; component `tower-hmr` (shipped off). |
142
+ | `tower_resource` | optional | Advisory leases; component `tower-leases`. |
114
143
  | `tower_review` | core | Independent review verdicts. |
115
144
  | `tower_roster` | core | Roster and live activity. |
116
145
  | `tower_send` | core | Durable messages to agents. |
117
146
  | `tower_spawn` | core | Starts workers and reviewers. |
118
147
  | `tower_status` | core | The dashboard. |
119
148
  | `tower_teardown` | core | Worktree cleanup and archiving. |
120
- | `tower_test` | optional | Explicit discovery; `tools.exposeTests`. |
149
+ | `tower_test` | optional | Explicit discovery; component `tower-tests`. |
121
150
 
122
151
  ## Non-goals
123
152
 
@@ -1,13 +1,14 @@
1
1
  # Tools overview (generated)
2
2
 
3
3
  > Generated by `npm run docs:generate:tools` from the registered tool definitions. Do not edit by hand.
4
- > Surface: config keys: 64 · sections: 15 · tools: 27 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
4
+ > Surface: config keys: 56 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
5
5
 
6
6
  | Tool | Description |
7
7
  |---|---|
8
8
  | `tower_adopt` | Reconcile the roster with the durable subagent catalog after a restart or new session. Agents that are provably gone are marked retired (records are kept) or missing (retireMissing:false, so the tower can inspect/take over); agents whose parent catalog cannot be read stay unknown and are never changed. Only the tower may adopt. |
9
9
  | `tower_batch` | Manage finite evidence batches: create freezes missionIds/requiredChecks/receiptPaths, status summarizes members, close verifies every required check and receipt hash, abandon retires a stale open batch. Membership and exclusions are immutable after create; close/abandon never remove missions. |
10
10
  | `tower_complete` | Worker-only: report mission completion. Enforced by the store: every task done or dropped, no blockers, the current owner, the mission branch has at least one commit since its spawn base, and no unread inbox messages. checks records verifiable evidence (name/command/exitCode/evidence); gates.requireChecks can require at least one before merge. Stores the full report, capped at gates.maxReportBytes (default 32768 UTF-8 bytes); a larger report is rejected with invalid-argument and an actionable nextAction, never truncated. Persists reportBytes on the stored report. completion means ready for preflight and optional review, not merged. |
11
+ | `tower_control` | Enter or leave Tower mode for the calling agent. Allowed only when the deployment enables allowAgentControl; use it when the user intent or task state clearly calls for Tower orchestration or a return to normal collaboration, and always state a concrete reason. |
11
12
  | `tower_diff` | Read-only view of a mission branch for review: merge base, changed files, commits, and a capped patch against the recorded base. Safe for reviewers; it never modifies the repository. |
12
13
  | `tower_doctor` | Read-only environment and repository health check: the concept-budget surface (config keys/sections/tools), git, state schema, case-sensitivity detection (caseInsensitive and the active scope comparison mode), config, presets/subagents/HMR, the state lock age/heartbeat freshness, the repo-scoped git lock holder (gitLock: {held,pid,phase,heldMs}), workspace lock, roster staleness, worktree ownership, test discovery, leases, and the terminal-mission archive (count, bytes, years, unreadable diagnostics and the configured afterDays/maxMissions policy). Returns checks with pass/warn/fail, state-lock and git-lock reports and an environment contract. Never writes state and never probes the filesystem. |
13
14
  | `tower_finding` | File a structured finding (bug / improve / vuln / idea) for the tower to route. Use this for anything notable OUTSIDE your mission scope instead of editing out-of-scope files. Severity is one of low|medium|high|critical. Use tower_findings to list, read, or close findings later. |
@@ -19,11 +20,12 @@
19
20
  | `tower_legacy_map` | Audit-only mapping between a legacy snapshot id and a current mission. map records sourceHash/oldId/newMissionId; status lists mappings. Mapping never adopts or resumes a legacy mission. |
20
21
  | `tower_merge` | Merge a mission branch into the recorded base branch with --no-ff, after the hard gate passes. Verifies the two merge parents and records the mission as merged. If the merge commit already landed (manual merge, crashed or slow pre-merge hook), it detects the merge commit and records it as reconciled instead of merging again. A failed gate returns stable blocker codes without merging; a hook failure aborts cleanly and returns "merge-hook-failed" while a real content conflict returns "merge-conflict". |
21
22
  | `tower_mission` | Update one mission: lifecycle status, decision note, blocker, clear blockers, mark a task done, drop a task with a reason, or widen the scope (tower only). "merged" is never set here; only a future tower_merge records it. Merged and abandoned are terminal. |
23
+ | `tower_mode` | Report whether Tower mode is active for the calling agent, who changed it last, whether the model may toggle it, any goal paused on entry, the workspace lock, HMR availability and the plugin version. |
22
24
  | `tower_plan` | Plan one or more missions in an initialized repository. Each mission has a printable-Unicode title (NFC-normalized, at most 120 characters), relative scope globs, optional task checklist, optional mission-id dependencies, and kind build|survey. Assigns M1.. ids and auto-adds a dependency when build scopes overlap (serializes merges); rejects dependency cycles. |
23
25
  | `tower_preflight` | Read-only merge gate for one mission: returns ok plus stable blocker codes (branch-missing, deps-unmerged, read-only-survey, stale-base, out-of-scope, base-dirty, branch-owned-by-closed-missions, and mission/worker guards). Review blockers (no-review, review-hold, not-clean, review-not-approved, tip-moved, review-mission-mismatch) apply only when review.requireReview is on. Never changes anything. |
24
26
  | `tower_rebase` | Rebase a mission branch onto the current base tip inside its own worktree. Refuses while its worker is running or the worktree is dirty. A clean conflict-free rebase waives re-review for the old review; a conflict is aborted and the mission is marked blocked. If the branch is already contained in the base, it returns alreadyMerged and leaves the branch untouched so tower_merge can record the landed merge. |
25
27
  | `tower_recover` | Read-only recovery diagnostics and an explicit stale-state-lock repair. status reports the state lock owner with its age, heartbeat age and staleness; clear-stale-lock requires confirm:"CLEAR_STALE_LOCK" and only moves a lock whose PID is provably dead to a backup file. force:true additionally clears a live-but-silent PID (a recycled PID) and still renames the record to a .recovered-<ts> backup. It never resets Tower state or worktrees. |
26
- | `tower_reload` | Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself. Registered while dev.hotReload is on; fails with reload-unavailable when the HMR service is absent. |
28
+ | `tower_reload` | Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. Registered only while the tower-hmr component is on (the row is shipped off). The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself. Fails with reload-unavailable when the HMR service is absent. |
27
29
  | `tower_resource` | Advisory resource leases per repository (coordination only; no OS CPU/memory hard cap). acquire/release require a token and are tower-only; status is read-only. Expired leases are never taken over silently. |
28
30
  | `tower_review` | Submit an independent review verdict bound to the current branch tip: a completed mission starts a review round, and while the mission is active a later route may still record a verdict for that same reviewed tip, appending to the same round without changing the status (a moved tip is rejected with an explanation). status is "clean", "p1-Nitems", or "p2-Nitems"; merge is "merge", "fix-then-merge", or "hold" (only clean + merge is later approval). Non-clean returns the mission to active. Reviewers are capped at 5 rounds per branch; a tower-submitted review is break-glass and requires breakGlassReason. |
29
31
  | `tower_roster` | List tower roster agents with authoritative live activity from the subagent catalog (running/inactive/missing/unknown). Read-only; never resumes children. Use before completion or merge decisions. |