dsh-tower 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/CONTRIBUTING.md +36 -0
  3. package/README.en.md +60 -23
  4. package/README.md +54 -21
  5. package/SECURITY.md +20 -0
  6. package/cordis.patch.yml +32 -0
  7. package/docs/README.md +17 -0
  8. package/docs/architecture/overview.md +70 -0
  9. package/docs/examples/first-mission.md +58 -0
  10. package/docs/faq.md +74 -0
  11. package/docs/guide/hot-reload.md +189 -0
  12. package/docs/guide/install-upgrade-migration.md +59 -0
  13. package/docs/guide/quickstart.md +97 -0
  14. package/docs/operations/runbook.md +80 -0
  15. package/docs/operations/security.md +62 -0
  16. package/docs/reference/blockers.md +58 -0
  17. package/docs/reference/config.md +114 -0
  18. package/docs/reference/doctor.md +31 -0
  19. package/docs/reference/errors.md +50 -0
  20. package/docs/reference/events.md +45 -0
  21. package/docs/reference/operations.md +52 -0
  22. package/docs/reference/testing.md +61 -0
  23. package/docs/reference/tools.md +75 -0
  24. package/docs/zh/README.md +58 -0
  25. package/docs/zh/faq.md +72 -0
  26. package/docs/zh/guide/hot-reload.md +178 -0
  27. package/docs/zh/guide/install-upgrade-migration.md +57 -0
  28. package/docs/zh/guide/quickstart.md +96 -0
  29. package/docs/zh/operations/security.md +42 -0
  30. package/docs/zh/reference/blockers.md +62 -0
  31. package/docs/zh/reference/config.md +113 -0
  32. package/docs/zh/reference/doctor.md +30 -0
  33. package/docs/zh/reference/errors.md +52 -0
  34. package/docs/zh/reference/events.md +46 -0
  35. package/docs/zh/reference/operations.md +48 -0
  36. package/docs/zh/reference/testing.md +60 -0
  37. package/docs/zh/reference/tools.md +75 -0
  38. package/index.js +293 -49
  39. package/lib/batch.js +578 -0
  40. package/lib/config.js +381 -0
  41. package/lib/doctor.js +165 -0
  42. package/lib/errors.js +111 -0
  43. package/lib/hmr.js +196 -0
  44. package/lib/leases.js +317 -0
  45. package/lib/legacy.js +413 -0
  46. package/lib/modeStore.js +47 -16
  47. package/lib/store.js +24 -1
  48. package/lib/tests.js +628 -0
  49. package/lib/tools.js +1594 -160
  50. package/lib/tower.js +1025 -101
  51. package/lib/workspaceLock.js +117 -0
  52. package/lib/worktreeOwnership.js +166 -0
  53. package/package.json +21 -4
  54. package/reviewer.js +40 -0
  55. package/scripts/check-docs.mjs +135 -0
  56. package/scripts/enable-dev-hmr.mjs +347 -0
  57. package/skills/tower-recovery.md +40 -0
  58. package/skills/tower-review.md +16 -0
  59. package/skills/tower-worker-protocol.md +15 -0
  60. package/skills.js +63 -0
  61. package/worker.js +7 -4
@@ -0,0 +1,58 @@
1
+ # Blockers and failure codes
2
+
3
+ `tower_preflight` returns `{ok:false, blockers:[{code, detail}]}`. `tower_merge` and `tower_rebase`
4
+ return the same shape for operation failures. Codes are stable; the detail is human-facing.
5
+
6
+ ## Gate blockers (26)
7
+
8
+ | Code | Meaning / next action |
9
+ |---|---|
10
+ | `mission-missing` | Unknown mission id. Check `tower_status`. |
11
+ | `mission-not-completed` | Mission is not `completed`; only a completed mission (awaiting review) merges. |
12
+ | `tasks-open` | Open tasks remain; the worker must finish or `task_drop` with a reason. |
13
+ | `blockers-present` | The mission has blockers; clear them after resolving. |
14
+ | `no-check-evidence` | `gates.requireChecks` is enabled and the completion report has no `checks`. Return the completed mission to `active`, re-run `tower_complete` with checks, then review again. |
15
+ | `branch-missing` | No worktree/branch yet: spawn a worker first. |
16
+ | `stale-base` | The branch does not contain the base tip: run `tower_rebase`. |
17
+ | `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
18
+ | `out-of-scope` | Changed files outside the mission scope; widen scope deliberately or revert. |
19
+ | `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
20
+ | `deps-unmerged` | A dependency mission is not merged/abandoned. |
21
+ | `no-review` | No review on the branch: spawn a reviewer. |
22
+ | `review-mission-mismatch` | The latest review belongs to another mission. |
23
+ | `not-clean` | Latest review is `p1-Nitems`/`p2-Nitems`; another round is required. |
24
+ | `review-hold` | Latest review recommends hold; a later clean review must supersede it. |
25
+ | `review-not-approved` | Latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
26
+ | `tip-moved` | The branch changed after review; review again (or use a clean rebase waiver). |
27
+ | `worker-busy` | A roster agent for this mission is running; wait or retire it. |
28
+ | `worker-status-unknown` | The parent catalog is unavailable, so a live roster agent cannot be proven stopped; retry after the catalog recovers or adopt it. |
29
+ | `base-wrong-branch` | The main checkout is not on the recorded base branch. |
30
+ | `branch-owned-by-closed-missions` | The mission is already merged/abandoned; re-plan under a new title. |
31
+ | `worker-stale` | A roster worker for this mission has no catalog activity beyond `spawn.staleMs`; adopt it (or interrupt it only if it is actually running) before merge. |
32
+ | `tests-missing` | `gates.requireTests` is enabled and there is no passing discovery receipt for the reviewed tip; run `tower_test` or `tower_complete`. |
33
+ | `tests-failed` | The latest discovery receipt failed or did not meet minCount/required checks. Fix the tests and re-run. |
34
+ | `review-exemption-invalid` | The mission requests `reviewPolicy:"none"` but `gates.allowReviewExemption` is off (or the reason is missing). |
35
+ | `merge-intent-pending` | A merge intent was recorded before a crash/failure; run `tower_merge` to reconcile before another gate. |
36
+
37
+ ## Merge failures (4)
38
+
39
+ | Code | Meaning / next action |
40
+ |---|---|
41
+ | `merge-conflict` | Real index conflict; the mission is `blocked`. Resolve, restore the mission to `active`/`completed`, retry. |
42
+ | `merge-hook-failed` | A hook/process failed before a commit existed; repository restored, mission stays `completed`. Retry; `timeout: true` means the git process timed out. Raise `timeoutMs` for slow hooks. |
43
+ | `merge-shape` | Git produced unexpected merge parents; inspect the repository, do not force. |
44
+ | `batch-invalid` | A batch id/member/receipt/check reference is invalid or close evidence is incomplete. |
45
+
46
+ A merge that already landed is **not** a blocker: `tower_merge` records it (`reconciled`,
47
+ `alreadyLanded`) or recovers it (`recoveredFromFailure`, `warnings`).
48
+
49
+ ## Rebase failures (3; `worker-busy` shared)
50
+
51
+ | Code | Meaning / next action |
52
+ |---|---|
53
+ | `worktree-dirty` | Commit or discard worktree changes, then retry. |
54
+ | `rebase-conflict` | A real conflict; the mission is `blocked`. Resolve manually, return it to `active`, retry. |
55
+ | `worker-busy` | An agent is running for this mission; wait or retire it first. |
56
+
57
+ `tower_rebase` returns `alreadyMerged: true` when the branch is already contained in the base; call
58
+ `tower_merge` to record the landed merge.
@@ -0,0 +1,114 @@
1
+ # Config reference
2
+
3
+ Every key is optional; defaults reproduce the shipped behavior. Unknown top-level keys are rejected.
4
+ `section` and `allowAgentControl` are legacy aliases for `mode.promptSection` and
5
+ `mode.allowAgentControl`. A legacy alias only fills a gap: when both forms are set, the canonical
6
+ `mode.*` key wins.
7
+
8
+ ## Layers and precedence
9
+
10
+ Lowest precedence first:
11
+
12
+ 1. code defaults (the table below),
13
+ 2. profile config (the Cordis patch that mounts `dsh-tower` passes it to `apply()`),
14
+ 3. repository config at `<git-common-dir>/dsh-tower/config.json` (usually `.git/dsh-tower/config.json`),
15
+ 4. per-call tool arguments such as `worktreeRoot` and `timeoutMs`.
16
+
17
+ The repository layer is read every time a Tower tool opens a repository, so a repository can tune
18
+ review, merge, worktree and spawn policy without editing the profile. It uses the same JSON shape as
19
+ the profile config (the same groups and the legacy aliases) and is merged group by group: repo keys
20
+ win, profile keys survive when the repo does not set them. `tower_status` reports
21
+ `config.path` and `config.repoConfigPresent`.
22
+
23
+ ```json
24
+ {
25
+ "review": { "maxRoundsPerReviewer": 3 },
26
+ "merge": { "timeoutMs": 1800000 },
27
+ "worktrees": { "root": "D:/tower-worktrees" },
28
+ "spawn": { "reviewerPreset": "tower-reviewer", "maxDepth": 2 }
29
+ }
30
+ ```
31
+
32
+ The `mode.*` group is session-scoped, not repository-scoped: it is accepted in the repository file
33
+ for schema compatibility but only the profile config controls the Tower-mode prompt and the
34
+ model-controlled toggle. A malformed file, a non-object value, unknown top-level keys, or a value that
35
+ violates the declared schema (for example `merge.timeoutMs` below 1000) fail with the stable
36
+ `config-invalid` / `config-unknown-key` tool error codes.
37
+
38
+ | Key | Default | Meaning |
39
+ |---|---|---|
40
+ | `section` | `''` | Legacy alias: replace the whole Tower-mode prompt text. Empty keeps the built-in reminder. |
41
+ | `allowAgentControl` | `false` (bundled patch: `true`) | Legacy alias: register `tower_control`. |
42
+ | `init.autoGitInit` | `false` (bundled patch: `true`) | `tower_init` may run `git init` + an empty initial commit when `repoRoot` is an absolute, existing directory that is not a repository. |
43
+ | `init.snapshotDirty` | `false` | `tower_init` may snapshot uncommitted **tracked** changes into a dangling commit used as the mission baseline (untracked files are not included; the commit is kept alive under `refs/tower/snapshot/*`). |
44
+ | `mode.promptSection` | `''` | Replacement prompt text (takes precedence over `section` when both set). |
45
+ | `mode.sectionAppend` | `''` | Extra text appended after the reminder (team conventions). |
46
+ | `mode.allowAgentControl` | `false` | Register `tower_control`; the model may enter/leave with a reason. |
47
+ | `mode.autoPauseGoal` | `true` | Pause an active goal on entry; the goal is never auto-resumed on exit. |
48
+ | `mode.requireResumeAuthorization` | `false` | Resuming a mission paused with `pauseCause:user` requires an `authorization` reference. |
49
+ | `mode.workspaceLock` | `false` (bundled patch: `true`) | Allow only one Tower session per repository (`tower.lock`); others get `workspace-busy`. |
50
+ | `mode.workspaceLockScope` | `all` | `all` locks every repo tool; `mutations` skips the workspace lock for read-only tools. |
51
+ | `mode.planModePolicy` | `clear` | Tower entry clears an active plan mode (or `refuse` until the plan is exited); a pre-step also clears a plan activated later. |
52
+ | `mode.workspaceStaleMs` | `300000` | A workspace lock older than this can be taken over. |
53
+ | `mode.heartbeatMs` | `10000` | Workspace-lock heartbeat interval. |
54
+ | `mode.denyTools` | goal/todo/team/workflow/subagent tools | Tools refused while the mode is active (12 defaults). |
55
+ | `mode.storePath` | `$DSH_HOME/tower-mode` | Directory for `modes.json`. |
56
+ | `mode.maxEntries` | `200` | Maximum persisted mode entries (1–2000). |
57
+ | `review.maxRoundsPerReviewer` | `5` | Review-round cap per reviewer per branch. |
58
+ | `merge.timeoutMs` | `900000` (15 min) | Default `tower_merge` timeout, including hooks. A per-call `timeoutMs` wins. |
59
+ | `merge.maxBufferBytes` | `67108864` (64 MB) | Merge output buffer. |
60
+ | `worktrees.root` | `''` | Default worktree parent; empty derives `<checkout parent>/.dsh-tower-worktrees/<hash>`. |
61
+ | `spawn.workerPreset` | `tower-worker` | Preset used for `role=worker`. |
62
+ | `spawn.reviewerPreset` | `tower-reviewer` | Preset used for `role=reviewer`. |
63
+ | `spawn.maxDepth` | `1` | Subagent depth requested at spawn. |
64
+ | `spawn.provider` | `''` | Subagent provider id; empty prefers `spawn`, then the first provider. |
65
+ | `spawn.workerModel` | `{}` | Optional `{provider, model, reasoningEffort, maxTokens}` for workers. |
66
+ | `spawn.reviewerModel` | `{}` | Optional model route for reviewers; independent review is stronger with a different model. |
67
+ | `spawn.maxLiveWorkers` | `4` | Maximum live worker entries per repository (idle `inactive`, failed, interrupted, missing, stale and retired entries do not count); exceeding it returns `worker-limit`. |
68
+ | `spawn.staleMs` | `15000` | Roster entries without catalog activity after this age are marked `stale` and block merge (`worker-stale`); 0 disables. |
69
+ | `spawn.maxWaitMs` | `90000` | Upper bound for `tower_spawn.waitMs` (Kimi-compatible 90s default). |
70
+ | `spawn.requireLease` | `''` | Optional lease name that must be held before a worker can spawn. |
71
+ | `send.maxPerMinute` | `120` | Per-repository `tower_send` rate limit (0 disables); exceeding it returns `rate-limited`. |
72
+ | `send.interruptDefault` | `false` (bundled patch: `true`) | Default for `tower_send.interrupt` (interrupt the running turn before delivering). |
73
+ | `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. |
74
+ | `tests.admission` | `complete` | `spawn` runs discovery before starting a worker, `complete` at `tower_complete`, `off` only on explicit `tower_test`. |
75
+ | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
76
+ | `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
77
+ | `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
78
+ | `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
79
+ | `gates.requireChecks` | `false` | Require at least one `tower_complete.checks` entry before merge (`no-check-evidence`). |
80
+ | `gates.requireTests` | `false` | Require a passing discovery receipt for the reviewed tip (`tests-missing`). |
81
+ | `gates.allowReviewExemption` | `false` | Allow `reviewPolicy:"none"` + reason to skip review blockers; default off deliberately. |
82
+ | `docs.board` | `true` | Write `MISSIONS.md` after mutations. |
83
+ | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`; `repo` = `<repoRoot>/MISSIONS.md`. |
84
+ | `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
85
+ | `dev.hotReload` | `true` | Register `tower_reload`; `/tower reload` is disabled when off. It returns `reload-unavailable` when the profile has no HMR service. |
86
+ | `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. |
87
+ | `dev.reloadRequiredIds` | four Tower loader ids | Entry ids passed to `reconcileProfile` as required. |
88
+
89
+ ## Example
90
+
91
+ ```yaml
92
+ - id: tower
93
+ name: 'dsh-tower'
94
+ config:
95
+ mode:
96
+ allowAgentControl: true
97
+ sectionAppend: 'Team conventions: tests are mandatory.'
98
+ workspaceLock: true
99
+ review:
100
+ maxRoundsPerReviewer: 3
101
+ merge:
102
+ timeoutMs: 1800000
103
+ spawn:
104
+ reviewerModel:
105
+ provider: deepseek-account
106
+ model: deepseek-flash
107
+ dev:
108
+ hotReload: true
109
+ ```
110
+
111
+ ## Looking up the effective schema
112
+
113
+ `cordis_inspect_query` with `Config.listConfigs({ name: "dsh-tower" })` returns the projected JSON
114
+ Schema once the plugin declares `Config` (this release does).
@@ -0,0 +1,31 @@
1
+ # Doctor
2
+
3
+ `tower_doctor({repoRoot})` is read-only. It never takes the state lock, writes state, or changes Git.
4
+ It returns:
5
+
6
+ ```json
7
+ {
8
+ "ok": true,
9
+ "checks": [
10
+ { "id": "git", "status": "pass", "detail": "git version 2.55.0" },
11
+ { "id": "state", "status": "pass", "detail": ".../state.json revision 7" }
12
+ ],
13
+ "environmentContract": {
14
+ "platform": "win32",
15
+ "gitVersion": "git version 2.55.0",
16
+ "repoRoot": "D:/repo",
17
+ "commonDir": "D:/repo/.git",
18
+ "stateDir": "D:/repo/.git/dsh-tower"
19
+ }
20
+ }
21
+ ```
22
+
23
+ Checks cover: git availability, repository identity, state schema/load, repo config presence,
24
+ worker/reviewer preset names, subagents and HMR services, workspace lock holder/staleness, live roster,
25
+ worktree existence/ownership/dirtiness, test-discovery policy, legacy snapshots, batches and leases.
26
+ `warn` is advisory; `fail` means the repository needs attention before spawning/merging. A state-load
27
+ failure is reported as a failed check rather than thrown so doctor remains useful on a broken
28
+ repository.
29
+
30
+ The environment contract is informational; it is not an identity or authorization proof. `tower_init`
31
+ still requires the tower role, and `tower_spawn` still checks the live subagent catalog.
@@ -0,0 +1,50 @@
1
+ # Tool error codes
2
+
3
+ Tool failures are thrown errors, not `{ok:false}` results (gate outcomes are the exception: see
4
+ [blockers.md](blockers.md)). Every thrown error carries a stable code as a message prefix:
5
+
6
+ ```
7
+ [tower:mission-not-found] unknown mission "M9"
8
+ ```
9
+
10
+ Callers can match the prefix instead of the prose. Codes are reserved for the conditions below; a
11
+ message without a more specific match is reported as `tool-internal`.
12
+
13
+ | Code | Meaning | Typical trigger |
14
+ |---|---|---|
15
+ | `invalid-argument` | A tool argument is missing, malformed, or not allowed. | `missionId`/`id` omitted; unknown status; empty report; bad scope glob; `timeoutMs < 1000`; `status: "all"` on an update. |
16
+ | `repo-invalid` | `repoRoot` is missing, relative, or not inside a Git repository. | passing a path in a directory that is not a repo |
17
+ | `not-initialized` | Tower is not initialized for this repository. | a read tool before `tower_init` |
18
+ | `mission-not-found` | No mission with that id exists. | a stale or mistyped `M9` |
19
+ | `finding-not-found` | No finding with that id exists. | `tower_findings({id:"F99"})` |
20
+ | `invalid-transition` | The requested state transition is not allowed. | a blocked→merged attempt; a finding transition not in the graph; a reviewer mutating findings; completing a mission with open tasks, blockers, no commits, or unread inbox messages. |
21
+ | `state-locked` | Another Tower writer holds the state lock. | concurrent mutation after the lock timeout |
22
+ | `state-corrupt` | The Tower state file is unreadable or invalid. | hand-edited `state.json` |
23
+ | `config-invalid` | Tower configuration is invalid. | malformed repo `config.json`, a value that violates the schema |
24
+ | `config-unknown-key` | Tower configuration has an unknown top-level key. | a typo such as `allowAgentToggle` |
25
+ | `git-failed` | A git command failed for a reason other than the delegated cases. | a rejected merge command, invalid ref |
26
+ | `git-timeout` | A git command timed out or exceeded its output buffer. | slow pre-merge hook, huge diff |
27
+ | `spawn-unavailable` | The subagents service is unavailable. | a Host without `subagents` |
28
+ | `provider-missing` | No subagent provider is registered. | an empty provider list |
29
+ | `preset-missing` | The configured agent preset is not registered. | missing `tower-worker`/`tower-reviewer` preset rows, DSH not restarted |
30
+ | `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | reviewer spawned before the mission is `completed`, or before a worktree exists |
31
+ | `not-authorized` | The calling agent role may not perform this action. | a worker calling a tower-only tool or changing mission status/scope; a reviewer mutating findings |
32
+ | `workspace-busy` | Another Tower session owns this repository's workspace lock. | `mode.workspaceLock:true` and a second session calls a repo-scoped tool |
33
+ | `worker-limit` | The repository reached `spawn.maxLiveWorkers` live workers. | spawning a fifth worker with the default limit |
34
+ | `rate-limited` | The `tower_send` per-minute rate limit was reached. | `send.maxPerMinute` exceeded; retry later |
35
+ | `takeover-refused` | The mission still has a running worker. | `tower_spawn({takeover:true})` before `tower_interrupt` |
36
+ | `reload-unavailable` | The DSH HMR service is not available in this profile. | `tower_reload` / `/tower reload` on a profile without HMR |
37
+ | `reload-failed` | The HMR reload failed. | `reconcileProfile` threw; check the DSH log and restart |
38
+ | `tests-failed` | Declared test discovery failed or did not meet admission requirements. | nonzero command, malformed output, minCount/required shortfall |
39
+ | `tests-timeout` | Test discovery exceeded its time or output budget. | `tests.timeoutMs`, or stdout over `tests.maxBytes` |
40
+ | `lease-busy` | Another holder owns the requested advisory lease. | `tower_resource acquire` while `heavy-ci` is held |
41
+ | `lease-token` | The lease release token does not match the holder. | releasing with another session's token |
42
+ | `batch-invalid` | A batch request or its close evidence is invalid. | unknown member, duplicate id, unsafe receipt path, missing receipt hash |
43
+ | `legacy-invalid` | The legacy snapshot or mapping is invalid. | unknown schema, source inside the Tower state dir, mapping rewrite |
44
+ | `tool-internal` | An unexpected failure. | a bug; read the message and the activity log |
45
+
46
+ ## Why thrown?
47
+
48
+ Returned `{ok:false}` shapes are reserved for expected policy outcomes that the tower decides on
49
+ (`tower_merge`/`tower_rebase` gate blockers and `tower_preflight`). Anything that prevents the tool
50
+ from producing a decision is thrown so the model does not mistake it for a normal negative result.
@@ -0,0 +1,45 @@
1
+ # Tower events
2
+
3
+ Tower emits process-local Cordis events after successful tool calls (mutations and read-only tools), so
4
+ other plugins can observe a repository without polling. Events are **not** sent over the network and carry
5
+ no telemetry.
6
+
7
+ ```js
8
+ ctx.on('tower/merge', (payload) => {
9
+ // payload.tool, payload.repoRoot, payload.value (the tool's JSON result)
10
+ })
11
+ ```
12
+
13
+ | Event | Emitted after |
14
+ |---|---|
15
+ | `tower/init` | `tower_init` created or re-anchored state |
16
+ | `tower/plan` | `tower_plan` created missions |
17
+ | `tower/mission` | `tower_mission` changed one mission |
18
+ | `tower/spawn` | `tower_spawn` recorded a worker/reviewer |
19
+ | `tower/complete` | `tower_complete` recorded a completion report |
20
+ | `tower/review` | `tower_review` recorded a verdict |
21
+ | `tower/merge` | `tower_merge` returned (including gate failures; inspect `value.ok`) |
22
+ | `tower/rebase` | `tower_rebase` returned |
23
+ | `tower/teardown` | `tower_teardown` removed worktrees |
24
+ | `tower/finding` | `tower_finding` created one finding |
25
+ | `tower/findings` | `tower_findings` read/listed/updated findings |
26
+ | `tower/send` | `tower_send` persisted a message |
27
+ | `tower/inbox` | `tower_inbox` acknowledged messages |
28
+ | `tower/interrupt` | `tower_interrupt` stopped a live agent |
29
+ | `tower/diff` | `tower_diff` read a mission diff |
30
+ | `tower/adopt` | `tower_adopt` reconciled the roster |
31
+ | `tower/roster` | `tower_roster` read the roster |
32
+ | `tower/status` | `tower_status` read the dashboard |
33
+ | `tower/preflight` | `tower_preflight` evaluated the gate |
34
+ | `tower/doctor` | `tower_doctor` completed its read-only checks |
35
+ | `tower/test` | `tower_test` ran discovery |
36
+ | `tower/batch` | `tower_batch` created, inspected or closed a batch |
37
+ | `tower/resource` | `tower_resource` acquired/released/inspected leases |
38
+ | `tower/import_legacy` | `tower_import_legacy` stored a legacy snapshot |
39
+ | `tower/legacy_map` | `tower_legacy_map` mapped or listed legacy ids |
40
+
41
+ `tower_mode`, `tower_control` and `tower_reload` do not emit Tower events. Payloads are JSON:
42
+ `{tool, repoRoot, value}`. Listener exceptions never affect the tool result.
43
+
44
+ For a durable audit trail use `<git-common-dir>/dsh-tower/activity.jsonl` and `MISSIONS.md` instead;
45
+ events are process-local and are not replayed.
@@ -0,0 +1,52 @@
1
+ # Batches, leases and legacy import
2
+
3
+ ## Evidence batches
4
+
5
+ `tower_batch` groups finite mission completion evidence.
6
+
7
+ - `create` freezes `missionIds`, `exclusions`, `requiredChecks` (default `["check"]`) and exact
8
+ relative `receiptPaths`. Members must be known missions and only paused legacy missions may be
9
+ excluded; membership is immutable after create.
10
+ - `status` summarizes members and pending/failed checks.
11
+ - `close` verifies every required check for completed members (from `completionReport.checks`,
12
+ `testRuns`, or close-time `checks`), checks `treeTip` when provided, and requires every frozen
13
+ receipt path to be supplied in `receiptHashes`/`receipts` (or bound to a passing check with
14
+ `receiptHash`). Missing evidence returns `{ok:false, blockers}`; nothing is deleted.
15
+
16
+ A failed close is not a partial close: the batch stays `open` and can be retried after evidence is
17
+ fixed.
18
+
19
+ ## Advisory leases
20
+
21
+ `tower_resource` manages per-repository leases in `<git-common-dir>/dsh-tower/leases.json`:
22
+
23
+ ```text
24
+ tower_resource({action:"acquire", name:"heavy-ci", ttlMs:900000})
25
+ tower_resource({action:"status"})
26
+ tower_resource({action:"release", name:"heavy-ci", token:"<token from acquire>"})
27
+ ```
28
+
29
+ - acquire is tower-only; the caller gets a token, pid, holder, created/expiry timestamps;
30
+ - the same token renews; another holder gets `[tower:lease-busy]`;
31
+ - release requires the token; an **expired** lease still requires the token and explicit `force:true`
32
+ (no silent takeover);
33
+ - `spawn.requireLease` makes a worker spawn fail unless that lease is held.
34
+
35
+ Leases coordinate local jobs and CI windows. They are **not** an OS CPU/memory hard cap: DSH has no
36
+ public Job Object quota API, so Tower cannot enforce per-process resource limits; use the live-worker
37
+ cap and your OS/CI scheduler for those.
38
+
39
+ ## Legacy import and mapping
40
+
41
+ `tower_import_legacy({repoRoot,sourcePath})` accepts a Tower Codex `schemaVersion:1` state file or a
42
+ DSH legacy state. It:
43
+
44
+ - requires an absolute source path outside `<git-common-dir>/dsh-tower`;
45
+ - validates the schema (16 MiB cap), stores the raw payload byte-for-byte under
46
+ `<git-common-dir>/dsh-tower/legacy/<sha256>.json`, and records only a summary in
47
+ `state.legacySnapshots`;
48
+ - never activates, adopts, or resumes paused missions.
49
+
50
+ `tower_legacy_map` records an audit-only `sourceHash + oldId → newMissionId?` mapping. Mappings are
51
+ append-only; rewriting an existing mapping to another mission is rejected. Mapping does not change
52
+ mission state.
@@ -0,0 +1,61 @@
1
+ # Testing, discovery and evidence
2
+
3
+ Tower 2.1 can execute a repository's declared test discovery and turn its result into durable
4
+ completion evidence. Discovery is **opt-in by configuration**: a worktree with no
5
+ `package.json.tower.testDiscovery` block behaves exactly as before.
6
+
7
+ ## Configuration
8
+
9
+ `package.json` (Tower Codex-compatible):
10
+
11
+ ```json
12
+ {
13
+ "tower": {
14
+ "testDiscovery": {
15
+ "version": 1,
16
+ "commands": [
17
+ {
18
+ "id": "unit",
19
+ "command": "node --test dist/tests/*.test.js",
20
+ "format": "node-tap",
21
+ "minCount": 1,
22
+ "required": ["smoke"]
23
+ }
24
+ ]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ | Field | Meaning |
31
+ |---|---|
32
+ | `id` | Unique, `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`; also used in diagnostic file names. |
33
+ | `command` | Shell command executed with the worktree as cwd (Windows `pwsh`, POSIX `bash`). |
34
+ | `format` | `node-tap` (TAP `ok N - name` / `not ok`) or `json-array` (JSON array of `{name, ok?}`). |
35
+ | `minCount` | Minimum discovered tests for this command (default 1). |
36
+ | `required` | Test names that must appear and pass. |
37
+
38
+ `tests.*` in the Tower config controls admission, timeout, output cap and whether package discovery
39
+ is allowed. `tests.admission` is `spawn` (before a worker starts; fail-closed, no child), `complete`
40
+ (at `tower_complete`; default), or `off` (explicit `tower_test` only).
41
+
42
+ ## Receipts and gates
43
+
44
+ Each run stores a receipt under `<git-common-dir>/dsh-tower/test-runs/<mission>-<stamp>/` with
45
+ stdout/stderr files, SHA-256 hashes, exit codes, counts, required names, the worktree tip, and an
46
+ overall `ok`. `state.missions[].testRuns` keeps the most recent 50 receipts. The tool returns
47
+ `ok:false` with diagnostics on failure; `tower_complete` throws `[tower:tests-failed]` when automatic
48
+ discovery fails.
49
+
50
+ - `tower_test({repoRoot,missionId})` runs discovery on demand and records a receipt.
51
+ - `tower_complete` adds a `tests` check when `tests.autoChecks` is on.
52
+ - `gates.requireChecks` requires any completion check; `gates.requireTests` requires a passing receipt
53
+ for the reviewed tip (`tests-missing`).
54
+
55
+ ## Trust boundary
56
+
57
+ Discovery commands are repository-controlled code. `tests.allowPackageDiscovery:false` disables the
58
+ package block; a CI or policy profile should set it when cloning untrusted repositories. The command
59
+ runs with the same OS permissions as DSH, in the mission worktree, with no network access added by
60
+ Tower. Output is capped at `tests.maxBytes` (16 MiB default) and each command at `tests.timeoutMs`
61
+ (60 s default); over-budget runs fail closed.
@@ -0,0 +1,75 @@
1
+ # Tools reference
2
+
3
+ All tools take `repoRoot` (an absolute path inside the target Git repository) and return JSON.
4
+ The build exposes up to 28 tools: 19 core repository tools below (including `tower_interrupt`),
5
+ `tower_mode`, `tower_control` when `mode.allowAgentControl` is enabled (the bundled patch enables it),
6
+ `tower_reload` while `dev.hotReload` is on, and the 2.1 verification/ops tools `tower_doctor`,
7
+ `tower_test`, `tower_batch`, `tower_resource`, `tower_import_legacy`, `tower_legacy_map`.
8
+
9
+ `tower_mission` uses `id` (alias `missionId`); every other mission-scoped tool uses `missionId`
10
+ (alias `id`). Other input aliases: `task_done`/`taskDone`, `task_drop`/`taskDrop`,
11
+ `task_drop_reason`/`taskDropReason`, `clear_blockers`/`clearBlockers`,
12
+ `include_read`/`includeRead`, `dry_run`/`dryRun`, `timeoutMs`/`timeout_ms`,
13
+ `waitMs`/`wait_ms`, `mission_id`/`missionId` on findings, `agent_id`/`agentId` on interrupt,
14
+ `include_gate`/`includeGate` on status, `retire_missing`/`retireMissing` on adopt. Outputs use
15
+ canonical camelCase names.
16
+
17
+ ## Roles
18
+
19
+ Roster roles are resolved from `exec.agent` + the roster. Mission-scoped tools (`tower_interrupt`,
20
+ `tower_init`, `tower_plan`, `tower_spawn`, `tower_merge`, `tower_rebase`, `tower_teardown`,
21
+ `tower_adopt`, `tower_reload`) are **tower only**. `tower_batch` create/close, `tower_resource`
22
+ acquire/release, `tower_import_legacy`, `tower_legacy_map` map and review-exemption planning are
23
+ tower-only; their read-only status actions are open to roster roles. A worker may call `tower_mission`
24
+ only for `task_done`/`task_drop`/`blocker`/`note`; status changes, scope widening, `clear_blockers` and
25
+ `completed` are tower-only (completion must go through `tower_complete`). A roster reviewer may read
26
+ findings but not mutate them. Violations throw `[tower:not-authorized]` or
27
+ `[tower:invalid-transition]`.
28
+
29
+ ## Tools
30
+
31
+ | Tool | Parameters | Purpose / notes |
32
+ |---|---|---|
33
+ | `tower_init` | `base?`, `autoGitInit?`, `snapshotDirty?`, `reanchor?` | Create `<git-common-dir>/dsh-tower/state.json`. `autoGitInit` runs `git init` + empty initial commit; `snapshotDirty` records a dangling snapshot commit of uncommitted tracked changes used as the mission baseline; `reanchor` changes base only when no non-terminal mission exists. Returns `dirty`, `snapshotCommit`, `reanchored`. |
34
+ | `tower_plan` | `missions[]` (`title`, `scope[]`, `tasks[]?`, `deps[]?`, `kind?`, `context?`, `references[]?` ≤20, `reviewPolicy?`, `reviewExemptionReason?`) | Create missions `M1..`; overlapping build scopes auto-add a dependency; rejects unknown deps and cycles. `reviewPolicy:"none"` requires a reason and `gates.allowReviewExemption`. |
35
+ | `tower_mission` | `id` (alias `missionId`), `status?`, `task_done?`, `task_drop?`+`task_drop_reason?`, `blocker?`, `clear_blockers?`, `note?`, `scope?`, `pause_cause?`, `authorization?` | Update one mission. Worker-safe fields are task/blocker/note; `merged` cannot be set here. Paused missions record `pause_cause` (user/failure/resource/dependency); resuming a user-paused mission honors `mode.requireResumeAuthorization`. |
36
+ | `tower_status` | `missionId?`, `offset?`, `limit?`, `includeGate?`, `format?` | Dashboard by default. `missionId` returns full detail (tasks/notes/reviews/findings/completionReport). `offset`/`limit` page missions (default 20, max 100), `includeGate` adds gate digests, `format:"markdown"` returns the board. |
37
+ | `tower_spawn` | `missionId` (alias `id`), `role` (`worker`/`reviewer`), `name?`, `instructions?`, `worktreeRoot?`, `wait?`, `waitMs?`, `takeover?`, `includeDirtyBase?` | Provision/reuse the worktree and start a subagent. Worker uses `tower-worker`; reviewer requires a `completed` mission and uses the read-only `tower-reviewer`. `takeover:true` replaces a live worker entry that is no longer running (increments ownerGeneration). Only one live worker per mission; `spawn.maxLiveWorkers` caps the repository. |
38
+ | `tower_roster` | — | Roster with live activity (`running`/`inactive`/`missing`/`unknown`) and `stale` flags from the subagent catalog. Read-only. |
39
+ | `tower_adopt` | `retireMissing?` | Reconcile roster entries with the durable subagent catalog. Provably gone agents are retired (default) or marked `missing` (`retireMissing:false`) so they can be inspected/taken over. |
40
+ | `tower_diff` | `missionId` (alias `id`), `maxBytes?` | Read-only merge-base diff: files, commits, capped patch. Safe for reviewers. |
41
+ | `tower_send` | `to`, `subject`, `body`, `scope?`, `interrupt?`, `consentRef?` | Durable message to a roster name, `tower`, or `all`; delivers live to a running agent. `interrupt:true` interrupts the running turn first (Kimi-style), then delivers; `consentRef` is recorded for audit. Rate-limited by `send.maxPerMinute`. |
42
+ | `tower_inbox` | `limit?`, `include_read?` | Read/ack messages addressed to the caller; unread messages block `tower_complete`. |
43
+ | `tower_complete` | `missionId` (alias `id`), `report`, `checks?` | Worker reports completion (mission becomes `completed`, awaiting review). Requires current owner, no open tasks/blockers, ≥1 commit, no unread inbox. `checks` records `{name, command?, exitCode?, evidence?}` evidence; `gates.requireChecks` can demand it. When configured, test discovery runs automatically and adds a `tests` check; failure throws `[tower:tests-failed]`. |
44
+ | `tower_interrupt` | `to?` (roster name), `agentId?`, `reason?` | Interrupt one running roster agent via `subagents.interrupt`, record `state:"interrupted"`. Idempotent (not running → `interrupted:false`). |
45
+ | `tower_finding` | `type`, `title`, `summary`, `details`, `suggested_fix`, `severity?`, `missionId?`, `location?` | Record an out-of-scope issue; reviewers use this instead of editing. Finds start as `open`. |
46
+ | `tower_findings` | `id?`, `status?`, `note?`, filters (`type?`, `severity?`, `missionId?`, `open?`, `limit?`) | Lifecycle tool: list/read/transition. Statuses `open → triaged → fixed`/`wontfix`, reopen allowed. Only the tower or a worker may mutate; a reviewer can list/read. |
47
+ | `tower_review` | `missionId` (alias `id`), `status` (`clean`/`p1-Nitems`/`p2-Nitems`), `merge` (`merge`/`fix-then-merge`/`hold`), `findings`, `decision`, `checks?`, `breakGlassReason?` | Reviewer verdict bound to the current tip. Reviewer must be the assigned reviewer (or tower break-glass). |
48
+ | `tower_preflight` | `missionId` (alias `id`) | Read-only gate: returns `{ok, blockers[], branchTip, baseTip, changedFiles, review}`. |
49
+ | `tower_merge` | `missionId` (alias `id`), `timeoutMs?` (alias `timeout_ms`) | Merge after a green gate. Records already-landed merges (`reconciled`), recovers a failed call whose commit exists (`recoveredFromFailure`), and separates `merge-hook-failed` from `merge-conflict`. |
50
+ | `tower_rebase` | `missionId` (alias `id`) | Rebase the branch onto the base in its worktree. Refuses while the worker runs or the worktree is dirty. Already-contained branches return `alreadyMerged` untouched. |
51
+ | `tower_teardown` | `force?`, `exclude?`, `dry_run?` (alias `dryRun`) | Remove eligible merged/abandoned worktrees and branches; running agents are always kept. |
52
+ | `tower_mode` | — | Report mode, last change, paused goal, denied tools, store path, plugin version, HMR availability, workspace-lock holder. |
53
+ | `tower_control` | `action` (`enter`/`exit`), `reason` | Model-initiated mode change; only when `mode.allowAgentControl` is true; reason is mandatory. |
54
+ | `tower_reload` | `repoRoot?` | Dev-only: ask DSH HMR to reconcile the profile and reload the Host plugin from disk. Registered while `dev.hotReload` is on; scheduled after the result (default 50 ms); fails with `reload-unavailable` without HMR. When `repoRoot` is supplied the caller must be the tower. |
55
+ | `tower_doctor` | — (takes `repoRoot`) | Read-only health contract: git, state schema, config, presets/subagents/HMR, workspace lock, roster, worktree realpath/ownership, test config, leases. Returns `{ok, checks[{id,status,detail}], environmentContract}`. Never writes. |
56
+ | `tower_test` | `missionId` (alias `id`) | Run the worktree's `package.json` `tower.testDiscovery` and record a receipt; `ok:false` on failure. Use `tower_complete` for automatic admission checks. |
57
+ | `tower_batch` | `action` (`create`/`status`/`close`), `missionIds?`, `exclusions?`, `requiredChecks?`, `receiptPaths?`, `batchId?`, `checks?`, `treeTip?`, `receiptHashes?` | Finite evidence batch. Create freezes members/checks/receipts; close verifies every required check and receipt hash. Membership is immutable; close never deletes missions. |
58
+ | `tower_resource` | `action` (`acquire`/`release`/`status`), `name?`, `holder?`, `token?`, `ttlMs?`, `force?` | Advisory per-repository lease in `<common>/dsh-tower/leases.json`. No OS CPU/memory hard cap; expired leases require explicit `force` and the correct token. |
59
+ | `tower_import_legacy` | `sourcePath` | Read-only import of a Tower Codex `schemaVersion:1` or DSH legacy state. Full payload goes under `<common>/dsh-tower/legacy/<sha256>.json`; state stores a summary only. Never activates/adopts/resumes paused missions. |
60
+ | `tower_legacy_map` | `action` (`map`/`status`), `sourceHash?`, `oldId?`, `newMissionId?` | Audit-only legacy-id mapping; mappings are append-only and never adopt a mission. |
61
+
62
+ ## Workspace lock
63
+
64
+ With `mode.workspaceLock:true` (the bundled patch enables it), the first repo-scoped Tower tool call
65
+ from a Tower-mode session acquires `<git-common-dir>/dsh-tower/tower.lock`; another session gets
66
+ `[tower:workspace-busy]` until the holder runs `/tower off` or the lock exceeds
67
+ `mode.workspaceStaleMs`. `mode.workspaceLockScope:"mutations"` skips the lock for read-only tools.
68
+ Code default is off; the shipped patch is on for Kimi-compatible single-active-tower behavior.
69
+
70
+ ## Failure results
71
+
72
+ Gate tools return `{ok:false, blockers:[{code, detail}]}`; the code list is in
73
+ [blockers.md](blockers.md). Thrown tool errors carry a stable `[tower:<code>]` prefix
74
+ ([errors.md](errors.md)). Successful mutations emit process-local events ([events.md](events.md)),
75
+ and, with `docs.board:true`, refresh `<git-common-dir>/dsh-tower/MISSIONS.md`.
@@ -0,0 +1,58 @@
1
+ # dsh-tower 中文文档
2
+
3
+ [English docs](../README.md) | **简体中文**
4
+
5
+ 英文文档是权威来源;本页是中文翻译入口。英文文档位于 [docs/](../README.md),仓库主页见
6
+ [README.md](../../README.md)。
7
+
8
+ ## 2.1 能力概览
9
+
10
+ - **只读健康检查**:[Doctor](reference/doctor.md)(`tower_doctor`)检查 git、state、配置、preset、
11
+ workspace 锁、roster、worktree 归属、测试策略与租约,不写任何状态。
12
+ - **测试发现与完成证据**:[测试、发现与证据](reference/testing.md)。仓库可在
13
+ `package.json.tower.testDiscovery` 声明发现命令;回执可自动附加到 `tower_complete`,
14
+ `gates.requireTests` 可要求针对被审查 tip 的通过回执。
15
+ - **证据批次、咨询式租约、旧版导入**:[批次、租约与旧版导入](reference/operations.md)
16
+ (`tower_batch`、`tower_resource`、`tower_import_legacy`、`tower_legacy_map`)。
17
+ - **workspace 锁与计划模式**:`mode.workspaceLock` 限制每个仓库同时只有一个 Tower 会话,
18
+ `mode.workspaceLockScope:"mutations"` 对只读工具跳过锁;`mode.planModePolicy` 控制进入 Tower
19
+ 时如何处理活跃的 plan mode。
20
+ - **可观察性**:成功的工具调用发出进程内 [Tower 事件](reference/events.md);稳定
21
+ [工具错误码](reference/errors.md) 与 [blocker code](reference/blockers.md) 可被程序匹配。
22
+ - **工具面**:本构建最多 28 个工具(含可选的 `tower_control` 与 `tower_reload`);详见
23
+ [工具参考](reference/tools.md)。
24
+
25
+ ## 选择阅读路径
26
+
27
+ | 你是 | 从这里开始 |
28
+ |---|---|
29
+ | 新用户 | [快速开始](guide/quickstart.md) → [工具参考](reference/tools.md) → [第一个 mission 示例(英文)](../examples/first-mission.md) |
30
+ | 运维 | [运维 runbook(英文)](../operations/runbook.md) → [blocker 与失败码](reference/blockers.md) → [错误码](reference/errors.md) → [事件](reference/events.md) → [Doctor](reference/doctor.md) → [测试](reference/testing.md) → [批次/租约/旧版](reference/operations.md) → [安全模型](operations/security.md) |
31
+ | 维护者 | [架构与状态机(英文)](../architecture/overview.md) → [配置参考](reference/config.md) → [热重载](guide/hot-reload.md) → [贡献指南](../../CONTRIBUTING.md) |
32
+
33
+ 参考页是当前发行版的权威说明;CI 中的 `npm run docs:check` 会校验文档与实现一致(包括中文页)。
34
+
35
+ ## 全部中文页面
36
+
37
+ - [快速开始](guide/quickstart.md)
38
+ - [安装、升级与迁移](guide/install-upgrade-migration.md)
39
+ - [热重载(开发)](guide/hot-reload.md)
40
+ - [工具参考](reference/tools.md)
41
+ - [配置参考](reference/config.md)
42
+ - [blocker 与失败码](reference/blockers.md)
43
+ - [工具错误码](reference/errors.md)
44
+ - [Tower 事件](reference/events.md)
45
+ - [测试、发现与证据](reference/testing.md)
46
+ - [批次、租约与旧版导入](reference/operations.md)
47
+ - [Doctor](reference/doctor.md)
48
+ - [FAQ](faq.md)
49
+ - [安全模型](operations/security.md)
50
+
51
+ 以下页面目前只有英文版:[架构与状态机](../architecture/overview.md)、
52
+ [运维 runbook](../operations/runbook.md)、[示例:第一个 mission](../examples/first-mission.md)。
53
+
54
+ ## 语言 / Language
55
+
56
+ - [English docs](../README.md)
57
+ - **中文文档**(本页)
58
+ - [仓库主页 README(中文)](../../README.md) · [English README](../../README.en.md)
package/docs/zh/faq.md ADDED
@@ -0,0 +1,72 @@
1
+ # FAQ
2
+
3
+ [English](../faq.md) | **简体中文**
4
+
5
+ **为什么 worker 刚完成,`tower_preflight` 就因 `no-review` 失败?**
6
+ 这是预期行为:`completed` 的 mission 正在等待审查。spawn 一个 reviewer;干净审查后门禁就会变绿。
7
+
8
+ **需要轮询 worker 吗?**
9
+ 不需要。worker 的最终报告会送回本会话;`tower_roster` 显示实时活动。轮询只会浪费回合。
10
+
11
+ **`/tower` 显示模式已开启,但没有 `tower_control`。**
12
+ `mode.allowAgentControl` 关闭了。在 profile patch 中把它设为 `true` 并重启 DSH。
13
+
14
+ **合并钩子要跑几分钟,`tower_merge` 失败了。**
15
+ 用更大的 `timeoutMs` 重试:`tower_merge({ missionId, timeoutMs: 1800000 })`。默认超时是 15 分钟,
16
+ 合并输出最多缓冲 64 MB。钩子失败返回 `merge-hook-failed`,并保持仓库干净以便重试。
17
+
18
+ **合并提交已存在,但 Tower 说 mission 未合并。**
19
+ 再次调用 `tower_merge`。它会检测已落地的合并提交(`reconciled`),或恢复“命令失败但提交已存在”的
20
+ 情况(`recoveredFromFailure`)并记录。
21
+
22
+ **`tower_rebase` 返回 `alreadyMerged` 且什么都没改。**
23
+ 正确:分支已经包含在基线中。此时调用 `tower_merge` 记录已落地的合并,而不是 rebase。
24
+
25
+ **reviewer 能修改文件吗?**
26
+ 不能。reviewer 使用只读的 `tower-reviewer` preset(没有 write/edit/pwsh);reviewer spawn 之后分支
27
+ 被改动,审查会被拒绝。mission 之外的问题用 `tower_finding` 记录,不要直接改文件。
28
+
29
+ **`tower_finding` 与 `tower_findings` 有什么区别?**
30
+ `tower_finding` 创建单条 finding(`open`)。`tower_findings` 是生命周期工具:列出/读取,并在
31
+ `open → triaged → fixed`/`wontfix` 之间流转(允许重新打开)。只有控制塔或 worker 可以修改状态;
32
+ roster 中的 reviewer 只能列出和读取。
33
+
34
+ **如何只给一个仓库设置配置?**
35
+ 创建 `<git-common-dir>/dsh-tower/config.json`(普通检出通常是 `.git/dsh-tower/config.json`),
36
+ 使用与 profile 配置相同的 JSON 分组(`review`、`merge`、`worktrees`、`spawn`,以及兼容别名)。
37
+ 仓库配置层优先于 profile;单次工具参数又优先于两者。不需要重启,因为每次 Tower 工具打开仓库时都会
38
+ 读取该文件。
39
+
40
+ **worker 能用 Tower 工具 merge 或 spawn 吗?**
41
+ 不能。worker preset 只包含 mission/进度工具;merge 与 spawn 属于上级控制塔。
42
+
43
+ **worker 卡住或失控了,怎么停下它?**
44
+ 用 `tower_interrupt({to:"worker-m1",reason:"..."})`;它会调用宿主子代理中断,并把 roster 条目记录为
45
+ `interrupted`。如果你已经中断过它并想要一个全新的 owner,用 `takeover:true` spawn。仍在运行的 worker
46
+ 不能被接管(`takeover-refused`)。
47
+
48
+ **两个会话想驱动同一个仓库。**
49
+ 开启 `mode.workspaceLock:true`:第一个调用仓库级工具的 Tower 会话拥有
50
+ `<git-common-dir>/dsh-tower/tower.lock`;其他会话会得到 `[tower:workspace-busy]`,直到 owner 执行
51
+ `/tower off` 或锁过期(`mode.workspaceStaleMs`)。
52
+
53
+ **能否不重启 DSH 就重载插件?**
54
+ 可以,在装有 DSH HMR 服务的 profile 中:`/tower reload` 或 `tower_reload` 工具会对账 profile 并替换
55
+ 变更的 linked 包。先运行一次 `npm run dev:hmr` 把仓库加入 profile 的 HMR 监视 root(然后重启 DSH
56
+ 一次);见[热重载](guide/hot-reload.md)。`plugin_manager` 的安装/更新本身就会即时生效。
57
+
58
+ **如何让完成证据成为强制要求?**
59
+ worker 可以给 `tower_complete` 传 `checks`;设置 `gates.requireChecks:true` 后,没有 checks 的
60
+ completed mission 会被阻止合并(`no-check-evidence`)。
61
+
62
+ **Tower 把状态存在哪里?**
63
+ 目标仓库的 `<git-common-dir>/dsh-tower/state.json`、`activity.jsonl`,以及可供人阅读的
64
+ `MISSIONS.md`;模式状态在 `$DSH_HOME/tower-mode/modes.json`。worktree 位于检出旁边的
65
+ `.dsh-tower-worktrees/`。
66
+
67
+ **如何修改审查轮数上限或 preset?**
68
+ 使用 `review.maxRoundsPerReviewer` 与 `spawn.workerPreset`/`spawn.reviewerPreset`;见
69
+ [配置](reference/config.md)。修改 profile 配置后重启 DSH。
70
+
71
+ **Tower 会把数据发到别处吗?**
72
+ 不会。它没有遥测,也没有网络服务;所有状态都在本地。