dsh-tower 2.0.3 → 2.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/COMPATIBILITY.md +38 -0
  3. package/CONTRIBUTING.md +150 -2
  4. package/README.en.md +59 -16
  5. package/README.md +57 -16
  6. package/SECURITY.md +31 -1
  7. package/adapters/default.js +10 -0
  8. package/adapters/store-fs.js +6 -0
  9. package/adapters/vcs-git.js +4 -0
  10. package/app/batch.js +79 -0
  11. package/app/board.js +25 -0
  12. package/app/checkCompletion.js +76 -0
  13. package/app/enrichedMissions.js +26 -0
  14. package/app/findings.js +78 -0
  15. package/app/gate.js +108 -0
  16. package/app/init.js +249 -0
  17. package/app/merge.js +567 -0
  18. package/app/messages.js +66 -0
  19. package/app/mission.js +35 -0
  20. package/app/missionContext.js +14 -0
  21. package/app/plan.js +42 -0
  22. package/app/provision.js +193 -0
  23. package/app/read.js +57 -0
  24. package/app/rebase.js +302 -0
  25. package/app/recording.js +72 -0
  26. package/app/review.js +145 -0
  27. package/app/roster.js +123 -0
  28. package/app/spawn.js +125 -0
  29. package/app/teardown.js +184 -0
  30. package/app/tower.js +524 -0
  31. package/cordis.patch.yml +61 -53
  32. package/docs/README.md +33 -1
  33. package/docs/architecture/overview.md +114 -14
  34. package/docs/board.md +72 -0
  35. package/docs/examples/first-mission.md +2 -1
  36. package/docs/faq.md +11 -7
  37. package/docs/guide/hot-reload.md +1 -1
  38. package/docs/guide/install-upgrade-migration.md +22 -6
  39. package/docs/guide/quickstart.md +12 -7
  40. package/docs/guide/release.md +125 -0
  41. package/docs/guide/scenarios-and-philosophy.md +86 -69
  42. package/docs/operations/security.md +65 -14
  43. package/docs/reference/blockers.md +11 -10
  44. package/docs/reference/config.generated.md +66 -0
  45. package/docs/reference/config.md +11 -7
  46. package/docs/reference/errors.generated.md +39 -0
  47. package/docs/reference/errors.md +10 -2
  48. package/docs/reference/events.md +7 -4
  49. package/docs/reference/testing.md +2 -1
  50. package/docs/reference/tools.generated.md +33 -0
  51. package/docs/reference/tools.md +8 -7
  52. package/docs/zh/README.md +4 -3
  53. package/docs/zh/faq.md +9 -6
  54. package/docs/zh/guide/hot-reload.md +1 -1
  55. package/docs/zh/guide/install-upgrade-migration.md +17 -5
  56. package/docs/zh/guide/quickstart.md +13 -8
  57. package/docs/zh/guide/scenarios-and-philosophy.md +55 -50
  58. package/docs/zh/operations/security.md +43 -7
  59. package/docs/zh/reference/blockers.md +10 -10
  60. package/docs/zh/reference/config.md +11 -7
  61. package/docs/zh/reference/errors.md +4 -1
  62. package/docs/zh/reference/events.md +6 -3
  63. package/docs/zh/reference/testing.md +3 -2
  64. package/docs/zh/reference/tools.md +8 -7
  65. package/domain/collab/batch.js +112 -0
  66. package/domain/collab/messages.js +127 -0
  67. package/domain/collab/roster.js +158 -0
  68. package/domain/collab/spawn.js +188 -0
  69. package/domain/config/schema.js +404 -0
  70. package/domain/contracts/outputs.js +279 -0
  71. package/domain/contracts/tool-inputs/collaboration.js +58 -0
  72. package/domain/contracts/tool-inputs/findings.js +29 -0
  73. package/domain/contracts/tool-inputs/index.js +94 -0
  74. package/domain/contracts/tool-inputs/integration.js +37 -0
  75. package/domain/contracts/tool-inputs/missions.js +94 -0
  76. package/domain/contracts/tool-inputs/ops.js +75 -0
  77. package/domain/contracts/tool-inputs/shared.js +25 -0
  78. package/domain/errors.js +96 -0
  79. package/domain/gate.js +125 -0
  80. package/domain/integration/gate.js +176 -0
  81. package/domain/integration/init.js +225 -0
  82. package/domain/integration/liveness.js +31 -0
  83. package/domain/integration/merge.js +377 -0
  84. package/domain/integration/provision.js +124 -0
  85. package/domain/integration/rebase.js +236 -0
  86. package/domain/integration/review.js +52 -0
  87. package/domain/integration/teardown.js +118 -0
  88. package/domain/mission.js +68 -0
  89. package/domain/missions/completion.js +114 -0
  90. package/domain/missions/findings.js +201 -0
  91. package/domain/missions/lifecycle.js +185 -0
  92. package/domain/missions/plan.js +149 -0
  93. package/domain/missions/review.js +179 -0
  94. package/domain/planning.js +118 -0
  95. package/domain/read/enrichedMissions.js +58 -0
  96. package/domain/read/missionContext.js +53 -0
  97. package/domain/read/views.js +128 -0
  98. package/domain/report.js +59 -0
  99. package/domain/roster.js +40 -0
  100. package/domain/scope.js +105 -0
  101. package/domain/state/schema.js +233 -0
  102. package/domain/state/semantics.js +117 -0
  103. package/domain/state.js +33 -0
  104. package/domain/summary.js +114 -0
  105. package/domain/vocabulary.js +179 -0
  106. package/index.js +86 -34
  107. package/lib/briefing.js +176 -0
  108. package/lib/compatibility.js +108 -0
  109. package/lib/config.js +54 -302
  110. package/lib/doctor.js +108 -9
  111. package/lib/git.js +93 -15
  112. package/lib/hmr.js +4 -3
  113. package/lib/leases.js +3 -2
  114. package/lib/legacy.js +20 -9
  115. package/lib/modeState.js +39 -0
  116. package/lib/modeStore.js +35 -33
  117. package/lib/persistence.js +349 -0
  118. package/lib/presets.js +35 -0
  119. package/lib/spawn-prechecks.js +168 -0
  120. package/lib/spawn-support.js +79 -0
  121. package/lib/store.js +57 -85
  122. package/lib/tests.js +46 -11
  123. package/lib/tool-registry.js +544 -0
  124. package/lib/workspaceLock.js +46 -22
  125. package/lib/worktreeOwnership.js +5 -4
  126. package/locale/en.json +23 -1
  127. package/locale/zh.json +22 -0
  128. package/package.json +33 -4
  129. package/ports/clock.js +6 -0
  130. package/ports/ids.js +7 -0
  131. package/ports/index.js +2 -0
  132. package/ports/store.js +11 -0
  133. package/ports/vcs.js +11 -0
  134. package/scripts/architecture.mjs +94 -0
  135. package/scripts/check-docs.mjs +44 -3
  136. package/scripts/check-pack.mjs +49 -0
  137. package/scripts/domain-metrics.mjs +48 -0
  138. package/scripts/generate-config-reference.mjs +46 -0
  139. package/scripts/generate-error-reference.mjs +29 -0
  140. package/scripts/generate-tools-reference.mjs +56 -0
  141. package/scripts/issue-audit.mjs +259 -0
  142. package/scripts/lib/cli.mjs +76 -0
  143. package/scripts/lib/tmp.mjs +20 -0
  144. package/scripts/mount-smoke/fixtures.mjs +162 -0
  145. package/scripts/mount-smoke/host.json +4 -0
  146. package/scripts/mount-smoke/probe.mjs +125 -0
  147. package/scripts/mount-smoke/surface.mjs +141 -0
  148. package/scripts/mount-smoke.mjs +270 -0
  149. package/scripts/pack-manifest.txt +203 -0
  150. package/scripts/release-check.mjs +303 -0
  151. package/skills/tower-recovery.md +7 -5
  152. package/tools/adopt.js +72 -0
  153. package/tools/batch.js +78 -0
  154. package/tools/complete.js +53 -0
  155. package/tools/diff.js +39 -0
  156. package/tools/doctor.js +50 -0
  157. package/tools/finding.js +40 -0
  158. package/tools/findings.js +39 -0
  159. package/tools/import-legacy.js +42 -0
  160. package/tools/inbox.js +39 -0
  161. package/tools/index.js +39 -0
  162. package/tools/init.js +63 -0
  163. package/tools/interrupt.js +42 -0
  164. package/tools/legacy-map.js +44 -0
  165. package/tools/merge.js +39 -0
  166. package/tools/mission.js +41 -0
  167. package/tools/plan.js +39 -0
  168. package/tools/preflight.js +44 -0
  169. package/tools/rebase.js +39 -0
  170. package/tools/recover.js +45 -0
  171. package/tools/reload.js +52 -0
  172. package/tools/resource.js +54 -0
  173. package/tools/review.js +45 -0
  174. package/tools/roster.js +60 -0
  175. package/tools/send.js +147 -0
  176. package/tools/spawn.js +196 -0
  177. package/tools/status.js +94 -0
  178. package/tools/teardown.js +42 -0
  179. package/tools/test.js +61 -0
  180. package/types/cordis.d.ts +6 -0
  181. package/types/index.d.ts +339 -0
  182. package/ui-bundle/README.md +111 -0
  183. package/ui-bundle/client.js +1980 -0
  184. package/ui-bundle/cordis.patch.yml +3 -0
  185. package/ui-bundle/icon.svg +6 -0
  186. package/ui-bundle/index.js +2 -0
  187. package/ui-bundle/locale/en.json +6 -0
  188. package/ui-bundle/locale/zh.json +6 -0
  189. package/ui-bundle/package.json +35 -0
  190. package/lib/errors.js +0 -111
  191. package/lib/tools.js +0 -2250
  192. package/lib/tower.js +0 -2689
  193. /package/{lib → domain}/batch.js +0 -0
@@ -2,24 +2,61 @@
2
2
 
3
3
  ## Components
4
4
 
5
+ The bundle is all-or-nothing: `cordis.patch.yml` inserts one `tower-bundle` `cordis:group` row whose
6
+ internal sections are the Host plugin, the skills provider, and the worker/reviewer presets. The
7
+ group is the only user-facing component; disabling it disables every section together, so no preset
8
+ or skill can remain mounted while the `tower_*` tools are gone.
9
+
5
10
  ```
6
11
  Host plugin (index.js)
7
12
  ├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control, /tower reload)
8
- ├─ lib/tools.js -> 19 repository tools incl. tower_diff, tower_findings and tower_interrupt
9
- │ (+ tower_reload while dev.hotReload is on; fails reload-unavailable without HMR)
10
- └─ lib/tower.js -> Tower domain: missions, gates, worktrees, roster, review, findings, merge
11
- ├─ lib/git.js minimal git port (execFile, bounded output, timeout classification)
12
- ├─ lib/store.js repo-scoped lock + atomic JSON + activity.jsonl
13
- ├─ lib/config.js Config schema + profile/repo layers + runtime snapshot
14
- ├─ lib/errors.js stable tool error codes and classification
15
- ├─ lib/workspaceLock.js optional one-Tower-session-per-repository advisory lock
16
- ├─ lib/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
17
- └─ lib/modeStore.js per-agent Tower mode persistence
18
- Subagents
13
+ ├─ lib/tool-registry.js -> registration aggregator, actor guards, shared helpers
14
+ │ (+ 27 registered repository tools)
15
+ │ (+ tower_reload while dev.hotReload is on; fails reload-unavailable without HMR)
16
+ ├─ tools/index.js -> thin public wiring (re-exports the registry + briefings)
17
+ └─ app/tower.js -> orchestration surface; delegates to use-case modules
18
+ ├─ app/init.js, provision.js, merge.js, rebase.js, teardown.js
19
+ ├─ app/review.js, findings.js, batch.js, messages.js, roster.js
20
+ ├─ app/plan.js, mission.js, checkCompletion.js, spawn.js, recording.js
21
+ └─ app/gate.js, read.js, board.js, missionContext.js, enrichedMissions.js
22
+ ports (explicit contracts):
23
+ ports/store.js vcs.js clock.js ids.js
24
+ adapters (the only I/O):
25
+ adapters/store-fs.js the only module importing node:fs
26
+ adapters/vcs-git.js the only module importing node:child_process
27
+ adapters/default.js composition root: store, vcs, clock, ids
28
+ infrastructure (lib/):
29
+ lib/store.js repo-scoped lock + atomic JSON + activity.jsonl
30
+ lib/git.js bounded git commands + timeout classification
31
+ lib/config.js profile/repo config layers over the pure schema
32
+ lib/workspaceLock.js optional one-Tower-session-per-repository advisory lock
33
+ lib/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
34
+ lib/modeStore.js per-agent Tower mode persistence
35
+ pure domain modules (no fs/git):
36
+ domain/errors.js stable, structural tool error catalog
37
+ domain/batch.js batch admission create/close/summary validation
38
+ domain/config/schema.js Schemastery config schema, defaults, validation
39
+ domain/contracts/outputs.js tool output-schema catalog
40
+ domain/contracts/tool-inputs/ per-area TypeBox input schemas + ToolInputs map
41
+ domain/vocabulary.js canonical status/type/action enums + predicates
42
+ domain/mission.js statuses, transitions, pause causes
43
+ domain/gate.js completion and merge-gate snapshot decisions
44
+ domain/scope.js glob/scope validation and overlap
45
+ domain/summary.js mission/finding summaries
46
+ domain/planning.js mission numbering, dependency DAG, auto-deps
47
+ domain/state.js load-time state validation
48
+ domain/report.js board rendering
49
+ domain/roster.js roster predicates
50
+ tools layer:
51
+ lib/tool-registry.js registration aggregator + shared guards/helpers
52
+ tools/index.js thin public wiring (re-exports only)
53
+ tools/*.js one module per tool, handler + imported input schema
54
+ Subagents
19
55
  ├─ tower-worker (dsh-tower/worker) read/write/edit/pwsh + mission tools + findings
20
56
  └─ tower-reviewer (dsh-tower/reviewer) read/glob/grep + tower_diff + review/finding (read-only)
21
57
  ```
22
58
 
59
+
23
60
  The main checkout is the control-tower repo; each mission has a branch `tower/<id>-<slug>` and a
24
61
  worktree in `.dsh-tower-worktrees/<hash>/<missionId>`. The plugin never operates on the main checkout's
25
62
  working tree except merge and the read-only diff.
@@ -46,8 +83,9 @@ set by `tower_merge`/reconciliation.
46
83
  3. Survey missions must not change files; build missions must stay inside `scope`.
47
84
  4. The main checkout must be clean for the files this merge would overwrite and on the base branch.
48
85
  5. Dependency missions must be merged/abandoned.
49
- 6. A review must exist, belong to this mission, be clean, approve merge, and be bound to the tip
50
- (unless a clean rebase waived it). No worker may be running.
86
+ 6. If `review.requireReview:true` (default false), a review must exist, belong to this mission, be
87
+ clean, approve merge, and be bound to the tip (unless a clean rebase waived it). No worker may be
88
+ running.
51
89
 
52
90
  ## Merge
53
91
 
@@ -65,6 +103,68 @@ After the command, regardless of exit status, it inspects `HEAD`:
65
103
  |---|---|
66
104
  | `<git-common-dir>/dsh-tower/state.json` | missions, roster, reviews, findings, messages, reviews sequence |
67
105
  | `<git-common-dir>/dsh-tower/activity.jsonl` | append-only activity |
68
- | `<git-common-dir>/dsh-tower/state.lock` | single-writer lock (stale locks are reported, never stolen) |
106
+ | `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (dead-PID reclaimed, live lock reported) |
107
+ | `<git-common-dir>/dsh-tower/git.lock` | repo-scoped serialization for git mutations outside the state lock |
108
+ | `<git-common-dir>/dsh-tower/lock-holds.jsonl` | state/git lock-hold telemetry |
69
109
  | `<checkout parent>/.dsh-tower-worktrees/<hash>/<missionId>` | mission worktree |
70
110
  | `$DSH_HOME/tower-mode/modes.json` | Tower mode per agent/session |
111
+ | `$DSH_HOME/tower-mode/modes.lock` | mode-store single-writer lock |
112
+
113
+ `state.json` and `modes.json` both use an atomic write (temp + rename), token-checked lock release,
114
+ dead-PID reclamation, and bounded lock waits. `state.json` is the mission persistence contract;
115
+ `modes.json` is the per-agent mode contract; neither is written without its own lock.
116
+
117
+ ## Layering rules
118
+
119
+ `npm run architecture:check` (also in CI) enforces the rules as **errors**:
120
+
121
+ - `domain/**` must not import I/O or adapters (`domain-pure`).
122
+ - Only `adapters/vcs-git.js` may import `node:child_process`, and only
123
+ `adapters/store-fs.js` may import `node:fs` (`child-process-surface`, `fs-surface`).
124
+ The rule is enforced over the source roots — `lib/`, `app/`, `domain/`, `tools/`,
125
+ `adapters/`, `ports/` — and does not cover `scripts/**`, which holds maintenance
126
+ entry points (release check, mount smoke, docs generators) that may spawn git or npm.
127
+ - `tools/**` must stay thin and free of status/type literals (`tools-thin`, `tools-vocabulary`);
128
+ `tools/index.js` is the thin public wiring module and stays exempt from the thinness rule,
129
+ while registration/shared machinery lives in `lib/tool-registry.js`.
130
+ - Aggregate files have size ceilings; top-level mutable state is limited to explicit composition
131
+ roots (`aggregate-size`, `ambient-state`). The per-tool ceiling is 200 lines; only the
132
+ `tools/index.js` wiring module is exempt, and it is now far below the ceiling.
133
+
134
+ A fixture test seeds one violation per rule, so a rule that cannot fail is itself a test failure.
135
+
136
+ ## Mount smoke and host internals
137
+
138
+ `npm run smoke:mount` (#95) boots a real profile through the pinned host instead of mocking the loader.
139
+ `scripts/mount-smoke/probe.mjs` is the one file that imports the host's internals — `runProfile` from
140
+ `dsh/lib/profile-boot.js` and the layered env from `dsh-app-boot` — because those are the entry points
141
+ `dsh` itself calls to compose a profile; the probe uses them to boot without a model, browser, or
142
+ session. `scripts/mount-smoke/host.json` is the single host/pnpm pin (a unit test keeps its `dsh` field
143
+ equal to the newest `COMPATIBILITY.md` row), so a host bump can move those entry points and may require
144
+ probe adjustments in the same change.
145
+
146
+ ## #56 definition of done
147
+
148
+ All epic DoD items now have an executable gate:
149
+
150
+ - Pure domain share: **37.9%** (5,993 / 15,820 source lines), enforced by
151
+ `npm run domain:check` (threshold 35%).
152
+ - `domain/**` imports no fs, child_process, adapters, app, tools, or lib infrastructure;
153
+ `domain-pure` is a blocking architecture rule.
154
+ - Inside the source roots, only `adapters/vcs-git.js` imports `node:child_process`; only
155
+ `adapters/store-fs.js` imports `node:fs`; both are blocking rules with fixtures.
156
+ - `lib`, `app`, and `domain` files stay <= 800 lines; every `tools/**` file stays
157
+ 40-200 lines (one handler module per tool; `tools/index.js` is 40-line wiring).
158
+ - No `withLock` scope starts a process; the two-phase tests instrument every git call.
159
+ - `tools/**` has no status/type/enum literal; vocabulary is owned by
160
+ `domain/vocabulary.js`; the `tools-vocabulary` rule is blocking.
161
+ - `validateState` has a four-line body over `domain/state/schema.js` and
162
+ `domain/state/semantics.js`.
163
+ - `state.json` and `modes.json` share `lib/persistence.js` atomic write, token-owned
164
+ lock, dead-PID quarantine, and bounded-wait primitives.
165
+ - The in-memory lifecycle and adapter contracts run a full mission lifecycle with no
166
+ disk and no git.
167
+ - The seven fitness rules are errors, each with a seeded failing fixture.
168
+
169
+
170
+
package/docs/board.md ADDED
@@ -0,0 +1,72 @@
1
+ # Tracker board
2
+
3
+ The three questions an owner asks weekly, as commands that actually work. Run them from the
4
+ repository root (or add `--repo louifinn/dsh-tower`). Verified against the live tracker on
5
+ 2026-10-22; the counts below are evidence for the commands, not a promise they stay fixed.
6
+
7
+ Priority lives in the `P0`–`P3` labels and area in `area/*`; issue titles carry no `[P*]`
8
+ prefix any more. Every open issue has exactly one `P*` label and at least one `area/*` label.
9
+
10
+ ## Open P1
11
+
12
+ ```bash
13
+ gh issue list --state open --label P1
14
+ ```
15
+
16
+ Verified: **10** open on 2026-10-22 (#56, #69, #76, #77, #82, #94, #95, #96, #97, #98).
17
+
18
+ `--label` uses REST filters, so it does not wait for the search index after an edit. Do **not**
19
+ write `--label P0,P1`: `gh` treats that as one literal label name and returns nothing (verified).
20
+ For "P0 or P1" use `--search 'label:P0,P1'` — inside `--search`, the comma means OR, while
21
+ `label:P0 label:P1` (a space) means AND and returns only issues carrying both.
22
+
23
+ ## Unmilestoned P0/P1
24
+
25
+ ```bash
26
+ gh issue list --state open --search 'no:milestone label:P0,P1'
27
+ ```
28
+
29
+ Verified: **0** on 2026-10-22 — every open P0/P1 is in a milestone. A non-zero result means a
30
+ release-scoped issue has no home; put it in the next release milestone or demote it to P2/P3.
31
+
32
+ ## Closed this week by area
33
+
34
+ ```bash
35
+ # week starts Monday; compute it rather than pasting yesterday's date
36
+ # GNU date (Linux/CI); on macOS use `gdate` or work the date out by hand
37
+ MONDAY=$(date -d 'last monday' +%F)
38
+ gh issue list --state closed --search "closed:>=$MONDAY label:area/ci"
39
+ ```
40
+
41
+ ```powershell
42
+ $MONDAY = (Get-Date).Date.AddDays(-(([int](Get-Date).DayOfWeek+6)%7)).ToString('yyyy-MM-dd')
43
+ gh issue list --state closed --search "closed:>=$MONDAY label:area/ci"
44
+ ```
45
+
46
+ Swap `area/ci` for `area/state`, `area/tools`, `area/docs`, `area/release`, `area/process`,
47
+ `area/architecture`, or `area/scope`. Verified for `area/ci` with `closed:>=2026-10-19`:
48
+ **0**, because all **54** issues closed that week carry no `area/*` label at all. The query is
49
+ right; the history is unlabelled. Label a closure before you close it (or backfill) and the
50
+ by-area view starts working.
51
+
52
+ ## Release rule query
53
+
54
+ A release may not be tagged while its milestone holds an open P0/P1 unless the release notes
55
+ say why. Check the milestone that is about to ship (see the [release checklist](guide/release.md)):
56
+
57
+ ```bash
58
+ gh issue list --state open --search 'milestone:2.1.0-beta.1 label:P0,P1' --json number,title
59
+ ```
60
+
61
+ Verified: **8** on 2026-10-22 (#76, #77, #82, #94, #95, #96, #97, #98) — the delivery gate's own
62
+ tickets block the release until they land. That is the rule doing its job, not a false positive.
63
+
64
+ ## Milestones
65
+
66
+ ```bash
67
+ gh api 'repos/louifinn/dsh-tower/milestones?state=all&per_page=100' \
68
+ --jq '.[] | "\(.number)\t\(.title)\topen=\(.open_issues)\tclosed=\(.closed_issues)"'
69
+ ```
70
+
71
+ Only issues that actually block a release belong in a milestone; everything else stays
72
+ unmilestoned and is queried by priority.
@@ -23,7 +23,8 @@ 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
- Spawn the reviewer:
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:
27
28
 
28
29
  ```json
29
30
  {"repoRoot":"D:\\work\\demo","missionId":"M1","role":"reviewer"}
package/docs/faq.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # FAQ
2
2
 
3
3
  **When should I use Tower instead of Agent Teams, Ralph, Workflow, or ordinary subagents?**
4
- Use Tower when the output is a reviewed, integrated change to one Git repository. Use ordinary
4
+ Use Tower when the output is an integrated change to one Git repository (enable review when you also
5
+ need an independent verdict). Use ordinary
5
6
  subagents for read-only exploration or quick independent subtasks, Agent Teams for cross-cutting human
6
7
  collaboration, Ralph for repeated fresh attempts at one objective, and Workflow for programmatic
7
8
  high-fan-out analysis. The full decision guide, scenarios, and comparison table are in
@@ -9,19 +10,21 @@ high-fan-out analysis. The full decision guide, scenarios, and comparison table
9
10
 
10
11
  **Can I combine Tower with Team/Workflow/Ralph?**
11
12
  Not concurrently in the same session: Tower mode refuses their tools and clears plan mode. Compose by
12
- phase instead—use Workflow/Team to analyze and decide, then start Tower to execute and review the
13
- repository change. See the [full guide](guide/scenarios-and-philosophy.md#4-how-tower-differs-from-other-dsh-workflow-patterns).
13
+ phase instead—use Workflow/Team to analyze and decide, then start Tower to execute (and optionally
14
+ review) the repository change. See the [full guide](guide/scenarios-and-philosophy.md#4-how-tower-differs-from-other-dsh-workflow-patterns).
14
15
 
15
16
  **Why did `tower_preflight` fail with `no-review` right after the worker finished?**
16
- That is expected: a completed mission awaits review. Spawn a reviewer; after a clean review the gate
17
- turns green.
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.
18
20
 
19
21
  **Do I need to poll the worker?**
20
22
  No. The worker's final report is delivered to this session; `tower_roster` shows live activity. Polling
21
23
  wastes turns.
22
24
 
23
25
  **`/tower` says the mode is on but `tower_control` is missing.**
24
- `mode.allowAgentControl` is off. Set it to `true` in the profile patch and restart DSH.
26
+ `mode.allowAgentControl` is disabled. It defaults to `true`; if it was set to `false`, set it back to
27
+ `true` in the profile patch and restart DSH.
25
28
 
26
29
  **The merge hook takes minutes and `tower_merge` failed.**
27
30
  Retry with a larger `timeoutMs`: `tower_merge({ missionId, timeoutMs: 1800000 })`. The default is 15
@@ -39,7 +42,8 @@ instead of rebasing.
39
42
  **Can I require independent review?**
40
43
  Yes. Set `review.requireReview:true` in the profile or repository config to enable the global review
41
44
  gate (default is `false`, review optional). When enabled, mission-level `reviewPolicy:"none"` + a
42
- reason is the narrower exemption. `tower_review` works and records verdicts either way.
45
+ reason + `gates.allowReviewExemption:true` is the narrower exemption. `tower_review` works and records
46
+ verdicts either way.
43
47
 
44
48
  **Can a reviewer edit files?**
45
49
  No. Reviewers use the read-only `tower-reviewer` preset (no write/edit/pwsh); a branch changed after the
@@ -101,7 +101,7 @@ The bridge behind the tool follows the `dev` config group:
101
101
  |---|---|
102
102
  | `dev.hotReload` | Register `tower_reload`; `/tower reload` is disabled when off (default `true`). Without the HMR service the tool is still registered so it can return `reload-unavailable`. |
103
103
  | `dev.reloadDelayMs` | Delay before the scheduled reconciliation runs, in milliseconds (default `50`), so the tool/command result reaches the model before the plugin is replaced. |
104
- | `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`). An inactive required entry is reported as a reload failure even if it was already inactive. |
104
+ | `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower-bundle`, `tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`). An inactive required entry is reported as a reload failure even if it was already inactive. |
105
105
 
106
106
  A reload is **scheduled, not awaited**: `/tower reload` and `tower_reload` return immediately with
107
107
  `scheduled`, `delayMs`, `requiredIds` (and the current `lastError` when one is pending), then HMR
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Requirements
4
4
 
5
- - DSH `0.2.1-rc.2` or newer
5
+ - DSH `0.2.1-rc.2`; see [COMPATIBILITY.md](../../COMPATIBILITY.md) for the builds actually verified per release
6
6
  - Node.js 22+
7
7
  - Git (local worktrees; no network access is required)
8
8
 
@@ -29,13 +29,29 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
29
29
 
30
30
  ## Migration notes
31
31
 
32
- - **1.x → 2.0 (unreleased)**: no state migration is needed (`schemaVersion` stays 1; new fields are
32
+ - **2.0.4 → 2.1.0-beta.1**: no state/config migration. This is a **pre-release** published on the
33
+ `next` dist-tag, so `latest` still resolves to 2.0.4 — install it with
34
+ `dsh plugin --profile web add dsh-tower@next` (or the exact version). The runtime behavior is
35
+ unchanged; the release carries the delivery gate (mount smoke in CI, `npm run release:check`, the
36
+ evidence conventions and the tracker policy) rather than tool changes. These docs merge before the
37
+ human publish step: `next` only resolves to 2.1.0-beta.1 after the [release checklist](release.md)
38
+ runs `npm publish --tag next`, and `latest` stays on 2.0.4 until a stable release.
39
+ - **2.0.3 → 2.0.4**: no state/config migration. This is a security-hardening and integrity release
40
+ (authorization, merge SHA binding, dirty-worktree completion, stale-lock recovery, limited test/git
41
+ process environment, `tower_recover`, UI 0.2.2). Review `review.requireReview`, `tests.admission` and
42
+ worker tool permissions against your threat model after upgrading.
43
+ - **2.0.1 → 2.0.3**: no state migration. Independent review became **optional by default** in 2.0.3
44
+ (`review.requireReview:false`); set it to `true` to keep the old review-gated merges. 2.0.1 added
45
+ `tower_batch` `abandon`, accepted merged members in batch close, made doctor skip torn-down worktrees,
46
+ and fixed terminal-mission spawn/preflight edge cases. The optional `ui-bundle` 0.2.1 adds the
47
+ persistent Tower-mode chip and mode-aware denied-tool rows.
48
+ - **1.x → 2.0 (released)**: no state migration is needed (`schemaVersion` stays 1; new fields are
33
49
  optional). Behavior changes to review: `mode.denyTools` now also denies `workflow`, `ralph`, `subagent`,
34
50
  `subagent_fork`, `wait_agent` and `team_task_list` while Tower mode is active (override with an explicit
35
- list); `spawn.maxLiveWorkers` defaults to 4; `mode.workspaceLock` is off by default. Workers can no
36
- longer complete/abandon/scope a mission directly — use `tower_complete`. New tools: `tower_interrupt`,
37
- and `tower_reload` when HMR is available. Restart DSH once after upgrading; see
38
- [hot reload](hot-reload.md).
51
+ list); `spawn.maxLiveWorkers` defaults to 4; `mode.workspaceLock` is off in the code default and on in
52
+ the bundled patch. Workers can no longer complete/abandon/scope a mission directly — use
53
+ `tower_complete`. New tools: `tower_interrupt`, and `tower_reload` when HMR is available. Restart DSH
54
+ once after upgrading; see [hot reload](hot-reload.md).
39
55
  - **1.3 → 1.4**: no state migration is needed. New: repository-level config
40
56
  (`<git-common-dir>/dsh-tower/config.json`), stable `[tower:<code>]` tool errors, `tower_findings`,
41
57
  and `id`/`missionId` + snake/camel input aliases. Existing calls keep working; restart DSH to load
@@ -31,14 +31,17 @@ The call returns immediately with the child agent id. The worker works inside th
31
31
  commits, marks tasks done, and calls `tower_complete`; its final report is delivered back to this
32
32
  session. Use `tower_roster` to observe activity; do not poll in a loop.
33
33
 
34
- ## 4. Review
34
+ ## 4. Optional: review
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:
35
38
 
36
39
  ```
37
40
  tower_spawn repoRoot=D:\work\repo missionId=M1 role=reviewer
38
41
  ```
39
42
 
40
43
  Reviewers use the read-only `tower-reviewer` preset. They inspect `tower_diff` and submit
41
- `tower_review`. A `clean` + `merge` verdict is required to merge.
44
+ `tower_review`; with `review.requireReview:true`, a `clean` + `merge` verdict is required to merge.
42
45
 
43
46
  ## 5. Gate, merge, clean up
44
47
 
@@ -48,8 +51,9 @@ tower_merge repoRoot=D:\work\repo missionId=M1
48
51
  tower_teardown repoRoot=D:\work\repo
49
52
  ```
50
53
 
51
- `tower_preflight` before review is expected to report `no-review`; after a clean review it should be
52
- `ok:true`. `tower_merge` refuses while blockers remain. `tower_teardown` removes merged/abandoned
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
53
57
  worktrees and branches.
54
58
 
55
59
  ```
@@ -59,8 +63,9 @@ worktrees and branches.
59
63
  ## If something fails
60
64
 
61
65
  Read the blocker code and use [the blocker reference](../reference/blockers.md). The most common cases:
62
- `stale-base` (run `tower_rebase`), `no-review` (spawn a reviewer), `merge-hook-failed` (retry, or raise
63
- `timeoutMs`), `tip-moved` (the branch changed after review; review again).
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;
68
+ the branch changed after review, review again).
64
69
 
65
70
  Thrown tool failures carry a `[tower:<code>]` prefix; see [error codes](../reference/errors.md)
66
71
  (e.g. `preset-missing` means restart DSH after installing the bundle, `not-initialized` means run
@@ -73,7 +78,7 @@ JSON file at `<git-common-dir>/dsh-tower/config.json` (usually `.git/dsh-tower/c
73
78
 
74
79
  ```json
75
80
  {
76
- "review": { "maxRoundsPerReviewer": 3 },
81
+ "review": { "requireReview": true, "maxRoundsPerReviewer": 3 },
77
82
  "merge": { "timeoutMs": 1800000 },
78
83
  "worktrees": { "root": "D:/tower-worktrees" }
79
84
  }
@@ -0,0 +1,125 @@
1
+ # Release checklist
2
+
3
+ One page, six steps, in order. The only **human-only** step is `npm publish` (npm
4
+ credentials + 2FA); an agent can do everything else in this repository. A release is a claim —
5
+ `npm run release:check` is the mechanical half of it.
6
+
7
+ ## Milestone rule (before step 1)
8
+
9
+ A release may not be tagged while its milestone holds an open P0/P1, unless the release
10
+ notes say why it was accepted:
11
+
12
+ ```bash
13
+ gh issue list --state open --search 'milestone:2.1.0-beta.1 label:P0,P1' --json number,title
14
+ ```
15
+
16
+ Empty output → proceed. Non-empty → close the issues (with the two-line closure contract in
17
+ [CONTRIBUTING.md](../../CONTRIBUTING.md#the-two-line-closure-contract)), or name each accepted
18
+ issue and the reason in the release notes. The same query is saved in [the board](../board.md);
19
+ comma means OR inside `--search`, while `--label P0,P1` matches one literal label name and
20
+ returns nothing.
21
+
22
+ `npm run release:check` prints `git describe`, the previous tag, the commit count since that
23
+ tag and whether Unreleased is non-empty on every run. On a PR it warns; on a tag push it fails
24
+ when `package.json` did not move past the last tag, when the tag is not `v<version>`, or when
25
+ the CHANGELOG has no release content. CI runs it on every push and PR.
26
+
27
+ ## 1. Bump the version
28
+
29
+ Edit `package.json` (`"version": "2.1.0-beta.1"`). The previous tag is found from the pushed
30
+ tag's parent (`<tag>^`), so a tag push that repeats the old version fails.
31
+
32
+ ```bash
33
+ npm run release:check -- --mode tag --tag v2.1.0-beta.1
34
+ ```
35
+
36
+ ## 2. Write the changelog
37
+
38
+ Move the Unreleased entries under a version heading, so the GitHub Release body can be
39
+ extracted mechanically. Keep the `## 未发布(Unreleased)` heading (it may be empty).
40
+
41
+ ```markdown
42
+ ## 未发布(Unreleased)
43
+
44
+ ## 2.1.0-beta.1(2026-10-22)
45
+
46
+ - feat: ...
47
+ ```
48
+
49
+ The gate accepts a non-empty Unreleased section (bump first, rename later) **or** a non-empty
50
+ section whose heading names the version. Write the version section as user-facing notes.
51
+
52
+ ## 3. Add the COMPATIBILITY row
53
+
54
+ `COMPATIBILITY.md` keeps the columns `| dsh version | tag / commit | verified on | what was run |`.
55
+ Put the **host build in cell 0** and the **tag in cell 1**:
56
+
57
+ ```markdown
58
+ | 0.2.1-rc.2 | v2.1.0-beta.1 | 2026-10-22 | `npm test`, `npm run docs:check`, mount smoke |
59
+ ```
60
+
61
+ `lib/compatibility.js#parseCompatibility` reads cells 0–3 only: never insert a column before
62
+ the tag and never add one before "what was run", or `tower_doctor` stops seeing the row. The
63
+ host pin in `scripts/mount-smoke/host.json` must equal cell 0 of the newest row (a unit test
64
+ enforces it).
65
+
66
+ Commit steps 1–3 as the release PR against protected `main`, merge it, then continue from a
67
+ **clean clone/worktree at the merge commit** — not the integration worktree.
68
+
69
+ ## 4. Publish — human-only (2FA)
70
+
71
+ ```bash
72
+ git status --porcelain # must be empty: files:["docs/"] packs untracked docs too
73
+ npm ci
74
+ npm test && npm run docs:check && npm run pack:check
75
+ npm pack --dry-run # inspect the published file list
76
+ npm publish --tag next --otp <code>
77
+ npm view dsh-tower dist-tags --json
78
+ npm view dsh-tower@next version
79
+ ```
80
+
81
+ - Publish pre-releases with `--tag next`, **never** `latest`; `latest` moves only for a stable minor.
82
+ - Publish **before tagging**. If publishing fails, do not tag. If you already tagged, delete it
83
+ (`git tag -d <tag>` and `git push --delete origin <tag>`) instead of leaving a tag on an
84
+ unpublished commit.
85
+ - Both checks must name the version just published, and `next` must differ from `latest`.
86
+
87
+ ## 5. Annotated tag on the published commit
88
+
89
+ ```bash
90
+ npm run release:check -- --mode tag --tag v2.1.0-beta.1
91
+ git tag -a "v2.1.0-beta.1" -m "dsh-tower 2.1.0-beta.1"
92
+ git push origin "v2.1.0-beta.1"
93
+ git for-each-ref refs/tags/v2.1.0-beta.1 --format='%(objecttype)|%(subject)'
94
+ # tag|dsh-tower 2.1.0-beta.1 (a lightweight tag prints commit, and is not allowed)
95
+ ```
96
+
97
+ The pushed tag must be exactly `v<version>`. The tag push re-runs `release:check` against
98
+ `<tag>^`; this is why tagging a commit whose `package.json` did not move fails.
99
+
100
+ ## 6. GitHub Release from the CHANGELOG section
101
+
102
+ ```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
109
+ gh release view "v$VERSION" --json tagName,isPrerelease,body
110
+ ```
111
+
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
121
+ ```
122
+
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.