dsh-tower 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/COMPATIBILITY.md +1 -0
  3. package/CONTRIBUTING.md +5 -3
  4. package/GLOSSARY.md +67 -0
  5. package/README.md +6 -0
  6. package/app/archive.js +16 -1
  7. package/app/board.js +16 -1
  8. package/app/gate.js +14 -0
  9. package/app/spawn-prechecks.js +18 -3
  10. package/cordis.patch.yml +21 -6
  11. package/docs/adr/0004-surface-budget.md +10 -4
  12. package/docs/adr/0005-component-surface.md +166 -0
  13. package/docs/guide/install-upgrade-migration.md +21 -0
  14. package/docs/guide/quickstart.md +71 -0
  15. package/docs/guide/release.md +60 -19
  16. package/docs/reference/component-surface.md +27 -15
  17. package/docs/reference/config.generated.md +4 -7
  18. package/docs/reference/config.md +7 -5
  19. package/docs/reference/doctor.md +11 -5
  20. package/docs/reference/surface-budget.md +29 -11
  21. package/docs/reference/tools.generated.md +1 -1
  22. package/docs/reference/tools.md +2 -2
  23. package/docs/zh/guide/install-upgrade-migration.md +1 -1
  24. package/docs/zh/reference/config.md +7 -5
  25. package/docs/zh/reference/doctor.md +7 -2
  26. package/docs/zh/reference/tools.md +3 -2
  27. package/domain/component-rules.js +216 -0
  28. package/domain/components.js +169 -49
  29. package/domain/config/schema.js +46 -23
  30. package/domain/gate.js +17 -2
  31. package/domain/scope.js +176 -5
  32. package/domain/surface.js +21 -7
  33. package/host/capability-archive.js +18 -0
  34. package/host/capability-board.js +18 -0
  35. package/host/capability-registry.js +4 -0
  36. package/host/doctor.js +50 -5
  37. package/host/presets.js +17 -0
  38. package/host/tool-registry.js +21 -4
  39. package/index.js +17 -6
  40. package/locale/archive/en.json +6 -0
  41. package/locale/archive/zh.json +6 -0
  42. package/locale/batch/en.json +1 -1
  43. package/locale/batch/zh.json +1 -1
  44. package/locale/board/en.json +6 -0
  45. package/locale/board/zh.json +6 -0
  46. package/locale/en.json +37 -22
  47. package/locale/hmr/en.json +2 -2
  48. package/locale/hmr/zh.json +1 -1
  49. package/locale/import/en.json +1 -1
  50. package/locale/import/zh.json +1 -1
  51. package/locale/leases/en.json +2 -2
  52. package/locale/leases/zh.json +2 -2
  53. package/locale/preset-reviewer/en.json +1 -1
  54. package/locale/preset-reviewer/zh.json +1 -1
  55. package/locale/preset-worker/en.json +1 -1
  56. package/locale/preset-worker/zh.json +1 -1
  57. package/locale/skills/en.json +1 -1
  58. package/locale/skills/zh.json +1 -1
  59. package/locale/tests/en.json +2 -2
  60. package/locale/tests/zh.json +2 -2
  61. package/locale/zh.json +36 -21
  62. package/package.json +11 -2
  63. package/scripts/check-components.mjs +582 -42
  64. package/scripts/component-surface.json +85 -27
  65. package/scripts/generate-capability-locale.mjs +3 -0
  66. package/scripts/mount-smoke/surface.mjs +2 -0
  67. package/scripts/pack-manifest.txt +9 -0
  68. package/scripts/release-notes.mjs +256 -0
  69. package/scripts/surface-baseline.json +32 -3
  70. package/scripts/surface-baseline.mjs +15 -5
  71. package/skills.js +4 -1
  72. package/tools/complete.js +3 -1
  73. package/tools/spawn.js +5 -5
  74. package/tools/test.js +7 -7
  75. package/ui-bundle/locale/en.json +1 -1
  76. package/ui-bundle/locale/zh.json +1 -1
@@ -100,3 +100,74 @@ Statuses are `open → triaged → fixed` / `wontfix`, and `fixed`/`wontfix` can
100
100
  tower or a worker may change a finding; reviewers can list and read.
101
101
 
102
102
  A complete copy-paste example: [examples/first-mission.md](../examples/first-mission.md).
103
+
104
+ ## Component rows, runtime preconditions and message language
105
+
106
+ The optional component rows are the only switches in this bundle (#121, #136). Three runtime rules
107
+ follow from that, plus the language policy for everything a tool prints.
108
+
109
+ ### A row is the only switch
110
+
111
+ `tests.enabled` was removed (#136). Discovery is on exactly while the `tower-tests` row is mounted:
112
+ with the row off there is no `tower_test` tool and no test receipt to gate on, so a second boolean
113
+ could only disagree with the mounted profile. A repository config that still writes
114
+ `tests.enabled` is rejected as `config-unknown-key`; delete the key and enable or disable the row
115
+ instead.
116
+
117
+ The same reasoning covers the other rows:
118
+
119
+ - `tower-leases` owns `tower_resource`. `spawn.requireLease` is satisfied through that tool, so a
120
+ config that requires a lease while the row is off is refused by the runtime preconditions with
121
+ `config-invalid`, naming the row and both remedies ("re-enable the tower-leases component or clear
122
+ spawn.requireLease") instead of failing every spawn at `lease-busy` with advice to use a tool that
123
+ is not registered (#131).
124
+ - `tower-tests` must stay mounted while `gates.requireTests` is true. Planning and mode entry refuse
125
+ the combination; the merge gate's `tests-missing` blocker also names the disabled row as the cause
126
+ (#140).
127
+ - `tower-hmr` ships off; `/tower reload` reports it by row name.
128
+
129
+ No runtime path may claim a repository is "not configured" when a component row is the reason.
130
+ Runtime refusals report the cause as `disabled by component <id>` and come with the fix (`enable the
131
+ <id> row in the plugin page and restart DSH`).
132
+
133
+ ### Preconditions run on mode entry, not on every `/tower` input (#142)
134
+
135
+ `/tower` re-validates the runtime preconditions (presets, component/config contradictions) only when
136
+ it **enters** Tower mode:
137
+
138
+ | Input | Preconditions |
139
+ |---|---|
140
+ | `/tower`, `/tower on`, `/tower <text>` while the mode is off | validated |
141
+ | `/tower off`, `/tower off <text>` | not validated |
142
+ | `/tower <text>` while the mode is already on | not validated |
143
+ | `/tower ack`, `/tower base <branch>`, `/tower reload` | not validated |
144
+
145
+ The reason: a row disabled after the session started (or a profile reloaded with a stricter config)
146
+ must not make `/tower off` — the command a human needs to leave the mode — or an ordinary forwarded
147
+ instruction fail. Capability guards still run where the work happens: `tower_spawn`, planning and
148
+ merge/preflight evaluate their own preconditions, so the checks cannot be bypassed by entering the
149
+ mode first.
150
+
151
+ ### Language of tool output
152
+
153
+ Machine-facing output is **English only**, regardless of the request locale:
154
+
155
+ - tool errors (`[tower:<code>] ...`), `nextAction`, blocker `detail` strings, `tower_status` /
156
+ `tower_doctor` detail lines, and completion reports;
157
+ - anything that a script, a log search or another agent is expected to pattern-match.
158
+
159
+ Localized, human-facing copy lives in `locale/*.json` (component descriptions, UI labels) and is the
160
+ only translated surface. Two rules keep the split honest:
161
+
162
+ 1. New detail/error strings are written in English even when the surrounding UI is Chinese; they are
163
+ not added to `locale/*.json`.
164
+ 2. A localized label never shadows a machine-readable cause. The row id, the error code and the
165
+ English remedy phrase stay verbatim inside the message, so a grep for
166
+ `disabled by component tower-tests` works in every locale.
167
+
168
+ `tower_status` and `tower_doctor` follow this policy but are not textually identical: `tower_status`
169
+ returns the cause only — `components.disabled` is `[{id, reason}]` with `reason` set to
170
+ `disabled by component <id>` — while `tower_doctor` repeats that reason and adds the English remedy
171
+ (`enable the reported row in the plugin page and restart DSH` for the `components` check; a
172
+ capability-specific check names the row directly, e.g. `enable the tower-tests row and restart DSH`).
173
+ Neither interpolates a translated label.
@@ -97,29 +97,70 @@ git for-each-ref refs/tags/v2.1.0-beta.1 --format='%(objecttype)|%(subject)'
97
97
  The pushed tag must be exactly `v<version>`. The tag push re-runs `release:check` against
98
98
  `<tag>^`; this is why tagging a commit whose `package.json` did not move fails.
99
99
 
100
- ## 6. GitHub Release from the CHANGELOG section
100
+ ## 6. GitHub Release with cumulative notes
101
+
102
+ A Release body is not just its own CHANGELOG section: it carries **every** section after the
103
+ previous GitHub Release (see [Cumulative Release notes](#cumulative-release-notes--累积发布说明)
104
+ below). `scripts/release-notes.mjs` is the authority for that body — do not hand-extract a single
105
+ section. The checklist step is `npm run release:notes -- --version <v> --since <prev-release> --output notes.md`, then pass that file to `gh release create`/`gh release edit` with `--notes-file notes.md`.
101
106
 
102
107
  ```bash
103
- VERSION=2.1.0-beta.1
104
- awk -v v="$VERSION" '$0 ~ "^##[[:space:]]+.*" v {s=1; next} s && /^##[[:space:]]/ {exit} s {print}' \
105
- CHANGELOG.md > release-notes.md
106
- test -s release-notes.md || { echo "empty section for $VERSION" >&2; exit 1; }
107
- gh release create "v$VERSION" --title "dsh-tower $VERSION" --notes-file release-notes.md \
108
- --prerelease --verify-tag --latest=false
108
+ # Stable release (the common case): no pre-release flags.
109
+ VERSION=2.3.0
110
+ SINCE=2.1.0 # the previous GitHub Release, not the previous tag
111
+ npm run release:notes -- --version "$VERSION" --since "$SINCE" --output notes.md
112
+ npm run release:notes -- --version "$VERSION" --since "$SINCE" --check notes.md
113
+ gh release create "v$VERSION" --title "dsh-tower $VERSION" --notes-file notes.md --verify-tag
114
+ # editing a Release that already exists (for example, to correct its window):
115
+ gh release edit "v$VERSION" --notes-file notes.md
109
116
  gh release view "v$VERSION" --json tagName,isPrerelease,body
110
117
  ```
111
118
 
112
- PowerShell extraction of the same section:
113
-
114
- ```powershell
115
- $version = '2.1.0-beta.1'; $lines = Get-Content CHANGELOG.md
116
- $start = ($lines | Select-String -Pattern "^##\s+.*$([regex]::Escape($version))" | Select-Object -First 1).LineNumber
117
- if (-not $start) { throw "CHANGELOG has no section for $version" }
118
- $next = ($lines | Select-String -Pattern '^##\s+' | Where-Object LineNumber -gt $start | Select-Object -First 1).LineNumber
119
- $end = if ($next) { $next - 2 } else { $lines.Count - 1 }
120
- ($lines[$start..$end] -join "`n").Trim() | Set-Content release-notes.md -Encoding utf8
119
+ ```bash
120
+ # Pre-release variant (a beta, for example 2.5.0-beta.1): mark the Release and keep "latest"
121
+ # on the previous stable Release.
122
+ VERSION=2.5.0-beta.1
123
+ SINCE=2.4.2 # the previous GitHub Release; a pre-release counts as one too
124
+ npm run release:notes -- --version "$VERSION" --since "$SINCE" --output notes.md
125
+ gh release create "v$VERSION" --title "dsh-tower $VERSION" --notes-file notes.md \
126
+ --prerelease --verify-tag --latest=false
121
127
  ```
122
128
 
123
- `--verify-tag` refuses to create the tag at the default branch when the pushed tag is missing;
124
- `--prerelease --latest=false` keeps the GitHub "latest" marker on the stable release. Releases
125
- are what consumers and the plugin directory can see — a tag alone is not a release.
129
+ `--check notes.md` exits 1 when the file differs from the generated text (it prints the first
130
+ differing line and both byte counts), so a correction is verifiable. The script exits 2 on a
131
+ missing/unknown flag, an invalid version, `since >= version`, or an empty window; it never emits
132
+ a Release body it cannot justify.
133
+
134
+ `--verify-tag` refuses to create the tag at the default branch when the pushed tag is missing.
135
+ A beta Release adds `--prerelease --latest=false`: GitHub marks it as a pre-release and keeps
136
+ the "latest" marker on the stable Release. Releases are what consumers and the plugin directory
137
+ can see — a tag alone is not a release.
138
+
139
+ ## Cumulative Release notes / 累积发布说明
140
+
141
+ A GitHub Release is cumulative: it aggregates every CHANGELOG section **after the previous
142
+ Release**, up to and including the version being released.
143
+
144
+ - The **previous Release** is the latest published GitHub Release by version. Pre-releases
145
+ created with `--prerelease` count as Releases, so a `2.1.0-beta.1` Release is a boundary too.
146
+ - **Tag-only versions have no Release.** A version that was tagged or published to npm without
147
+ creating a GitHub Release — 2.1.1 and 2.1.2 were tag-only, and 2.2.0 too — would otherwise
148
+ lose its notes; its CHANGELOG section rolls into the next Release.
149
+ - Example: once 2.1.0 is the previous Release, the 2.3.0 Release includes 2.1.1, 2.1.2, 2.2.0
150
+ and 2.3.0:
151
+
152
+ ```bash
153
+ npm run release:notes -- --version 2.3.0 --since 2.1.0 --output notes.md
154
+ ```
155
+
156
+ The window is `(2.1.0, 2.3.0]`; `2.1.0-beta.1` sorts before 2.1.0 and therefore stays out.
157
+ - `npm run release:notes` (`scripts/release-notes.mjs`) is the authoritative generator: it reads
158
+ `CHANGELOG.md`, filters the window, orders sections by SemVer (pre-release identifiers
159
+ included), and emits the `# dsh-tower <v>` title, a provenance quote naming the previous
160
+ Release and the included versions, then the sections separated by `---`.
161
+ - `--check <file>` re-verifies an existing notes file byte-for-byte (exit 1 on a mismatch), so
162
+ the body of a published Release can be audited without editing it. Use `--root <dir>` to check
163
+ another checkout.
164
+
165
+ Never hand-extract only the current version's section: that is what produced single-section
166
+ Releases whose tag-only predecessors were invisible.
@@ -3,7 +3,7 @@
3
3
  > Generated by `npm run docs:generate:components` from `scripts/component-surface.json` and
4
4
  > `scripts/surface-baseline.json`. Do not edit by hand.
5
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`)
6
+ Decision: [ADR 0005](../adr/0005-component-surface.md) · decision date: 2026-10-23 · baseline 2026-10-23 · visible components: 11 (`tower`, `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-board`, `tower-archive`, `tower-hmr`, `preset-tower-reviewer`, `tower-ui`)
7
7
 
8
8
  The component axis answers one question for every config key: may a user turn it
9
9
  off, and if so, must that switch be a component? Four classes cover every key, so
@@ -29,11 +29,14 @@ row is a section of its parent and is not independently switchable.
29
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
30
  | `tower-batch` | visible | row | tower_batch is not registered; missions, gates and merges are unaffected because no state depends on a batch. |
31
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. |
32
+ | `tower-leases` | visible | row | tower_resource is not registered; advisory leases are coordination only, but spawn.requireLease still needs the tool to acquire one, so a config that sets spawn.requireLease is rejected at runtime preconditions instead of failing every spawn at lease-busy. |
33
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. |
34
+ | `tower-board` | visible | row | MISSIONS.md is not written or refreshed, so an existing board goes stale. Mission state tools, gates and every other capability keep working; docs.boardLocation still chooses where a board would be written. |
35
+ | `tower-archive` | visible | row | Terminal missions are not moved out of state.json, so the hot state keeps growing and status/doctor keep loading their full history. Merges, gates and every other capability keep working; nothing is deleted. afterDays/maxMissions keep their tuning meaning. |
36
+ | `tower-hmr` | visible | row | Development-only tooling: tower_reload and /tower reload are unavailable, so a linked-profile reload has to be triggered outside Tower. A normal profile behaves exactly as before; the row ships disabled on purpose. |
35
37
  | `preset-tower-worker` | internal | row | Declared by dsh-tower/preset-worker: tower_spawn fails without the worker preset. A mandatory dependency, never a switch. |
36
38
  | `preset-tower-reviewer` | visible | row | Declared by dsh-tower/preset-reviewer: 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. |
39
+ | `tower-ui` | visible | row | The Tower web UI plugin does not load in this profile: no Tower tool cards, composer-dock summary or status strip. Host tools, missions, state and the MISSIONS.md board are unaffected. |
37
40
 
38
41
  ## Forbidden to disable
39
42
 
@@ -71,9 +74,9 @@ All keys from `domain/config/schema.js`, classified on the component axis.
71
74
  |---|---|---|
72
75
  | `section` | `core` | Legacy alias for mode.promptSection: the Tower-mode prompt contract, not a capability. |
73
76
  | `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
+ | `init.autoGitInit` | `optional-config` | Bootstrap convenience; off only means tower_init will not run git init. Tuning exemption: Tuning, not capability: tower_init stays the component and the switch only decides whether it runs git init. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
78
+ | `init.snapshotDirty` | `optional-config` | Dirty-work baseline convenience; no capability is removed. Tuning exemption: Tuning, not capability: the init component remains available with or without a dirty-work snapshot baseline. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
79
+ | `init.autoExclude` | `optional-config` | Keeps Tower artifacts out of .git/info/exclude; safe either way. Tuning exemption: Tuning, not capability: both values keep the init component and only change which artifacts the exclude file names. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
77
80
  | `mode.promptSection` | `core` | Replaces the whole Tower-mode prompt text; contract text, not a capability. |
78
81
  | `mode.sectionAppend` | `optional-config` | Appends team conventions to the prompt. |
79
82
  | `mode.allowAgentControl` | `core` | Decides whether the model may toggle the mode (ADR 0003). A governance rule, never a component. |
@@ -104,28 +107,37 @@ All keys from `domain/config/schema.js`, classified on the component axis.
104
107
  | `spawn.maxLiveWorkers` | `core` | Resource-safety cap on live workers. |
105
108
  | `spawn.staleMs` | `optional-config` | Roster staleness window before merge is blocked. |
106
109
  | `spawn.maxWaitMs` | `optional-config` | Upper bound for a blocking spawn wait. |
107
- | `spawn.requireLease` | `optional-config` | Advisory-lease gate before a worker starts. |
110
+ | `spawn.requireLease` | `optional-config` | Advisory-lease gate before a worker starts. Tuning exemption: Tuning the safety level inside the optional tower-leases row; with leases off the Host refuses requireLease instead of removing the spawn capability. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
108
111
  | `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
+ | `send.interruptDefault` | `optional-config` | Default for the interrupt-before-deliver flag. Tuning exemption: Tuning, not capability: the send component always exists and the switch only picks a delivery default. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
113
+ | `tests.admission` | `optional-config` | Chooses when discovery runs inside the optional tests capability. Tuning exemption: Tuning when the admission check runs inside the optional tower-tests row, which still owns whether test tooling exists. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
112
114
  | `tests.timeoutMs` | `optional-config` | Per-command discovery timeout. |
113
115
  | `tests.maxBytes` | `optional-config` | Captured discovery output cap. |
114
- | `tests.autoChecks` | `optional-config` | Attaches a tests check to completion when discovery runs. |
116
+ | `tests.autoChecks` | `optional-config` | Attaches a tests check to completion when discovery runs. Tuning exemption: Tuning the default receipt policy inside the optional tower-tests row, which still owns whether any test tooling exists. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
115
117
  | `tests.allowPackageDiscovery` | `core` | Allows repository code to execute; a security contract, not a component. |
116
118
  | `gates.requireChecks` | `forbidden-to-disable` | Requires completion evidence before merge; a merge gate. |
117
119
  | `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
120
  | `gates.allowReviewExemption` | `forbidden-to-disable` | The review-exemption path is part of the review and merge contract. |
119
121
  | `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. |
122
+ | `docs.boardLocation` | `optional-config` | Chooses where the board is written; whether it is written is the tower-board component row (#133). |
123
+ | `archive.afterDays` | `optional-config` | Archive age threshold inside the optional tower-archive component (#135). |
124
124
  | `archive.maxMissions` | `optional-config` | Hot terminal-mission cap. |
125
125
  | `leases.ttlMs` | `optional-config` | Tuning inside the optional tower-leases component. |
126
126
  | `dev.reloadDelayMs` | `optional-config` | Delay so a tool result is delivered before a scheduled reload inside the optional tower-hmr component. |
127
127
  | `dev.reloadRequiredIds` | `optional-config` | Loader ids passed to reconcileProfile as required by the tower-hmr component. |
128
128
 
129
+ ## Behaviour knobs
130
+
131
+ Non-boolean keys that gate behaviour are reviewed on the same axis as booleans
132
+ (#138): each must be `core`, `optional-component`, `forbidden-to-disable`, or an
133
+ `optional-config` key with a dated tuning-only exemption. A string or union that
134
+ hides a capability cannot use the tuning class without that record.
135
+
136
+ | Key |
137
+ |---|
138
+ | `spawn.requireLease` |
139
+ | `tests.admission` |
140
+
129
141
  ## Non-goals
130
142
 
131
143
  This page does not decide whether a key is useful, and it is not an approval
@@ -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: 56 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
4
+ > Surface: config keys: 53 · 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
  |---|---|---|
@@ -10,7 +10,7 @@
10
10
  | `init.autoGitInit` | `false` | tower_init may run git init and create an empty initial commit when the directory is not a repository. |
11
11
  | `init.snapshotDirty` | `false` | tower_init may snapshot uncommitted tracked changes into a dangling commit used as the mission baseline. |
12
12
  | `init.autoExclude` | `true` | Add in-repo Tower artifacts (MISSIONS.md, in-repo worktree roots) to .git/info/exclude so they do not look like user dirt. |
13
- | `mode.promptSection` | `` | Replacement Tower-mode system prompt text. Empty keeps the built-in reminder. |
13
+ | `mode.promptSection` | `""` | Replacement Tower-mode system prompt text. Empty keeps the built-in reminder. |
14
14
  | `mode.sectionAppend` | `` | Extra text appended to the Tower-mode system prompt section (team conventions). |
15
15
  | `mode.allowAgentControl` | `true` | Register tower_control so the model may enter/leave the mode with a reason. |
16
16
  | `mode.autoPauseGoal` | `true` | Pause an active goal when entering Tower mode. |
@@ -43,7 +43,6 @@
43
43
  | `spawn.requireLease` | `` | Optional lease name tower_spawn must hold before starting a worker. |
44
44
  | `send.maxPerMinute` | `120` | Per-repository tower_send rate limit per minute (0 disables). |
45
45
  | `send.interruptDefault` | `false` | Default value of tower_send.interrupt when the caller omits it. |
46
- | `tests.enabled` | `true` | Run declared test discovery when a mission configures it. |
47
46
  | `tests.admission` | `"off"` | Run discovery before spawning a worker, when completing, or only on explicit tower_test (default off: repository code runs only on an explicit tower_test). |
48
47
  | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
49
48
  | `tests.maxBytes` | `16777216` | Maximum captured stdout bytes per command. |
@@ -53,10 +52,8 @@
53
52
  | `gates.requireTests` | `false` | Require a passing test-discovery receipt for the reviewed worktree tip before merge. |
54
53
  | `gates.allowReviewExemption` | `false` | Allow reviewPolicy:none missions with a non-empty reviewExemptionReason to skip review blockers. |
55
54
  | `gates.maxReportBytes` | `32768` | Hard cap, in UTF-8 bytes, for tower_complete.report; a larger report is rejected with an actionable nextAction, never truncated. |
56
- | `docs.board` | `true` | Write a human-readable MISSIONS.md after mission changes. |
57
- | `docs.boardLocation` | `"common"` | common = <git-common-dir>/dsh-tower/MISSIONS.md; repo = <repoRoot>/MISSIONS.md. |
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
- | `archive.afterDays` | `30` | Terminal missions untouched for at least this many days are archived on the next mutation (0 archives them immediately). |
55
+ | `docs.boardLocation` | `"common"` | common = <git-common-dir>/dsh-tower/MISSIONS.md; repo = <repoRoot>/MISSIONS.md. Whether the board is written at all is the tower-board component row (#133). |
56
+ | `archive.afterDays` | `30` | Terminal missions untouched for at least this many days are archived on the next mutation (0 archives them immediately). Archival itself is the tower-archive component row (#135). |
60
57
  | `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
58
  | `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
62
59
  | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload, so the tool result is delivered first. |
@@ -5,6 +5,11 @@ Every key is optional; defaults reproduce the shipped behavior. Unknown top-leve
5
5
  `mode.allowAgentControl`. A legacy alias only fills a gap: when both forms are set, the canonical
6
6
  `mode.*` key wins.
7
7
 
8
+ Two component-shaped switches left the config form (#133/#135): `docs.board` is now the `tower-board`
9
+ component row and `archive.enabled` is now the `tower-archive` component row. A config that still sets
10
+ either one fails with `config-unknown-key` and an upgrade note naming the replacing row; the tuning
11
+ keys (`docs.boardLocation`, `archive.afterDays`, `archive.maxMissions`) stay here.
12
+
8
13
  ## Layers and precedence
9
14
 
10
15
  Lowest precedence first:
@@ -74,7 +79,6 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
74
79
  | `spawn.requireLease` | `''` | Optional lease name that must be held before a worker can spawn. |
75
80
  | `send.maxPerMinute` | `120` | Per-repository `tower_send` rate limit (0 disables); exceeding it returns `rate-limited`. |
76
81
  | `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. 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
82
  | `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
83
  | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
80
84
  | `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
@@ -84,10 +88,8 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
84
88
  | `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
89
  | `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
90
  | `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
- | `docs.board` | `true` | Write `MISSIONS.md` after mutations. |
88
- | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`; `repo` = `<repoRoot>/MISSIONS.md`. |
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
- | `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
+ | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`; `repo` = `<repoRoot>/MISSIONS.md`. Whether the board is written at all is the `tower-board` component row (#133); `docs.board` was removed and is rejected as an unknown key. |
92
+ | `archive.afterDays` | `30` | Age trigger: a terminal mission untouched for at least this many days is archived on the next mutation (`0` archives immediately). Archival itself is the `tower-archive` component row (#135); `archive.enabled` was removed and is rejected as an unknown key. |
91
93
  | `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
94
  | `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
93
95
  | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. Tuning inside the optional `tower-hmr` component. |
@@ -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: 56 · sections: 14 · tools: 29 (baseline 2026-10-23)" },
11
+ { "id": "surface", "status": "pass", "detail": "config keys: 53 · 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": 56,
17
+ "configKeys": 53,
18
18
  "sections": 14,
19
19
  "tools": 29,
20
20
  "baselineDate": "2026-10-23",
21
- "line": "config keys: 56 · sections: 14 · tools: 29"
21
+ "line": "config keys: 53 · sections: 14 · tools: 29"
22
22
  },
23
23
  "stateLock": {
24
24
  "held": true,
@@ -100,9 +100,15 @@ it for the configured merge timeout (15 minutes by default), so a held lock is a
100
100
  failure; the holder's phase and age make a stalled tower diagnosable. `tower_status` returns the same
101
101
  `gitLock` object.
102
102
 
103
+ `board` names the `MISSIONS.md` location (`docs.boardLocation`) and whether the `tower-board` component
104
+ row is mounted. With the row off the check is a `warn` that says the board is not written (an existing
105
+ board goes stale) and names the row; every state tool and gate still works.
106
+
103
107
  `archive` reports the terminal-mission archive: active `missions`, `archivedMissions` and `stateBytes`,
104
- the archive directory's `archiveBytes`/`years`/`files`, every `unreadable` diagnostic, and the configured
105
- `policy` (`enabled`, `afterDays`, `maxMissions`). A missing archive directory is a healthy empty archive
108
+ the archive directory's `archiveBytes`/`years`/`files`, every `unreadable` diagnostic, and the tuning
109
+ `policy` (`afterDays`, `maxMissions`). `policy.enabled` reflects the `tower-archive` component row, not a
110
+ config key: with the row off the policy line says archival is disabled by the component, terminal
111
+ missions stay hot and `state.json` keeps growing. A missing archive directory is a healthy empty archive
106
112
  (`archive` check `pass`); a truncated index or year file is a `warn` with the diagnostic, and archiving is
107
113
  paused until it is repaired rather than overwriting it. Nothing is lost while the archive is unreadable:
108
114
  the eligible missions stay in `state.json`.
@@ -19,10 +19,15 @@ these tables fails `npm run surface:check`.
19
19
  A second axis asks who may turn a key off. It is generated from
20
20
  [`scripts/component-surface.json`](../../scripts/component-surface.json) into
21
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
22
+ `optional-component`, `optional-config` or `forbidden-to-disable`. A boolean (or a
23
+ recorded non-boolean behaviour knob) cannot stay `optional-config` without a dated
24
+ tuning-only exemption. Both the component count and the config-key count are
25
+ baselined two-way: an increase or a contraction needs a dated decision
26
+ (`npm run docs:check:components`, [ADR 0005](../adr/0005-component-surface.md)).
27
+ Every decision on those two surfaces must carry a real `date` (`YYYY-MM-DD`); a
28
+ from/to record without one fails the brake even when it reaches the committed
29
+ count.
30
+ The same source of truth records
26
31
  the `gateableTools` allowlist; it is empty since #122, so a future
27
32
  `tools.expose*` route to any tool is refused before it can remove a review,
28
33
  merge or host tool.
@@ -44,8 +49,24 @@ profile's 28 loaded tools and pins 29 for the all-rows-on surface.
44
49
 
45
50
  #121 also shrank the config surface: the seven capability `tools.expose*` keys
46
51
  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.
52
+ sections 15 → 14. Sections keep the ADR 0004 cheap-shrink rule. #136 shrank the
53
+ config keys once more, 56 → 55: `tests.enabled` was a second switch over the
54
+ `tower-tests` component row, so the row is now the only switch and the key is
55
+ gone (a config that still writes it fails with `config-unknown-key`). #133 and
56
+ #135 shrank it again, 55 → 53: `docs.board` and `archive.enabled` were the same
57
+ kind of second switch over the new `tower-board` and `tower-archive` rows, so
58
+ each row is now the only switch and both keys are gone (a config that still sets
59
+ either fails with `config-unknown-key`, and the upgrade note names the replacing
60
+ row).
61
+
62
+ Since #138 that 64 → 53 history is one dated decision in
63
+ [`scripts/surface-baseline.json`](../../scripts/surface-baseline.json), and the
64
+ config-key count is two-way: a later increase **or** decrease needs its own
65
+ decision naming an ADR, unlike sections and tools. The same wave makes the gate
66
+ import `OPERATIONAL_TOOLS` instead of scraping it, discover every shipped
67
+ `cordis.patch.yml` in both directions, review the non-boolean behaviour knobs
68
+ (`spawn.requireLease`, `tests.admission`), and refuse a row config outside its
69
+ component's `allowedOverrides`.
49
70
 
50
71
  ## Config keys
51
72
 
@@ -89,7 +110,6 @@ not free growth: a later increase still needs its own decision.
89
110
  | `spawn.requireLease` | niche | Advisory-lease gate added for one multi-agent workflow. Removal note: fold into `tower_resource` policy in the next minor unless a second workflow uses it. |
90
111
  | `send.maxPerMinute` | tuning | Message rate limit; zero disables it. |
91
112
  | `send.interruptDefault` | tuning | Default for the interrupt-before-deliver flag. |
92
- | `tests.enabled` | core | Whether declared discovery may run at all. |
93
113
  | `tests.admission` | core | Chooses when repository code is executed. |
94
114
  | `tests.timeoutMs` | tuning | Per-command discovery timeout. |
95
115
  | `tests.maxBytes` | tuning | Captured discovery output cap. |
@@ -99,10 +119,8 @@ not free growth: a later increase still needs its own decision.
99
119
  | `gates.requireTests` | core | Requires a test receipt before merge. |
100
120
  | `gates.allowReviewExemption` | core | Allows a review exemption and its reason. |
101
121
  | `gates.maxReportBytes` | tuning | Report size cap; the refusal itself is not configurable. |
102
- | `docs.board` | tuning | Writes the human-readable board. |
103
- | `docs.boardLocation` | tuning | Chooses the board location. |
104
- | `archive.enabled` | core | Controls whether terminal missions leave the hot state. |
105
- | `archive.afterDays` | tuning | Archive age threshold. |
122
+ | `docs.boardLocation` | tuning | Chooses where the board is written; whether it is written is the tower-board row. |
123
+ | `archive.afterDays` | tuning | Archive age threshold; archival itself is the tower-archive row. |
106
124
  | `archive.maxMissions` | tuning | Hot terminal-mission cap. |
107
125
  | `leases.ttlMs` | tuning | Default advisory lease lifetime. |
108
126
  | `dev.reloadDelayMs` | tuning | Delay so a tool result is delivered before reload. |
@@ -1,7 +1,7 @@
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: 56 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
4
+ > Surface: config keys: 53 · 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
  |---|---|
@@ -52,7 +52,7 @@ findings but not mutate them. Violations throw `[tower:not-authorized]` or
52
52
  | `tower_preflight` | `missionId` (alias `id`) | Read-only gate: returns `{ok, blockers[], branchTip, baseTip, changedFiles, review}`. |
53
53
  | `tower_merge` | `missionId` (alias `id`), `timeoutMs?` (alias `timeout_ms`) | Merge after a green gate. Records already-landed merges (`reconciled`), recovers a failed call whose commit exists (`recoveredFromFailure`), and separates `merge-hook-failed` from `merge-conflict`. |
54
54
  | `tower_rebase` | `missionId` (alias `id`) | Rebase the branch onto the base in its worktree. Refuses while the worker runs or the worktree is dirty. Already-contained branches return `alreadyMerged` untouched. |
55
- | `tower_teardown` | `force?`, `exclude?`, `dry_run?` (alias `dryRun`) | Remove eligible merged/abandoned worktrees and branches; running agents are always kept. The final pass applies the `archive` policy: terminal missions whose worktree is gone are moved to `<git-common-dir>/dsh-tower/archive/` once `archive.afterDays`/`archive.maxMissions` says so, and `archived` lists their ids. Nothing is deleted; `archive.afterDays:0` archives a just-torn-down mission immediately. |
55
+ | `tower_teardown` | `force?`, `exclude?`, `dry_run?` (alias `dryRun`) | Remove eligible merged/abandoned worktrees and branches; running agents are always kept. The final pass applies the `archive` policy while the `tower-archive` component row is mounted: terminal missions whose worktree is gone are moved to `<git-common-dir>/dsh-tower/archive/` once `archive.afterDays`/`archive.maxMissions` says so, and `archived` lists their ids. Nothing is deleted; `archive.afterDays:0` archives a just-torn-down mission immediately. |
56
56
  | `tower_mode` | — | Report mode, last change, paused goal, denied tools, store path, plugin version, HMR availability, workspace-lock holder. |
57
57
  | `tower_control` | `action` (`enter`/`exit`), `reason` | Model-initiated mode change; registered by default; disable with `mode.allowAgentControl:false`; reason is mandatory. |
58
58
  | `tower_reload` | `repoRoot?` | Dev-only: ask DSH HMR to reconcile the profile and reload the Host plugin from disk. Registered while the `tower-hmr` component row is on (the row ships off); scheduled after the result (default 50 ms); fails with `reload-unavailable` without HMR. When `repoRoot` is supplied the caller must be the tower. |
@@ -77,4 +77,4 @@ Code default is off; the shipped patch is on for Kimi-compatible single-active-t
77
77
  Gate tools return `{ok:false, blockers:[{code, detail}]}`; the code list is in
78
78
  [blockers.md](blockers.md). Thrown tool errors carry a stable `[tower:<code>]` prefix
79
79
  ([errors.md](errors.md)). Successful mutations emit process-local events ([events.md](events.md)),
80
- and, with `docs.board:true`, refresh `<git-common-dir>/dsh-tower/MISSIONS.md`.
80
+ and, with the `tower-board` component row mounted, refresh `<git-common-dir>/dsh-tower/MISSIONS.md`. The board location stays `docs.boardLocation`; the old `docs.board` switch is a component row now (#133).
@@ -56,7 +56,7 @@ Host 插件在 DSH 启动时加载:**安装或更新后需要重启 DSH**。
56
56
  这是发布到 `next` dist-tag 的**预发布**,
57
57
  `latest` 仍指向 2.0.4——请用 `dsh plugin --profile web add dsh-tower@next`(或精确版本号)安装。
58
58
  运行时行为未变;本版本承载的是交付闸门(CI 中的 mount smoke、`npm run release:check`、证据约定
59
- 与追踪策略),而不是工具变化。本文档先于人工发布步骤合入:只有在[发布清单](release.md)执行
59
+ 与追踪策略),而不是工具变化。本文档先于人工发布步骤合入:只有在[发布清单(英文)](../../guide/release.md)执行
60
60
  `npm publish --tag next` 之后,`next` 才会解析到 2.1.0-beta.1;`latest` 在稳定版发布前保持 2.0.4。
61
61
  - **2.0.3 → 2.0.4**:不需要迁移 state/config。本版本是安全加固与完整性修复(授权、merge SHA 绑定、
62
62
  dirty worktree 完成拒绝、stale lock 恢复、限制 test/git 进程环境、`tower_recover`、UI 0.2.2)。
@@ -6,6 +6,11 @@
6
6
  `section` 与 `allowAgentControl` 是 `mode.promptSection` 与 `mode.allowAgentControl` 的兼容别名。
7
7
  兼容别名只用于补缺:同时设置两种写法时,规范的 `mode.*` 键优先。
8
8
 
9
+ 两个组件形态的开关已离开配置(#133/#135):`docs.board` 现在是 `tower-board` 组件行,
10
+ `archive.enabled` 现在是 `tower-archive` 组件行。仍设置其中任一个的配置会以 `config-unknown-key`
11
+ 失败,并附上指明替代行的升级提示;调优键(`docs.boardLocation`、`archive.afterDays`、
12
+ `archive.maxMissions`)仍保留在这里。
13
+
9
14
  ## 层级与优先级
10
15
 
11
16
  从低到高:
@@ -73,7 +78,6 @@ merge、worktree 与 spawn 策略。它使用与 profile 配置相同的 JSON
73
78
  | `spawn.requireLease` | `''` | worker spawn 前必须持有的可选租约名。 |
74
79
  | `send.maxPerMinute` | `120` | 每个仓库的 `tower_send` 每分钟速率限制(0 关闭);超出返回 `rate-limited`。 |
75
80
  | `send.interruptDefault` | `false`(随包 patch:`true`) | `tower_send.interrupt` 的默认值(投递前先中断运行中的回合)。 |
76
- | `tests.enabled` | `true` | worktree 的 `package.json` 配置了测试发现时运行它。它只调整可选的 `tower-tests` 组件:该行关闭时既没有 `tower_test` 工具也无法记录回执,`gates.requireTests:true` 会被拒绝而不是静默失效。 |
77
81
  | `tests.admission` | `off` | `spawn` 在启动 worker 前运行发现(fail-closed,不启动子代理);`complete` 在 `tower_complete` 时运行;`off` 只在显式 `tower_test` 时运行(默认;不会自动执行仓库代码)。 |
78
82
  | `tests.timeoutMs` | `60000` | 每条发现命令的超时。 |
79
83
  | `tests.maxBytes` | `16777216`(16 MiB) | 每条命令捕获的 stdout 上限。 |
@@ -83,10 +87,8 @@ merge、worktree 与 spawn 策略。它使用与 profile 配置相同的 JSON
83
87
  | `gates.requireTests` | `false` | 要求针对当前 mission 分支 tip 的通过测试回执(`tests-missing`);与审查门禁独立。`tower-tests` 组件行关闭时,规划/进入模式会带解释拒绝该组合。 |
84
88
  | `gates.allowReviewExemption` | `false` | 在 `review.requireReview:true` 时,允许用 `reviewPolicy:"none"` + 非空理由跳过审查 blocker;默认故意关闭;全局审查关闭时无影响。 |
85
89
  | `gates.maxReportBytes` | `32768`(32 KiB) | `tower_complete.report` 的 UTF-8 字节硬上限。超出时以 `invalid-argument` 拒绝并给出可执行的 `nextAction`(把全文写进 mission worktree 的文件并引用路径),绝不截断。已存在的超限报告仍可读取。 |
86
- | `docs.board` | `true` | 变更后写入 `MISSIONS.md`。 |
87
- | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`;`repo` = `<repoRoot>/MISSIONS.md`。 |
88
- | `archive.enabled` | `true` | 在变更边界把 worktree 已移除的终态(`merged`/`abandoned`)任务移入 `<git-common-dir>/dsh-tower/archive/`。归档任务保留 tasks、reviews、findings 与完成报告;这是移动,不是删除。 |
89
- | `archive.afterDays` | `30` | 年龄触发:终态任务超过这么多天未更新,会在下次变更时归档(`0` 表示立即归档)。 |
90
+ | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`;`repo` = `<repoRoot>/MISSIONS.md`。是否写出看板由 `tower-board` 组件行决定(#133);`docs.board` 已移除,会作为未知键被拒绝。 |
91
+ | `archive.afterDays` | `30` | 年龄触发:终态任务超过这么多天未更新,会在下次变更时归档(`0` 表示立即归档)。归档本身由 `tower-archive` 组件行决定(#135);`archive.enabled` 已移除,会作为未知键被拒绝。 |
90
92
  | `archive.maxMissions` | `100` | 数量触发:热状态中的终态任务超过此数时,从最旧的开始归档直到满足上限(`0` 表示归档所有符合条件的终态任务)。worktree 仍存在的任务永不归档。 |
91
93
  | `leases.ttlMs` | `900000` | 默认咨询式租约寿命;过期不会自动释放。 |
92
94
  | `dev.reloadDelayMs` | `50` | 计划中的 reload 延迟,先让工具结果送达。属于可选的 `tower-hmr` 组件。 |
@@ -61,9 +61,14 @@ PID 存活并不等于写者存活:停止心跳的持有者会报告 `stale: t
61
61
  因此“被持有”是 `warn` 而不是失败;持有者的阶段与时长让卡住的控制塔可被诊断。`tower_status` 返回同一个
62
62
  `gitLock` 对象。
63
63
 
64
+ `board` 报告 `MISSIONS.md` 位置(`docs.boardLocation`)以及 `tower-board` 组件行是否挂载。该行关闭时
65
+ 检查为 `warn`,说明看板不会被写出(已有看板会变陈旧)并指出该行名;所有状态工具与门禁仍然工作。
66
+
64
67
  `archive` 报告终态任务归档:活跃 `missions`、`archivedMissions` 与 `stateBytes`,归档目录的
65
- `archiveBytes`/`years`/`files`,每条 `unreadable` 诊断,以及配置的 `policy`(`enabled`、`afterDays`、
66
- `maxMissions`)。归档目录不存在是健康的空归档(`archive` 检查为 `pass`);截断的 index/year 文件是
68
+ `archiveBytes`/`years`/`files`,每条 `unreadable` 诊断,以及调优字段 `policy`(`afterDays`、
69
+ `maxMissions`)。`policy.enabled` 反映 `tower-archive` 组件行,而不是配置键:该行关闭时策略行说明
70
+ 归档被组件禁用,终态任务留在热状态,`state.json` 持续增长。归档目录不存在是健康的空归档
71
+ (`archive` 检查为 `pass`);截断的 index/year 文件是
67
72
  `warn` 并附带诊断,且归档会暂停而不是覆盖它——归档不可读期间没有数据会丢失,符合条件的 mission 仍留在
68
73
  `state.json`。
69
74
 
@@ -74,5 +74,6 @@ roster 角色由 `exec.agent` 与 roster 共同决定。mission 级工具(`tow
74
74
 
75
75
  门禁类工具返回 `{ok:false, blockers:[{code, detail}]}`;code 列表见 [blockers.md](blockers.md)。
76
76
  抛出的工具错误带稳定的 `[tower:<code>]` 前缀(见 [errors.md](errors.md))。成功的变更会发出进程内
77
- 事件(见 [events.md](events.md)),并且在 `docs.board:true` 时刷新
78
- `<git-common-dir>/dsh-tower/MISSIONS.md`。
77
+ 事件(见 [events.md](events.md)),并在挂载 `tower-board` 组件行时刷新
78
+ `<git-common-dir>/dsh-tower/MISSIONS.md`。看板位置仍由 `docs.boardLocation` 决定;原先的
79
+ `docs.board` 开关现在是组件行(#133)。