dsh-tower 2.0.4 → 2.1.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 +152 -0
- package/COMPATIBILITY.md +39 -0
- package/CONTRIBUTING.md +157 -2
- package/README.en.md +46 -3
- package/README.md +44 -3
- package/SECURITY.md +27 -0
- package/adapters/default.js +12 -0
- package/adapters/store-fs.js +6 -0
- package/adapters/vcs-git.js +4 -0
- package/app/archive.js +190 -0
- package/app/batch.js +79 -0
- package/app/board.js +27 -0
- package/app/checkCompletion.js +89 -0
- package/app/enrichedMissions.js +26 -0
- package/app/findings.js +78 -0
- package/app/gate.js +126 -0
- package/app/init.js +291 -0
- package/app/merge.js +573 -0
- package/app/messages.js +66 -0
- package/app/mission.js +37 -0
- package/app/missionContext.js +14 -0
- package/app/plan.js +50 -0
- package/app/provision.js +193 -0
- package/app/read.js +113 -0
- package/app/rebase.js +302 -0
- package/app/recording.js +72 -0
- package/app/review.js +147 -0
- package/app/roster.js +123 -0
- package/app/spawn.js +125 -0
- package/app/teardown.js +205 -0
- package/app/tower.js +552 -0
- package/cordis.patch.yml +61 -53
- package/docs/README.md +3 -1
- package/docs/agents/domain.md +38 -0
- package/docs/agents/issue-tracker.md +46 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/architecture/overview.md +114 -13
- package/docs/board.md +72 -0
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/install-upgrade-migration.md +25 -1
- package/docs/guide/release.md +125 -0
- package/docs/operations/security.md +47 -2
- package/docs/reference/blockers.md +1 -1
- package/docs/reference/config.generated.md +70 -0
- package/docs/reference/config.md +8 -1
- package/docs/reference/doctor.md +58 -8
- package/docs/reference/errors.generated.md +41 -0
- package/docs/reference/errors.md +7 -3
- package/docs/reference/tools.generated.md +33 -0
- package/docs/reference/tools.md +7 -6
- package/docs/zh/README.md +3 -2
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/guide/install-upgrade-migration.md +18 -1
- package/docs/zh/operations/security.md +30 -1
- package/docs/zh/reference/blockers.md +1 -1
- package/docs/zh/reference/config.md +8 -1
- package/docs/zh/reference/doctor.md +35 -2
- package/docs/zh/reference/errors.md +6 -2
- package/docs/zh/reference/tools.md +3 -3
- package/domain/collab/batch.js +112 -0
- package/domain/collab/messages.js +127 -0
- package/domain/collab/roster.js +158 -0
- package/domain/collab/spawn.js +188 -0
- package/domain/config/schema.js +427 -0
- package/domain/contracts/outputs.js +305 -0
- package/domain/contracts/tool-inputs/collaboration.js +58 -0
- package/domain/contracts/tool-inputs/findings.js +29 -0
- package/domain/contracts/tool-inputs/index.js +94 -0
- package/domain/contracts/tool-inputs/integration.js +37 -0
- package/domain/contracts/tool-inputs/missions.js +95 -0
- package/domain/contracts/tool-inputs/ops.js +75 -0
- package/domain/contracts/tool-inputs/shared.js +25 -0
- package/domain/errors.js +98 -0
- package/domain/gate.js +125 -0
- package/domain/integration/gate.js +190 -0
- package/domain/integration/init.js +261 -0
- package/domain/integration/liveness.js +31 -0
- package/domain/integration/merge.js +377 -0
- package/domain/integration/provision.js +124 -0
- package/domain/integration/rebase.js +236 -0
- package/domain/integration/review.js +52 -0
- package/domain/integration/teardown.js +118 -0
- package/domain/mission.js +68 -0
- package/domain/missions/completion.js +167 -0
- package/domain/missions/findings.js +201 -0
- package/domain/missions/lifecycle.js +186 -0
- package/domain/missions/plan.js +149 -0
- package/domain/missions/review.js +179 -0
- package/domain/planning.js +121 -0
- package/domain/read/enrichedMissions.js +58 -0
- package/domain/read/missionContext.js +53 -0
- package/domain/read/views.js +141 -0
- package/domain/report.js +67 -0
- package/domain/roster.js +40 -0
- package/domain/scope.js +177 -0
- package/domain/state/archive.js +257 -0
- package/domain/state/migrations.js +145 -0
- package/domain/state/schema.js +239 -0
- package/domain/state/semantics.js +117 -0
- package/domain/state.js +183 -0
- package/domain/summary.js +116 -0
- package/domain/vocabulary.js +179 -0
- package/index.js +63 -20
- package/lib/archive.js +279 -0
- package/lib/briefing.js +176 -0
- package/lib/caseSensitivity.js +66 -0
- package/lib/compatibility.js +108 -0
- package/lib/config.js +33 -375
- package/lib/doctor.js +275 -4
- package/lib/git.js +60 -16
- package/lib/hmr.js +4 -3
- package/lib/leases.js +17 -7
- package/lib/legacy.js +39 -14
- package/lib/modeState.js +39 -0
- package/lib/modeStore.js +11 -43
- package/lib/persistence.js +356 -0
- package/lib/presets.js +35 -0
- package/lib/spawn-prechecks.js +168 -0
- package/lib/spawn-support.js +79 -0
- package/lib/stateFile.js +99 -0
- package/lib/store.js +67 -135
- package/lib/tests.js +17 -12
- package/lib/tool-registry.js +544 -0
- package/lib/workspaceLock.js +2 -2
- package/lib/worktreeOwnership.js +22 -10
- package/locale/en.json +23 -1
- package/locale/zh.json +22 -0
- package/package.json +32 -4
- package/ports/clock.js +6 -0
- package/ports/ids.js +7 -0
- package/ports/index.js +2 -0
- package/ports/store.js +11 -0
- package/ports/vcs.js +11 -0
- package/scripts/architecture.mjs +94 -0
- package/scripts/check-docs.mjs +9 -3
- package/scripts/check-pack.mjs +49 -0
- package/scripts/domain-metrics.mjs +48 -0
- package/scripts/generate-config-reference.mjs +46 -0
- package/scripts/generate-error-reference.mjs +29 -0
- package/scripts/generate-tools-reference.mjs +56 -0
- package/scripts/issue-audit.mjs +259 -0
- package/scripts/lib/cli.mjs +76 -0
- package/scripts/lib/tmp.mjs +20 -0
- package/scripts/mount-smoke/fixtures.mjs +162 -0
- package/scripts/mount-smoke/host.json +4 -0
- package/scripts/mount-smoke/probe.mjs +125 -0
- package/scripts/mount-smoke/surface.mjs +141 -0
- package/scripts/mount-smoke.mjs +270 -0
- package/scripts/pack-manifest.txt +212 -0
- package/scripts/release-check.mjs +303 -0
- package/tools/adopt.js +72 -0
- package/tools/batch.js +78 -0
- package/tools/complete.js +53 -0
- package/tools/diff.js +39 -0
- package/tools/doctor.js +50 -0
- package/tools/finding.js +40 -0
- package/tools/findings.js +39 -0
- package/tools/import-legacy.js +42 -0
- package/tools/inbox.js +39 -0
- package/tools/index.js +39 -0
- package/tools/init.js +63 -0
- package/tools/interrupt.js +42 -0
- package/tools/legacy-map.js +44 -0
- package/tools/merge.js +39 -0
- package/tools/mission.js +41 -0
- package/tools/plan.js +39 -0
- package/tools/preflight.js +45 -0
- package/tools/rebase.js +39 -0
- package/tools/recover.js +45 -0
- package/tools/reload.js +52 -0
- package/tools/resource.js +54 -0
- package/tools/review.js +45 -0
- package/tools/roster.js +60 -0
- package/tools/send.js +147 -0
- package/tools/spawn.js +196 -0
- package/tools/status.js +102 -0
- package/tools/teardown.js +42 -0
- package/tools/test.js +61 -0
- package/types/cordis.d.ts +6 -0
- package/types/index.d.ts +373 -0
- package/lib/errors.js +0 -123
- package/lib/tools.js +0 -2460
- package/lib/tower.js +0 -2912
- /package/{lib → domain}/batch.js +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Domain Docs
|
|
2
|
+
|
|
3
|
+
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **`GLOSSARY.md`** at the repo root, or
|
|
8
|
+
- **`GLOSSARY-MAP.md`** at the repo root if it exists: it points at one `GLOSSARY.md` per context. Read each one relevant to the topic.
|
|
9
|
+
- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
10
|
+
|
|
11
|
+
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
|
12
|
+
|
|
13
|
+
## File structure
|
|
14
|
+
|
|
15
|
+
This repo is **single-context**: one `GLOSSARY.md` and one `docs/adr/` at the repo root.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/
|
|
19
|
+
├── GLOSSARY.md
|
|
20
|
+
├── docs/adr/
|
|
21
|
+
│ ├── 0001-event-sourced-orders.md
|
|
22
|
+
│ └── 0002-postgres-for-write-model.md
|
|
23
|
+
└── src/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
(If the repo ever grows per-package contexts, switch to the multi-context layout: a root `GLOSSARY-MAP.md` pointing at per-context `GLOSSARY.md` files, with system-wide decisions in `docs/adr/` and context decisions in `src/<context>/docs/adr/`.)
|
|
27
|
+
|
|
28
|
+
## Use the glossary's vocabulary
|
|
29
|
+
|
|
30
|
+
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `GLOSSARY.md`. Don't drift to synonyms the glossary explicitly avoids.
|
|
31
|
+
|
|
32
|
+
If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
|
33
|
+
|
|
34
|
+
## Flag ADR conflicts
|
|
35
|
+
|
|
36
|
+
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
37
|
+
|
|
38
|
+
> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Issue tracker: GitHub
|
|
2
|
+
|
|
3
|
+
Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
|
8
|
+
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
|
|
9
|
+
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
|
10
|
+
- **Make an issue a sub-issue of a parent**: `gh issue create --parent <parent> ...`, or `gh issue edit <parent> --add-sub-issue <child>` afterwards (`gh` 2.94+). Older `gh`: `gh api --method POST repos/<owner>/<repo>/issues/<parent>/sub_issues -F sub_issue_id=<child-db-id>` (database id, as in **Blocking** below). Without sub-issues, put `Part of #<parent>` at the top of the child body.
|
|
11
|
+
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
|
12
|
+
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
|
13
|
+
- **Close**: `gh issue close <number> --comment "..."`
|
|
14
|
+
|
|
15
|
+
Infer the repo from `git remote -v`; `gh` does this automatically when run inside a clone. For this repo the remote is `louifinn/dsh-tower`.
|
|
16
|
+
|
|
17
|
+
## Pull requests as a triage surface
|
|
18
|
+
|
|
19
|
+
**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_
|
|
20
|
+
|
|
21
|
+
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
|
22
|
+
|
|
23
|
+
- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
|
|
24
|
+
- **List external PRs for triage**: `gh api --paginate 'repos/{owner}/{repo}/pulls?state=open' --jq '.[] | select(.author_association | IN("OWNER","MEMBER","COLLABORATOR") | not) | {number, title, author: .user.login, author_association, labels: [.labels[].name]}'`.
|
|
25
|
+
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.
|
|
26
|
+
|
|
27
|
+
GitHub shares one number space across issues and PRs, so a bare `#42` may be either: resolve with `gh pr view 42` and fall back to `gh issue view 42`.
|
|
28
|
+
|
|
29
|
+
## When a skill says "publish to the issue tracker"
|
|
30
|
+
|
|
31
|
+
Create a GitHub issue.
|
|
32
|
+
|
|
33
|
+
## When a skill says "fetch the relevant ticket"
|
|
34
|
+
|
|
35
|
+
Run `gh issue view <number> --comments`.
|
|
36
|
+
|
|
37
|
+
## Wayfinding operations
|
|
38
|
+
|
|
39
|
+
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
40
|
+
|
|
41
|
+
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
|
42
|
+
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (see **Make an issue a sub-issue of a parent**). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
|
43
|
+
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only, the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
|
44
|
+
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
|
45
|
+
- **Claim**: `gh issue edit <n> --add-assignee @me`, the session's first write.
|
|
46
|
+
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Triage Labels
|
|
2
|
+
|
|
3
|
+
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
|
4
|
+
|
|
5
|
+
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
|
6
|
+
| -------------------------- | -------------------- | ---------------------------------------- |
|
|
7
|
+
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
|
8
|
+
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
|
9
|
+
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
|
10
|
+
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
|
11
|
+
| `wontfix` | `wontfix` | Will not be actioned |
|
|
12
|
+
|
|
13
|
+
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
|
14
|
+
|
|
15
|
+
Edit the right-hand column to match whatever vocabulary you actually use. The right column currently equals the role name, so these are the defaults — `wontfix` already exists on the GitHub repo, while `needs-triage`, `needs-info`, `ready-for-agent`, and `ready-for-human` will be created by `/triage` on first use (or add them upfront with `gh label create`).
|
|
@@ -2,24 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
## Components
|
|
4
4
|
|
|
5
|
+
The bundle is all-or-nothing: `cordis.patch.yml` inserts one `tower-bundle` `cordis:group` row whose
|
|
6
|
+
internal sections are the Host plugin, the skills provider, and the worker/reviewer presets. The
|
|
7
|
+
group is the only user-facing component; disabling it disables every section together, so no preset
|
|
8
|
+
or skill can remain mounted while the `tower_*` tools are gone.
|
|
9
|
+
|
|
5
10
|
```
|
|
6
11
|
Host plugin (index.js)
|
|
7
12
|
├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control, /tower reload)
|
|
8
|
-
├─ lib/
|
|
9
|
-
│
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├─
|
|
14
|
-
├─
|
|
15
|
-
├─
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
├─ lib/tool-registry.js -> registration aggregator, actor guards, shared helpers
|
|
14
|
+
│ (+ 27 registered repository tools)
|
|
15
|
+
│ (+ tower_reload while dev.hotReload is on; fails reload-unavailable without HMR)
|
|
16
|
+
├─ tools/index.js -> thin public wiring (re-exports the registry + briefings)
|
|
17
|
+
└─ app/tower.js -> orchestration surface; delegates to use-case modules
|
|
18
|
+
├─ app/init.js, provision.js, merge.js, rebase.js, teardown.js
|
|
19
|
+
├─ app/review.js, findings.js, batch.js, messages.js, roster.js
|
|
20
|
+
├─ app/plan.js, mission.js, checkCompletion.js, spawn.js, recording.js
|
|
21
|
+
└─ app/gate.js, read.js, board.js, missionContext.js, enrichedMissions.js
|
|
22
|
+
ports (explicit contracts):
|
|
23
|
+
ports/store.js vcs.js clock.js ids.js
|
|
24
|
+
adapters (the only I/O):
|
|
25
|
+
adapters/store-fs.js the only module importing node:fs
|
|
26
|
+
adapters/vcs-git.js the only module importing node:child_process
|
|
27
|
+
adapters/default.js composition root: store, vcs, clock, ids
|
|
28
|
+
infrastructure (lib/):
|
|
29
|
+
lib/store.js repo-scoped lock + atomic JSON + activity.jsonl
|
|
30
|
+
lib/git.js bounded git commands + timeout classification
|
|
31
|
+
lib/config.js profile/repo config layers over the pure schema
|
|
32
|
+
lib/workspaceLock.js optional one-Tower-session-per-repository advisory lock
|
|
33
|
+
lib/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
|
|
34
|
+
lib/modeStore.js per-agent Tower mode persistence
|
|
35
|
+
pure domain modules (no fs/git):
|
|
36
|
+
domain/errors.js stable, structural tool error catalog
|
|
37
|
+
domain/batch.js batch admission create/close/summary validation
|
|
38
|
+
domain/config/schema.js Schemastery config schema, defaults, validation
|
|
39
|
+
domain/contracts/outputs.js tool output-schema catalog
|
|
40
|
+
domain/contracts/tool-inputs/ per-area TypeBox input schemas + ToolInputs map
|
|
41
|
+
domain/vocabulary.js canonical status/type/action enums + predicates
|
|
42
|
+
domain/mission.js statuses, transitions, pause causes
|
|
43
|
+
domain/gate.js completion and merge-gate snapshot decisions
|
|
44
|
+
domain/scope.js glob/scope validation and overlap
|
|
45
|
+
domain/summary.js mission/finding summaries
|
|
46
|
+
domain/planning.js mission numbering, dependency DAG, auto-deps
|
|
47
|
+
domain/state.js load-time state validation
|
|
48
|
+
domain/report.js board rendering
|
|
49
|
+
domain/roster.js roster predicates
|
|
50
|
+
tools layer:
|
|
51
|
+
lib/tool-registry.js registration aggregator + shared guards/helpers
|
|
52
|
+
tools/index.js thin public wiring (re-exports only)
|
|
53
|
+
tools/*.js one module per tool, handler + imported input schema
|
|
54
|
+
Subagents
|
|
19
55
|
├─ tower-worker (dsh-tower/worker) read/write/edit/pwsh + mission tools + findings
|
|
20
56
|
└─ tower-reviewer (dsh-tower/reviewer) read/glob/grep + tower_diff + review/finding (read-only)
|
|
21
57
|
```
|
|
22
58
|
|
|
59
|
+
|
|
23
60
|
The main checkout is the control-tower repo; each mission has a branch `tower/<id>-<slug>` and a
|
|
24
61
|
worktree in `.dsh-tower-worktrees/<hash>/<missionId>`. The plugin never operates on the main checkout's
|
|
25
62
|
working tree except merge and the read-only diff.
|
|
@@ -64,8 +101,72 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
64
101
|
|
|
65
102
|
| Path | Contents |
|
|
66
103
|
|---|---|
|
|
67
|
-
| `<git-common-dir>/dsh-tower/state.json` | missions, roster, reviews, findings, messages, reviews sequence |
|
|
104
|
+
| `<git-common-dir>/dsh-tower/state.json` | active missions, roster, reviews, findings, messages, reviews sequence |
|
|
105
|
+
| `<git-common-dir>/dsh-tower/archive/index.json` | compact terminal-mission index (ids, status, branch, timestamps) |
|
|
106
|
+
| `<git-common-dir>/dsh-tower/archive/<year>.json` | full archived missions plus their linked reviews/findings |
|
|
68
107
|
| `<git-common-dir>/dsh-tower/activity.jsonl` | append-only activity |
|
|
69
|
-
| `<git-common-dir>/dsh-tower/state.lock` | single-writer lock (
|
|
108
|
+
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (dead-PID reclaimed, live lock reported) |
|
|
109
|
+
| `<git-common-dir>/dsh-tower/git.lock` | repo-scoped serialization for git mutations outside the state lock |
|
|
110
|
+
| `<git-common-dir>/dsh-tower/lock-holds.jsonl` | state/git lock-hold telemetry |
|
|
70
111
|
| `<checkout parent>/.dsh-tower-worktrees/<hash>/<missionId>` | mission worktree |
|
|
71
112
|
| `$DSH_HOME/tower-mode/modes.json` | Tower mode per agent/session |
|
|
113
|
+
| `$DSH_HOME/tower-mode/modes.lock` | mode-store single-writer lock |
|
|
114
|
+
|
|
115
|
+
`state.json` and `modes.json` both use an atomic write (temp + rename), token-checked lock release,
|
|
116
|
+
dead-PID reclamation, and bounded lock waits. `state.json` is the mission persistence contract;
|
|
117
|
+
`modes.json` is the per-agent mode contract; neither is written without its own lock.
|
|
118
|
+
|
|
119
|
+
## Layering rules
|
|
120
|
+
|
|
121
|
+
`npm run architecture:check` (also in CI) enforces the rules as **errors**:
|
|
122
|
+
|
|
123
|
+
- `domain/**` must not import I/O or adapters (`domain-pure`).
|
|
124
|
+
- Only `adapters/vcs-git.js` may import `node:child_process`, and only
|
|
125
|
+
`adapters/store-fs.js` may import `node:fs` (`child-process-surface`, `fs-surface`).
|
|
126
|
+
The rule is enforced over the source roots — `lib/`, `app/`, `domain/`, `tools/`,
|
|
127
|
+
`adapters/`, `ports/` — and does not cover `scripts/**`, which holds maintenance
|
|
128
|
+
entry points (release check, mount smoke, docs generators) that may spawn git or npm.
|
|
129
|
+
- `tools/**` must stay thin and free of status/type literals (`tools-thin`, `tools-vocabulary`);
|
|
130
|
+
`tools/index.js` is the thin public wiring module and stays exempt from the thinness rule,
|
|
131
|
+
while registration/shared machinery lives in `lib/tool-registry.js`.
|
|
132
|
+
- Aggregate files have size ceilings; top-level mutable state is limited to explicit composition
|
|
133
|
+
roots (`aggregate-size`, `ambient-state`). The per-tool ceiling is 200 lines; only the
|
|
134
|
+
`tools/index.js` wiring module is exempt, and it is now far below the ceiling.
|
|
135
|
+
|
|
136
|
+
A fixture test seeds one violation per rule, so a rule that cannot fail is itself a test failure.
|
|
137
|
+
|
|
138
|
+
## Mount smoke and host internals
|
|
139
|
+
|
|
140
|
+
`npm run smoke:mount` (#95) boots a real profile through the pinned host instead of mocking the loader.
|
|
141
|
+
`scripts/mount-smoke/probe.mjs` is the one file that imports the host's internals — `runProfile` from
|
|
142
|
+
`dsh/lib/profile-boot.js` and the layered env from `dsh-app-boot` — because those are the entry points
|
|
143
|
+
`dsh` itself calls to compose a profile; the probe uses them to boot without a model, browser, or
|
|
144
|
+
session. `scripts/mount-smoke/host.json` is the single host/pnpm pin (a unit test keeps its `dsh` field
|
|
145
|
+
equal to the newest `COMPATIBILITY.md` row), so a host bump can move those entry points and may require
|
|
146
|
+
probe adjustments in the same change.
|
|
147
|
+
|
|
148
|
+
## #56 definition of done
|
|
149
|
+
|
|
150
|
+
All epic DoD items now have an executable gate:
|
|
151
|
+
|
|
152
|
+
- Pure domain share: **37.9%** (5,993 / 15,820 source lines), enforced by
|
|
153
|
+
`npm run domain:check` (threshold 35%).
|
|
154
|
+
- `domain/**` imports no fs, child_process, adapters, app, tools, or lib infrastructure;
|
|
155
|
+
`domain-pure` is a blocking architecture rule.
|
|
156
|
+
- Inside the source roots, only `adapters/vcs-git.js` imports `node:child_process`; only
|
|
157
|
+
`adapters/store-fs.js` imports `node:fs`; both are blocking rules with fixtures.
|
|
158
|
+
- `lib`, `app`, and `domain` files stay <= 800 lines; every `tools/**` file stays
|
|
159
|
+
40-200 lines (one handler module per tool; `tools/index.js` is 40-line wiring).
|
|
160
|
+
- No `withLock` scope starts a process; the two-phase tests instrument every git call.
|
|
161
|
+
- `tools/**` has no status/type/enum literal; vocabulary is owned by
|
|
162
|
+
`domain/vocabulary.js`; the `tools-vocabulary` rule is blocking.
|
|
163
|
+
- `validateState` has a four-line body over `domain/state/schema.js` and
|
|
164
|
+
`domain/state/semantics.js`.
|
|
165
|
+
- `state.json` and `modes.json` share `lib/persistence.js` atomic write, token-owned
|
|
166
|
+
lock, dead-PID quarantine, and bounded-wait primitives.
|
|
167
|
+
- The in-memory lifecycle and adapter contracts run a full mission lifecycle with no
|
|
168
|
+
disk and no git.
|
|
169
|
+
- The seven fitness rules are errors, each with a seeded failing fixture.
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
package/docs/board.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Tracker board
|
|
2
|
+
|
|
3
|
+
The three questions an owner asks weekly, as commands that actually work. Run them from the
|
|
4
|
+
repository root (or add `--repo louifinn/dsh-tower`). Verified against the live tracker on
|
|
5
|
+
2026-10-22; the counts below are evidence for the commands, not a promise they stay fixed.
|
|
6
|
+
|
|
7
|
+
Priority lives in the `P0`–`P3` labels and area in `area/*`; issue titles carry no `[P*]`
|
|
8
|
+
prefix any more. Every open issue has exactly one `P*` label and at least one `area/*` label.
|
|
9
|
+
|
|
10
|
+
## Open P1
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
gh issue list --state open --label P1
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Verified: **10** open on 2026-10-22 (#56, #69, #76, #77, #82, #94, #95, #96, #97, #98).
|
|
17
|
+
|
|
18
|
+
`--label` uses REST filters, so it does not wait for the search index after an edit. Do **not**
|
|
19
|
+
write `--label P0,P1`: `gh` treats that as one literal label name and returns nothing (verified).
|
|
20
|
+
For "P0 or P1" use `--search 'label:P0,P1'` — inside `--search`, the comma means OR, while
|
|
21
|
+
`label:P0 label:P1` (a space) means AND and returns only issues carrying both.
|
|
22
|
+
|
|
23
|
+
## Unmilestoned P0/P1
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gh issue list --state open --search 'no:milestone label:P0,P1'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Verified: **0** on 2026-10-22 — every open P0/P1 is in a milestone. A non-zero result means a
|
|
30
|
+
release-scoped issue has no home; put it in the next release milestone or demote it to P2/P3.
|
|
31
|
+
|
|
32
|
+
## Closed this week by area
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# week starts Monday; compute it rather than pasting yesterday's date
|
|
36
|
+
# GNU date (Linux/CI); on macOS use `gdate` or work the date out by hand
|
|
37
|
+
MONDAY=$(date -d 'last monday' +%F)
|
|
38
|
+
gh issue list --state closed --search "closed:>=$MONDAY label:area/ci"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
$MONDAY = (Get-Date).Date.AddDays(-(([int](Get-Date).DayOfWeek+6)%7)).ToString('yyyy-MM-dd')
|
|
43
|
+
gh issue list --state closed --search "closed:>=$MONDAY label:area/ci"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Swap `area/ci` for `area/state`, `area/tools`, `area/docs`, `area/release`, `area/process`,
|
|
47
|
+
`area/architecture`, or `area/scope`. Verified for `area/ci` with `closed:>=2026-10-19`:
|
|
48
|
+
**0**, because all **54** issues closed that week carry no `area/*` label at all. The query is
|
|
49
|
+
right; the history is unlabelled. Label a closure before you close it (or backfill) and the
|
|
50
|
+
by-area view starts working.
|
|
51
|
+
|
|
52
|
+
## Release rule query
|
|
53
|
+
|
|
54
|
+
A release may not be tagged while its milestone holds an open P0/P1 unless the release notes
|
|
55
|
+
say why. Check the milestone that is about to ship (see the [release checklist](guide/release.md)):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gh issue list --state open --search 'milestone:2.1.0-beta.1 label:P0,P1' --json number,title
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Verified: **8** on 2026-10-22 (#76, #77, #82, #94, #95, #96, #97, #98) — the delivery gate's own
|
|
62
|
+
tickets block the release until they land. That is the rule doing its job, not a false positive.
|
|
63
|
+
|
|
64
|
+
## Milestones
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
gh api 'repos/louifinn/dsh-tower/milestones?state=all&per_page=100' \
|
|
68
|
+
--jq '.[] | "\(.number)\t\(.title)\topen=\(.open_issues)\tclosed=\(.closed_issues)"'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Only issues that actually block a release belong in a milestone; everything else stays
|
|
72
|
+
unmilestoned and is queried by priority.
|
package/docs/guide/hot-reload.md
CHANGED
|
@@ -101,7 +101,7 @@ The bridge behind the tool follows the `dev` config group:
|
|
|
101
101
|
|---|---|
|
|
102
102
|
| `dev.hotReload` | Register `tower_reload`; `/tower reload` is disabled when off (default `true`). Without the HMR service the tool is still registered so it can return `reload-unavailable`. |
|
|
103
103
|
| `dev.reloadDelayMs` | Delay before the scheduled reconciliation runs, in milliseconds (default `50`), so the tool/command result reaches the model before the plugin is replaced. |
|
|
104
|
-
| `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`). An inactive required entry is reported as a reload failure even if it was already inactive. |
|
|
104
|
+
| `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower-bundle`, `tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`). An inactive required entry is reported as a reload failure even if it was already inactive. |
|
|
105
105
|
|
|
106
106
|
A reload is **scheduled, not awaited**: `/tower reload` and `tower_reload` return immediately with
|
|
107
107
|
`scheduled`, `delayMs`, `requiredIds` (and the current `lastError` when one is pending), then HMR
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Requirements
|
|
4
4
|
|
|
5
|
-
- DSH `0.2.1-rc.2
|
|
5
|
+
- DSH `0.2.1-rc.2`; see [COMPATIBILITY.md](../../COMPATIBILITY.md) for the builds actually verified per release
|
|
6
6
|
- Node.js 22+
|
|
7
7
|
- Git (local worktrees; no network access is required)
|
|
8
8
|
|
|
@@ -29,6 +29,30 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
|
|
|
29
29
|
|
|
30
30
|
## Migration notes
|
|
31
31
|
|
|
32
|
+
- **state schema v1 → v2 (unreleased)**: `state.json` migrates automatically. The first **write** after
|
|
33
|
+
upgrading keeps a `state.json.v1.bak` backup, advances `revision` once, and appends
|
|
34
|
+
`state.migrated {from,to}` to `activity.jsonl`; read-only tools keep reading a v1 file without
|
|
35
|
+
rewriting it. `tower_doctor` reports the on-disk version and the pending migration. A file written by
|
|
36
|
+
a newer plugin is refused with `[tower:state-version-unsupported]` and is never rewritten or
|
|
37
|
+
downgraded; a migration that throws reports `[tower:state-migration-failed]` and leaves the original
|
|
38
|
+
file byte-identical.
|
|
39
|
+
|
|
40
|
+
- **state schema v2 → v3 (unreleased, #90)**: scope matching follows the checkout's filesystem. The
|
|
41
|
+
migration backfills `caseInsensitive: null` ("not detected"); a migration never reads the filesystem,
|
|
42
|
+
so the first `tower_init` after upgrading probes the checkout once and records `true`/`false`. Until
|
|
43
|
+
then the safe `case-sensitive` comparison applies, and `tower_doctor` reports the unknown mode as a
|
|
44
|
+
`warn` (`case-mode`). On a case-insensitive checkout, `src/Api/**` and `src/api/**` then count as
|
|
45
|
+
overlapping and case-only mismatches are no longer refused by the out-of-scope gate. Stored scope
|
|
46
|
+
strings are never rewritten: the board keeps showing what the operator typed.
|
|
47
|
+
|
|
48
|
+
- **2.0.4 → 2.1.0-beta.1**: no config migration; the state file migrates automatically (v1/v2 → v3) as
|
|
49
|
+
described above. This is a **pre-release** published on the
|
|
50
|
+
`next` dist-tag, so `latest` still resolves to 2.0.4 — install it with
|
|
51
|
+
`dsh plugin --profile web add dsh-tower@next` (or the exact version). The runtime behavior is
|
|
52
|
+
unchanged; the release carries the delivery gate (mount smoke in CI, `npm run release:check`, the
|
|
53
|
+
evidence conventions and the tracker policy) rather than tool changes. These docs merge before the
|
|
54
|
+
human publish step: `next` only resolves to 2.1.0-beta.1 after the [release checklist](release.md)
|
|
55
|
+
runs `npm publish --tag next`, and `latest` stays on 2.0.4 until a stable release.
|
|
32
56
|
- **2.0.3 → 2.0.4**: no state/config migration. This is a security-hardening and integrity release
|
|
33
57
|
(authorization, merge SHA binding, dirty-worktree completion, stale-lock recovery, limited test/git
|
|
34
58
|
process environment, `tower_recover`, UI 0.2.2). Review `review.requireReview`, `tests.admission` and
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Release checklist
|
|
2
|
+
|
|
3
|
+
One page, six steps, in order. The only **human-only** step is `npm publish` (npm
|
|
4
|
+
credentials + 2FA); an agent can do everything else in this repository. A release is a claim —
|
|
5
|
+
`npm run release:check` is the mechanical half of it.
|
|
6
|
+
|
|
7
|
+
## Milestone rule (before step 1)
|
|
8
|
+
|
|
9
|
+
A release may not be tagged while its milestone holds an open P0/P1, unless the release
|
|
10
|
+
notes say why it was accepted:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
gh issue list --state open --search 'milestone:2.1.0-beta.1 label:P0,P1' --json number,title
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Empty output → proceed. Non-empty → close the issues (with the two-line closure contract in
|
|
17
|
+
[CONTRIBUTING.md](../../CONTRIBUTING.md#the-two-line-closure-contract)), or name each accepted
|
|
18
|
+
issue and the reason in the release notes. The same query is saved in [the board](../board.md);
|
|
19
|
+
comma means OR inside `--search`, while `--label P0,P1` matches one literal label name and
|
|
20
|
+
returns nothing.
|
|
21
|
+
|
|
22
|
+
`npm run release:check` prints `git describe`, the previous tag, the commit count since that
|
|
23
|
+
tag and whether Unreleased is non-empty on every run. On a PR it warns; on a tag push it fails
|
|
24
|
+
when `package.json` did not move past the last tag, when the tag is not `v<version>`, or when
|
|
25
|
+
the CHANGELOG has no release content. CI runs it on every push and PR.
|
|
26
|
+
|
|
27
|
+
## 1. Bump the version
|
|
28
|
+
|
|
29
|
+
Edit `package.json` (`"version": "2.1.0-beta.1"`). The previous tag is found from the pushed
|
|
30
|
+
tag's parent (`<tag>^`), so a tag push that repeats the old version fails.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run release:check -- --mode tag --tag v2.1.0-beta.1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 2. Write the changelog
|
|
37
|
+
|
|
38
|
+
Move the Unreleased entries under a version heading, so the GitHub Release body can be
|
|
39
|
+
extracted mechanically. Keep the `## 未发布(Unreleased)` heading (it may be empty).
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
## 未发布(Unreleased)
|
|
43
|
+
|
|
44
|
+
## 2.1.0-beta.1(2026-10-22)
|
|
45
|
+
|
|
46
|
+
- feat: ...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The gate accepts a non-empty Unreleased section (bump first, rename later) **or** a non-empty
|
|
50
|
+
section whose heading names the version. Write the version section as user-facing notes.
|
|
51
|
+
|
|
52
|
+
## 3. Add the COMPATIBILITY row
|
|
53
|
+
|
|
54
|
+
`COMPATIBILITY.md` keeps the columns `| dsh version | tag / commit | verified on | what was run |`.
|
|
55
|
+
Put the **host build in cell 0** and the **tag in cell 1**:
|
|
56
|
+
|
|
57
|
+
```markdown
|
|
58
|
+
| 0.2.1-rc.2 | v2.1.0-beta.1 | 2026-10-22 | `npm test`, `npm run docs:check`, mount smoke |
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`lib/compatibility.js#parseCompatibility` reads cells 0–3 only: never insert a column before
|
|
62
|
+
the tag and never add one before "what was run", or `tower_doctor` stops seeing the row. The
|
|
63
|
+
host pin in `scripts/mount-smoke/host.json` must equal cell 0 of the newest row (a unit test
|
|
64
|
+
enforces it).
|
|
65
|
+
|
|
66
|
+
Commit steps 1–3 as the release PR against protected `main`, merge it, then continue from a
|
|
67
|
+
**clean clone/worktree at the merge commit** — not the integration worktree.
|
|
68
|
+
|
|
69
|
+
## 4. Publish — human-only (2FA)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git status --porcelain # must be empty: files:["docs/"] packs untracked docs too
|
|
73
|
+
npm ci
|
|
74
|
+
npm test && npm run docs:check && npm run pack:check
|
|
75
|
+
npm pack --dry-run # inspect the published file list
|
|
76
|
+
npm publish --tag next --otp <code>
|
|
77
|
+
npm view dsh-tower dist-tags --json
|
|
78
|
+
npm view dsh-tower@next version
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- Publish pre-releases with `--tag next`, **never** `latest`; `latest` moves only for a stable minor.
|
|
82
|
+
- Publish **before tagging**. If publishing fails, do not tag. If you already tagged, delete it
|
|
83
|
+
(`git tag -d <tag>` and `git push --delete origin <tag>`) instead of leaving a tag on an
|
|
84
|
+
unpublished commit.
|
|
85
|
+
- Both checks must name the version just published, and `next` must differ from `latest`.
|
|
86
|
+
|
|
87
|
+
## 5. Annotated tag on the published commit
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run release:check -- --mode tag --tag v2.1.0-beta.1
|
|
91
|
+
git tag -a "v2.1.0-beta.1" -m "dsh-tower 2.1.0-beta.1"
|
|
92
|
+
git push origin "v2.1.0-beta.1"
|
|
93
|
+
git for-each-ref refs/tags/v2.1.0-beta.1 --format='%(objecttype)|%(subject)'
|
|
94
|
+
# tag|dsh-tower 2.1.0-beta.1 (a lightweight tag prints commit, and is not allowed)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The pushed tag must be exactly `v<version>`. The tag push re-runs `release:check` against
|
|
98
|
+
`<tag>^`; this is why tagging a commit whose `package.json` did not move fails.
|
|
99
|
+
|
|
100
|
+
## 6. GitHub Release from the CHANGELOG section
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
VERSION=2.1.0-beta.1
|
|
104
|
+
awk -v v="$VERSION" '$0 ~ "^##[[:space:]]+.*" v {s=1; next} s && /^##[[:space:]]/ {exit} s {print}' \
|
|
105
|
+
CHANGELOG.md > release-notes.md
|
|
106
|
+
test -s release-notes.md || { echo "empty section for $VERSION" >&2; exit 1; }
|
|
107
|
+
gh release create "v$VERSION" --title "dsh-tower $VERSION" --notes-file release-notes.md \
|
|
108
|
+
--prerelease --verify-tag --latest=false
|
|
109
|
+
gh release view "v$VERSION" --json tagName,isPrerelease,body
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
PowerShell extraction of the same section:
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
$version = '2.1.0-beta.1'; $lines = Get-Content CHANGELOG.md
|
|
116
|
+
$start = ($lines | Select-String -Pattern "^##\s+.*$([regex]::Escape($version))" | Select-Object -First 1).LineNumber
|
|
117
|
+
if (-not $start) { throw "CHANGELOG has no section for $version" }
|
|
118
|
+
$next = ($lines | Select-String -Pattern '^##\s+' | Where-Object LineNumber -gt $start | Select-Object -First 1).LineNumber
|
|
119
|
+
$end = if ($next) { $next - 2 } else { $lines.Count - 1 }
|
|
120
|
+
($lines[$start..$end] -join "`n").Trim() | Set-Content release-notes.md -Encoding utf8
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`--verify-tag` refuses to create the tag at the default branch when the pushed tag is missing;
|
|
124
|
+
`--prerelease --latest=false` keeps the GitHub "latest" marker on the stable release. Releases
|
|
125
|
+
are what consumers and the plugin directory can see — a tag alone is not a release.
|
|
@@ -44,6 +44,49 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
|
|
|
44
44
|
- **Legacy import is read-only.** Imported snapshots live under the canonical state directory and are never
|
|
45
45
|
activated, adopted, or resumed; an operator must explicitly re-plan/migrate work.
|
|
46
46
|
|
|
47
|
+
## Repository protection
|
|
48
|
+
|
|
49
|
+
This section is about **this repository**, not about a repository Tower runs on. Server-side branch
|
|
50
|
+
protection is not a plugin feature — Tower writes to the local base branch and cannot configure a
|
|
51
|
+
remote — but `dsh-tower` enables it for its own `main`, because the code that is the gate is also the
|
|
52
|
+
code an attacker would most want to change:
|
|
53
|
+
|
|
54
|
+
- `main` requires a pull request for every change, **enforced for admins**, so a direct push is
|
|
55
|
+
rejected even for the maintainer.
|
|
56
|
+
- Required checks are `test (22)`, `test (24)` and `mount`. The mount job packs the tarball and boots
|
|
57
|
+
it in a throwaway profile, so "the tests pass" cannot land a bundle that fails to appear after
|
|
58
|
+
`dsh plugin add`.
|
|
59
|
+
- Force-push and branch deletion are blocked, so history cannot be rewritten under a published tag.
|
|
60
|
+
- There are no mandatory approvals: with one maintainer, a self-approval requirement would be a rubber
|
|
61
|
+
stamp. The evidence instead is the four-line PR body (failure mode, change, test, blast radius) for
|
|
62
|
+
the two-key paths, written in [CONTRIBUTING.md](../../CONTRIBUTING.md#landing-changes).
|
|
63
|
+
|
|
64
|
+
What this buys: no untested change reaches `main`, a red or missing required check cannot be bypassed
|
|
65
|
+
by the owner, and history is append-only. What it does **not** buy: a second reader, protection against
|
|
66
|
+
a compromised maintainer token, or any protection for the repositories Tower itself operates on — those
|
|
67
|
+
remain deployment concerns. The settings, their rationale and a verification command are recorded in
|
|
68
|
+
[SECURITY.md](../../SECURITY.md#repository-protection-on-main).
|
|
69
|
+
|
|
70
|
+
## Model-initiated mode entry (threat model)
|
|
71
|
+
|
|
72
|
+
`mode.allowAgentControl:true` means a prompt injection in a repository file, issue text, or fetched
|
|
73
|
+
page can ask the model to “enter tower mode and fix this”. Tower does **not** authenticate the human
|
|
74
|
+
intent behind that entry. What it does provide:
|
|
75
|
+
|
|
76
|
+
- the entry is recorded with actor, action, reason, and timestamp; `tower_status.mode.history`
|
|
77
|
+
surfaces the recent transitions without grepping the activity log;
|
|
78
|
+
- the model cannot merge without the preflight gate, review policy, scope checks, tests, and
|
|
79
|
+
clean-base checks; writing the plan does not bypass them;
|
|
80
|
+
- workers are tool-restricted and instructed to stay in their worktree, but the main-checkout
|
|
81
|
+
protection is enforced by scope/merge checks and the preset, **not** by an OS sandbox;
|
|
82
|
+
- `mode.workspaceLock` prevents a second Tower session from driving the same repository.
|
|
83
|
+
|
|
84
|
+
It does **not** defend against: a model that legitimately passes every gate while acting on malicious
|
|
85
|
+
instructions; repository hooks or test commands the repository itself declares; or filesystem/shell
|
|
86
|
+
access outside the worktree by a worker that ignores its instructions. For a higher-assurance team,
|
|
87
|
+
set `mode.allowAgentControl:false`, keep `review.requireReview:true`, review the repository before
|
|
88
|
+
Tower work, and run the host under OS-level isolation.
|
|
89
|
+
|
|
47
90
|
## Not enforced by the plugin (deployment concerns)
|
|
48
91
|
|
|
49
92
|
- **Worktree isolation is a protocol convention, not an OS sandbox.** Workers are assigned a dedicated
|
|
@@ -65,8 +108,10 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
|
|
|
65
108
|
from disk. Only enable HMR watch roots for checkouts you trust; a reload executes the current files.
|
|
66
109
|
- **Prompt injection.** The reviewer reads untrusted branch content (diff/comments). The read-only preset
|
|
67
110
|
limits impact, but no model-side protection is absolute.
|
|
68
|
-
- **Base branch protection.** Tower writes to the local base branch; server-side branch protection
|
|
69
|
-
rules are not part of the plugin.
|
|
111
|
+
- **Base branch protection.** Tower writes to the local base branch; server-side branch protection and
|
|
112
|
+
push rules are not part of the plugin. This repository's own `main` protection is described in
|
|
113
|
+
[Repository protection](#repository-protection); enabling equivalent protection on the repository you
|
|
114
|
+
run Tower on remains a deployment concern.
|
|
70
115
|
|
|
71
116
|
## Reporting
|
|
72
117
|
|
|
@@ -16,7 +16,7 @@ tables below define 32 stable blocker/failure codes.
|
|
|
16
16
|
| `branch-missing` | No worktree/branch yet: spawn a worker first. |
|
|
17
17
|
| `stale-base` | The branch does not contain the base tip: run `tower_rebase`. |
|
|
18
18
|
| `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
|
|
19
|
-
| `out-of-scope` | Changed files outside the mission scope; widen scope deliberately or revert. |
|
|
19
|
+
| `out-of-scope` | Changed files outside the mission scope; widen scope deliberately or revert. On a case-sensitive checkout a case-only mismatch is still rejected and the detail names the scope entry it would match if case were folded. |
|
|
20
20
|
| `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
|
|
21
21
|
| `deps-unmerged` | A dependency mission is not merged/abandoned. |
|
|
22
22
|
| `no-review` | `review.requireReview:true` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
|