dsh-tower 2.1.0 → 2.3.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.
- package/CHANGELOG.md +103 -0
- package/COMPATIBILITY.md +5 -1
- package/CONTRIBUTING.md +64 -5
- package/PRINCIPLES.md +145 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/SECURITY.md +6 -0
- package/{lib → adapters}/archive.js +1 -1
- package/{lib → adapters}/caseSensitivity.js +1 -1
- package/adapters/default.js +51 -10
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +1 -1
- package/{lib → adapters}/legacy.js +1 -1
- package/adapters/persistence.js +543 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +9 -5
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +1 -1
- package/app/archive.js +4 -6
- package/app/batch.js +9 -10
- package/app/board.js +2 -4
- package/app/checkCompletion.js +9 -10
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +1 -2
- package/app/init.js +24 -29
- package/app/merge.js +58 -51
- package/app/messages.js +7 -8
- package/app/mission.js +5 -5
- package/app/plan.js +8 -5
- package/app/provision.js +12 -14
- package/app/read.js +6 -3
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +15 -8
- package/app/roster.js +14 -15
- package/{lib → app}/spawn-prechecks.js +4 -3
- package/app/spawn.js +12 -13
- package/app/teardown.js +8 -9
- package/app/tower.js +89 -35
- package/docs/README.md +6 -2
- package/docs/adr/0001-ports-adapters-as-data.md +49 -0
- package/docs/adr/0002-single-state-json.md +50 -0
- package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
- package/docs/adr/0004-surface-budget.md +50 -0
- package/docs/adr/README.md +51 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +167 -36
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/config.generated.md +1 -0
- package/docs/reference/doctor.md +48 -2
- package/docs/reference/errors.generated.md +41 -37
- package/docs/reference/errors.md +53 -41
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +128 -0
- package/docs/reference/tools.generated.md +5 -4
- package/docs/reference/tools.md +4 -4
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +4 -4
- package/domain/batch.js +2 -2
- package/domain/config/schema.js +25 -1
- package/domain/contracts/outputs.js +6 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +297 -42
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +17 -2
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/missions/completion.js +14 -0
- package/domain/missions/lifecycle.js +9 -3
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/read/views.js +3 -0
- package/domain/state/archive.js +1 -1
- package/domain/state.js +1 -1
- package/domain/surface.js +248 -0
- package/{lib → host}/doctor.js +116 -9
- package/{lib → host}/modeStore.js +1 -1
- package/{lib → host}/tool-registry.js +106 -31
- package/host/tower.js +32 -0
- package/index.js +12 -9
- package/package.json +10 -3
- package/ports/archive.js +18 -0
- package/ports/caseSensitivity.js +15 -0
- package/ports/clock.js +10 -2
- package/ports/config.js +19 -0
- package/ports/contract.js +38 -0
- package/ports/events.js +24 -0
- package/ports/ids.js +10 -2
- package/ports/index.js +54 -2
- package/ports/ownership.js +17 -0
- package/ports/state.js +16 -0
- package/ports/store.js +52 -3
- package/ports/vcs.js +25 -4
- package/scripts/architecture-waivers.json +19 -0
- package/scripts/architecture.mjs +634 -23
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-config-reference.mjs +14 -1
- package/scripts/generate-error-reference.mjs +68 -14
- package/scripts/generate-events-reference.mjs +83 -0
- package/scripts/generate-tools-reference.mjs +11 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/pack-manifest.txt +58 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +21 -0
- package/scripts/surface-baseline.mjs +120 -0
- package/tools/adopt.js +2 -3
- package/tools/batch.js +2 -3
- package/tools/complete.js +2 -3
- package/tools/diff.js +3 -3
- package/tools/doctor.js +7 -10
- package/tools/finding.js +2 -3
- package/tools/findings.js +3 -3
- package/tools/import-legacy.js +3 -5
- package/tools/inbox.js +3 -3
- package/tools/index.js +7 -7
- package/tools/init.js +3 -6
- package/tools/interrupt.js +2 -3
- package/tools/legacy-map.js +3 -5
- package/tools/merge.js +3 -3
- package/tools/mission.js +2 -3
- package/tools/plan.js +6 -5
- package/tools/preflight.js +2 -3
- package/tools/rebase.js +3 -3
- package/tools/recover.js +38 -13
- package/tools/reload.js +3 -6
- package/tools/resource.js +3 -5
- package/tools/review.js +3 -4
- package/tools/roster.js +2 -3
- package/tools/send.js +2 -2
- package/tools/spawn.js +8 -5
- package/tools/status.js +3 -3
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +1 -1
- package/ui-bundle/README.md +37 -3
- package/ui-bundle/client.cards.js +356 -0
- package/ui-bundle/client.denied.js +142 -0
- package/ui-bundle/client.dock.js +333 -0
- package/ui-bundle/client.helpers.js +271 -0
- package/ui-bundle/client.js +79 -1747
- package/ui-bundle/client.locales.js +247 -0
- package/ui-bundle/client.mode.js +206 -0
- package/ui-bundle/client.overlay.js +64 -0
- package/ui-bundle/client.styles.js +151 -0
- package/ui-bundle/client.toolview.js +82 -0
- package/ui-bundle/package.json +1 -0
- package/lib/persistence.js +0 -356
- package/lib/store.js +0 -133
- /package/{lib → adapters}/stateFile.js +0 -0
- /package/{lib → app}/briefing.js +0 -0
- /package/{lib → app}/spawn-support.js +0 -0
- /package/{lib → host}/compatibility.js +0 -0
- /package/{lib → host}/config.js +0 -0
- /package/{lib → host}/hmr.js +0 -0
- /package/{lib → host}/modeState.js +0 -0
- /package/{lib → host}/presets.js +0 -0
|
@@ -10,7 +10,7 @@ or skill can remain mounted while the `tower_*` tools are gone.
|
|
|
10
10
|
```
|
|
11
11
|
Host plugin (index.js)
|
|
12
12
|
├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control, /tower reload)
|
|
13
|
-
├─
|
|
13
|
+
├─ host/tool-registry.js -> registration aggregator, actor guards, shared helpers
|
|
14
14
|
│ (+ 27 registered repository tools)
|
|
15
15
|
│ (+ tower_reload while dev.hotReload is on; fails reload-unavailable without HMR)
|
|
16
16
|
├─ tools/index.js -> thin public wiring (re-exports the registry + briefings)
|
|
@@ -18,20 +18,37 @@ Host plugin (index.js)
|
|
|
18
18
|
├─ app/init.js, provision.js, merge.js, rebase.js, teardown.js
|
|
19
19
|
├─ app/review.js, findings.js, batch.js, messages.js, roster.js
|
|
20
20
|
├─ app/plan.js, mission.js, checkCompletion.js, spawn.js, recording.js
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
├─ app/spawn-prechecks.js, spawn-support.js, briefing.js
|
|
22
|
+
└─ app/archive.js, gate.js, read.js, board.js, missionContext.js, enrichedMissions.js
|
|
23
|
+
host (composition root + process/session glue; the only layer that may hold ambient state):
|
|
24
|
+
host/tool-registry.js registration aggregator + shared guards/helpers
|
|
25
|
+
host/config.js profile/repo config layers over the pure schema
|
|
26
|
+
host/doctor.js read-only environment contract and health checks
|
|
27
|
+
host/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
|
|
28
|
+
host/modeStore.js per-agent Tower mode persistence
|
|
29
|
+
host/modeState.js owned in-memory mode state
|
|
30
|
+
host/presets.js cross-field preset validation before work starts
|
|
31
|
+
host/compatibility.js COMPATIBILITY.md provenance
|
|
32
|
+
host/tower.js default-deps Tower binding for tests/embedders
|
|
33
|
+
ports (explicit runtime contracts, no implementation):
|
|
34
|
+
ports/contract.js definePort/assertPort runtime contract primitives
|
|
35
|
+
ports/{store,vcs,clock,ids,config,state,ownership,caseSensitivity,archive}.js
|
|
36
|
+
ports/index.js PORTS table + assertPorts(deps) injection check
|
|
37
|
+
adapters (the only I/O; they implement the ports):
|
|
38
|
+
adapters/store-fs.js the only module importing node:fs; createStorePort()
|
|
39
|
+
adapters/vcs-git.js the only module importing node:child_process; createVcsPort()
|
|
40
|
+
adapters/git.js bounded git commands + timeout classification
|
|
41
|
+
adapters/store.js repo-scoped lock + atomic JSON + activity.jsonl
|
|
42
|
+
adapters/persistence.js shared atomic write / lock / heartbeat primitives
|
|
43
|
+
adapters/archive.js terminal-mission archive files
|
|
44
|
+
adapters/leases.js repo-scoped advisory lease ledger
|
|
45
|
+
adapters/workspaceLock.js optional one-Tower-session-per-repository lock
|
|
46
|
+
adapters/worktreeOwnership.js worktree identity verification
|
|
47
|
+
adapters/stateFile.js state-file load/migrate/persist
|
|
48
|
+
adapters/caseSensitivity.js filesystem case-folding probe
|
|
49
|
+
adapters/legacy.js read-only legacy snapshot validation + storage
|
|
50
|
+
adapters/tests.js test discovery execution + TAP/JSON parsing
|
|
51
|
+
adapters/default.js createDefaultDeps(): the one default port implementation set
|
|
35
52
|
pure domain modules (no fs/git):
|
|
36
53
|
domain/errors.js stable, structural tool error catalog
|
|
37
54
|
domain/batch.js batch admission create/close/summary validation
|
|
@@ -48,7 +65,6 @@ Host plugin (index.js)
|
|
|
48
65
|
domain/report.js board rendering
|
|
49
66
|
domain/roster.js roster predicates
|
|
50
67
|
tools layer:
|
|
51
|
-
lib/tool-registry.js registration aggregator + shared guards/helpers
|
|
52
68
|
tools/index.js thin public wiring (re-exports only)
|
|
53
69
|
tools/*.js one module per tool, handler + imported input schema
|
|
54
70
|
Subagents
|
|
@@ -61,6 +77,54 @@ The main checkout is the control-tower repo; each mission has a branch `tower/<i
|
|
|
61
77
|
worktree in `.dsh-tower-worktrees/<hash>/<missionId>`. The plugin never operates on the main checkout's
|
|
62
78
|
working tree except merge and the read-only diff.
|
|
63
79
|
|
|
80
|
+
## Ports and the composition root (#69)
|
|
81
|
+
|
|
82
|
+
The seam is real, not documented-only. `ports/` exports **runtime contracts**: each module defines a
|
|
83
|
+
frozen `{ name, methods }` descriptor plus an `assert*Port` helper, and `ports/index.js` exposes the
|
|
84
|
+
`PORTS` table and `assertPorts(deps)`.
|
|
85
|
+
|
|
86
|
+
`app/**` imports only app siblings, `domain/**`, `ports/**` and `node:path`. Every effectful call goes
|
|
87
|
+
through the `deps` object:
|
|
88
|
+
|
|
89
|
+
| deps member | contract | default implementation |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `store` | StorePort: `pathsFor/exists/ensureDir/readJson/writeJsonAtomic/appendJsonl/withLock/withGitLock/readActivityTail` plus the raw-file and lock primitives `readText/writeText/appendText/rename/renameWithRetry/stat/unlink/readGitLock/gitLockView` | `adapters/store-fs.js` `createStorePort()` |
|
|
92
|
+
| `vcs` | VcsPort: `git/tryGit/revParse/isClean/isAncestor/resolveRepo/currentBranch/countCommits/listWorktrees` | `adapters/vcs-git.js` `createVcsPort()` |
|
|
93
|
+
| `clock`, `ids` | ClockPort / IdsPort | `adapters/default.js` |
|
|
94
|
+
| `config` | ConfigPort: `runtimeConfig/resolveRepoConfig` | `host/config.js` |
|
|
95
|
+
| `state` | StatePort: `loadStateFile` | `adapters/stateFile.js` |
|
|
96
|
+
| `archive` | ArchivePort: `readIndex/readEntry/writeEntries` | `adapters/archive.js` |
|
|
97
|
+
| `ownership` | OwnershipPort: `assertOwnership/canonicalPath` | `adapters/worktreeOwnership.js` |
|
|
98
|
+
| `caseSensitivity` | CaseSensitivityPort: `detect` | `adapters/caseSensitivity.js` |
|
|
99
|
+
|
|
100
|
+
`index.js` is the composition root: it calls `createDefaultDeps()` once and passes the object to
|
|
101
|
+
`registerTowerTools`. The registry adopts and validates that object (`assertPorts`) instead of building
|
|
102
|
+
its own adapter set, hands every tool an `openTower(repoRoot)` factory bound to those ports, and
|
|
103
|
+
`Tower.open(repoRoot, deps)` / `new Tower(repo, config, deps)` validates the object again before use.
|
|
104
|
+
`app/tower.js` itself imports no adapter and no `host/` module. The one piece of lock policy the app and
|
|
105
|
+
the store share (the git-mutex budget, #93) is pure and lives in `domain/lockBudget.js`;
|
|
106
|
+
`adapters/store.js` re-exports it for the infrastructure callers.
|
|
107
|
+
|
|
108
|
+
`store.rename` and `store.renameWithRetry` are both declared because they are not interchangeable: the
|
|
109
|
+
retrying helper has temp-file semantics and removes its source when the rename finally fails, which is
|
|
110
|
+
right for a disposable temp file but would destroy the lock bytes `tower_recover` must keep auditable.
|
|
111
|
+
The audit rename therefore uses the plain `rename`. The contract test scans `app/**` **and** `tools/**`
|
|
112
|
+
for every `deps.<port>.<method>` call and fails if the named port does not declare it, so a tool that
|
|
113
|
+
reaches a new port method cannot slip past `assertPorts`.
|
|
114
|
+
|
|
115
|
+
`lib/` no longer exists (#70): every module was `git mv`-ed into the layer that owns it. The I/O
|
|
116
|
+
implementations now sit in `adapters/` (`store`, `persistence`, `git`, `archive`, `stateFile`,
|
|
117
|
+
`leases`, `workspaceLock`, `worktreeOwnership`, `caseSensitivity`, `legacy`, `tests`), the
|
|
118
|
+
composition root and process/session glue in `host/`, and spawn policy in `app/`. `host/**` is the
|
|
119
|
+
only source root, besides `adapters/**` itself, allowed to import an adapter; `app/**` and `domain/**`
|
|
120
|
+
may not import `lib/**` (rule `no-lib-from-core`) and may not hold ambient module state
|
|
121
|
+
(`host-scope`). Tools receive effectful helpers from the registry instead of importing an adapter.
|
|
122
|
+
|
|
123
|
+
A full mission lifecycle (init → plan → provision → spawn → complete → review → gate → merge →
|
|
124
|
+
teardown) runs through the real app use cases on an in-memory store and a scripted vcs:
|
|
125
|
+
`tests/ports-lifecycle.test.mjs`. The default adapter set and the in-memory doubles are both asserted
|
|
126
|
+
against the same port contracts in `tests/memory-adapters.contract.test.mjs`.
|
|
127
|
+
|
|
64
128
|
## Mission state machine
|
|
65
129
|
|
|
66
130
|
```
|
|
@@ -82,7 +146,7 @@ set by `tower_merge`/reconciliation.
|
|
|
82
146
|
against the merge base.
|
|
83
147
|
3. Survey missions must not change files; build missions must stay inside `scope`.
|
|
84
148
|
4. The main checkout must be clean for the files this merge would overwrite and on the base branch.
|
|
85
|
-
5. Dependency missions must be merged/abandoned.
|
|
149
|
+
5. Dependency missions must be merged/abandoned, except a completed survey dependency which is its terminal success state (#107).
|
|
86
150
|
6. If `review.requireReview:true` (default false), a review must exist, belong to this mission, be
|
|
87
151
|
clean, approve merge, and be bound to the tip (unless a clean rebase waived it). No worker may be
|
|
88
152
|
running.
|
|
@@ -105,7 +169,7 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
105
169
|
| `<git-common-dir>/dsh-tower/archive/index.json` | compact terminal-mission index (ids, status, branch, timestamps) |
|
|
106
170
|
| `<git-common-dir>/dsh-tower/archive/<year>.json` | full archived missions plus their linked reviews/findings |
|
|
107
171
|
| `<git-common-dir>/dsh-tower/activity.jsonl` | append-only activity |
|
|
108
|
-
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (
|
|
172
|
+
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (heartbeat reclaim; a recycled PID cannot pin it) |
|
|
109
173
|
| `<git-common-dir>/dsh-tower/git.lock` | repo-scoped serialization for git mutations outside the state lock |
|
|
110
174
|
| `<git-common-dir>/dsh-tower/lock-holds.jsonl` | state/git lock-hold telemetry |
|
|
111
175
|
| `<checkout parent>/.dsh-tower-worktrees/<hash>/<missionId>` | mission worktree |
|
|
@@ -113,8 +177,10 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
113
177
|
| `$DSH_HOME/tower-mode/modes.lock` | mode-store single-writer lock |
|
|
114
178
|
|
|
115
179
|
`state.json` and `modes.json` both use an atomic write (temp + rename), token-checked lock release,
|
|
116
|
-
|
|
117
|
-
|
|
180
|
+
heartbeat-based reclamation (a live PID with a silent heartbeat is reclaimed after byte verification;
|
|
181
|
+
a live holder that keeps heartbeating is never stolen), and bounded lock waits. `state.json` is the
|
|
182
|
+
mission persistence contract; `modes.json` is the per-agent mode contract; neither is written without
|
|
183
|
+
its own lock.
|
|
118
184
|
|
|
119
185
|
## Layering rules
|
|
120
186
|
|
|
@@ -123,17 +189,66 @@ dead-PID reclamation, and bounded lock waits. `state.json` is the mission persis
|
|
|
123
189
|
- `domain/**` must not import I/O or adapters (`domain-pure`).
|
|
124
190
|
- Only `adapters/vcs-git.js` may import `node:child_process`, and only
|
|
125
191
|
`adapters/store-fs.js` may import `node:fs` (`child-process-surface`, `fs-surface`).
|
|
126
|
-
The rule is enforced over the source roots — `
|
|
192
|
+
The rule is enforced over the source roots — `host/`, `app/`, `domain/`, `tools/`,
|
|
127
193
|
`adapters/`, `ports/` — and does not cover `scripts/**`, which holds maintenance
|
|
128
194
|
entry points (release check, mount smoke, docs generators) that may spawn git or npm.
|
|
195
|
+
The optional Web client (`ui-bundle/`) is its own tier with its own rules.
|
|
129
196
|
- `tools/**` must stay thin and free of status/type literals (`tools-thin`, `tools-vocabulary`);
|
|
130
|
-
`tools/index.js` is the thin public wiring module
|
|
131
|
-
while registration/shared machinery lives in `
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
197
|
+
`tools/index.js` is the thin public wiring module (it imports `host/tool-registry.js`, not a
|
|
198
|
+
`node:` module), while registration/shared machinery lives in `host/tool-registry.js`.
|
|
199
|
+
Tools receive effectful helpers (repository resolution, leases, recovery, legacy import) from
|
|
200
|
+
the registry instead of importing an adapter.
|
|
201
|
+
- Aggregate files have size ceilings; top-level mutable state is limited to `host/**`
|
|
202
|
+
(`aggregate-size`, `host-scope`). The per-tool ceiling is 200 lines; `tools/index.js`
|
|
203
|
+
currently sits inside the 40-200 range, so the tools allow-list is empty rather than silently
|
|
204
|
+
exempting the wiring module.
|
|
205
|
+
- `app/**` may import only app siblings, `domain/**`, `ports/**` and `node:path` (`app-ports-only`);
|
|
206
|
+
an app module that reaches an adapter, a `host/` module or `lib/` fails the gate.
|
|
207
|
+
- `app/**` and `domain/**` may not import the retired `lib/` layer (`no-lib-from-core`); with the
|
|
208
|
+
layer gone this keeps it from creeping back.
|
|
209
|
+
- `host/**` is the only source root that may hold ambient module state (`host-scope`);
|
|
210
|
+
`app/**` and `domain/**` must stay stateless at module scope. Only `host/**` and `adapters/**`
|
|
211
|
+
may import an adapter (`composition-root`); `app/`, `tools/`, `domain/` and `ports/` never may.
|
|
212
|
+
- Every `ports/**` module must export a runtime contract (`const`/`function`/`class`) and must not
|
|
213
|
+
import an implementation (`port-shape`); a typedef plus `export {}` is no longer accepted.
|
|
214
|
+
- `ui-bundle/**` is a browser tier, not host code (#71): no `node:*` import
|
|
215
|
+
(`client-no-node` — DOM globals are fine; host capabilities must come through the
|
|
216
|
+
plugin's registration surface), every client file stays at or below 800 lines
|
|
217
|
+
(`client-size`), and module-level mutable state is banned exactly as it is for
|
|
218
|
+
`app/**` (`client-ambient`); the split surfaces keep their state inside the
|
|
219
|
+
`create*` closure the entry composes. `ui-bundle/` is also excluded from
|
|
220
|
+
`aggregate-size`, which it would otherwise duplicate.
|
|
135
221
|
|
|
136
|
-
|
|
222
|
+
### Exemptions are justified data
|
|
223
|
+
|
|
224
|
+
The gate's only exemptions live in `scripts/architecture-waivers.json`; `scripts/architecture.mjs`
|
|
225
|
+
keeps no allow-lists of its own. Since #70 the file has exactly two entries, both I/O adapter
|
|
226
|
+
boundaries (`adapters/vcs-git.js` for `node:child_process` and `adapters/store-fs.js` for
|
|
227
|
+
`node:fs`). Each entry names the `rule` and `path` and must carry a non-empty `reason` plus an
|
|
228
|
+
`owner` or `ref`. An entry missing any of those is itself a blocking `waiver-contract` violation
|
|
229
|
+
that names the rule and the path, so a bare path cannot turn the gate green. The waiver file is a
|
|
230
|
+
two-key path in `CONTRIBUTING.md`; there is no `reviewBy`/expiry field in this release, so the
|
|
231
|
+
reason and the owner are the reviewed decision.
|
|
232
|
+
|
|
233
|
+
`host-scope` replaces the old `ambient-state` waivers (#70). It scans `app/**` and `domain/**` and
|
|
234
|
+
records every module-level `let`; `host/**` owns the process/session state and needs no waiver. The
|
|
235
|
+
scan is statement-aware rather than line-based: it walks the module's token stream and records every
|
|
236
|
+
binding of every top-level `let` declaration. A second declarator (`let a = 1, b = 2`), a second
|
|
237
|
+
statement on one line (`let a = 1; let b = 2`), and any new top-level `let` in `app/` or `domain/`
|
|
238
|
+
are each their own violation. Strings, comments, template literals and regex literals are skipped,
|
|
239
|
+
and `let` inside a function, block, class or `for (…)` header is not module-level state.
|
|
240
|
+
|
|
241
|
+
`domain-pure`, `host-scope`, `no-lib-from-core`, and anything under `domain/**` are load-bearing: no
|
|
242
|
+
waiver may target them, even a justified one. A `waiver-contract` violation rejects that entry and
|
|
243
|
+
the underlying rule still fails.
|
|
244
|
+
|
|
245
|
+
A green run prints `architecture: ok (waivers: N)`; N is the number of entries that actually
|
|
246
|
+
suppressed a violation, so a stale waiver is visible in the output and in the tests. The fixture
|
|
247
|
+
suite seeds an unjustified waiver (it fails), a justified one (it passes), a `host-scope` fixture
|
|
248
|
+
that puts ambient state in `host/` (allowed) and in `app/`/`domain/` (rejected), a `no-lib-from-core`
|
|
249
|
+
import from `app/` (rejected), a same-line second binding (rejected), and a new violation inside
|
|
250
|
+
each rule. A separate fixture seeds one violation per rule, so a rule that cannot fail is itself a
|
|
251
|
+
test failure.
|
|
137
252
|
|
|
138
253
|
## Mount smoke and host internals
|
|
139
254
|
|
|
@@ -149,24 +264,40 @@ probe adjustments in the same change.
|
|
|
149
264
|
|
|
150
265
|
All epic DoD items now have an executable gate:
|
|
151
266
|
|
|
152
|
-
- Pure domain share: **
|
|
153
|
-
`npm run domain:check` (threshold 35%).
|
|
154
|
-
|
|
267
|
+
- Pure domain share: **39.5%** (7,475 / 18,936 host source lines), enforced by
|
|
268
|
+
`npm run domain:check` (threshold 35%). `npm run domain:metrics` also prints a
|
|
269
|
+
separate `ui-bundle` line, so Web-client growth is visible in CI output while the
|
|
270
|
+
share itself still measures the Host architecture.
|
|
271
|
+
- `domain/**` imports no fs, child_process, adapters, app, tools, or host infrastructure;
|
|
155
272
|
`domain-pure` is a blocking architecture rule.
|
|
156
273
|
- Inside the source roots, only `adapters/vcs-git.js` imports `node:child_process`; only
|
|
157
|
-
`adapters/store-fs.js` imports `node:fs`; both are blocking rules
|
|
158
|
-
-
|
|
274
|
+
`adapters/store-fs.js` imports `node:fs`; both are blocking rules whose files are the only
|
|
275
|
+
`child-process-surface`/`fs-surface` entries in `scripts/architecture-waivers.json`.
|
|
276
|
+
- `host`, `app`, and `domain` files stay <= 800 lines; the same ceiling applies to
|
|
277
|
+
`ui-bundle/**` (`client-size`); every `tools/**` file stays
|
|
159
278
|
40-200 lines (one handler module per tool; `tools/index.js` is 40-line wiring).
|
|
160
279
|
- No `withLock` scope starts a process; the two-phase tests instrument every git call.
|
|
161
280
|
- `tools/**` has no status/type/enum literal; vocabulary is owned by
|
|
162
281
|
`domain/vocabulary.js`; the `tools-vocabulary` rule is blocking.
|
|
163
282
|
- `validateState` has a four-line body over `domain/state/schema.js` and
|
|
164
283
|
`domain/state/semantics.js`.
|
|
165
|
-
- `state.json` and `modes.json` share `
|
|
284
|
+
- `state.json` and `modes.json` share `adapters/persistence.js` atomic write, token-owned
|
|
166
285
|
lock, dead-PID quarantine, and bounded-wait primitives.
|
|
167
|
-
- The in-memory lifecycle
|
|
168
|
-
|
|
169
|
-
|
|
286
|
+
- The in-memory lifecycle runs the real app use cases (init → plan → provision → spawn → complete →
|
|
287
|
+
review → gate → merge → teardown) on an in-memory store and scripted vcs supplied through the
|
|
288
|
+
composition root, with no disk, no git and no `defaultDeps` (#69).
|
|
289
|
+
- The eleven host fitness rules are errors, each with a seeded failing fixture; the
|
|
290
|
+
three `client-*` rules form the Web-client tier with one fixture each, and the
|
|
291
|
+
`waiver-contract` rule makes an unjustified exemption blocking as well.
|
|
292
|
+
|
|
293
|
+
## Non-goals
|
|
294
|
+
|
|
295
|
+
This layer map is not a dependency-injection container, a service locator, or a
|
|
296
|
+
place to register classes by reflection. Ports stay runtime contracts and the
|
|
297
|
+
composition root stays the only place that assembles implementations.
|
|
298
|
+
|
|
299
|
+
Enforced by: `scripts/architecture.mjs` and `ports/index.js`; tests in
|
|
300
|
+
`tests/architecture.test.mjs`; gate: `npm run architecture:check`.
|
|
170
301
|
|
|
171
302
|
|
|
172
303
|
|
package/docs/guide/hot-reload.md
CHANGED
|
@@ -86,7 +86,7 @@ To stop watching, run `npm run dev:hmr -- --remove` and restart DSH once.
|
|
|
86
86
|
|
|
87
87
|
## The edit loop
|
|
88
88
|
|
|
89
|
-
1. Edit `
|
|
89
|
+
1. Edit `host/*.js`, `adapters/*.js`, `app/*.js`, `domain/**/*.js`, `index.js`, `worker.js`, `reviewer.js` or `skills.js`.
|
|
90
90
|
2. Save the file. The watcher debounces (DSH default 100 ms) and reconciles the profile; changed
|
|
91
91
|
modules are replaced and the affected Loader entries restart.
|
|
92
92
|
3. Watch the DSH log for `reload plugin at <path>` / `restart required: …` messages.
|
package/docs/guide/release.md
CHANGED
|
@@ -58,7 +58,7 @@ Put the **host build in cell 0** and the **tag in cell 1**:
|
|
|
58
58
|
| 0.2.1-rc.2 | v2.1.0-beta.1 | 2026-10-22 | `npm test`, `npm run docs:check`, mount smoke |
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
`
|
|
61
|
+
`host/compatibility.js#parseCompatibility` reads cells 0–3 only: never insert a column before
|
|
62
62
|
the tag and never add one before "what was run", or `tower_doctor` stops seeing the row. The
|
|
63
63
|
host pin in `scripts/mount-smoke/host.json` must equal cell 0 of the newest row (a unit test
|
|
64
64
|
enforces it).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Incidents and regressions
|
|
2
|
+
|
|
3
|
+
A **regression** is something that *worked in a released version, then stopped*.
|
|
4
|
+
It is not a feature request and not an ordinary bug: a user could rely on the old
|
|
5
|
+
behavior, so the record must say which release broke it, who was affected, and
|
|
6
|
+
which test now prevents it from happening again.
|
|
7
|
+
|
|
8
|
+
Only shipped regressions get this record. Ordinary bugs, missing features and
|
|
9
|
+
questions use the normal issue flow.
|
|
10
|
+
|
|
11
|
+
## The six-field record
|
|
12
|
+
|
|
13
|
+
Copy this into the issue body (or the closing comment) when a regression is
|
|
14
|
+
confirmed. All six lines are part of the closure; the `Regression test:` line is
|
|
15
|
+
the one a machine can check.
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Symptom: what the user sees, with the exact error text
|
|
19
|
+
Introduced in: version / commit range
|
|
20
|
+
Blast radius: who is affected (all sessions? restored ones? one platform?)
|
|
21
|
+
Root cause: one paragraph, mechanism not blame; include the fix
|
|
22
|
+
Regression test: path to the test that fails without the fix
|
|
23
|
+
Released in: version that carries the fix, and how it was verified
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The fields answer, in order: what broke, which release introduced it, the user
|
|
27
|
+
impact, the cause and the fix, the proof, and the release that carries the fix.
|
|
28
|
+
`Introduced in` and `Released in` are different answers; do not fill both with the
|
|
29
|
+
current version.
|
|
30
|
+
|
|
31
|
+
## Label
|
|
32
|
+
|
|
33
|
+
The `regression` label means exactly **"worked in a released version, then
|
|
34
|
+
stopped"**. It is mutually exclusive with `bug` for a shipped break: the regression
|
|
35
|
+
label is the narrower claim. The issue title still describes the symptom.
|
|
36
|
+
|
|
37
|
+
## Closing rule
|
|
38
|
+
|
|
39
|
+
A `regression` issue may not be closed while any of these is true:
|
|
40
|
+
|
|
41
|
+
- no `Regression test:` line names a path;
|
|
42
|
+
- the path does not exist in the repository;
|
|
43
|
+
- the path is not a test file (a script or a doc is not evidence).
|
|
44
|
+
|
|
45
|
+
The offline audit reads a local tracker export, so it needs no network and no
|
|
46
|
+
token:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
# 1) Export (network, once) — same export as the P0/P1 audit
|
|
50
|
+
New-Item -ItemType Directory -Force tracker | Out-Null
|
|
51
|
+
$all = foreach ($n in (gh issue list --repo louifinn/dsh-tower --state all --limit 500 --json number --jq '.[].number')) {
|
|
52
|
+
gh issue view $n --repo louifinn/dsh-tower --json number,title,state,labels,closedAt,body,comments | ConvertFrom-Json
|
|
53
|
+
}
|
|
54
|
+
@{ issues = @($all) } | ConvertTo-Json -Depth 8 | Set-Content tracker/issues.json -Encoding utf8
|
|
55
|
+
|
|
56
|
+
# 2) Audit (offline)
|
|
57
|
+
npm run audit:regressions
|
|
58
|
+
npm run audit:regressions -- --tracker path/to/export.json --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Exit codes: `0` — every closed regression names an existing test; `1` — at least
|
|
62
|
+
one is missing or points at a nonexistent/non-test path; `2` — the export is
|
|
63
|
+
missing or malformed.
|
|
64
|
+
|
|
65
|
+
The audit is a script because a checklist is only as reliable as the reader.
|
|
66
|
+
`tests/regression-audit.test.mjs` proves the check fails for a seeded missing path
|
|
67
|
+
and passes for a real one, so the rule cannot silently become prose.
|
|
68
|
+
|
|
69
|
+
## Where the practice starts
|
|
70
|
+
|
|
71
|
+
The first real entry is #68 (all tower write tools failed with `unknown` after a
|
|
72
|
+
session restore). The tower retro-fills that issue with these six fields; this
|
|
73
|
+
document is the template it used.
|
|
74
|
+
|
|
75
|
+
## Non-goals
|
|
76
|
+
|
|
77
|
+
No blameless-postmortem ceremony for ordinary bugs, no SLA commitments, no
|
|
78
|
+
five-whys, and no incident commander. Only shipped regressions get the six fields.
|
|
79
|
+
|
|
80
|
+
Unowned. This is a writing habit and a closing convention; no runtime code
|
|
81
|
+
enforces it. The one checkable part — the `Regression test:` path — is enforced by
|
|
82
|
+
`scripts/regression-audit.mjs`.
|
|
@@ -48,9 +48,18 @@ fails, the previous plugin code no longer runs: restart DSH and check the HMR lo
|
|
|
48
48
|
|
|
49
49
|
## State lock
|
|
50
50
|
|
|
51
|
-
`tower state is locked by another writer` means an operation holds `state.lock`. Wait and retry
|
|
52
|
-
the
|
|
53
|
-
|
|
51
|
+
`tower state is locked by another writer` means an operation holds `state.lock`. Wait and retry: a
|
|
52
|
+
live holder rewrites the record's `at` heartbeat every second, so it is never stolen while it runs.
|
|
53
|
+
A crashed holder stops heartbeating; once its last heartbeat is older than three intervals (3 s), the
|
|
54
|
+
next mutation reclaims the record after byte verification. A reclaimed live-PID record is kept as
|
|
55
|
+
`state.lock.reclaimed-<ts>-<id>` for audit; a provably dead PID (ESRCH) is reclaimed immediately.
|
|
56
|
+
|
|
57
|
+
If a lock never clears, `tower_recover({action:"status"})` reports the owner's `pid`, `startedAt`,
|
|
58
|
+
`ageMs` and `heartbeatAgeMs`, and `tower_doctor` reports the same. `tower_recover
|
|
59
|
+
clear-stale-lock` with `confirm:"CLEAR_STALE_LOCK"` moves a provably dead lock to
|
|
60
|
+
`state.lock.recovered-<ts>`; add `force: true` to clear a live-but-silent PID (the recycled-PID case)
|
|
61
|
+
after confirming no writer is active. The repair always renames, never deletes, so the bytes stay
|
|
62
|
+
auditable. Never delete `state.lock` by hand while an agent may still be running.
|
|
54
63
|
|
|
55
64
|
## Worktrees and teardown
|
|
56
65
|
|
|
@@ -18,10 +18,10 @@ tables below define 32 stable blocker/failure codes.
|
|
|
18
18
|
| `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
|
|
19
19
|
| `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
20
|
| `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
|
|
21
|
-
| `deps-unmerged` | A dependency mission is not merged
|
|
21
|
+
| `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
22
|
| `no-review` | `review.requireReview:true` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
|
|
23
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. |
|
|
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
25
|
| `review-hold` | `review.requireReview:true` and latest review recommends hold; a later clean review must supersede it. |
|
|
26
26
|
| `review-not-approved` | `review.requireReview:true` and latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
|
|
27
27
|
| `tip-moved` | `review.requireReview:true` and the branch changed after review; review again (or use a clean rebase waiver). |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Config reference (generated)
|
|
2
2
|
|
|
3
3
|
> Generated by `npm run docs:generate:config` from `domain/config/schema.js`. Values are defaults before repo config.
|
|
4
|
+
> Surface: config keys: 64 · sections: 15 · tools: 27 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
|
|
4
5
|
|
|
5
6
|
| Key | Default | Description |
|
|
6
7
|
|---|---|---|
|
package/docs/reference/doctor.md
CHANGED
|
@@ -8,10 +8,35 @@ It returns:
|
|
|
8
8
|
"ok": true,
|
|
9
9
|
"checks": [
|
|
10
10
|
{ "id": "git", "status": "pass", "detail": "git version 2.55.0" },
|
|
11
|
+
{ "id": "surface", "status": "pass", "detail": "config keys: 64 · sections: 15 · tools: 27 (baseline 2026-10-23)" },
|
|
11
12
|
{ "id": "state", "status": "pass", "detail": ".../state.json revision 7" },
|
|
12
13
|
{ "id": "state-schema", "status": "pass", "detail": "schemaVersion 3 is current" },
|
|
13
14
|
{ "id": "case-mode", "status": "pass", "detail": "caseInsensitive: true; scope matching uses case-insensitive comparison" }
|
|
14
15
|
],
|
|
16
|
+
"surface": {
|
|
17
|
+
"configKeys": 64,
|
|
18
|
+
"sections": 15,
|
|
19
|
+
"tools": 27,
|
|
20
|
+
"baselineDate": "2026-10-23",
|
|
21
|
+
"line": "config keys: 64 · sections: 15 · tools: 27"
|
|
22
|
+
},
|
|
23
|
+
"stateLock": {
|
|
24
|
+
"held": true,
|
|
25
|
+
"pid": 4242,
|
|
26
|
+
"startedAt": 1761280000000,
|
|
27
|
+
"at": 1761280120000,
|
|
28
|
+
"alive": true,
|
|
29
|
+
"ageMs": 121000,
|
|
30
|
+
"heartbeatAgeMs": 1000,
|
|
31
|
+
"staleAfterMs": 3000,
|
|
32
|
+
"stale": false
|
|
33
|
+
},
|
|
34
|
+
"gitLock": {
|
|
35
|
+
"held": true,
|
|
36
|
+
"pid": 4242,
|
|
37
|
+
"phase": "merge",
|
|
38
|
+
"heldMs": 92000
|
|
39
|
+
},
|
|
15
40
|
"stateSchema": {
|
|
16
41
|
"currentVersion": 3,
|
|
17
42
|
"supportedVersion": 3,
|
|
@@ -40,6 +65,14 @@ It returns:
|
|
|
40
65
|
}
|
|
41
66
|
```
|
|
42
67
|
|
|
68
|
+
`surface` (and the `surface` check) reports the concept budget in the same format
|
|
69
|
+
as the generated references: `config keys: N · sections: N · tools: N`, plus the
|
|
70
|
+
committed baseline date from `scripts/surface-baseline.json`. The counts are live
|
|
71
|
+
(the config schema and the defined tool surface, independent of the operational
|
|
72
|
+
switches); `npm run surface:check` is what fails when a count grew without an ADR
|
|
73
|
+
or a matching deletion. See
|
|
74
|
+
[surface-budget.md](surface-budget.md) for the classification table.
|
|
75
|
+
|
|
43
76
|
`stateSchema` describes the file **on disk**, not the in-memory document: `currentVersion` is the
|
|
44
77
|
`schemaVersion` doctor read, `supportedVersion` is the build's `SCHEMA_VERSION`, and `migrations` is
|
|
45
78
|
the ordered v→v path a write would apply (`[]` when current). `status` is one of `current`,
|
|
@@ -49,6 +82,19 @@ and keeps a `state.json.v<N>.bak` backup). A file written by a newer plugin repo
|
|
|
49
82
|
`fail`: this build refuses to downgrade it. A state-load failure is reported as a failed check rather
|
|
50
83
|
than thrown so doctor remains useful on a broken repository.
|
|
51
84
|
|
|
85
|
+
`stateLock` (and the `state-lock` check) reports the held lock's age (`ageMs`, from the record's
|
|
86
|
+
`since`) and heartbeat freshness (`heartbeatAgeMs`, from its last `at`) against the stale window
|
|
87
|
+
(`staleAfterMs = 3 × 1000 ms`). A live PID is not proof of a live writer: a holder that stopped
|
|
88
|
+
heartbeating is reported as `stale: true` and is reclaimable by the next mutation (or by
|
|
89
|
+
`tower_recover clear-stale-lock force:true`), while a fresh heartbeat is reported as held and is never
|
|
90
|
+
stolen. This is what makes a recycled PID diagnosable without reading `state.lock` by hand.
|
|
91
|
+
|
|
92
|
+
`gitLock` (and the `git-lock` check) reports the repo-scoped git mutex: `{held, pid, phase, heldMs}`
|
|
93
|
+
while a merge/rebase/init/teardown/provision holds it, and `{held: false}` when free. A merge may hold
|
|
94
|
+
it for the configured merge timeout (15 minutes by default), so a held lock is a `warn` rather than a
|
|
95
|
+
failure; the holder's phase and age make a stalled tower diagnosable. `tower_status` returns the same
|
|
96
|
+
`gitLock` object.
|
|
97
|
+
|
|
52
98
|
`archive` reports the terminal-mission archive: active `missions`, `archivedMissions` and `stateBytes`,
|
|
53
99
|
the archive directory's `archiveBytes`/`years`/`files`, every `unreadable` diagnostic, and the configured
|
|
54
100
|
`policy` (`enabled`, `afterDays`, `maxMissions`). A missing archive directory is a healthy empty archive
|
|
@@ -68,8 +114,8 @@ never probes — a probe creates a temporary file, and a read-only call must not
|
|
|
68
114
|
applies.
|
|
69
115
|
|
|
70
116
|
Checks cover: git availability, repository identity, state schema/load, the detected case mode, repo
|
|
71
|
-
config presence, worker/reviewer preset names, subagents and HMR services,
|
|
72
|
-
holder/staleness, live roster, worktree existence/ownership/dirtiness, test-discovery policy, legacy
|
|
117
|
+
config presence, worker/reviewer preset names, subagents and HMR services, state lock age/heartbeat,
|
|
118
|
+
workspace lock holder/staleness, live roster, worktree existence/ownership/dirtiness, test-discovery policy, legacy
|
|
73
119
|
snapshots, batches and leases. Missions intentionally cleaned up by `tower_teardown` are skipped (the
|
|
74
120
|
check notes the removed count) instead of being reported as missing.
|
|
75
121
|
`warn` is advisory; `fail` means the repository needs attention before spawning/merging.
|
|
@@ -2,40 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
> Generated by `npm run docs:generate:errors` from `domain/errors.js`. Do not edit by hand.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
11
|
-
| `
|
|
12
|
-
| `invalid
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
5
|
+
The catalog is the source of truth for the stable codes and the recovery metadata the tool boundary
|
|
6
|
+
renders: `retryable` marks a failure that can succeed later unchanged, and `nextAction` is the
|
|
7
|
+
single recovery step appended to the message as ` — next: …` (with ` (retryable)` when retryable).
|
|
8
|
+
|
|
9
|
+
| Code | Meaning | Retryable | Next action |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `invalid-argument` | A tool argument is missing, malformed, or not allowed. | no | — |
|
|
12
|
+
| `repo-invalid` | repoRoot is missing, relative, or not inside a Git repository. | no | pass an absolute path to a directory inside a Git repository |
|
|
13
|
+
| `not-initialized` | Tower is not initialized for this repository; run tower_init. | no | run tower_init for this repository |
|
|
14
|
+
| `mission-not-found` | No mission with that id exists. | no | list missions with tower_status |
|
|
15
|
+
| `finding-not-found` | No finding with that id exists. | no | list findings with tower_findings |
|
|
16
|
+
| `invalid-transition` | The requested state transition is not allowed. | no | — |
|
|
17
|
+
| `stale-base` | The repository state changed while the operation was being prepared; retry. | yes | retry the tool; if it keeps failing, run tower_preflight and inspect tower_status |
|
|
18
|
+
| `state-locked` | Another Tower writer holds the state lock. | yes | wait for the other Tower writer to finish, then retry |
|
|
19
|
+
| `state-corrupt` | The Tower state file is unreadable or invalid. | no | restore state.json from a backup, or re-initialize after filing the corruption |
|
|
20
|
+
| `state-version-unsupported` | The Tower state file uses a schema version this build cannot read or downgrade. | no | upgrade dsh-tower to a release that supports this state schemaVersion |
|
|
21
|
+
| `state-migration-failed` | A Tower state migration failed; the original state file was not modified. | no | restore the pre-migration backup state.json.v<N>.bak and report the migration bug |
|
|
22
|
+
| `config-invalid` | Tower configuration is invalid. | no | fix the repository config file, then retry |
|
|
23
|
+
| `config-unknown-key` | Tower configuration has an unknown top-level key. | no | remove the unknown key from the repository config file, then retry |
|
|
24
|
+
| `git-failed` | A git command failed. | no | — |
|
|
25
|
+
| `git-timeout` | A git command timed out or exceeded its output buffer. | yes | retry; if it persists, raise git.timeoutMs or merge.timeoutMs |
|
|
26
|
+
| `worktree-dirty` | The mission worktree has uncommitted changes and cannot complete. | no | commit or remove the worktree changes, then retry tower_complete |
|
|
27
|
+
| `spawn-unavailable` | The subagents service is unavailable. | no | enable the DSH subagents service, then retry |
|
|
28
|
+
| `provider-missing` | No subagent provider is registered. | no | register a subagent provider in the DSH profile, then retry |
|
|
29
|
+
| `provider-cooldown` | The provider is throttled; spawn is in a short cooldown. | yes | wait for the cooldown to expire, then retry |
|
|
30
|
+
| `preset-missing` | The configured agent preset is not registered. | no | install the tower-worker and tower-reviewer agent presets and restart DSH |
|
|
31
|
+
| `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | no | complete the mission first, then spawn the reviewer |
|
|
32
|
+
| `not-authorized` | The calling agent role may not perform this action. | no | call the tool as the tower, or as the worker/reviewer it is assigned to |
|
|
33
|
+
| `workspace-busy` | Another Tower session owns this repository workspace lock. | yes | run /tower off in the other session or wait for its lock to become stale |
|
|
34
|
+
| `worker-limit` | The repository reached spawn.maxLiveWorkers live workers. | no | wait for a live worker to finish or raise spawn.maxLiveWorkers |
|
|
35
|
+
| `rate-limited` | The tower_send per-minute rate limit was reached. | yes | wait for the next per-minute window or raise send.maxPerMinute |
|
|
36
|
+
| `takeover-refused` | The mission already has a live worker that was not stopped first. | no | run tower_interrupt for the live worker, then retry the takeover |
|
|
37
|
+
| `reload-unavailable` | The DSH HMR service is not available in this profile. | no | restart DSH instead of reloading |
|
|
38
|
+
| `reload-failed` | The HMR reload failed; check the DSH log and restart if needed. | no | check the DSH log and restart DSH |
|
|
39
|
+
| `tests-failed` | Declared test discovery failed or did not meet its admission requirements. | no | fix the failing tests, then retry tower_complete |
|
|
40
|
+
| `tests-timeout` | Test discovery exceeded its time or output budget. | no | raise tests.timeoutMs or tests.maxBytes (or speed up the suite), then retry |
|
|
41
|
+
| `lease-busy` | Another holder owns the requested advisory lease. | no | release the lease or wait for it to expire, then retry |
|
|
42
|
+
| `lease-token` | The lease release token does not match the current holder. | no | use the token returned when the lease was acquired |
|
|
43
|
+
| `batch-invalid` | The batch request or its close evidence is invalid. | no | fix the batch request or the receipt hashes, then retry |
|
|
44
|
+
| `legacy-invalid` | The legacy snapshot or mapping is invalid. | no | check the legacy snapshot path, schema and mapping, then retry |
|
|
45
|
+
| `tool-internal` | An unexpected failure; read the message and the activity log. | no | — |
|