dsh-tower 2.1.0 → 2.4.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 +137 -0
- package/COMPATIBILITY.md +6 -1
- package/CONTRIBUTING.md +78 -5
- package/PRINCIPLES.md +163 -0
- package/README.en.md +35 -19
- package/README.md +28 -18
- 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/cordis.patch.yml +56 -28
- package/docs/README.md +7 -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 +70 -0
- package/docs/adr/0005-component-surface.md +130 -0
- package/docs/adr/README.md +52 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +192 -43
- package/docs/guide/hot-reload.md +5 -5
- package/docs/guide/install-upgrade-migration.md +13 -0
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/operations/security.md +1 -1
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/component-surface.md +135 -0
- package/docs/reference/config.generated.md +2 -9
- package/docs/reference/config.md +11 -9
- package/docs/reference/doctor.md +53 -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 +157 -0
- package/docs/reference/tools.generated.md +8 -5
- package/docs/reference/tools.md +10 -7
- package/docs/zh/README.md +1 -1
- package/docs/zh/guide/hot-reload.md +4 -4
- package/docs/zh/guide/install-upgrade-migration.md +10 -0
- package/docs/zh/operations/security.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/config.md +9 -9
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +10 -8
- package/domain/batch.js +2 -2
- package/domain/components.js +641 -0
- package/domain/config/schema.js +62 -28
- package/domain/contracts/outputs.js +16 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +308 -43
- 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/mission.js +12 -1
- 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 +347 -0
- package/host/capability-batch.js +15 -0
- package/host/capability-hmr.js +30 -0
- package/host/capability-import.js +11 -0
- package/host/capability-leases.js +11 -0
- package/host/capability-registry.js +149 -0
- package/host/capability-tests.js +16 -0
- package/{lib → host}/doctor.js +157 -18
- package/{lib → host}/hmr.js +2 -4
- package/{lib → host}/modeStore.js +1 -1
- package/host/presets.js +77 -0
- package/{lib → host}/tool-registry.js +235 -44
- package/host/tower.js +32 -0
- package/index.js +40 -25
- package/locale/en.json +31 -11
- package/locale/zh.json +30 -10
- package/package.json +24 -4
- 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-components.mjs +235 -0
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/component-surface.json +395 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-component-surface.mjs +59 -0
- 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 +18 -25
- package/scripts/generate-types.mjs +127 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/lib/tool-surface.mjs +85 -0
- package/scripts/mount-smoke/fixtures.mjs +3 -3
- package/scripts/mount-smoke/surface.mjs +96 -27
- package/scripts/mount-smoke.mjs +3 -1
- package/scripts/pack-manifest.txt +75 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +52 -0
- package/scripts/surface-baseline.mjs +118 -0
- package/scripts/types-coverage.mjs +184 -0
- package/scripts/types-strict-manifest.txt +10 -0
- package/scripts/types-strict.mjs +141 -0
- package/skills.js +8 -1
- 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 +9 -8
- 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 +36 -40
- 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 +11 -4
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +29 -13
- 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/presets.js +0 -35
- 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}/modeState.js +0 -0
|
@@ -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
|
|
|
@@ -104,7 +104,7 @@ Tower work, and run the host under OS-level isolation.
|
|
|
104
104
|
cannot make those safe; review hooks before running Tower on an untrusted repository.
|
|
105
105
|
- **Package test commands.** Declared discovery commands have the same trust as repository hooks; a
|
|
106
106
|
malicious `package.json` can run code. Disable `tests.allowPackageDiscovery` and review the file.
|
|
107
|
-
- **HMR reload.** With `
|
|
107
|
+
- **HMR reload.** With the `tower-hmr` component row on, `/tower reload`/`tower_reload` asks DSH to re-import the plugin
|
|
108
108
|
from disk. Only enable HMR watch roots for checkouts you trust; a reload executes the current files.
|
|
109
109
|
- **Prompt injection.** The reviewer reads untrusted branch content (diff/comments). The read-only preset
|
|
110
110
|
limits impact, but no model-side protection is absolute.
|
|
@@ -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). |
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Component surface (generated)
|
|
2
|
+
|
|
3
|
+
> Generated by `npm run docs:generate:components` from `scripts/component-surface.json` and
|
|
4
|
+
> `scripts/surface-baseline.json`. Do not edit by hand.
|
|
5
|
+
|
|
6
|
+
Decision: [ADR 0005](../adr/0005-component-surface.md) · decision date: 2026-10-23 · baseline 2026-10-23 · visible components: 8 (`tower`, `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-hmr`, `preset-tower-reviewer`)
|
|
7
|
+
|
|
8
|
+
The component axis answers one question for every config key: may a user turn it
|
|
9
|
+
off, and if so, must that switch be a component? Four classes cover every key, so
|
|
10
|
+
"should this be a component?" is a table lookup rather than a judgement call.
|
|
11
|
+
|
|
12
|
+
| Class | Meaning |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `core` | Removing it breaks the Tower contract. It is never a switch. |
|
|
15
|
+
| `optional-component` | A capability whose off state leaves the rest of Tower intact. It must be a user-visible switch with a documented fallback. |
|
|
16
|
+
| `optional-config` | A tuning knob, not a capability. It stays in the config form and never hides a component. |
|
|
17
|
+
| `forbidden-to-disable` | The host, the health contract, the recovery escape, the review contract and the merge gates. No group, row or boolean key may turn it off. |
|
|
18
|
+
|
|
19
|
+
## Components
|
|
20
|
+
|
|
21
|
+
The shipped `cordis.patch.yml` rows, with the fallback contract each off state
|
|
22
|
+
must satisfy. A visible row is a component the plugin page may switch; an internal
|
|
23
|
+
row is a section of its parent and is not independently switchable.
|
|
24
|
+
|
|
25
|
+
| Component | Scope | Kind | Off state and fallback |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `tower` | visible | group | Off equals uninstalling Tower: no tools, presets or skills remain. That is the accepted all-off boundary of the mandatory host anchor, not an optional capability. |
|
|
28
|
+
| `tower-host` | internal | row | The Host plugin itself, nested under the mandatory tower anchor. A mandatory dependency, never its own switch. |
|
|
29
|
+
| `tower-skills` | visible | row | The 3 skills leave the catalog; the worker/reviewer PROTOCOL sections still ship through systemPrompt.section, so only the tower-recovery guidance is lost. |
|
|
30
|
+
| `tower-batch` | visible | row | tower_batch is not registered; missions, gates and merges are unaffected because no state depends on a batch. |
|
|
31
|
+
| `tower-import` | visible | row | tower_import_legacy and tower_legacy_map are not registered; new repository state is unaffected. |
|
|
32
|
+
| `tower-leases` | visible | row | tower_resource is not registered; advisory leases are coordination only, so nothing else depends on them. |
|
|
33
|
+
| `tower-tests` | visible | row | tower_test is not registered and no test receipt can be recorded. With gates.requireTests:true planning/mode entry is rejected with an explanation, and the merge gate still reports tests-missing. |
|
|
34
|
+
| `tower-hmr` | visible | row | tower_reload and /tower reload are unavailable; a profile without HMR behaves exactly as before. Shipped disabled: enable the row for a linked dev profile. |
|
|
35
|
+
| `preset-tower-worker` | internal | row | tower_spawn fails without the worker preset. A mandatory dependency, never a switch. |
|
|
36
|
+
| `preset-tower-reviewer` | visible | row | The reviewer cannot be dispatched. Supported while review.requireReview is false (merge is unaffected); with the gate on planning/mode entry is locked with an explanatory error. |
|
|
37
|
+
|
|
38
|
+
## Forbidden to disable
|
|
39
|
+
|
|
40
|
+
The host, the health contract, the recovery escape, the review contract and the
|
|
41
|
+
merge gates are configuration or code, never switches. `npm run
|
|
42
|
+
docs:check:components` derives the live disablers from `host/tool-registry.js`
|
|
43
|
+
and `cordis.patch.yml`; a disabler that is not recorded here fails the gate.
|
|
44
|
+
|
|
45
|
+
| Capability | Why it may not be disabled | Recorded disablers | Decision |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| the Tower host | Without the host the plugin registers nothing; there is no off state that leaves the rest working, and no config boolean may remove it. The visible `tower` anchor is the all-off boundary, and the host section nested under it may never become its own switch. | none | none may exist |
|
|
48
|
+
| tower_doctor (read-only health contract) | Diagnosis must always exist so a failure message can point at it. #122 removed exposeDoctor and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
|
|
49
|
+
| tower_recover (stale state-lock escape) | The escape hatch must exist whenever the lock it repairs can be left behind. #122 removed exposeRecovery and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
|
|
50
|
+
| the review contract | review.requireReview is a merge blocker, not a component; a row may not switch it off. The reviewer row is conditional, not a way to weaken the gate. | none | none may exist |
|
|
51
|
+
| the merge gates | gates.* decides delivery; a component may not bypass it. | none | none may exist |
|
|
52
|
+
|
|
53
|
+
## Gateable tools
|
|
54
|
+
|
|
55
|
+
The allowlist of boolean routes in `host/tool-registry.js`. Every shipped
|
|
56
|
+
`tools.expose*` route must match this table. A route to a tool that is not listed
|
|
57
|
+
here is refused, so no boolean key can quietly remove a review, merge or host
|
|
58
|
+
tool. Since #122 the shipped table is empty: `tower_doctor` and `tower_recover`
|
|
59
|
+
are registered for every configuration and no other capability may become a
|
|
60
|
+
boolean switch.
|
|
61
|
+
|
|
62
|
+
| Tool | Switch key |
|
|
63
|
+
|---|---|
|
|
64
|
+
| _(none)_ | — |
|
|
65
|
+
|
|
66
|
+
## Config keys
|
|
67
|
+
|
|
68
|
+
All keys from `domain/config/schema.js`, classified on the component axis.
|
|
69
|
+
|
|
70
|
+
| Key | Component class | Decision note |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `section` | `core` | Legacy alias for mode.promptSection: the Tower-mode prompt contract, not a capability. |
|
|
73
|
+
| `allowAgentControl` | `core` | Legacy alias for mode.allowAgentControl: the human-owned mode rule (ADR 0003), not a component. |
|
|
74
|
+
| `init.autoGitInit` | `optional-config` | Bootstrap convenience; off only means tower_init will not run git init. |
|
|
75
|
+
| `init.snapshotDirty` | `optional-config` | Dirty-work baseline convenience; no capability is removed. |
|
|
76
|
+
| `init.autoExclude` | `optional-config` | Keeps Tower artifacts out of .git/info/exclude; safe either way. |
|
|
77
|
+
| `mode.promptSection` | `core` | Replaces the whole Tower-mode prompt text; contract text, not a capability. |
|
|
78
|
+
| `mode.sectionAppend` | `optional-config` | Appends team conventions to the prompt. |
|
|
79
|
+
| `mode.allowAgentControl` | `core` | Decides whether the model may toggle the mode (ADR 0003). A governance rule, never a component. |
|
|
80
|
+
| `mode.autoPauseGoal` | `core` | Entering the mode pauses an active goal; exit never resumes it. |
|
|
81
|
+
| `mode.requireResumeAuthorization` | `core` | Guards resuming a user-paused mission. |
|
|
82
|
+
| `mode.requireHumanAckOnFirstEntry` | `core` | First entry can require an explicit human ack. |
|
|
83
|
+
| `mode.workspaceLock` | `core` | One Tower session per repository is a safety contract; a row may not turn it off. |
|
|
84
|
+
| `mode.workspaceLockScope` | `optional-config` | Chooses whether read-only tools take the workspace lock. |
|
|
85
|
+
| `mode.planModePolicy` | `core` | Plan mode and Tower mode stay mutually exclusive. |
|
|
86
|
+
| `mode.workspaceStaleMs` | `optional-config` | Stale window for the advisory workspace lock. |
|
|
87
|
+
| `mode.heartbeatMs` | `optional-config` | Heartbeat interval while the mode is on. |
|
|
88
|
+
| `mode.denyTools` | `core` | The denied-tool list is part of the mode guard. |
|
|
89
|
+
| `mode.storePath` | `optional-config` | Custom mode-store location; no capability is removed. |
|
|
90
|
+
| `mode.maxEntries` | `optional-config` | Bounds the persisted mode store. |
|
|
91
|
+
| `mode.lockTimeoutMs` | `optional-config` | Bounded wait for the cross-process mode lock. |
|
|
92
|
+
| `review.requireReview` | `forbidden-to-disable` | Turns independent review into a merge blocker; no component may switch it off. With it on, the preset-tower-reviewer row may not be off (planning/mode entry refuse the combination). |
|
|
93
|
+
| `review.maxRoundsPerReviewer` | `forbidden-to-disable` | Caps review rounds; part of the review contract. |
|
|
94
|
+
| `merge.timeoutMs` | `optional-config` | Default merge timeout; a per-call value wins. |
|
|
95
|
+
| `merge.maxBufferBytes` | `optional-config` | Captured merge output buffer. |
|
|
96
|
+
| `git.timeoutMs` | `optional-config` | Default Git command timeout outside merge. |
|
|
97
|
+
| `worktrees.root` | `optional-config` | Default worktree placement. |
|
|
98
|
+
| `spawn.workerPreset` | `core` | Chooses the worker tool surface; a dependency, never a switch. |
|
|
99
|
+
| `spawn.reviewerPreset` | `core` | Chooses the read-only reviewer surface; a dependency, never a switch. |
|
|
100
|
+
| `spawn.maxDepth` | `optional-config` | Subagent depth requested at spawn. |
|
|
101
|
+
| `spawn.provider` | `optional-config` | Provider route for spawned agents. |
|
|
102
|
+
| `spawn.workerModel` | `optional-config` | Optional model route for workers. |
|
|
103
|
+
| `spawn.reviewerModel` | `optional-config` | Optional independent model route for reviewers. |
|
|
104
|
+
| `spawn.maxLiveWorkers` | `core` | Resource-safety cap on live workers. |
|
|
105
|
+
| `spawn.staleMs` | `optional-config` | Roster staleness window before merge is blocked. |
|
|
106
|
+
| `spawn.maxWaitMs` | `optional-config` | Upper bound for a blocking spawn wait. |
|
|
107
|
+
| `spawn.requireLease` | `optional-config` | Advisory-lease gate before a worker starts. |
|
|
108
|
+
| `send.maxPerMinute` | `optional-config` | Message rate limit; zero disables it. |
|
|
109
|
+
| `send.interruptDefault` | `optional-config` | Default for the interrupt-before-deliver flag. |
|
|
110
|
+
| `tests.enabled` | `optional-component` | Tuning inside the optional tower-tests component: it decides whether discovery runs when the row is on. Off with gates.requireTests:true is rejected at config resolution. |
|
|
111
|
+
| `tests.admission` | `optional-config` | Chooses when discovery runs inside the optional tests capability. |
|
|
112
|
+
| `tests.timeoutMs` | `optional-config` | Per-command discovery timeout. |
|
|
113
|
+
| `tests.maxBytes` | `optional-config` | Captured discovery output cap. |
|
|
114
|
+
| `tests.autoChecks` | `optional-config` | Attaches a tests check to completion when discovery runs. |
|
|
115
|
+
| `tests.allowPackageDiscovery` | `core` | Allows repository code to execute; a security contract, not a component. |
|
|
116
|
+
| `gates.requireChecks` | `forbidden-to-disable` | Requires completion evidence before merge; a merge gate. |
|
|
117
|
+
| `gates.requireTests` | `forbidden-to-disable` | Requires a test receipt before merge; a merge gate. With the tower-tests row off, planning/mode entry refuse the combination. |
|
|
118
|
+
| `gates.allowReviewExemption` | `forbidden-to-disable` | The review-exemption path is part of the review and merge contract. |
|
|
119
|
+
| `gates.maxReportBytes` | `forbidden-to-disable` | The report cap is part of the completion contract; the refusal itself is never configurable away. |
|
|
120
|
+
| `docs.board` | `optional-config` | Writes the human-readable board; no capability is removed. |
|
|
121
|
+
| `docs.boardLocation` | `optional-config` | Chooses the board location. |
|
|
122
|
+
| `archive.enabled` | `optional-config` | Archival is maintenance, not a tool capability; off only leaves terminal missions hot. |
|
|
123
|
+
| `archive.afterDays` | `optional-config` | Archive age threshold. |
|
|
124
|
+
| `archive.maxMissions` | `optional-config` | Hot terminal-mission cap. |
|
|
125
|
+
| `leases.ttlMs` | `optional-config` | Tuning inside the optional tower-leases component. |
|
|
126
|
+
| `dev.reloadDelayMs` | `optional-config` | Delay so a tool result is delivered before a scheduled reload inside the optional tower-hmr component. |
|
|
127
|
+
| `dev.reloadRequiredIds` | `optional-config` | Loader ids passed to reconcileProfile as required by the tower-hmr component. |
|
|
128
|
+
|
|
129
|
+
## Non-goals
|
|
130
|
+
|
|
131
|
+
This page does not decide whether a key is useful, and it is not an approval
|
|
132
|
+
process. It records the class so the gate can refuse an unclassified or
|
|
133
|
+
forbidden surface.
|
|
134
|
+
|
|
135
|
+
Enforced by: `scripts/check-components.mjs`; tests in `tests/components.test.mjs`; gate: `npm run docs:check:components`.
|
|
@@ -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: 56 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
|
|
4
5
|
|
|
5
6
|
| Key | Default | Description |
|
|
6
7
|
|---|---|---|
|
|
@@ -57,14 +58,6 @@
|
|
|
57
58
|
| `archive.enabled` | `true` | Move terminal (merged/abandoned) missions whose worktree is gone into <git-common-dir>/dsh-tower/archive/ at the next mutation, so state.json stops carrying their full history. |
|
|
58
59
|
| `archive.afterDays` | `30` | Terminal missions untouched for at least this many days are archived on the next mutation (0 archives them immediately). |
|
|
59
60
|
| `archive.maxMissions` | `100` | Maximum terminal missions kept hot; once exceeded, the oldest eligible terminal missions are archived until the cap holds (0 archives every eligible terminal mission). |
|
|
60
|
-
| `tools.exposeOperational` | `true` | Master switch for batch/resource/import/legacy/recover/test/doctor tools. |
|
|
61
|
-
| `tools.exposeBatch` | `true` | Register tower_batch. |
|
|
62
|
-
| `tools.exposeImport` | `true` | Register tower_import_legacy/tower_legacy_map. |
|
|
63
|
-
| `tools.exposeLeases` | `true` | Register tower_resource advisory leases. |
|
|
64
|
-
| `tools.exposeTests` | `true` | Register tower_test; gates.requireTests requires it. |
|
|
65
|
-
| `tools.exposeRecovery` | `true` | Register tower_recover (stale state-lock escape). |
|
|
66
|
-
| `tools.exposeDoctor` | `true` | Register tower_doctor (read-only health contract). |
|
|
67
61
|
| `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
|
|
68
|
-
| `dev.hotReload` | `true` | Register tower_reload and /tower reload when the DSH HMR service is available. |
|
|
69
62
|
| `dev.reloadDelayMs` | `50` | Delay before a scheduled reload, so the tool result is delivered first. |
|
|
70
|
-
| `dev.reloadRequiredIds` | `["tower
|
|
63
|
+
| `dev.reloadRequiredIds` | `["tower","tower-host","preset-tower-worker"]` | Loader entry ids passed to reconcileProfile as required. Optional component rows are deliberately not required, so a row switched off is not a reload failure. |
|
package/docs/reference/config.md
CHANGED
|
@@ -74,14 +74,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
|
|
|
74
74
|
| `spawn.requireLease` | `''` | Optional lease name that must be held before a worker can spawn. |
|
|
75
75
|
| `send.maxPerMinute` | `120` | Per-repository `tower_send` rate limit (0 disables); exceeding it returns `rate-limited`. |
|
|
76
76
|
| `send.interruptDefault` | `false` (bundled patch: `true`) | Default for `tower_send.interrupt` (interrupt the running turn before delivering). |
|
|
77
|
-
| `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. |
|
|
77
|
+
| `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. It only tunes the optional `tower-tests` component: with that row off there is no `tower_test` tool and no receipt, and `gates.requireTests:true` is refused rather than silently unmet. |
|
|
78
78
|
| `tests.admission` | `off` | `spawn` runs discovery before starting a worker, `complete` at `tower_complete`, `off` only on explicit `tower_test` (default; repository code is not executed automatically). |
|
|
79
79
|
| `tests.timeoutMs` | `60000` | Per discovery command timeout. |
|
|
80
80
|
| `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
|
|
81
81
|
| `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
|
|
82
82
|
| `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
|
|
83
83
|
| `gates.requireChecks` | `false` | Require at least one `tower_complete.checks` entry before merge (`no-check-evidence`). |
|
|
84
|
-
| `gates.requireTests` | `false` | Require a passing discovery receipt for the current mission branch tip (`tests-missing`); independent of the review gate. |
|
|
84
|
+
| `gates.requireTests` | `false` | Require a passing discovery receipt for the current mission branch tip (`tests-missing`); independent of the review gate. With the `tower-tests` component row off, planning/mode entry refuse this combination with an explanation. |
|
|
85
85
|
| `gates.allowReviewExemption` | `false` | With `review.requireReview:true`, allow `reviewPolicy:"none"` + a non-empty reason to skip review blockers. Off deliberately; irrelevant while the global review gate is off. |
|
|
86
86
|
| `gates.maxReportBytes` | `32768` (32 KiB) | Hard cap in UTF-8 bytes for `tower_complete.report`. A larger report is rejected with `invalid-argument` and an actionable `nextAction` (file the full text in the mission worktree and reference its path); it is never truncated. Existing larger reports still load. |
|
|
87
87
|
| `docs.board` | `true` | Write `MISSIONS.md` after mutations. |
|
|
@@ -89,16 +89,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
|
|
|
89
89
|
| `archive.enabled` | `true` | Move terminal (`merged`/`abandoned`) missions whose worktree is gone into `<git-common-dir>/dsh-tower/archive/` at a mutation boundary. Archived missions keep tasks, reviews, findings and the completion report; they are moved, never deleted. |
|
|
90
90
|
| `archive.afterDays` | `30` | Age trigger: a terminal mission untouched for at least this many days is archived on the next mutation (`0` archives immediately). |
|
|
91
91
|
| `archive.maxMissions` | `100` | Cap trigger: once more than this many terminal missions are hot, the oldest eligible ones are archived until the cap holds (`0` archives every eligible terminal mission). A mission with a live worktree is never archived. |
|
|
92
|
-
| `tools.exposeOperational` | `true` | Register batch/resource/import/legacy/recover/test/doctor tools; `false` shrinks the fixed tool-schema context. |
|
|
93
92
|
| `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
|
|
94
|
-
| `dev.
|
|
95
|
-
| `dev.
|
|
96
|
-
| `dev.reloadRequiredIds` | five Tower loader ids | Entry ids passed to `reconcileProfile` as required. |
|
|
93
|
+
| `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. Tuning inside the optional `tower-hmr` component. |
|
|
94
|
+
| `dev.reloadRequiredIds` | three Tower loader ids | Entry ids passed to `reconcileProfile` as required by the `tower-hmr` component. Optional component rows are deliberately not required. |
|
|
97
95
|
|
|
98
96
|
## Example
|
|
99
97
|
|
|
100
98
|
```yaml
|
|
101
|
-
- id: tower
|
|
99
|
+
- id: tower-host
|
|
102
100
|
name: 'dsh-tower'
|
|
103
101
|
config:
|
|
104
102
|
mode:
|
|
@@ -113,10 +111,14 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
|
|
|
113
111
|
reviewerModel:
|
|
114
112
|
provider: deepseek-account
|
|
115
113
|
model: deepseek-flash
|
|
116
|
-
dev:
|
|
117
|
-
hotReload: true
|
|
118
114
|
```
|
|
119
115
|
|
|
116
|
+
The optional capabilities (`tower-skills`, `tower-batch`, `tower-import`,
|
|
117
|
+
`tower-leases`, `tower-tests`, `tower-hmr`, `preset-tower-reviewer`) are switched
|
|
118
|
+
from the plugin page, not from a config key. The keys that used to switch them
|
|
119
|
+
(`tools.expose*`, `dev.hotReload`) were removed in 2.4.0 and are rejected as
|
|
120
|
+
unknown keys.
|
|
121
|
+
|
|
120
122
|
## Looking up the effective schema
|
|
121
123
|
|
|
122
124
|
`cordis_inspect_query` with `Config.listConfigs({ name: "dsh-tower" })` returns the projected JSON
|
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: 56 · sections: 14 · tools: 29 (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": 56,
|
|
18
|
+
"sections": 14,
|
|
19
|
+
"tools": 29,
|
|
20
|
+
"baselineDate": "2026-10-23",
|
|
21
|
+
"line": "config keys: 56 · sections: 14 · tools: 29"
|
|
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,19 @@ 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 every tool the build ships — `define()`-registered tools,
|
|
72
|
+
the `tower_mode`/`tower_control` pair the Host entry registers directly (#118),
|
|
73
|
+
and the capability tools whose optional component row may be off (#121) —
|
|
74
|
+
independent of the operational switches. The `tools` count is therefore the full
|
|
75
|
+
build surface (29 in 2.4.0), even though the shipped profile mounts 28 because
|
|
76
|
+
`tower-hmr` ships off; the `components` check names the mounted subset and every
|
|
77
|
+
disabled row. `npm run surface:check` is what fails when a count grew without an
|
|
78
|
+
ADR, a matching deletion or a recorded measurement correction. See
|
|
79
|
+
[surface-budget.md](surface-budget.md) for the classification table.
|
|
80
|
+
|
|
43
81
|
`stateSchema` describes the file **on disk**, not the in-memory document: `currentVersion` is the
|
|
44
82
|
`schemaVersion` doctor read, `supportedVersion` is the build's `SCHEMA_VERSION`, and `migrations` is
|
|
45
83
|
the ordered v→v path a write would apply (`[]` when current). `status` is one of `current`,
|
|
@@ -49,6 +87,19 @@ and keeps a `state.json.v<N>.bak` backup). A file written by a newer plugin repo
|
|
|
49
87
|
`fail`: this build refuses to downgrade it. A state-load failure is reported as a failed check rather
|
|
50
88
|
than thrown so doctor remains useful on a broken repository.
|
|
51
89
|
|
|
90
|
+
`stateLock` (and the `state-lock` check) reports the held lock's age (`ageMs`, from the record's
|
|
91
|
+
`since`) and heartbeat freshness (`heartbeatAgeMs`, from its last `at`) against the stale window
|
|
92
|
+
(`staleAfterMs = 3 × 1000 ms`). A live PID is not proof of a live writer: a holder that stopped
|
|
93
|
+
heartbeating is reported as `stale: true` and is reclaimable by the next mutation (or by
|
|
94
|
+
`tower_recover clear-stale-lock force:true`), while a fresh heartbeat is reported as held and is never
|
|
95
|
+
stolen. This is what makes a recycled PID diagnosable without reading `state.lock` by hand.
|
|
96
|
+
|
|
97
|
+
`gitLock` (and the `git-lock` check) reports the repo-scoped git mutex: `{held, pid, phase, heldMs}`
|
|
98
|
+
while a merge/rebase/init/teardown/provision holds it, and `{held: false}` when free. A merge may hold
|
|
99
|
+
it for the configured merge timeout (15 minutes by default), so a held lock is a `warn` rather than a
|
|
100
|
+
failure; the holder's phase and age make a stalled tower diagnosable. `tower_status` returns the same
|
|
101
|
+
`gitLock` object.
|
|
102
|
+
|
|
52
103
|
`archive` reports the terminal-mission archive: active `missions`, `archivedMissions` and `stateBytes`,
|
|
53
104
|
the archive directory's `archiveBytes`/`years`/`files`, every `unreadable` diagnostic, and the configured
|
|
54
105
|
`policy` (`enabled`, `afterDays`, `maxMissions`). A missing archive directory is a healthy empty archive
|
|
@@ -68,8 +119,8 @@ never probes — a probe creates a temporary file, and a read-only call must not
|
|
|
68
119
|
applies.
|
|
69
120
|
|
|
70
121
|
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
|
|
122
|
+
config presence, worker/reviewer preset names, subagents and HMR services, state lock age/heartbeat,
|
|
123
|
+
workspace lock holder/staleness, live roster, worktree existence/ownership/dirtiness, test-discovery policy, legacy
|
|
73
124
|
snapshots, batches and leases. Missions intentionally cleaned up by `tower_teardown` are skipped (the
|
|
74
125
|
check notes the removed count) instead of being reported as missing.
|
|
75
126
|
`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 | — |
|