dsh-tower 2.4.2 → 2.6.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 (108) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/COMPATIBILITY.md +2 -0
  3. package/CONTRIBUTING.md +5 -3
  4. package/GLOSSARY.md +73 -0
  5. package/PRINCIPLES.md +15 -7
  6. package/README.en.md +16 -15
  7. package/README.md +18 -10
  8. package/app/archive.js +16 -1
  9. package/app/board.js +16 -1
  10. package/app/gate.js +14 -0
  11. package/app/review.js +7 -0
  12. package/app/spawn-prechecks.js +30 -5
  13. package/app/tower.js +5 -5
  14. package/client.js +321 -0
  15. package/cordis.patch.yml +26 -9
  16. package/docs/adr/0004-surface-budget.md +10 -4
  17. package/docs/adr/0005-component-surface.md +222 -11
  18. package/docs/architecture/overview.md +12 -6
  19. package/docs/examples/first-mission.md +3 -2
  20. package/docs/faq.md +9 -7
  21. package/docs/guide/install-upgrade-migration.md +53 -7
  22. package/docs/guide/quickstart.md +91 -10
  23. package/docs/guide/release.md +60 -19
  24. package/docs/guide/scenarios-and-philosophy.md +35 -28
  25. package/docs/operations/security.md +11 -10
  26. package/docs/reference/blockers.md +12 -7
  27. package/docs/reference/component-surface.md +40 -21
  28. package/docs/reference/config.generated.md +6 -8
  29. package/docs/reference/config.md +36 -8
  30. package/docs/reference/doctor.md +11 -5
  31. package/docs/reference/errors.generated.md +1 -0
  32. package/docs/reference/errors.md +2 -1
  33. package/docs/reference/surface-budget.md +47 -12
  34. package/docs/reference/testing.md +1 -1
  35. package/docs/reference/tools.generated.md +5 -5
  36. package/docs/reference/tools.md +5 -5
  37. package/docs/zh/faq.md +7 -5
  38. package/docs/zh/guide/install-upgrade-migration.md +26 -7
  39. package/docs/zh/guide/quickstart.md +17 -10
  40. package/docs/zh/guide/scenarios-and-philosophy.md +23 -15
  41. package/docs/zh/operations/security.md +9 -8
  42. package/docs/zh/reference/blockers.md +11 -7
  43. package/docs/zh/reference/config.md +28 -8
  44. package/docs/zh/reference/doctor.md +7 -2
  45. package/docs/zh/reference/errors.md +2 -1
  46. package/docs/zh/reference/testing.md +2 -2
  47. package/docs/zh/reference/tools.md +6 -5
  48. package/domain/component-rules.js +216 -0
  49. package/domain/components.js +181 -53
  50. package/domain/config/schema.js +86 -28
  51. package/domain/contracts/outputs.js +2 -0
  52. package/domain/contracts/tool-inputs/missions.js +1 -1
  53. package/domain/errors.js +6 -0
  54. package/domain/gate.js +23 -5
  55. package/domain/integration/review.js +203 -9
  56. package/domain/scope.js +176 -5
  57. package/domain/surface.js +21 -7
  58. package/host/capability-archive.js +18 -0
  59. package/host/capability-board.js +18 -0
  60. package/host/capability-registry.js +4 -0
  61. package/host/config.js +40 -3
  62. package/host/doctor.js +68 -12
  63. package/host/presets.js +26 -8
  64. package/host/tool-registry.js +30 -5
  65. package/index.js +17 -6
  66. package/locale/archive/en.json +6 -0
  67. package/locale/archive/zh.json +6 -0
  68. package/locale/batch/en.json +1 -1
  69. package/locale/batch/zh.json +1 -1
  70. package/locale/board/en.json +6 -0
  71. package/locale/board/zh.json +6 -0
  72. package/locale/en.json +37 -22
  73. package/locale/hmr/en.json +2 -2
  74. package/locale/hmr/zh.json +1 -1
  75. package/locale/import/en.json +1 -1
  76. package/locale/import/zh.json +1 -1
  77. package/locale/leases/en.json +2 -2
  78. package/locale/leases/zh.json +2 -2
  79. package/locale/preset-reviewer/en.json +1 -1
  80. package/locale/preset-reviewer/zh.json +1 -1
  81. package/locale/preset-worker/en.json +1 -1
  82. package/locale/preset-worker/zh.json +1 -1
  83. package/locale/skills/en.json +1 -1
  84. package/locale/skills/zh.json +1 -1
  85. package/locale/tests/en.json +2 -2
  86. package/locale/tests/zh.json +2 -2
  87. package/locale/zh.json +36 -21
  88. package/package.json +17 -2
  89. package/scripts/check-components.mjs +698 -44
  90. package/scripts/check-pack.mjs +3 -0
  91. package/scripts/component-surface.json +101 -37
  92. package/scripts/generate-capability-locale.mjs +3 -0
  93. package/scripts/mount-smoke/surface.mjs +2 -0
  94. package/scripts/pack-manifest.txt +10 -0
  95. package/scripts/release-notes.mjs +256 -0
  96. package/scripts/surface-baseline.json +39 -3
  97. package/scripts/surface-baseline.mjs +15 -5
  98. package/scripts/types-coverage.mjs +7 -0
  99. package/skills.js +4 -1
  100. package/tools/complete.js +3 -1
  101. package/tools/preflight.js +1 -1
  102. package/tools/review.js +1 -1
  103. package/tools/spawn.js +7 -7
  104. package/tools/status.js +8 -1
  105. package/tools/test.js +7 -7
  106. package/types/index.d.ts +10 -0
  107. package/ui-bundle/locale/en.json +1 -1
  108. package/ui-bundle/locale/zh.json +1 -1
@@ -12,9 +12,14 @@ escape keep working.
12
12
 
13
13
  [Principle 11](../../PRINCIPLES.md) and
14
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
15
+ a capability whose off state leaves the rest intact, and that the host, the
16
+ health contract, the recovery escape and the merge gates are never switches.
17
+ The review policy is not one of them: `review.mode` is a single tri-state
18
+ control (`off`, `optional` or `required`) on the plugin-page configuration
19
+ form, not a component row and never one row per mode, and only the review gate
20
+ it turns on in `required` mode — the clean, tip-bound evidence and the
21
+ exemption path — is non-disableable. Which keys are allowed to be switches is a
22
+ table lookup in
18
23
  [component-surface.md](../reference/component-surface.md), generated from
19
24
  `scripts/component-surface.json`; the component count is baselined and checked by
20
25
  `npm run docs:check:components`. That gate also refuses a `tools.expose*` route
@@ -236,9 +241,10 @@ set by `tower_merge`/reconciliation.
236
241
  3. Survey missions must not change files; build missions must stay inside `scope`.
237
242
  4. The main checkout must be clean for the files this merge would overwrite and on the base branch.
238
243
  5. Dependency missions must be merged/abandoned, except a completed survey dependency which is its terminal success state (#107).
239
- 6. If `review.requireReview:true` (default false), a review must exist, belong to this mission, be
240
- clean, approve merge, and be bound to the tip (unless a clean rebase waived it). No worker may be
241
- running.
244
+ 6. If the review policy is `required` (`review.mode:required`; the default is `optional`), a review
245
+ must exist, belong to this mission, be clean, approve merge, and be bound to the tip (unless a clean
246
+ rebase waived it). With `off`, review is intentionally unavailable and the gate ignores it. No
247
+ worker may be running.
242
248
 
243
249
  ## Merge
244
250
 
@@ -23,8 +23,9 @@ Wait for the worker's final report (delivered to this session) or check `tower_r
23
23
  creates `src/greet.txt`, commits on `tower/m1-add-greeting`, marks both tasks done, and calls
24
24
  `tower_complete`.
25
25
 
26
- This example follows the strict workflow: enable `review.requireReview:true` in the profile or
27
- repository config before the review step (default is optional review). Spawn the reviewer:
26
+ This example follows the strict workflow: set the review policy to `required` (`review.mode:required`
27
+ in the profile or repository config, or the one tri-state control on the Plugins page) before the
28
+ review step; the default is `optional`. Spawn the reviewer:
28
29
 
29
30
  ```json
30
31
  {"repoRoot":"D:\\work\\demo","missionId":"M1","role":"reviewer"}
package/docs/faq.md CHANGED
@@ -14,9 +14,9 @@ phase instead—use Workflow/Team to analyze and decide, then start Tower to exe
14
14
  review) the repository change. See the [full guide](guide/scenarios-and-philosophy.md#4-how-tower-differs-from-other-dsh-workflow-patterns).
15
15
 
16
16
  **Why did `tower_preflight` fail with `no-review` right after the worker finished?**
17
- That happens only when `review.requireReview:true` is enabled: a completed mission awaits an
18
- independent review. Spawn a reviewer; after a clean review the gate turns green. With the default
19
- `review.requireReview:false`, review is optional and no `no-review` blocker is produced.
17
+ That happens only when the review policy is `required` (`review.mode:required`): a completed mission
18
+ awaits an independent review. Spawn a reviewer; after a clean review the gate turns green. With the
19
+ default `review.mode:optional`, review is optional and no `no-review` blocker is produced.
20
20
 
21
21
  **Do I need to poll the worker?**
22
22
  No. The worker's final report is delivered to this session; `tower_roster` shows live activity. Polling
@@ -40,10 +40,12 @@ Correct: the branch is already contained in the base. Call `tower_merge` to reco
40
40
  instead of rebasing.
41
41
 
42
42
  **Can I require independent review?**
43
- Yes. Set `review.requireReview:true` in the profile or repository config to enable the global review
44
- gate (default is `false`, review optional). When enabled, mission-level `reviewPolicy:"none"` + a
45
- reason + `gates.allowReviewExemption:true` is the narrower exemption. `tower_review` works and records
46
- verdicts either way.
43
+ Yes. Set the review policy to `required` — the one tri-state control on the Plugins page, or
44
+ `review.mode:required` in the profile or repository config (the default is `optional`). When required,
45
+ mission-level `reviewPolicy:"none"` + a reason + `gates.allowReviewExemption:true` is the narrower
46
+ exemption. `tower_review` works and records verdicts in `optional` mode too. The old boolean toggle is
47
+ a deprecated one-release alias; the mapping is in
48
+ [install, upgrade and migration](guide/install-upgrade-migration.md).
47
49
 
48
50
  **Can a reviewer edit files?**
49
51
  No. Reviewers use the read-only `tower-reviewer` preset (no write/edit/pwsh); a branch changed after the
@@ -29,6 +29,50 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
29
29
 
30
30
  ## Migration notes
31
31
 
32
+ - **2.5.0 → 2.6.0 (#M39/#M40) — review modes replace the boolean**: the review policy is one
33
+ tri-state control on the Plugins page in the `dsh-tower` configuration section, and the config key
34
+ is `review.mode`: `off`, `optional` (the default) or `required`.
35
+ - `off` is supported and documented: `tower_review` is not registered, reviewer spawn is refused,
36
+ and the merge gate ignores review.
37
+ - `optional` keeps `tower_review` but never blocks a merge.
38
+ - `required` keeps the merge gate: a completed mission needs a clean review bound to the branch tip
39
+ (the existing per-mission exemption still applies).
40
+ `review.requireReview` is a deprecated one-release alias: `true` resolves to `required`, `false`
41
+ to `optional`, and leaving it unset means `optional`. A `true` next to an explicit non-`required`
42
+ mode is rejected with `config-invalid`; `false` never overrides an explicit mode. Saving from the
43
+ plugin page always writes `review.mode` and normalizes the alias to `false`. The policy is not a
44
+ component row and there are no mutually exclusive review rows: `preset-tower-reviewer` remains an
45
+ optional capability the policy may leave unused. The row may be off while `review.mode` is `off`
46
+ or `optional`; when `review.mode` is `required` and the reviewer row is off, planning and mode
47
+ entry refuse the combination with an explanatory `preset-missing` error.
48
+ A profile that starts with the policy `off` never registered `tower_review`; switching to
49
+ `optional` or `required` changes the merge gate immediately, but the tool appears only after a DSH
50
+ restart (or a profile reload), and the plugin page states that after such a save.
51
+ Upgrade: replace `review.requireReview: true|false` with `review.mode: required|optional`; keep the
52
+ alias only if the same config is shared with older 2.5 installations, and remove it after this
53
+ release. No state migration.
54
+
55
+ - **2.4.2 → 2.5.0 (#131/#133/#135/#136/#142)**: three component-shaped config
56
+ keys are gone — `tests.enabled`, `docs.board`, and `archive.enabled`. The
57
+ `tower-tests`, `tower-board`, and `tower-archive` component rows are the only
58
+ switches. A profile or repository config that still writes any of these keys is
59
+ rejected as `config-unknown-key` with an upgrade note and the host stops
60
+ mounting, so delete the key and enable or disable the row in the plugin page
61
+ instead. `docs.boardLocation` and `archive.afterDays`/`archive.maxMissions`
62
+ remain tuning keys. With `tower-tests` off there is no `tower_test` tool and no
63
+ receipt, which also means `gates.requireTests` cannot be satisfied: planning and
64
+ mode entry refuse that combination, and the merge gate's `tests-missing` blocker
65
+ names the disabled row and the fix. `tower_test` no longer reports "not
66
+ configured" for a component cause; it reports `disabled by component
67
+ tower-tests` with the remedy.
68
+ Two more runtime rules became explicit: a config that sets `spawn.requireLease`
69
+ while the `tower-leases` row is off is refused up front with `config-invalid`
70
+ naming the row and both remedies (previously every spawn failed at `lease-busy`
71
+ advising an unregistered `tower_resource`), and `/tower` re-validates the runtime
72
+ preconditions only when it enters the mode, so `/tower off` and a forwarded
73
+ `/tower <text>` keep working while an unrelated row is disabled. No state
74
+ migration; see [component rows and messages](quickstart.md#component-rows-runtime-preconditions-and-message-language).
75
+
32
76
  - **state schema v1 → v2 (unreleased)**: `state.json` migrates automatically. The first **write** after
33
77
  upgrading keeps a `state.json.v1.bak` backup, advances `revision` once, and appends
34
78
  `state.migrated {from,to}` to `activity.jsonl`; read-only tools keep reading a v1 file without
@@ -49,14 +93,14 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
49
93
  now ships the mandatory `tower` host anchor (the Host plugin and the worker preset are its internal
50
94
  sections) plus individually switchable top-level rows: `tower-skills`, `tower-batch`, `tower-import`,
51
95
  `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
96
+ the review policy, today `review.mode`). The `tools` config group and `dev.hotReload` were removed: a
97
+ profile that still carries them is now rejected as an unknown key (`config-unknown-key`) and the host stops
54
98
  mounting until they are deleted, so no silent no-op switch remains. Move each capability to its row
55
99
  in the plugin page instead; `tower-hmr` ships off, so `tower_reload` and `/tower reload` are reported
56
100
  as "disabled by component tower-hmr" until you enable it for a linked dev profile. `tower_doctor` and
57
101
  `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.
102
+ while the policy is `off` or `optional`; with `review.mode:required`, planning and mode entry refuse
103
+ the combination with an explanatory error. No state migration is needed.
60
104
 
61
105
  - **2.0.4 → 2.1.0-beta.1**: no config migration; the state file migrates automatically (v1/v2 → v3) as
62
106
  described above. This is a **pre-release** published on the
@@ -68,10 +112,12 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
68
112
  runs `npm publish --tag next`, and `latest` stays on 2.0.4 until a stable release.
69
113
  - **2.0.3 → 2.0.4**: no state/config migration. This is a security-hardening and integrity release
70
114
  (authorization, merge SHA binding, dirty-worktree completion, stale-lock recovery, limited test/git
71
- process environment, `tower_recover`, UI 0.2.2). Review `review.requireReview`, `tests.admission` and
72
- worker tool permissions against your threat model after upgrading.
115
+ process environment, `tower_recover`, UI 0.2.2). Review the review policy (`review.mode`, or the
116
+ deprecated `review.requireReview` alias), `tests.admission` and worker tool permissions against your
117
+ threat model after upgrading.
73
118
  - **2.0.1 → 2.0.3**: no state migration. Independent review became **optional by default** in 2.0.3
74
- (`review.requireReview:false`); set it to `true` to keep the old review-gated merges. 2.0.1 added
119
+ (today's `review.mode:optional`); set `review.mode:required` to keep the old review-gated merges.
120
+ 2.0.1 added
75
121
  `tower_batch` `abandon`, accepted merged members in batch close, made doctor skip torn-down worktrees,
76
122
  and fixed terminal-mission spawn/preflight edge cases. The optional `ui-bundle` 0.2.1 adds the
77
123
  persistent Tower-mode chip and mode-aware denied-tool rows.
@@ -33,15 +33,25 @@ session. Use `tower_roster` to observe activity; do not poll in a loop.
33
33
 
34
34
  ## 4. Optional: review
35
35
 
36
- Independent review is optional by default. If you want the strict workflow, enable it in the profile
37
- or repository config first (`review.requireReview:true`), then:
36
+ The review policy is one tri-state control — `off`, `optional` (the default) or `required` — on the
37
+ Plugins page in the `dsh-tower` configuration section, or `review.mode` in the profile/repository
38
+ config. It is one control, not a component row and not three rows. Set it to `required` for the strict
39
+ workflow, then:
38
40
 
39
41
  ```
40
42
  tower_spawn repoRoot=D:\work\repo missionId=M1 role=reviewer
41
43
  ```
42
44
 
43
45
  Reviewers use the read-only `tower-reviewer` preset. They inspect `tower_diff` and submit
44
- `tower_review`; with `review.requireReview:true`, a `clean` + `merge` verdict is required to merge.
46
+ `tower_review`; with `review.mode:required`, a `clean` + `merge` verdict is required to merge.
47
+ `optional` records reviews but never blocks a merge, and `off` is a supported mode that does not
48
+ register `tower_review`.
49
+
50
+ The old boolean review toggle is a one-release alias (`true` → `required`, `false` → `optional`;
51
+ unset → `optional`). Prefer `review.mode`. A profile that started with
52
+ `off` only registers `tower_review` after a restart/reload, although the merge gate changes
53
+ immediately. See [install, upgrade and migration](install-upgrade-migration.md) for the full
54
+ migration.
45
55
 
46
56
  ## 5. Gate, merge, clean up
47
57
 
@@ -51,10 +61,10 @@ tower_merge repoRoot=D:\work\repo missionId=M1
51
61
  tower_teardown repoRoot=D:\work\repo
52
62
  ```
53
63
 
54
- With the review gate enabled, `tower_preflight` before review reports `no-review`, and after a clean
55
- review it should be `ok:true`. With the default (`review.requireReview:false`), no review blocker is
56
- required. `tower_merge` refuses while blockers remain. `tower_teardown` removes merged/abandoned
57
- worktrees and branches.
64
+ With `review.mode:required`, `tower_preflight` before review reports `no-review`, and after a clean
65
+ review it should be `ok:true`. With the default (`optional`), no review blocker is required.
66
+ `tower_merge` refuses while blockers remain. `tower_teardown` removes merged/abandoned worktrees and
67
+ branches.
58
68
 
59
69
  ```
60
70
  /tower off
@@ -63,8 +73,8 @@ worktrees and branches.
63
73
  ## If something fails
64
74
 
65
75
  Read the blocker code and use [the blocker reference](../reference/blockers.md). The most common cases:
66
- `stale-base` (run `tower_rebase`), `no-review` (only with `review.requireReview:true`; spawn a
67
- reviewer), `merge-hook-failed` (retry, or raise `timeoutMs`), `tip-moved` (only with the review gate;
76
+ `stale-base` (run `tower_rebase`), `no-review` (only with `review.mode:required`; spawn a reviewer),
77
+ `merge-hook-failed` (retry, or raise `timeoutMs`), `tip-moved` (only when the policy is `required`;
68
78
  the branch changed after review, review again).
69
79
 
70
80
  Thrown tool failures carry a `[tower:<code>]` prefix; see [error codes](../reference/errors.md)
@@ -78,7 +88,7 @@ JSON file at `<git-common-dir>/dsh-tower/config.json` (usually `.git/dsh-tower/c
78
88
 
79
89
  ```json
80
90
  {
81
- "review": { "requireReview": true, "maxRoundsPerReviewer": 3 },
91
+ "review": { "mode": "required", "maxRoundsPerReviewer": 3 },
82
92
  "merge": { "timeoutMs": 1800000 },
83
93
  "worktrees": { "root": "D:/tower-worktrees" }
84
94
  }
@@ -100,3 +110,74 @@ Statuses are `open → triaged → fixed` / `wontfix`, and `fixed`/`wontfix` can
100
110
  tower or a worker may change a finding; reviewers can list and read.
101
111
 
102
112
  A complete copy-paste example: [examples/first-mission.md](../examples/first-mission.md).
113
+
114
+ ## Component rows, runtime preconditions and message language
115
+
116
+ The optional component rows are the only switches in this bundle (#121, #136). Three runtime rules
117
+ follow from that, plus the language policy for everything a tool prints.
118
+
119
+ ### A row is the only switch
120
+
121
+ `tests.enabled` was removed (#136). Discovery is on exactly while the `tower-tests` row is mounted:
122
+ with the row off there is no `tower_test` tool and no test receipt to gate on, so a second boolean
123
+ could only disagree with the mounted profile. A repository config that still writes
124
+ `tests.enabled` is rejected as `config-unknown-key`; delete the key and enable or disable the row
125
+ instead.
126
+
127
+ The same reasoning covers the other rows:
128
+
129
+ - `tower-leases` owns `tower_resource`. `spawn.requireLease` is satisfied through that tool, so a
130
+ config that requires a lease while the row is off is refused by the runtime preconditions with
131
+ `config-invalid`, naming the row and both remedies ("re-enable the tower-leases component or clear
132
+ spawn.requireLease") instead of failing every spawn at `lease-busy` with advice to use a tool that
133
+ is not registered (#131).
134
+ - `tower-tests` must stay mounted while `gates.requireTests` is true. Planning and mode entry refuse
135
+ the combination; the merge gate's `tests-missing` blocker also names the disabled row as the cause
136
+ (#140).
137
+ - `tower-hmr` ships off; `/tower reload` reports it by row name.
138
+
139
+ No runtime path may claim a repository is "not configured" when a component row is the reason.
140
+ Runtime refusals report the cause as `disabled by component <id>` and come with the fix (`enable the
141
+ <id> row in the plugin page and restart DSH`).
142
+
143
+ ### Preconditions run on mode entry, not on every `/tower` input (#142)
144
+
145
+ `/tower` re-validates the runtime preconditions (presets, component/config contradictions) only when
146
+ it **enters** Tower mode:
147
+
148
+ | Input | Preconditions |
149
+ |---|---|
150
+ | `/tower`, `/tower on`, `/tower <text>` while the mode is off | validated |
151
+ | `/tower off`, `/tower off <text>` | not validated |
152
+ | `/tower <text>` while the mode is already on | not validated |
153
+ | `/tower ack`, `/tower base <branch>`, `/tower reload` | not validated |
154
+
155
+ The reason: a row disabled after the session started (or a profile reloaded with a stricter config)
156
+ must not make `/tower off` — the command a human needs to leave the mode — or an ordinary forwarded
157
+ instruction fail. Capability guards still run where the work happens: `tower_spawn`, planning and
158
+ merge/preflight evaluate their own preconditions, so the checks cannot be bypassed by entering the
159
+ mode first.
160
+
161
+ ### Language of tool output
162
+
163
+ Machine-facing output is **English only**, regardless of the request locale:
164
+
165
+ - tool errors (`[tower:<code>] ...`), `nextAction`, blocker `detail` strings, `tower_status` /
166
+ `tower_doctor` detail lines, and completion reports;
167
+ - anything that a script, a log search or another agent is expected to pattern-match.
168
+
169
+ Localized, human-facing copy lives in `locale/*.json` (component descriptions, UI labels) and is the
170
+ only translated surface. Two rules keep the split honest:
171
+
172
+ 1. New detail/error strings are written in English even when the surrounding UI is Chinese; they are
173
+ not added to `locale/*.json`.
174
+ 2. A localized label never shadows a machine-readable cause. The row id, the error code and the
175
+ English remedy phrase stay verbatim inside the message, so a grep for
176
+ `disabled by component tower-tests` works in every locale.
177
+
178
+ `tower_status` and `tower_doctor` follow this policy but are not textually identical: `tower_status`
179
+ returns the cause only — `components.disabled` is `[{id, reason}]` with `reason` set to
180
+ `disabled by component <id>` — while `tower_doctor` repeats that reason and adds the English remedy
181
+ (`enable the reported row in the plugin page and restart DSH` for the `components` check; a
182
+ capability-specific check names the row directly, e.g. `enable the tower-tests row and restart DSH`).
183
+ 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.
@@ -10,8 +10,8 @@ a better fit.
10
10
 
11
11
  If you only remember one sentence: **use Tower when the output is an integrated repository change that
12
12
  must land through a controlled merge; use the other patterns when the output is exploration, repeated
13
- attempts, or programmatic fan-out.** Enable `review.requireReview` when the change also needs an
14
- independent reviewer.
13
+ attempts, or programmatic fan-out.** Set the review policy to `required` when the change also needs
14
+ an independent reviewer; the default policy is `optional`.
15
15
 
16
16
  ## 1. The design philosophy
17
17
 
@@ -45,11 +45,13 @@ When review is enabled, a reviewer also inspects a specific branch tip and the g
45
45
  binding. Separation of duties is the point when review is on: the agent that wrote the code is not the
46
46
  agent that approves it, and neither is the agent that merges.
47
47
 
48
- Independent review is optional by default: `review.requireReview:true` enforces it (and then a complete
49
- mission must carry a clean, merge-approved review from a reviewer bound to the branch tip). Set the flag
50
- to `true` for sensitive repositories; `reviewPolicy:"none"` + a reason + `gates.allowReviewExemption:true`
51
- remains available as a per-mission exemption when the global gate is on. Reviews can be submitted and
52
- recorded either way.
48
+ The review policy is one tri-state control: `off`, `optional` (the default) or `required`. `required`
49
+ enforces independent review (a complete mission must carry a clean, merge-approved review from a
50
+ reviewer bound to the branch tip). Choose it for sensitive repositories; `reviewPolicy:"none"` + a
51
+ reason + `gates.allowReviewExemption:true` remains available as a per-mission exemption when the
52
+ policy is required. `optional` records reviews but never blocks a merge, and `off` is a supported
53
+ mode that removes the review tool. The policy is configuration, not a component row: there is one
54
+ control on the plugin page, not three mutually exclusive rows.
53
55
 
54
56
  ### 1.5 Evidence over assertion
55
57
 
@@ -184,7 +186,7 @@ integration, and what "done" means.**
184
186
  | Isolation | Git worktree + branch per mission; one writer per mission | Shared workspace; coordination by task ownership | Fresh child per round; no built-in branch/merge contract | Per-step child; no Git isolation | Child sessions; may share workspace |
185
187
  | Durable state | Git metadata + JSON state + activity + board | Team task board + sessions | Minimal; each round starts fresh | Run record/results; not a merge ledger | Session/subagent records |
186
188
  | Integration authority | Tower only, after preflight/gate (plus review when enabled) | Humans/teammates merge or edit manually | Usually the parent/worker applies results | Script applies results | Parent applies results |
187
- | Review / gate | Optional independent review (enable `review.requireReview`) + preflight/merge gate | Review is a collaboration convention, not a hard gate | No built-in review gate | No built-in review gate | No built-in review gate |
189
+ | Review / gate | Optional independent review (one `review.mode` control: off/optional/required) + preflight/merge gate | Review is a collaboration convention, not a hard gate | No built-in review gate | No built-in review gate | No built-in review gate |
188
190
  | Parallelism | Multiple missions, disjoint scopes, serialized merges | Many teammates, shared board | Sequential rounds (or external fan-out) | High fan-out, script-controlled | High fan-out |
189
191
  | Human role | Owns the mode; can inspect evidence and release | Collaborator/coordinator | Author of the repeated objective | Author of the orchestration script | Delegator |
190
192
  | Best for | Feature delivery, migrations, release-bound changes | Design, research, cross-cutting collaboration | Iterative search/repair with fresh context | Large audits, batch analysis, deterministic pipelines | Quick exploration or independent subtasks |
@@ -195,8 +197,8 @@ integration, and what "done" means.**
195
197
  Ordinary `subagent`/`subagent_fork` calls are cheap and flexible. They are the right tool for "read
196
198
  these five files in parallel" or "try three approaches". They do not provide a write scope, a
197
199
  dedicated worktree, a durable mission, independent review, or a merge gate. Tower wraps the same
198
- subagent primitive in those contracts; it also costs more per child. (Review is optional: enable
199
- `review.requireReview` when you need the reviewer role.)
200
+ subagent primitive in those contracts; it also costs more per child. (Review is optional: set the
201
+ policy to `required` when you need the reviewer role to gate a merge.)
200
202
 
201
203
  Choose subagents when the work is read-mostly, short-lived, or you will integrate the result
202
204
  yourself. Choose Tower when the result must land on a branch under a controlled merge; enable review
@@ -245,14 +247,19 @@ Tower entry clears an active plan (or refuses entry under `mode.planModePolicy:"
245
247
  Tower session cannot use the goal/todo/team/workflow tools. Plan first, then execute; do not expect
246
248
  Tower to discover the plan for you.
247
249
 
248
- ### 4.6 Review is optional by default
250
+ ### 4.6 The review policy is one tri-state control
249
251
 
250
- `review.requireReview:false` is the default, so a completed mission can merge without a reviewer for
251
- low-risk repositories, scratch work, or human-supervised sessions. Set it to `true` to enforce the
252
+ The review policy is one control — `off`, `optional` (the default) or `required` — in the plugin-page
253
+ `dsh-tower` configuration section, mirrored by `review.mode` in config. It is not a component row and
254
+ not three mutually exclusive rows. `optional` means a completed mission can merge without a reviewer
255
+ for low-risk repositories, scratch work, or human-supervised sessions. `required` enforces the
252
256
  independent review gate; the gate then re-checks `no-review`, `not-clean`, `review-hold`,
253
- `review-not-approved`, `tip-moved`, and `review-mission-mismatch`. `tower_review` is always available
254
- and records verdicts even when the gate is off. For one mission only, with the global gate on, set
255
- `reviewPolicy:"none"` + a reason and `gates.allowReviewExemption:true`.
257
+ `review-not-approved`, `tip-moved`, and `review-mission-mismatch`. `off` is a supported mode that
258
+ does not register `tower_review`; `optional` records verdicts but never blocks a merge. For one
259
+ mission only, with the policy `required`, set `reviewPolicy:"none"` + a reason and
260
+ `gates.allowReviewExemption:true`. The deprecated one-release boolean alias maps `true` → `required`
261
+ and `false` → `optional`; see
262
+ [install, upgrade and migration](install-upgrade-migration.md).
256
263
 
257
264
  ### 4.7 Concrete examples by workflow
258
265
 
@@ -309,8 +316,8 @@ A team wants "organization invitations": token model, email delivery, accept/dec
309
316
  `src/ui/invites/**`; make the API depend on tokens and email.
310
317
  2. Spawn workers for the independent missions; the API waits for its deps.
311
318
  3. Each worker commits on its branch, completes with checks.
312
- 4. With `review.requireReview:true`, spawn reviewers and require a clean+merge verdict; otherwise
313
- review is optional.
319
+ 4. With `review.mode:required`, spawn reviewers and require a clean+merge verdict; otherwise review
320
+ is optional or off.
314
321
  5. `tower_preflight` and `tower_merge` each mission; resolve scope/base/review blockers.
315
322
  6. `tower_teardown`; `MISSIONS.md` is the human-readable record.
316
323
 
@@ -344,9 +351,9 @@ Tower missions from those findings.
344
351
  ### Scenario E: sensitive release
345
352
 
346
353
  A library release with external consumers. Plan missions for the change, migration guide, and
347
- release notes; enable `review.requireReview:true` if the release requires independent review, require
348
- tests, and use `tower_batch` to close the release evidence only when all required checks are present;
349
- merge and tag outside Tower as your release process requires.
354
+ release notes; set `review.mode:required` if the release requires independent review, require tests,
355
+ and use `tower_batch` to close the release evidence only when all required checks are present; merge
356
+ and tag outside Tower as your release process requires.
350
357
 
351
358
  ## 6. Choosing in practice: a short decision path
352
359
 
@@ -354,9 +361,9 @@ merge and tag outside Tower as your release process requires.
354
361
  2. **Would you open a PR for it?** No → Tower is too heavy; use a normal agent.
355
362
  3. **Do the slices have stable scopes and a known definition of done?** No → plan/explore first; then
356
363
  return to Tower.
357
- 4. **Do you need a merge gate?** Repository changes with integration risk → Tower. Enable
358
- `review.requireReview:true` if you also need independent review. If neither isolation nor evidence
359
- is needed, plain subagents may be enough.
364
+ 4. **Do you need a merge gate?** Repository changes with integration risk → Tower. Set the review
365
+ policy to `required` if you also need independent review. If neither isolation nor evidence is
366
+ needed, plain subagents may be enough.
360
367
  5. **Do you need many parallel analyses or repeated fresh attempts?** Use Workflow/Ralph for that
361
368
  phase; encode the resulting changes as Tower missions.
362
369
  6. **Do you need cross-cutting human collaboration?** Use Agent Teams to decide; execute the decided
@@ -368,9 +375,9 @@ merge and tag outside Tower as your release process requires.
368
375
  round, gate, merge. Keep missions meaningful (roughly a PR-sized slice), not one task per function.
369
376
  - **Merges serialize**: overlapping scopes auto-add dependencies; independent missions still merge
370
377
  one at a time onto the base. Do not expect linear speedup for merge-heavy work.
371
- - **Review is not free when enabled**: each mission then needs a reviewer and a clean verdict.
372
- `review.requireReview:false` (the default) makes review optional; when the gate is on, exemptions
373
- are opt-in and require a reason.
378
+ - **Review is not free when required**: each mission then needs a reviewer and a clean verdict.
379
+ `review.mode:optional` (the default) makes review non-blocking, and `off` removes the review tool;
380
+ when the policy is `required`, exemptions are opt-in and require a reason.
374
381
  - **Too-fine missions** increase coordination and merge cost. **Too-coarse missions** lose the benefit
375
382
  of parallel review and make scope conflicts likely.
376
383
  - **Tower is not a sandbox**: it is a coordination protocol. Host permissions, repository hooks, and
@@ -385,7 +392,7 @@ merge and tag outside Tower as your release process requires.
385
392
  - [ ] The work produces a repository change that deserves a PR-style review.
386
393
  - [ ] I can name 2–8 missions with non-overlapping scopes and a rough dependency order.
387
394
  - [ ] Each mission has a testable definition of done (tasks, checks/tests, optional review).
388
- - [ ] If this change needs independent review, `review.requireReview` is on (default is off).
395
+ - [ ] If this change needs independent review, the review policy is `required` (default is `optional`).
389
396
  - [ ] I know which missions can run in parallel and which must wait.
390
397
  - [ ] I accept the merge-gate overhead (and review overhead if enabled) and will inspect
391
398
  `MISSIONS.md`/the branch.
@@ -4,15 +4,16 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
4
4
 
5
5
  ## Enforced by the plugin
6
6
 
7
- - **Review binding (when review is enabled).** `tower_review` records the branch tip it inspected;
8
- with `review.requireReview:true`, the gate rejects a moved tip (`tip-moved`) unless a clean
9
- conflict-free rebase waived it. A reviewer that modifies the branch after spawning is rejected
7
+ - **Review binding (when required).** `tower_review` records the branch tip it inspected; with
8
+ `review.mode:required`, the gate rejects a moved tip (`tip-moved`) unless a clean conflict-free
9
+ rebase waived it. A reviewer that modifies the branch after spawning is rejected
10
10
  (`reviewer must not modify the mission branch`).
11
- - **Review is optional by default, enforceable by config.** `review.requireReview:false` is the
12
- default; set it to `true` to enforce the independent-review gate. Mission-level
13
- `reviewPolicy:"none"` + reason is the narrower exemption when the global gate is on. With the gate
14
- off, separation of duties is not enforced by the plugin; use it only for trusted or human-supervised
15
- repositories.
11
+ - **Review is optional by default, enforceable by the tri-state policy.** `review.mode` is `off`,
12
+ `optional` (the default) or `required`. Set it to `required` to enforce the independent-review gate.
13
+ Mission-level `reviewPolicy:"none"` + reason is the narrower exemption when the policy is required.
14
+ With `off`, review is intentionally unavailable; with `optional`, separation of duties is not
15
+ enforced by the plugin, so use it only for trusted or human-supervised repositories. The policy is
16
+ one control on the plugin page, not a component row.
16
17
  - **Role enforcement.** The caller is resolved against the roster. Worker mission updates are limited to
17
18
  task/blocker/note; `completed`, `abandoned`, scope widening and blocker clearing are tower-only, and
18
19
  completion (with its unread-inbox/commit gates) must go through `tower_complete`. Tower-only tools reject
@@ -84,8 +85,8 @@ intent behind that entry. What it does provide:
84
85
  It does **not** defend against: a model that legitimately passes every gate while acting on malicious
85
86
  instructions; repository hooks or test commands the repository itself declares; or filesystem/shell
86
87
  access outside the worktree by a worker that ignores its instructions. For a higher-assurance team,
87
- set `mode.allowAgentControl:false`, keep `review.requireReview:true`, review the repository before
88
- Tower work, and run the host under OS-level isolation.
88
+ set `mode.allowAgentControl:false`, keep the review policy at `review.mode:required`, review the
89
+ repository before Tower work, and run the host under OS-level isolation.
89
90
 
90
91
  ## Not enforced by the plugin (deployment concerns)
91
92
 
@@ -4,6 +4,11 @@
4
4
  return the same shape for operation failures. Codes are stable; the detail is human-facing. The
5
5
  tables below define 32 stable blocker/failure codes.
6
6
 
7
+ The review blockers below (`no-review`, `review-mission-mismatch`, `not-clean`, `review-hold`,
8
+ `review-not-approved`, `tip-moved`, `review-exemption-invalid`) exist only when `review.mode` is
9
+ `required`. With `off` or `optional` no review blocker is produced; `off` additionally removes
10
+ `tower_review` and refuses reviewer spawn.
11
+
7
12
  ## Gate blockers (26)
8
13
 
9
14
  | Code | Meaning / next action |
@@ -19,12 +24,12 @@ tables below define 32 stable blocker/failure codes.
19
24
  | `out-of-scope` | Changed files outside the mission scope; widen scope deliberately or revert. On a case-sensitive checkout a case-only mismatch is still rejected and the detail names the scope entry it would match if case were folded. |
20
25
  | `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
21
26
  | `deps-unmerged` | A dependency mission is not satisfied: a build dependency must be `merged`/`abandoned`, while a survey dependency is satisfied at `completed` (archived entries included). |
22
- | `no-review` | `review.requireReview:true` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
23
- | `review-mission-mismatch` | `review.requireReview:true` and the latest review belongs to another mission. |
24
- | `not-clean` | `review.requireReview:true` and latest review is `p1-Nitems`/`p2-Nitems`; another round is required. Verdicts from later routes of the same round are appended to it instead of flipping the mission again. |
25
- | `review-hold` | `review.requireReview:true` and latest review recommends hold; a later clean review must supersede it. |
26
- | `review-not-approved` | `review.requireReview:true` and latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
27
- | `tip-moved` | `review.requireReview:true` and the branch changed after review; review again (or use a clean rebase waiver). |
27
+ | `no-review` | `review.mode:required` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
28
+ | `review-mission-mismatch` | `review.mode:required` and the latest review belongs to another mission. |
29
+ | `not-clean` | `review.mode:required` and latest review is `p1-Nitems`/`p2-Nitems`; another round is required. Verdicts from later routes of the same round are appended to it instead of flipping the mission again. |
30
+ | `review-hold` | `review.mode:required` and latest review recommends hold; a later clean review must supersede it. |
31
+ | `review-not-approved` | `review.mode:required` and latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
32
+ | `tip-moved` | `review.mode:required` and the branch changed after review; review again (or use a clean rebase waiver). |
28
33
  | `worker-busy` | A roster agent for this mission is running; wait or retire it. |
29
34
  | `worker-status-unknown` | The parent catalog is unavailable, so a live roster agent cannot be proven stopped; retry after the catalog recovers or adopt it. |
30
35
  | `base-wrong-branch` | The main checkout is not on the recorded base branch. |
@@ -32,7 +37,7 @@ tables below define 32 stable blocker/failure codes.
32
37
  | `worker-stale` | A roster worker for this mission has no catalog activity beyond `spawn.staleMs`; adopt it (or interrupt it only if it is actually running) before merge. |
33
38
  | `tests-missing` | `gates.requireTests` is enabled and there is no passing discovery receipt for the current branch tip; run `tower_test` or `tower_complete`. |
34
39
  | `tests-failed` | The latest discovery receipt failed or did not meet minCount/required checks. Fix the tests and re-run. |
35
- | `review-exemption-invalid` | `review.requireReview:true` and the mission requests `reviewPolicy:"none"` while `gates.allowReviewExemption` is off (or the reason is missing). |
40
+ | `review-exemption-invalid` | `review.mode:required` and the mission requests `reviewPolicy:"none"` while `gates.allowReviewExemption` is off (or the reason is missing). |
36
41
  | `merge-intent-pending` | A merge intent was recorded before a crash/failure; run `tower_merge` to reconcile before another gate. |
37
42
 
38
43
  ## Merge failures (4)