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
package/docs/reference/errors.md
CHANGED
|
@@ -4,54 +4,66 @@ Tool failures are thrown errors, not `{ok:false}` results (gate outcomes are the
|
|
|
4
4
|
[blockers.md](blockers.md)). Every thrown error carries a stable code as a message prefix. This
|
|
5
5
|
release defines 35 tool error codes.
|
|
6
6
|
|
|
7
|
-
Every `TowerToolError`
|
|
8
|
-
(structured context or `null`) for programmatic callers
|
|
9
|
-
|
|
7
|
+
Every `TowerToolError` carries `retryable` (boolean), `nextAction` (string or `null`) and `details`
|
|
8
|
+
(structured context or `null`) for programmatic callers. The model-visible surface is the message:
|
|
9
|
+
when the catalog supplies a recovery step, exactly one trailing ` — next: …` segment is rendered;
|
|
10
|
+
when the code is also retryable, that same segment ends with ` (retryable)`. Codes without a
|
|
11
|
+
`nextAction` keep the historical `[tower:<code>] <message>` form byte-for-byte, and a message that
|
|
12
|
+
already contains its action is not suffixed twice. The table below is generated from
|
|
13
|
+
`domain/errors.js`, the same catalog the runtime uses.
|
|
10
14
|
|
|
11
15
|
```
|
|
12
|
-
[tower:mission-not-found] unknown mission "M9"
|
|
16
|
+
[tower:mission-not-found] unknown mission "M9" — next: list missions with tower_status
|
|
13
17
|
```
|
|
14
18
|
|
|
15
19
|
Callers can match the prefix instead of the prose. Codes are reserved for the conditions below; a
|
|
16
20
|
plain `Error` without a structured code is reported as `tool-internal`.
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `state-locked` | Another Tower writer holds the state lock. | concurrent mutation after the lock timeout |
|
|
29
|
-
| `state-corrupt` | The Tower state file is unreadable or invalid. | hand-edited `state.json`; invalid JSON; no usable `schemaVersion` |
|
|
30
|
-
| `state-version-unsupported` | The state file
|
|
31
|
-
| `state-migration-failed` | A state migration
|
|
32
|
-
| `config-invalid` | Tower configuration is invalid. | malformed repo `config.json`, a value that violates the schema |
|
|
33
|
-
| `config-unknown-key` | Tower configuration has an unknown top-level key. | a typo such as `allowAgentToggle` |
|
|
34
|
-
| `git-failed` | A git command failed
|
|
35
|
-
| `git-timeout` | A git command timed out or exceeded its output buffer. | slow pre-merge hook, huge diff |
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `provider-
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `reload-
|
|
48
|
-
| `
|
|
49
|
-
| `tests-
|
|
50
|
-
| `
|
|
51
|
-
| `lease-
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
22
|
+
<!-- generated:error-table:start -->
|
|
23
|
+
| Code | Meaning | Retryable | Next action | Typical trigger |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| `invalid-argument` | A tool argument is missing, malformed, or not allowed. | no | — | `missionId`/`id` omitted; unknown status; empty report; bad scope glob; `timeoutMs < 1000`; `status: "all"` on an update. |
|
|
26
|
+
| `repo-invalid` | repoRoot is missing, relative, or not inside a Git repository. | no | pass an absolute path to a directory inside a Git repository | passing a path in a directory that is not a repo |
|
|
27
|
+
| `not-initialized` | Tower is not initialized for this repository; run tower_init. | no | run tower_init for this repository | a read tool before `tower_init` |
|
|
28
|
+
| `mission-not-found` | No mission with that id exists. | no | list missions with tower_status | a stale or mistyped `M9` |
|
|
29
|
+
| `finding-not-found` | No finding with that id exists. | no | list findings with tower_findings | `tower_findings({id:"F99"})` |
|
|
30
|
+
| `invalid-transition` | The requested state transition is not allowed. | no | — | 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. |
|
|
31
|
+
| `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 | two-phase mutation revalidation |
|
|
32
|
+
| `state-locked` | Another Tower writer holds the state lock. | yes | wait for the other Tower writer to finish, then retry | concurrent mutation after the lock timeout |
|
|
33
|
+
| `state-corrupt` | The Tower state file is unreadable or invalid. | no | restore state.json from a backup, or re-initialize after filing the corruption | hand-edited `state.json`; invalid JSON; no usable `schemaVersion` |
|
|
34
|
+
| `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 | opening a repository written by a newer plugin; an unknown older version; this build refuses to downgrade |
|
|
35
|
+
| `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 | a bug in a migration step; see the pre-migration `state.json.v<N>.bak` |
|
|
36
|
+
| `config-invalid` | Tower configuration is invalid. | no | fix the repository config file, then retry | malformed repo `config.json`, a value that violates the schema |
|
|
37
|
+
| `config-unknown-key` | Tower configuration has an unknown top-level key. | no | remove the unknown key from the repository config file, then retry | a typo such as `allowAgentToggle` |
|
|
38
|
+
| `git-failed` | A git command failed. | no | — | a rejected merge command, invalid ref |
|
|
39
|
+
| `git-timeout` | A git command timed out or exceeded its output buffer. | yes | retry; if it persists, raise git.timeoutMs or merge.timeoutMs | slow pre-merge hook, huge diff |
|
|
40
|
+
| `worktree-dirty` | The mission worktree has uncommitted changes and cannot complete. | no | commit or remove the worktree changes, then retry tower_complete | staged, modified, or untracked files left behind before `tower_complete` |
|
|
41
|
+
| `spawn-unavailable` | The subagents service is unavailable. | no | enable the DSH subagents service, then retry | a Host without `subagents` |
|
|
42
|
+
| `provider-missing` | No subagent provider is registered. | no | register a subagent provider in the DSH profile, then retry | an empty provider list |
|
|
43
|
+
| `provider-cooldown` | The provider is throttled; spawn is in a short cooldown. | yes | wait for the cooldown to expire, then retry | repeated retryable provider failures; wait for `blockedUntil` |
|
|
44
|
+
| `preset-missing` | The configured agent preset is not registered. | no | install the tower-worker and tower-reviewer agent presets and restart DSH | missing `tower-worker`/`tower-reviewer` preset rows, DSH not restarted |
|
|
45
|
+
| `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | no | complete the mission first, then spawn the reviewer | reviewer spawned before the mission is `completed`, or before a worktree exists |
|
|
46
|
+
| `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 | a worker calling a tower-only tool or changing mission status/scope; a reviewer mutating findings |
|
|
47
|
+
| `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 | `mode.workspaceLock:true` and a second session calls a repo-scoped tool |
|
|
48
|
+
| `worker-limit` | The repository reached spawn.maxLiveWorkers live workers. | no | wait for a live worker to finish or raise spawn.maxLiveWorkers | spawning a fifth worker with the default limit |
|
|
49
|
+
| `rate-limited` | The tower_send per-minute rate limit was reached. | yes | wait for the next per-minute window or raise send.maxPerMinute | `send.maxPerMinute` exceeded |
|
|
50
|
+
| `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 | `tower_spawn({takeover:true})` before `tower_interrupt` |
|
|
51
|
+
| `reload-unavailable` | The DSH HMR service is not available in this profile. | no | restart DSH instead of reloading | `tower_reload` / `/tower reload` on a profile without HMR |
|
|
52
|
+
| `reload-failed` | The HMR reload failed; check the DSH log and restart if needed. | no | check the DSH log and restart DSH | `reconcileProfile` threw |
|
|
53
|
+
| `tests-failed` | Declared test discovery failed or did not meet its admission requirements. | no | fix the failing tests, then retry tower_complete | nonzero command, malformed output, minCount/required shortfall |
|
|
54
|
+
| `tests-timeout` | Test discovery exceeded its time or output budget. | no | raise tests.timeoutMs or tests.maxBytes (or speed up the suite), then retry | `tests.timeoutMs`, or stdout over `tests.maxBytes` |
|
|
55
|
+
| `lease-busy` | Another holder owns the requested advisory lease. | no | release the lease or wait for it to expire, then retry | `tower_resource acquire` while `heavy-ci` is held |
|
|
56
|
+
| `lease-token` | The lease release token does not match the current holder. | no | use the token returned when the lease was acquired | releasing with another session's token |
|
|
57
|
+
| `batch-invalid` | The batch request or its close evidence is invalid. | no | fix the batch request or the receipt hashes, then retry | unknown member, duplicate id, unsafe receipt path, missing receipt hash |
|
|
58
|
+
| `legacy-invalid` | The legacy snapshot or mapping is invalid. | no | check the legacy snapshot path, schema and mapping, then retry | unknown schema, source inside the Tower state dir, mapping rewrite |
|
|
59
|
+
| `tool-internal` | An unexpected failure; read the message and the activity log. | no | — | a bug; read the message and the activity log |
|
|
60
|
+
<!-- generated:error-table:end -->
|
|
61
|
+
|
|
62
|
+
A git-mutex timeout is a `state-locked` error that names the holder. Its `details` is
|
|
63
|
+
`{ holderPid, holderPhase, heldMs, waitedMs }`, and the message renders the same facts as
|
|
64
|
+
`held by pid 1234 (merge) for 92 s; waited 60 s`. `holderPhase` is the operation that holds the lock
|
|
65
|
+
(`merge`, `rebase`, `init`, `teardown`, `provision`); `tower_status` and `tower_doctor` expose the same
|
|
66
|
+
holder as `gitLock: { held, pid, phase, heldMs }` while it is held.
|
|
55
67
|
|
|
56
68
|
## Why thrown?
|
|
57
69
|
|
package/docs/reference/events.md
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
# Tower events
|
|
2
2
|
|
|
3
|
+
> Generated by `npm run docs:generate:events` from `domain/events.js`. Do not edit by hand.
|
|
4
|
+
|
|
3
5
|
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. This release
|
|
5
|
-
|
|
6
|
+
other plugins can observe a repository without polling. This release defines 34 process-local events:
|
|
7
|
+
5 stable domain events and 29 deprecated tool-shaped
|
|
8
|
+
aliases. Events are **not** sent over the network and carry no telemetry.
|
|
9
|
+
|
|
10
|
+
## Domain events (stable)
|
|
11
|
+
|
|
12
|
+
Domain events describe what changed, not which tool ran. They are emitted only after the state write
|
|
13
|
+
succeeds, so `revision` is the revision a reader can load back from `state.json`; a failed tool emits
|
|
14
|
+
no domain event. `at` is the transition timestamp in epoch milliseconds. `state.json` stays
|
|
15
|
+
authoritative: these events are an observation channel, not an event-sourced log, and are never
|
|
16
|
+
replayed.
|
|
17
|
+
|
|
18
|
+
| Event | Payload | Emitted after |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `tower.mission.planned` | `{ missionId, revision, at }` | tower_plan persisted one or more new missions |
|
|
21
|
+
| `tower.mission.completed` | `{ missionId, revision, at }` | tower_complete recorded a completion report (or tower_mission set status "completed") |
|
|
22
|
+
| `tower.mission.reviewed` | `{ missionId, revision, at }` | tower_review recorded a verdict for the mission branch |
|
|
23
|
+
| `tower.mission.merged` | `{ missionId, revision, at, commit }` | tower_merge recorded the merge commit that landed the mission branch |
|
|
24
|
+
| `tower.mission.abandoned` | `{ missionId, revision, at }` | a mission transitioned to "abandoned" |
|
|
6
25
|
|
|
7
26
|
```js
|
|
8
|
-
ctx.on('tower
|
|
9
|
-
//
|
|
27
|
+
ctx.on('tower.mission.merged', ({ missionId, revision, at, commit }) => {
|
|
28
|
+
// the merge is already durable; commit is the merge commit that landed
|
|
10
29
|
})
|
|
11
30
|
```
|
|
12
31
|
|
|
32
|
+
## Tool-shaped aliases (deprecated)
|
|
33
|
+
|
|
34
|
+
The older `tower/<tool>` events are kept for one minor release as a **deprecated** alias for the
|
|
35
|
+
tool-shaped observation channel; prefer the domain events above and expect the alias to be removed in
|
|
36
|
+
the next minor. They carry `{ tool, repoRoot, value }`, fire after a successful tool call, and never
|
|
37
|
+
fail a tool when a listener throws. For `tower_merge` the alias also fires for a gate failure whose
|
|
38
|
+
`value.ok` is false.
|
|
39
|
+
|
|
13
40
|
| Event | Emitted after |
|
|
14
41
|
|---|---|
|
|
15
42
|
| `tower/init` | `tower_init` created or re-anchored state |
|
|
@@ -38,11 +65,16 @@ ctx.on('tower/merge', (payload) => {
|
|
|
38
65
|
| `tower/resource` | `tower_resource` acquired/released/inspected leases |
|
|
39
66
|
| `tower/import_legacy` | `tower_import_legacy` stored a legacy snapshot |
|
|
40
67
|
| `tower/legacy_map` | `tower_legacy_map` mapped or listed legacy ids |
|
|
41
|
-
| `tower/mode` | `tower_mode` read
|
|
68
|
+
| `tower/mode` | `tower_mode` read the session stance (`value.mode`, `allowAgentControl`, `deniedWhileActive`) |
|
|
42
69
|
| `tower/control` | `tower_control` entered/left the mode (`value.mode`, actor/reason in `value`) |
|
|
43
70
|
| `tower/reload` | `tower_reload` returned a reload schedule/result |
|
|
44
71
|
|
|
45
|
-
|
|
72
|
+
```js
|
|
73
|
+
ctx.on('tower/merge', (payload) => {
|
|
74
|
+
// deprecated: prefer tower.mission.merged
|
|
75
|
+
// payload.tool, payload.repoRoot, payload.value (the tool's JSON result)
|
|
76
|
+
})
|
|
77
|
+
```
|
|
46
78
|
|
|
47
79
|
For a durable audit trail use `<git-common-dir>/dsh-tower/activity.jsonl` and `MISSIONS.md` instead;
|
|
48
80
|
events are process-local and are not replayed.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Surface budget
|
|
2
|
+
|
|
3
|
+
The plugin measures its user-facing surface in four numbers: **config leaf keys**,
|
|
4
|
+
**config sections**, **registered tools**, and **visible component rows**. The
|
|
5
|
+
committed baseline is [`scripts/surface-baseline.json`](../../scripts/surface-baseline.json);
|
|
6
|
+
the brakes are `npm run surface:check` for the first three and
|
|
7
|
+
`npm run docs:check:components` for the component count; the decision records are
|
|
8
|
+
[ADR 0004](../adr/0004-surface-budget.md) and
|
|
9
|
+
[ADR 0005](../adr/0005-component-surface.md).
|
|
10
|
+
|
|
11
|
+
Decision date: 2026-10-23
|
|
12
|
+
|
|
13
|
+
Every key and tool is classified here so "why is this here" has an answer that a
|
|
14
|
+
check can read. `core` is a behavioural or safety contract, `tuning` is a safe
|
|
15
|
+
default a user may want to change, and `niche` was added for one workflow and
|
|
16
|
+
must carry a removal note for the next minor. A live key or tool missing from
|
|
17
|
+
these tables fails `npm run surface:check`.
|
|
18
|
+
|
|
19
|
+
A second axis asks who may turn a key off. It is generated from
|
|
20
|
+
[`scripts/component-surface.json`](../../scripts/component-surface.json) into
|
|
21
|
+
[component-surface.md](component-surface.md): every key is `core`,
|
|
22
|
+
`optional-component`, `optional-config` or `forbidden-to-disable`, and the
|
|
23
|
+
component count is the fourth baselined surface, two-way: a contraction needs a
|
|
24
|
+
dated decision just like growth does (`npm run docs:check:components`,
|
|
25
|
+
[ADR 0005](../adr/0005-component-surface.md)). The same source of truth records
|
|
26
|
+
the `gateableTools` allowlist; it is empty since #122, so a future
|
|
27
|
+
`tools.expose*` route to any tool is refused before it can remove a review,
|
|
28
|
+
merge or host tool.
|
|
29
|
+
|
|
30
|
+
The 2.3.0 baseline counted 27 tools because it recorded only the `define()` path.
|
|
31
|
+
`tower_mode` and `tower_control` are registered directly by `index.js`, so the
|
|
32
|
+
shipped surface was always 29. The 27 → 29 step is an explicit
|
|
33
|
+
*measurement correction* with a reason and date in
|
|
34
|
+
[`scripts/surface-baseline.json`](../../scripts/surface-baseline.json) (see
|
|
35
|
+
[ADR 0004](../adr/0004-surface-budget.md)), not surface growth; a later count
|
|
36
|
+
change still needs an ADR or a provably matching deletion.
|
|
37
|
+
|
|
38
|
+
The tool count is the **build** surface, not one profile's mounted subset (#121):
|
|
39
|
+
the recorder includes every tool this build can register, so a component row that
|
|
40
|
+
ships off (`tower-hmr`, whose `tower_reload` keeps the count at 29) does not
|
|
41
|
+
shrink the budget. `tower_doctor` reports the same 29 and names the mounted
|
|
42
|
+
subset through its `components` check; the mount smoke asserts the shipped
|
|
43
|
+
profile's 28 loaded tools and pins 29 for the all-rows-on surface.
|
|
44
|
+
|
|
45
|
+
#121 also shrank the config surface: the seven capability `tools.expose*` keys
|
|
46
|
+
and `dev.hotReload` became component rows, so config keys went 64 → 56 and
|
|
47
|
+
sections 15 → 14. Those are recorded shrank values in the committed baseline,
|
|
48
|
+
not free growth: a later increase still needs its own decision.
|
|
49
|
+
|
|
50
|
+
## Config keys
|
|
51
|
+
|
|
52
|
+
| Key | Class | Note |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `section` | core | Legacy alias for `mode.promptSection`; changes the Tower-mode prompt contract. |
|
|
55
|
+
| `allowAgentControl` | core | Legacy alias for `mode.allowAgentControl`; decides whether the model may toggle the mode. |
|
|
56
|
+
| `init.autoGitInit` | tuning | Repository bootstrap convenience; safe default is off. |
|
|
57
|
+
| `init.snapshotDirty` | tuning | Baseline snapshot convenience; tracks dirty state explicitly. |
|
|
58
|
+
| `init.autoExclude` | tuning | Keeps generated artifacts out of user dirt without touching `.gitignore`. |
|
|
59
|
+
| `mode.promptSection` | core | Replaces the whole Tower-mode prompt text. |
|
|
60
|
+
| `mode.sectionAppend` | tuning | Appends team conventions to the prompt. |
|
|
61
|
+
| `mode.allowAgentControl` | core | The human-owned-mode toggle described by ADR 0003. |
|
|
62
|
+
| `mode.autoPauseGoal` | core | Entering the mode pauses an active goal; exit never resumes it. |
|
|
63
|
+
| `mode.requireResumeAuthorization` | core | Guards resuming a user-paused mission. |
|
|
64
|
+
| `mode.requireHumanAckOnFirstEntry` | core | First entry can require an explicit human ack. |
|
|
65
|
+
| `mode.workspaceLock` | core | One Tower session per repository is a safety contract. |
|
|
66
|
+
| `mode.workspaceLockScope` | tuning | Chooses whether read-only tools skip the workspace lock. |
|
|
67
|
+
| `mode.planModePolicy` | core | Entering Tower mode either clears plan mode or refuses. |
|
|
68
|
+
| `mode.workspaceStaleMs` | tuning | Stale window for the advisory workspace lock. |
|
|
69
|
+
| `mode.heartbeatMs` | tuning | Heartbeat interval while the mode is on. |
|
|
70
|
+
| `mode.denyTools` | core | The denied-tool list is part of the mode guard. |
|
|
71
|
+
| `mode.storePath` | niche | Custom mode-store location added for one deployment workflow. Removal note: derive the path from `$DSH_HOME` in the next minor; no in-tree caller sets this key. |
|
|
72
|
+
| `mode.maxEntries` | tuning | Bounds the persisted mode store. |
|
|
73
|
+
| `mode.lockTimeoutMs` | tuning | Bounded wait for the cross-process mode lock. |
|
|
74
|
+
| `review.requireReview` | core | Turns the review gate into a merge blocker. |
|
|
75
|
+
| `review.maxRoundsPerReviewer` | core | Caps review rounds; changes the review lifecycle. |
|
|
76
|
+
| `merge.timeoutMs` | tuning | Default merge timeout; a per-call value wins. |
|
|
77
|
+
| `merge.maxBufferBytes` | tuning | Captured merge output buffer. |
|
|
78
|
+
| `git.timeoutMs` | tuning | Default Git command timeout outside merge. |
|
|
79
|
+
| `worktrees.root` | tuning | Default worktree placement. |
|
|
80
|
+
| `spawn.workerPreset` | core | Chooses the tool surface a worker receives. |
|
|
81
|
+
| `spawn.reviewerPreset` | core | Chooses the read-only reviewer surface. |
|
|
82
|
+
| `spawn.maxDepth` | tuning | Subagent depth requested at spawn. |
|
|
83
|
+
| `spawn.provider` | tuning | Provider route for spawned agents. |
|
|
84
|
+
| `spawn.workerModel` | tuning | Optional model route for workers. |
|
|
85
|
+
| `spawn.reviewerModel` | tuning | Optional independent model route for reviewers. |
|
|
86
|
+
| `spawn.maxLiveWorkers` | core | Resource-safety cap on live workers. |
|
|
87
|
+
| `spawn.staleMs` | tuning | Roster staleness window before merge is blocked. |
|
|
88
|
+
| `spawn.maxWaitMs` | tuning | Upper bound for a blocking spawn wait. |
|
|
89
|
+
| `spawn.requireLease` | niche | Advisory-lease gate added for one multi-agent workflow. Removal note: fold into `tower_resource` policy in the next minor unless a second workflow uses it. |
|
|
90
|
+
| `send.maxPerMinute` | tuning | Message rate limit; zero disables it. |
|
|
91
|
+
| `send.interruptDefault` | tuning | Default for the interrupt-before-deliver flag. |
|
|
92
|
+
| `tests.enabled` | core | Whether declared discovery may run at all. |
|
|
93
|
+
| `tests.admission` | core | Chooses when repository code is executed. |
|
|
94
|
+
| `tests.timeoutMs` | tuning | Per-command discovery timeout. |
|
|
95
|
+
| `tests.maxBytes` | tuning | Captured discovery output cap. |
|
|
96
|
+
| `tests.autoChecks` | tuning | Attaches a tests check to completion. |
|
|
97
|
+
| `tests.allowPackageDiscovery` | core | Allows repository code to execute; a security contract. |
|
|
98
|
+
| `gates.requireChecks` | core | Requires completion evidence before merge. |
|
|
99
|
+
| `gates.requireTests` | core | Requires a test receipt before merge. |
|
|
100
|
+
| `gates.allowReviewExemption` | core | Allows a review exemption and its reason. |
|
|
101
|
+
| `gates.maxReportBytes` | tuning | Report size cap; the refusal itself is not configurable. |
|
|
102
|
+
| `docs.board` | tuning | Writes the human-readable board. |
|
|
103
|
+
| `docs.boardLocation` | tuning | Chooses the board location. |
|
|
104
|
+
| `archive.enabled` | core | Controls whether terminal missions leave the hot state. |
|
|
105
|
+
| `archive.afterDays` | tuning | Archive age threshold. |
|
|
106
|
+
| `archive.maxMissions` | tuning | Hot terminal-mission cap. |
|
|
107
|
+
| `leases.ttlMs` | tuning | Default advisory lease lifetime. |
|
|
108
|
+
| `dev.reloadDelayMs` | tuning | Delay so a tool result is delivered before reload. |
|
|
109
|
+
| `dev.reloadRequiredIds` | niche | Loader ids added for one linked-profile development setup. Removal note: derive the required ids from the bundle manifest in the next minor. |
|
|
110
|
+
|
|
111
|
+
## Tools
|
|
112
|
+
|
|
113
|
+
Tools are `core` when the product does not work without them and `optional` when
|
|
114
|
+
a component row (`tower-batch`, `tower-import`, `tower-leases`, `tower-tests`,
|
|
115
|
+
`tower-hmr`), `mode.allowAgentControl`, or the dev mode can legitimately remove
|
|
116
|
+
them from the surface. `tower_doctor` and `tower_recover` are core: #122 made
|
|
117
|
+
them mandatory for every configuration.
|
|
118
|
+
|
|
119
|
+
| Tool | Class | Note |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `tower_adopt` | core | Reconciles a restored roster with the durable catalog. |
|
|
122
|
+
| `tower_batch` | optional | Evidence-batch workflow; component `tower-batch`. |
|
|
123
|
+
| `tower_complete` | core | The only completion path for a worker. |
|
|
124
|
+
| `tower_control` | optional | Model-initiated mode change; `mode.allowAgentControl:false` removes it (ADR 0003). |
|
|
125
|
+
| `tower_diff` | core | The read-only review view. |
|
|
126
|
+
| `tower_doctor` | core | Read-only health contract; registered for every configuration (#122). |
|
|
127
|
+
| `tower_finding` | core | Out-of-scope issue recording. |
|
|
128
|
+
| `tower_findings` | core | Finding lifecycle. |
|
|
129
|
+
| `tower_import_legacy` | optional | Legacy migration; component `tower-import`. |
|
|
130
|
+
| `tower_inbox` | core | Message delivery and acknowledgement. |
|
|
131
|
+
| `tower_init` | core | Creates the repository state. |
|
|
132
|
+
| `tower_interrupt` | core | Stops a runaway worker. |
|
|
133
|
+
| `tower_legacy_map` | optional | Legacy audit mapping; component `tower-import`. |
|
|
134
|
+
| `tower_merge` | core | The only merge path. |
|
|
135
|
+
| `tower_mission` | core | Mission progress and blockers. |
|
|
136
|
+
| `tower_mode` | core | Reports the session stance, workspace lock, HMR and plugin version; always registered. |
|
|
137
|
+
| `tower_plan` | core | Mission planning. |
|
|
138
|
+
| `tower_preflight` | core | The read-only merge gate. |
|
|
139
|
+
| `tower_rebase` | core | Keeps a mission branch current. |
|
|
140
|
+
| `tower_recover` | core | Stale-lock recovery; registered for every configuration (#122). |
|
|
141
|
+
| `tower_reload` | optional | Dev-only reload; component `tower-hmr` (shipped off). |
|
|
142
|
+
| `tower_resource` | optional | Advisory leases; component `tower-leases`. |
|
|
143
|
+
| `tower_review` | core | Independent review verdicts. |
|
|
144
|
+
| `tower_roster` | core | Roster and live activity. |
|
|
145
|
+
| `tower_send` | core | Durable messages to agents. |
|
|
146
|
+
| `tower_spawn` | core | Starts workers and reviewers. |
|
|
147
|
+
| `tower_status` | core | The dashboard. |
|
|
148
|
+
| `tower_teardown` | core | Worktree cleanup and archiving. |
|
|
149
|
+
| `tower_test` | optional | Explicit discovery; component `tower-tests`. |
|
|
150
|
+
|
|
151
|
+
## Non-goals
|
|
152
|
+
|
|
153
|
+
This is a measurement and a brake, not a cleanup epic. The numbers report the
|
|
154
|
+
surface; they do not decide which key is bad.
|
|
155
|
+
|
|
156
|
+
Enforced by: `scripts/surface-baseline.mjs` and `domain/surface.js`; tests in
|
|
157
|
+
`tests/surface.test.mjs`; gate: `npm run surface:check`.
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# Tools overview (generated)
|
|
2
2
|
|
|
3
3
|
> Generated by `npm run docs:generate:tools` from the registered tool definitions. Do not edit by hand.
|
|
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
|
| Tool | Description |
|
|
6
7
|
|---|---|
|
|
7
8
|
| `tower_adopt` | Reconcile the roster with the durable subagent catalog after a restart or new session. Agents that are provably gone are marked retired (records are kept) or missing (retireMissing:false, so the tower can inspect/take over); agents whose parent catalog cannot be read stay unknown and are never changed. Only the tower may adopt. |
|
|
8
9
|
| `tower_batch` | Manage finite evidence batches: create freezes missionIds/requiredChecks/receiptPaths, status summarizes members, close verifies every required check and receipt hash, abandon retires a stale open batch. Membership and exclusions are immutable after create; close/abandon never remove missions. |
|
|
9
10
|
| `tower_complete` | Worker-only: report mission completion. Enforced by the store: every task done or dropped, no blockers, the current owner, the mission branch has at least one commit since its spawn base, and no unread inbox messages. checks records verifiable evidence (name/command/exitCode/evidence); gates.requireChecks can require at least one before merge. Stores the full report, capped at gates.maxReportBytes (default 32768 UTF-8 bytes); a larger report is rejected with invalid-argument and an actionable nextAction, never truncated. Persists reportBytes on the stored report. completion means ready for preflight and optional review, not merged. |
|
|
11
|
+
| `tower_control` | Enter or leave Tower mode for the calling agent. Allowed only when the deployment enables allowAgentControl; use it when the user intent or task state clearly calls for Tower orchestration or a return to normal collaboration, and always state a concrete reason. |
|
|
10
12
|
| `tower_diff` | Read-only view of a mission branch for review: merge base, changed files, commits, and a capped patch against the recorded base. Safe for reviewers; it never modifies the repository. |
|
|
11
|
-
| `tower_doctor` | Read-only environment and repository health check: git, state schema, case-sensitivity detection (caseInsensitive and the active scope comparison mode), config, presets/subagents/HMR, workspace lock, roster staleness, worktree ownership, test discovery, leases, and the terminal-mission archive (count, bytes, years, unreadable diagnostics and the configured afterDays/maxMissions policy). Returns checks with pass/warn/fail and an environment contract. Never writes state and never probes the filesystem. |
|
|
13
|
+
| `tower_doctor` | Read-only environment and repository health check: the concept-budget surface (config keys/sections/tools), git, state schema, case-sensitivity detection (caseInsensitive and the active scope comparison mode), config, presets/subagents/HMR, the state lock age/heartbeat freshness, the repo-scoped git lock holder (gitLock: {held,pid,phase,heldMs}), workspace lock, roster staleness, worktree ownership, test discovery, leases, and the terminal-mission archive (count, bytes, years, unreadable diagnostics and the configured afterDays/maxMissions policy). Returns checks with pass/warn/fail, state-lock and git-lock reports and an environment contract. Never writes state and never probes the filesystem. |
|
|
12
14
|
| `tower_finding` | File a structured finding (bug / improve / vuln / idea) for the tower to route. Use this for anything notable OUTSIDE your mission scope instead of editing out-of-scope files. Severity is one of low|medium|high|critical. Use tower_findings to list, read, or close findings later. |
|
|
13
15
|
| `tower_findings` | List, read, or update findings for a repository. Without id it lists findings newest-first with counts by status (filters: status, type, severity, missionId, open, limit). With id it returns that finding; with id plus status and/or note it records a lifecycle transition. Statuses are open -> triaged -> fixed|wontfix, and fixed/wontfix can be reopened to open. Only the tower or a worker may change a finding; reviewers may only list and read. Read-only when no id is given. |
|
|
14
16
|
| `tower_import_legacy` | Read-only import of a Tower Codex (schemaVersion 1) or DSH legacy state file. Stores the full payload under <common>/dsh-tower/legacy/<sha256>.json and records only a summary in state.legacySnapshots; never activates, adopts, or resumes paused missions. |
|
|
@@ -18,16 +20,17 @@
|
|
|
18
20
|
| `tower_legacy_map` | Audit-only mapping between a legacy snapshot id and a current mission. map records sourceHash/oldId/newMissionId; status lists mappings. Mapping never adopts or resumes a legacy mission. |
|
|
19
21
|
| `tower_merge` | Merge a mission branch into the recorded base branch with --no-ff, after the hard gate passes. Verifies the two merge parents and records the mission as merged. If the merge commit already landed (manual merge, crashed or slow pre-merge hook), it detects the merge commit and records it as reconciled instead of merging again. A failed gate returns stable blocker codes without merging; a hook failure aborts cleanly and returns "merge-hook-failed" while a real content conflict returns "merge-conflict". |
|
|
20
22
|
| `tower_mission` | Update one mission: lifecycle status, decision note, blocker, clear blockers, mark a task done, drop a task with a reason, or widen the scope (tower only). "merged" is never set here; only a future tower_merge records it. Merged and abandoned are terminal. |
|
|
23
|
+
| `tower_mode` | Report whether Tower mode is active for the calling agent, who changed it last, whether the model may toggle it, any goal paused on entry, the workspace lock, HMR availability and the plugin version. |
|
|
21
24
|
| `tower_plan` | Plan one or more missions in an initialized repository. Each mission has a printable-Unicode title (NFC-normalized, at most 120 characters), relative scope globs, optional task checklist, optional mission-id dependencies, and kind build|survey. Assigns M1.. ids and auto-adds a dependency when build scopes overlap (serializes merges); rejects dependency cycles. |
|
|
22
25
|
| `tower_preflight` | Read-only merge gate for one mission: returns ok plus stable blocker codes (branch-missing, deps-unmerged, read-only-survey, stale-base, out-of-scope, base-dirty, branch-owned-by-closed-missions, and mission/worker guards). Review blockers (no-review, review-hold, not-clean, review-not-approved, tip-moved, review-mission-mismatch) apply only when review.requireReview is on. Never changes anything. |
|
|
23
26
|
| `tower_rebase` | Rebase a mission branch onto the current base tip inside its own worktree. Refuses while its worker is running or the worktree is dirty. A clean conflict-free rebase waives re-review for the old review; a conflict is aborted and the mission is marked blocked. If the branch is already contained in the base, it returns alreadyMerged and leaves the branch untouched so tower_merge can record the landed merge. |
|
|
24
|
-
| `tower_recover` | Read-only recovery diagnostics and an explicit stale-state-lock repair. status reports the state lock owner; clear-stale-lock requires confirm:"CLEAR_STALE_LOCK" and only moves a lock whose PID is provably dead to a backup file. It never resets Tower state or worktrees. |
|
|
25
|
-
| `tower_reload` | Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself.
|
|
27
|
+
| `tower_recover` | Read-only recovery diagnostics and an explicit stale-state-lock repair. status reports the state lock owner with its age, heartbeat age and staleness; clear-stale-lock requires confirm:"CLEAR_STALE_LOCK" and only moves a lock whose PID is provably dead to a backup file. force:true additionally clears a live-but-silent PID (a recycled PID) and still renames the record to a .recovered-<ts> backup. It never resets Tower state or worktrees. |
|
|
28
|
+
| `tower_reload` | Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. Registered only while the tower-hmr component is on (the row is shipped off). The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself. Fails with reload-unavailable when the HMR service is absent. |
|
|
26
29
|
| `tower_resource` | Advisory resource leases per repository (coordination only; no OS CPU/memory hard cap). acquire/release require a token and are tower-only; status is read-only. Expired leases are never taken over silently. |
|
|
27
|
-
| `tower_review` | Submit an independent review verdict
|
|
30
|
+
| `tower_review` | Submit an independent review verdict bound to the current branch tip: a completed mission starts a review round, and while the mission is active a later route may still record a verdict for that same reviewed tip, appending to the same round without changing the status (a moved tip is rejected with an explanation). status is "clean", "p1-Nitems", or "p2-Nitems"; merge is "merge", "fix-then-merge", or "hold" (only clean + merge is later approval). Non-clean returns the mission to active. Reviewers are capped at 5 rounds per branch; a tower-submitted review is break-glass and requires breakGlassReason. |
|
|
28
31
|
| `tower_roster` | List tower roster agents with authoritative live activity from the subagent catalog (running/inactive/missing/unknown). Read-only; never resumes children. Use before completion or merge decisions. |
|
|
29
32
|
| `tower_send` | Send a durable inbox message to a tower participant: a roster agent name, "tower", or "all" (broadcast). The message is persisted first; when the recipient is a currently running roster agent the tower also steers it into that child's current turn. Returns delivery results. |
|
|
30
33
|
| `tower_spawn` | Spawn a worker or reviewer for a mission. Workers use the tower-worker preset; reviewers require a completed mission and use the read-only tower-reviewer preset. Provisions (or reuses) the mission git worktree and branch, generates a self-contained briefing, and starts a background subagent in that worktree. The child final report is delivered back to this session; tower_roster shows authoritative activity. Records the agent in the roster; reviewer spawns record the branch tip so a reviewer that modifies the branch cannot submit a review. Returns immediately unless wait=true. |
|
|
31
|
-
| `tower_status` | Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only. |
|
|
34
|
+
| `tower_status` | Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Also reports the repo-scoped git lock holder (`gitLock: {held,pid,phase,heldMs}`) so a merge holding the mutex is visible. Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only. |
|
|
32
35
|
| `tower_teardown` | Tear down eligible mission worktrees after their missions are merged or abandoned. A worktree whose roster agent is running is always kept (force cannot override); dirty worktrees need force; exclude keeps named missions. dry_run previews the remove/keep decisions without changing anything. Terminal missions whose worktree is gone and whose configured archive policy is due (archive.afterDays / archive.maxMissions) are moved to <git-common-dir>/dsh-tower/archive/ so state.json stops rewriting them; nothing is deleted. |
|
|
33
36
|
| `tower_test` | Run the mission worktree's declared tower.testDiscovery (package.json) and record the receipt. Returns ok:false with diagnostics when discovery fails; does not change mission status. Use tower_complete for automatic admission checks. |
|
package/docs/reference/tools.md
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
All tools take `repoRoot` (an absolute path inside the target Git repository) and return JSON.
|
|
4
4
|
The build exposes up to 29 tools: 19 core repository tools below (including `tower_interrupt`),
|
|
5
5
|
`tower_mode`, `tower_control` (registered by default; set `mode.allowAgentControl:false` to disable),
|
|
6
|
-
`
|
|
7
|
-
`
|
|
6
|
+
the always-registered `tower_doctor` and `tower_recover` (#122), and the component-owned tools
|
|
7
|
+
`tower_reload` (`tower-hmr`), `tower_test` (`tower-tests`), `tower_batch` (`tower-batch`),
|
|
8
|
+
`tower_resource` (`tower-leases`), `tower_import_legacy` / `tower_legacy_map` (`tower-import`).
|
|
9
|
+
A component row that is off removes exactly its tools; `tower_status` and `tower_doctor` report it
|
|
10
|
+
as "disabled by component <id>".
|
|
8
11
|
|
|
9
12
|
`tower_mission` uses `id` (alias `missionId`); every other mission-scoped tool uses `missionId`
|
|
10
13
|
(alias `id`). Other input aliases: `task_done`/`taskDone`, `task_drop`/`taskDrop`,
|
|
@@ -34,7 +37,7 @@ findings but not mutate them. Violations throw `[tower:not-authorized]` or
|
|
|
34
37
|
| `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`. |
|
|
35
38
|
| `tower_plan` | `missions[]` (`title` — printable Unicode, NFC-normalized, ≤120 characters, `scope[]`, `tasks[]?`, `deps[]?`, `kind?`, `context?`, `references[]?` ≤20, `reviewPolicy?`, `reviewExemptionReason?`) | Create missions `M1..`; overlapping build scopes auto-add a dependency; rejects unknown deps and cycles. Mission ids stay ASCII (`M1`, `M2`); invisible characters and over-long titles are rejected with `invalid-argument`. `reviewPolicy:"none"` requires a non-empty reason; when the review gate is on, preflight also requires `gates.allowReviewExemption`. |
|
|
36
39
|
| `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`. |
|
|
37
|
-
\|\ `tower_status`\ \|\ `missionId\?`,\ `offset\?`,\ `limit\?`,\ `includeGate\?`,\ `includeArchived\?`,\ `format\?`\ \|\ Dashboard\ by\ default\.\ Paged\ missions\ carry\ `reportBytes`\ only;\ `missionId`\ returns\ full\ detail\ \(tasks/notes/reviews/findings/completionReport\ body\),\ and\ an\ archived\ mission\ is\ loaded\ from\ its\ year\ file\.\ `offset`/`limit`\ page\ active\ missions\ \(default\ 20,\ max\ 100\),\ `includeGate`\ adds\ gate\ digests,\ `includeArchived`\ appends\ compact\ archived\ summaries\ \(flagged\ `archived:true`,\ no\ report\ body\)\ after\ the\ active\ page,\ and\ `format:"markdown"`\ returns\ the\ board\ \(active\ rows\ plus\ an\ archived\ count\)\.\ Includes\ `mode`\ with\ `lastChange`\ and\ recent\ `history`\ when\ the\ host\ mode\ hook\ is\ available\.\ `archive`\ reports\ `\{count,\ years,\ unreadable,\ message}`;\ a\ corrupt\ archive\ degrades\ to\ `unreadable:true`\ instead\ of\ failing\ the\ dashboard\.\ \|
|
|
40
|
+
\|\ `tower_status`\ \|\ `missionId\?`,\ `offset\?`,\ `limit\?`,\ `includeGate\?`,\ `includeArchived\?`,\ `format\?`\ \|\ Dashboard\ by\ default\.\ Paged\ missions\ carry\ `reportBytes`\ only;\ `missionId`\ returns\ full\ detail\ \(tasks/notes/reviews/findings/completionReport\ body\),\ and\ an\ archived\ mission\ is\ loaded\ from\ its\ year\ file\.\ `offset`/`limit`\ page\ active\ missions\ \(default\ 20,\ max\ 100\),\ `includeGate`\ adds\ gate\ digests,\ `includeArchived`\ appends\ compact\ archived\ summaries\ \(flagged\ `archived:true`,\ no\ report\ body\)\ after\ the\ active\ page,\ and\ `format:"markdown"`\ returns\ the\ board\ \(active\ rows\ plus\ an\ archived\ count\)\.\ Includes\ `mode`\ with\ `lastChange`\ and\ recent\ `history`\ when\ the\ host\ mode\ hook\ is\ available\.\ `archive`\ reports\ `\{count,\ years,\ unreadable,\ message}`;\ a\ corrupt\ archive\ degrades\ to\ `unreadable:true`\ instead\ of\ failing\ the\ dashboard\.\ Also\ returns\ `gitLock`\ \(`\{held,\ pid,\ phase,\ heldMs}`\)\ for\ the\ repo-scoped\ git\ mutex,\ and\ `\{held:\ false}`\ when\ it\ is\ free\.\ \|
|
|
38
41
|
| `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. |
|
|
39
42
|
| `tower_roster` | — | Roster with live activity (`running`/`inactive`/`missing`/`unknown`) and `stale` flags from the subagent catalog. Read-only. |
|
|
40
43
|
| `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. |
|
|
@@ -45,16 +48,16 @@ findings but not mutate them. Violations throw `[tower:not-authorized]` or
|
|
|
45
48
|
| `tower_interrupt` | `to?` (roster name), `agentId?`, `reason?` | Interrupt one running roster agent via `subagents.interrupt`, record `state:"interrupted"`. Idempotent (not running → `interrupted:false`). |
|
|
46
49
|
| `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`. |
|
|
47
50
|
| `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. |
|
|
48
|
-
| `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). |
|
|
51
|
+
| `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). A later route may record a verdict for the same reviewed tip while the mission is active: it appends to that round (`appended:true`) without changing the status; a verdict for a moved tip is rejected with an explanation. |
|
|
49
52
|
| `tower_preflight` | `missionId` (alias `id`) | Read-only gate: returns `{ok, blockers[], branchTip, baseTip, changedFiles, review}`. |
|
|
50
53
|
| `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`. |
|
|
51
54
|
| `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. |
|
|
52
55
|
| `tower_teardown` | `force?`, `exclude?`, `dry_run?` (alias `dryRun`) | Remove eligible merged/abandoned worktrees and branches; running agents are always kept. The final pass applies the `archive` policy: terminal missions whose worktree is gone are moved to `<git-common-dir>/dsh-tower/archive/` once `archive.afterDays`/`archive.maxMissions` says so, and `archived` lists their ids. Nothing is deleted; `archive.afterDays:0` archives a just-torn-down mission immediately. |
|
|
53
56
|
| `tower_mode` | — | Report mode, last change, paused goal, denied tools, store path, plugin version, HMR availability, workspace-lock holder. |
|
|
54
57
|
| `tower_control` | `action` (`enter`/`exit`), `reason` | Model-initiated mode change; registered by default; disable with `mode.allowAgentControl:false`; reason is mandatory. |
|
|
55
|
-
| `tower_reload` | `repoRoot?` | Dev-only: ask DSH HMR to reconcile the profile and reload the Host plugin from disk. Registered while `
|
|
56
|
-
| `tower_doctor` | — (takes `repoRoot`) | Read-only health contract: git, state schema, config, presets/subagents/HMR, workspace lock, roster, worktree realpath/ownership, test config, leases and the terminal-mission archive. Returns `{ok, checks[{id,status,detail}], stateSchema, archive, environmentContract}`; `archive` reports `{missions, archivedMissions, stateBytes, archiveBytes, years, files, unreadable, policy{enabled,afterDays,maxMissions}}`. A missing archive directory is healthy; a corrupt file is a `warn` with its diagnostic. Never writes. |
|
|
57
|
-
| `tower_recover` | `action` (`status`/`clear-stale-lock`), `confirm?` | Recovery diagnostics plus an explicit stale state-lock repair. `clear-stale-lock` requires `confirm:"CLEAR_STALE_LOCK"` and only moves a lock whose PID is provably dead to a backup
|
|
58
|
+
| `tower_reload` | `repoRoot?` | Dev-only: ask DSH HMR to reconcile the profile and reload the Host plugin from disk. Registered while the `tower-hmr` component row is on (the row ships off); scheduled after the result (default 50 ms); fails with `reload-unavailable` without HMR. When `repoRoot` is supplied the caller must be the tower. |
|
|
59
|
+
| `tower_doctor` | — (takes `repoRoot`) | Read-only health contract: the concept-budget surface (`config keys: N · sections: N · tools: N`), git, state schema, config, presets/subagents/HMR, the state lock's age and heartbeat freshness, the repo-scoped git lock holder, workspace lock, roster, worktree realpath/ownership, test config, leases and the terminal-mission archive. Returns `{ok, checks[{id,status,detail}], surface, stateLock, gitLock, stateSchema, archive, environmentContract}`; `surface` reports `{configKeys, sections, tools, baselineDate, line}` so growth against `scripts/surface-baseline.json` is visible in the output, `stateLock` reports `{held, pid, startedAt, at, alive, ageMs, heartbeatAgeMs, staleAfterMs, stale}` so a stuck tower is diagnosable without reading the file, and `gitLock` reports `{held, pid, phase, heldMs}` (`{held:false}` when free) with a `git-lock` check. `archive` reports `{missions, archivedMissions, stateBytes, archiveBytes, years, files, unreadable, policy{enabled,afterDays,maxMissions}}`. A missing archive directory is healthy; a corrupt file is a `warn` with its diagnostic. Never writes. |
|
|
60
|
+
| `tower_recover` | `action` (`status`/`clear-stale-lock`), `confirm?`, `force?` | Recovery diagnostics plus an explicit stale state-lock repair. `clear-stale-lock` requires `confirm:"CLEAR_STALE_LOCK"` and only moves a lock whose PID is provably dead to a `.recovered-<ts>` backup. `force:true` additionally clears a live-but-silent PID (a recycled PID): the output names `{pid, startedAt, ageMs, heartbeatAgeMs}` and the record is still renamed, never unlinked. It never resets state/worktrees. |
|
|
58
61
|
| `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. |
|
|
59
62
|
| `tower_batch` | `action` (`create`/`status`/`close`/`abandon`), `missionIds?`, `exclusions?`, `requiredChecks?`, `receiptPaths?`, `batchId?`, `checks?`, `treeTip?`, `receiptHashes?`, `reason?` | Finite evidence batch. Create freezes members/checks/receipts; close verifies every required check and receipt hash; merged members are eligible; abandon retires a stale open batch without deleting missions. Membership is immutable. |
|
|
60
63
|
| `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. |
|
package/docs/zh/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
时如何处理活跃的 plan mode。
|
|
20
20
|
- **可观察性**:成功的工具调用发出进程内 [Tower 事件](reference/events.md);稳定
|
|
21
21
|
[工具错误码](reference/errors.md) 与 [blocker code](reference/blockers.md) 可被程序匹配。
|
|
22
|
-
- **工具面**:本构建最多
|
|
22
|
+
- **工具面**:本构建最多 29 个工具(含可选的 `tower_control` 与 `tower_reload`);详见
|
|
23
23
|
[工具参考](reference/tools.md)。
|
|
24
24
|
|
|
25
25
|
## 选择阅读路径
|
|
@@ -26,7 +26,7 @@ Tower 是 Host 插件:它的工具与提示段落在 DSH 启动时加载。DSH
|
|
|
26
26
|
- 仍然可以显式请求对账(`/tower reload`、`tower_reload` 或 `plugin_manager`);即使 `root: []`,
|
|
27
27
|
它也会比较已加载模块与磁盘,并替换发生变化的模块。监视只是让保存后自动触发这次对账。
|
|
28
28
|
- HMR 只为 profile 启动挂载(没有 `profileContext` 时 `disabled`)。没有 profile 启动的进程会报告
|
|
29
|
-
reload 不可用(`
|
|
29
|
+
reload 不可用(`tower-hmr` 组件行开启时 `tower_reload` 仍注册,因此返回 `reload-unavailable`,
|
|
30
30
|
而不是消失)。
|
|
31
31
|
- profile patch 层在 DSH 启动时加载。编辑它——实际就是 hmr 的 `root` 列表——需要**重启一次 DSH**
|
|
32
32
|
才生效。运行中的进程不会重新读取 HMR 行自身的配置。
|
|
@@ -82,7 +82,7 @@ Windows 上通常是 `C:\Users\<you>\.dsh`):
|
|
|
82
82
|
|
|
83
83
|
## 编辑循环
|
|
84
84
|
|
|
85
|
-
1. 编辑 `
|
|
85
|
+
1. 编辑 `host/*.js`、`adapters/*.js`、`app/*.js`、`domain/**/*.js`、`index.js`、`worker.js`、`reviewer.js` 或 `skills.js`。
|
|
86
86
|
2. 保存文件。监视器会去抖(DSH 默认 100 ms)并对账 profile;变更的模块被替换,受影响的 Loader
|
|
87
87
|
条目重启。
|
|
88
88
|
3. 观察 DSH 日志中的 `reload plugin at <path>` / `restart required: …` 消息。
|
|
@@ -95,9 +95,9 @@ Windows 上通常是 `C:\Users\<you>\.dsh`):
|
|
|
95
95
|
|
|
96
96
|
| 配置键 | 含义 |
|
|
97
97
|
|---|---|
|
|
98
|
-
| `
|
|
98
|
+
| `tower-hmr`(组件行) | 开关本体:开启时注册 `tower_reload` 与 `/tower reload`,关闭时报告“disabled by component tower-hmr”。该行随包关闭;联动开发 profile 在插件页启用它。`dev.hotReload` 已在 2.4.0 移除,现在会作为未知键被拒绝。 |
|
|
99
99
|
| `dev.reloadDelayMs` | 计划中的对账运行前的延迟,单位毫秒(默认 `50`),让工具/命令结果先送达模型,再替换插件。 |
|
|
100
|
-
| `dev.reloadRequiredIds` | profile 必须保持活跃的 Loader 条目 id(默认 `tower
|
|
100
|
+
| `dev.reloadRequiredIds` | profile 必须保持活跃的 Loader 条目 id(默认 `tower`、`tower-host`、`preset-tower-worker`)。即使某个 required 条目早已处于非活跃状态,也会被报告为重载失败。可选能力行刻意不在 required 中,因此关闭某一行不会被误报为 reload 失败。 |
|
|
101
101
|
|
|
102
102
|
重载是**调度而非等待**:`/tower reload` 与 `tower_reload` 立即返回 `scheduled`、`delayMs`、
|
|
103
103
|
`requiredIds`(以及存在待处理错误时的 `lastError`),随后 HMR 在延迟后对账 profile。这是有意为之——
|
|
@@ -42,6 +42,16 @@ Host 插件在 DSH 启动时加载:**安装或更新后需要重启 DSH**。
|
|
|
42
42
|
`tower_doctor` 会把未知模式报告为 `warn`(`case-mode`)。在大小写不敏感的检出上,
|
|
43
43
|
`src/Api/**` 与 `src/api/**` 从此视为重叠,仅大小写不同的改动不再被 out-of-scope 门禁拒绝。
|
|
44
44
|
存储的 scope 文本永远不会被改写:看板仍显示操作者输入的原样。
|
|
45
|
+
- **2.3.0 → 2.4.0(未发布)**:所有能力合一的 `tower-bundle` 组行已移除。`cordis.patch.yml`
|
|
46
|
+
现在随包提供强制的 `tower` host 锚点(Host 插件与 worker preset 是它的内部 section),以及可单独
|
|
47
|
+
开关的顶层行:`tower-skills`、`tower-batch`、`tower-import`、`tower-leases`、`tower-tests`、
|
|
48
|
+
`tower-hmr`(随包关闭)与 `preset-tower-reviewer`(受 `review.requireReview` 条件约束)。
|
|
49
|
+
`tools` 配置组与 `dev.hotReload` 已删除:仍带这些键的 profile 现在会作为未知键被拒绝
|
|
50
|
+
(`config-unknown-key`),在删除它们之前 host 不会挂载,因此不再存在静默无效的开关。请改为在插件页
|
|
51
|
+
使用对应的组件行;`tower-hmr` 随包关闭,因此在为联动开发 profile 开启它之前,`tower_reload` 与
|
|
52
|
+
`/tower reload` 会报告为 “disabled by component tower-hmr”。`tower_doctor` 与 `tower_recover`
|
|
53
|
+
在任何配置下都会注册,无需配置键。仅当 `review.requireReview` 为 false 时 reviewer 行才可关闭;
|
|
54
|
+
该门禁开启时,规划与模式进入会以带解释的错误拒绝该组合。不需要迁移 state。
|
|
45
55
|
- **2.0.4 → 2.1.0-beta.1**:不需要迁移 config;state 会按上述说明自动迁移(v1/v2 → v3)。
|
|
46
56
|
这是发布到 `next` dist-tag 的**预发布**,
|
|
47
57
|
`latest` 仍指向 2.0.4——请用 `dsh plugin --profile web add dsh-tower@next`(或精确版本号)安装。
|
|
@@ -70,7 +70,7 @@ OS 级隔离运行 host。
|
|
|
70
70
|
- **测试发现是仓库代码。** `tests.admission` 默认 `off`,只有显式 `tower_test` 才会执行仓库
|
|
71
71
|
`package.json` 的 `tower.testDiscovery` 命令;该命令以 DSH 的 OS 权限运行,不是沙箱。不可信仓库应
|
|
72
72
|
设 `tests.allowPackageDiscovery:false`。
|
|
73
|
-
- **HMR 重载。** 开启 `
|
|
73
|
+
- **HMR 重载。** 开启 `tower-hmr` 组件行时,`/tower reload`/`tower_reload` 会让 DSH 从磁盘重新导入
|
|
74
74
|
插件。只为你信任的检出配置 HMR 监视 root;重载会执行这些文件当前的代码。
|
|
75
75
|
- **提示注入。** reviewer 会读取不受信任的分支内容(diff/评论)。只读 preset 限制了影响面,但模型侧
|
|
76
76
|
没有绝对防护。
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
| `read-only-survey` | survey mission 修改了文件;survey mission 必须只读。 |
|
|
20
20
|
| `out-of-scope` | 改动文件超出 mission scope;有意为之就扩大 scope,否则回退。在大小写敏感的检出上,仅大小写不同的路径仍会被拒绝,detail 会指出折叠大小写后本可匹配的 scope 条目。 |
|
|
21
21
|
| `base-dirty` | 主检出存在未提交改动,合并会覆盖它们。 |
|
|
22
|
-
| `deps-unmerged` | 依赖 mission
|
|
22
|
+
| `deps-unmerged` | 依赖 mission 尚未满足:build 依赖须 `merged`/`abandoned`;survey 依赖在 `completed` 即视为满足(含归档记录)。 |
|
|
23
23
|
| `no-review` | `review.requireReview:true` 且分支上没有审查:spawn reviewer(或关闭审查门禁)。 |
|
|
24
24
|
| `review-mission-mismatch` | `review.requireReview:true` 且最新审查属于另一个 mission。 |
|
|
25
|
-
| `not-clean` | `review.requireReview:true` 且最新审查是 `p1-Nitems`/`p2-Nitems
|
|
25
|
+
| `not-clean` | `review.requireReview:true` 且最新审查是 `p1-Nitems`/`p2-Nitems`;需要再来一轮(同一轮的各 route 结论会追加记录,不会让状态反复翻转)。 |
|
|
26
26
|
| `review-hold` | `review.requireReview:true` 且最新审查建议 hold;必须由后续的干净审查取代。 |
|
|
27
27
|
| `review-not-approved` | `review.requireReview:true` 且最新干净审查给出的是 `fix-then-merge` 或 `hold`;只有 `merge` 才算批准。 |
|
|
28
28
|
| `tip-moved` | `review.requireReview:true` 且审查之后分支发生变化;重新审查(或使用干净 rebase 的免审豁免)。 |
|