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.
- package/CHANGELOG.md +141 -0
- package/COMPATIBILITY.md +38 -0
- package/CONTRIBUTING.md +150 -2
- package/README.en.md +59 -16
- package/README.md +57 -16
- package/SECURITY.md +31 -1
- package/adapters/default.js +10 -0
- package/adapters/store-fs.js +6 -0
- package/adapters/vcs-git.js +4 -0
- package/app/batch.js +79 -0
- package/app/board.js +25 -0
- package/app/checkCompletion.js +76 -0
- package/app/enrichedMissions.js +26 -0
- package/app/findings.js +78 -0
- package/app/gate.js +108 -0
- package/app/init.js +249 -0
- package/app/merge.js +567 -0
- package/app/messages.js +66 -0
- package/app/mission.js +35 -0
- package/app/missionContext.js +14 -0
- package/app/plan.js +42 -0
- package/app/provision.js +193 -0
- package/app/read.js +57 -0
- package/app/rebase.js +302 -0
- package/app/recording.js +72 -0
- package/app/review.js +145 -0
- package/app/roster.js +123 -0
- package/app/spawn.js +125 -0
- package/app/teardown.js +184 -0
- package/app/tower.js +524 -0
- package/cordis.patch.yml +61 -53
- package/docs/README.md +33 -1
- package/docs/architecture/overview.md +114 -14
- package/docs/board.md +72 -0
- package/docs/examples/first-mission.md +2 -1
- package/docs/faq.md +11 -7
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/install-upgrade-migration.md +22 -6
- package/docs/guide/quickstart.md +12 -7
- package/docs/guide/release.md +125 -0
- package/docs/guide/scenarios-and-philosophy.md +86 -69
- package/docs/operations/security.md +65 -14
- package/docs/reference/blockers.md +11 -10
- package/docs/reference/config.generated.md +66 -0
- package/docs/reference/config.md +11 -7
- package/docs/reference/errors.generated.md +39 -0
- package/docs/reference/errors.md +10 -2
- package/docs/reference/events.md +7 -4
- package/docs/reference/testing.md +2 -1
- package/docs/reference/tools.generated.md +33 -0
- package/docs/reference/tools.md +8 -7
- package/docs/zh/README.md +4 -3
- package/docs/zh/faq.md +9 -6
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/guide/install-upgrade-migration.md +17 -5
- package/docs/zh/guide/quickstart.md +13 -8
- package/docs/zh/guide/scenarios-and-philosophy.md +55 -50
- package/docs/zh/operations/security.md +43 -7
- package/docs/zh/reference/blockers.md +10 -10
- package/docs/zh/reference/config.md +11 -7
- package/docs/zh/reference/errors.md +4 -1
- package/docs/zh/reference/events.md +6 -3
- package/docs/zh/reference/testing.md +3 -2
- package/docs/zh/reference/tools.md +8 -7
- package/domain/collab/batch.js +112 -0
- package/domain/collab/messages.js +127 -0
- package/domain/collab/roster.js +158 -0
- package/domain/collab/spawn.js +188 -0
- package/domain/config/schema.js +404 -0
- package/domain/contracts/outputs.js +279 -0
- package/domain/contracts/tool-inputs/collaboration.js +58 -0
- package/domain/contracts/tool-inputs/findings.js +29 -0
- package/domain/contracts/tool-inputs/index.js +94 -0
- package/domain/contracts/tool-inputs/integration.js +37 -0
- package/domain/contracts/tool-inputs/missions.js +94 -0
- package/domain/contracts/tool-inputs/ops.js +75 -0
- package/domain/contracts/tool-inputs/shared.js +25 -0
- package/domain/errors.js +96 -0
- package/domain/gate.js +125 -0
- package/domain/integration/gate.js +176 -0
- package/domain/integration/init.js +225 -0
- package/domain/integration/liveness.js +31 -0
- package/domain/integration/merge.js +377 -0
- package/domain/integration/provision.js +124 -0
- package/domain/integration/rebase.js +236 -0
- package/domain/integration/review.js +52 -0
- package/domain/integration/teardown.js +118 -0
- package/domain/mission.js +68 -0
- package/domain/missions/completion.js +114 -0
- package/domain/missions/findings.js +201 -0
- package/domain/missions/lifecycle.js +185 -0
- package/domain/missions/plan.js +149 -0
- package/domain/missions/review.js +179 -0
- package/domain/planning.js +118 -0
- package/domain/read/enrichedMissions.js +58 -0
- package/domain/read/missionContext.js +53 -0
- package/domain/read/views.js +128 -0
- package/domain/report.js +59 -0
- package/domain/roster.js +40 -0
- package/domain/scope.js +105 -0
- package/domain/state/schema.js +233 -0
- package/domain/state/semantics.js +117 -0
- package/domain/state.js +33 -0
- package/domain/summary.js +114 -0
- package/domain/vocabulary.js +179 -0
- package/index.js +86 -34
- package/lib/briefing.js +176 -0
- package/lib/compatibility.js +108 -0
- package/lib/config.js +54 -302
- package/lib/doctor.js +108 -9
- package/lib/git.js +93 -15
- package/lib/hmr.js +4 -3
- package/lib/leases.js +3 -2
- package/lib/legacy.js +20 -9
- package/lib/modeState.js +39 -0
- package/lib/modeStore.js +35 -33
- package/lib/persistence.js +349 -0
- package/lib/presets.js +35 -0
- package/lib/spawn-prechecks.js +168 -0
- package/lib/spawn-support.js +79 -0
- package/lib/store.js +57 -85
- package/lib/tests.js +46 -11
- package/lib/tool-registry.js +544 -0
- package/lib/workspaceLock.js +46 -22
- package/lib/worktreeOwnership.js +5 -4
- package/locale/en.json +23 -1
- package/locale/zh.json +22 -0
- package/package.json +33 -4
- package/ports/clock.js +6 -0
- package/ports/ids.js +7 -0
- package/ports/index.js +2 -0
- package/ports/store.js +11 -0
- package/ports/vcs.js +11 -0
- package/scripts/architecture.mjs +94 -0
- package/scripts/check-docs.mjs +44 -3
- package/scripts/check-pack.mjs +49 -0
- package/scripts/domain-metrics.mjs +48 -0
- package/scripts/generate-config-reference.mjs +46 -0
- package/scripts/generate-error-reference.mjs +29 -0
- package/scripts/generate-tools-reference.mjs +56 -0
- package/scripts/issue-audit.mjs +259 -0
- package/scripts/lib/cli.mjs +76 -0
- package/scripts/lib/tmp.mjs +20 -0
- package/scripts/mount-smoke/fixtures.mjs +162 -0
- package/scripts/mount-smoke/host.json +4 -0
- package/scripts/mount-smoke/probe.mjs +125 -0
- package/scripts/mount-smoke/surface.mjs +141 -0
- package/scripts/mount-smoke.mjs +270 -0
- package/scripts/pack-manifest.txt +203 -0
- package/scripts/release-check.mjs +303 -0
- package/skills/tower-recovery.md +7 -5
- package/tools/adopt.js +72 -0
- package/tools/batch.js +78 -0
- package/tools/complete.js +53 -0
- package/tools/diff.js +39 -0
- package/tools/doctor.js +50 -0
- package/tools/finding.js +40 -0
- package/tools/findings.js +39 -0
- package/tools/import-legacy.js +42 -0
- package/tools/inbox.js +39 -0
- package/tools/index.js +39 -0
- package/tools/init.js +63 -0
- package/tools/interrupt.js +42 -0
- package/tools/legacy-map.js +44 -0
- package/tools/merge.js +39 -0
- package/tools/mission.js +41 -0
- package/tools/plan.js +39 -0
- package/tools/preflight.js +44 -0
- package/tools/rebase.js +39 -0
- package/tools/recover.js +45 -0
- package/tools/reload.js +52 -0
- package/tools/resource.js +54 -0
- package/tools/review.js +45 -0
- package/tools/roster.js +60 -0
- package/tools/send.js +147 -0
- package/tools/spawn.js +196 -0
- package/tools/status.js +94 -0
- package/tools/teardown.js +42 -0
- package/tools/test.js +61 -0
- package/types/cordis.d.ts +6 -0
- package/types/index.d.ts +339 -0
- package/ui-bundle/README.md +111 -0
- package/ui-bundle/client.js +1980 -0
- package/ui-bundle/cordis.patch.yml +3 -0
- package/ui-bundle/icon.svg +6 -0
- package/ui-bundle/index.js +2 -0
- package/ui-bundle/locale/en.json +6 -0
- package/ui-bundle/locale/zh.json +6 -0
- package/ui-bundle/package.json +35 -0
- package/lib/errors.js +0 -111
- package/lib/tools.js +0 -2250
- package/lib/tower.js +0 -2689
- /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/
|
|
9
|
-
│
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├─
|
|
14
|
-
├─
|
|
15
|
-
├─
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
50
|
-
(unless a clean rebase waived it). No worker may be
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/docs/guide/hot-reload.md
CHANGED
|
@@ -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
|
|
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
|
-
- **
|
|
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
|
|
36
|
-
longer complete/abandon/scope a mission directly — use
|
|
37
|
-
and `tower_reload` when HMR is available. Restart DSH
|
|
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
|
package/docs/guide/quickstart.md
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
52
|
-
`ok:true`.
|
|
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` (
|
|
63
|
-
`timeoutMs`), `tip-moved` (
|
|
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.
|