dsh-tower 2.1.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -0
- package/COMPATIBILITY.md +6 -1
- package/CONTRIBUTING.md +78 -5
- package/PRINCIPLES.md +163 -0
- package/README.en.md +35 -19
- package/README.md +28 -18
- package/SECURITY.md +6 -0
- package/{lib → adapters}/archive.js +1 -1
- package/{lib → adapters}/caseSensitivity.js +1 -1
- package/adapters/default.js +51 -10
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +1 -1
- package/{lib → adapters}/legacy.js +1 -1
- package/adapters/persistence.js +543 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +9 -5
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +1 -1
- package/app/archive.js +4 -6
- package/app/batch.js +9 -10
- package/app/board.js +2 -4
- package/app/checkCompletion.js +9 -10
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +1 -2
- package/app/init.js +24 -29
- package/app/merge.js +58 -51
- package/app/messages.js +7 -8
- package/app/mission.js +5 -5
- package/app/plan.js +8 -5
- package/app/provision.js +12 -14
- package/app/read.js +6 -3
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +15 -8
- package/app/roster.js +14 -15
- package/{lib → app}/spawn-prechecks.js +4 -3
- package/app/spawn.js +12 -13
- package/app/teardown.js +8 -9
- package/app/tower.js +89 -35
- package/cordis.patch.yml +56 -28
- package/docs/README.md +7 -2
- package/docs/adr/0001-ports-adapters-as-data.md +49 -0
- package/docs/adr/0002-single-state-json.md +50 -0
- package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
- package/docs/adr/0004-surface-budget.md +70 -0
- package/docs/adr/0005-component-surface.md +130 -0
- package/docs/adr/README.md +52 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +192 -43
- package/docs/guide/hot-reload.md +5 -5
- package/docs/guide/install-upgrade-migration.md +13 -0
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/operations/security.md +1 -1
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/component-surface.md +135 -0
- package/docs/reference/config.generated.md +2 -9
- package/docs/reference/config.md +11 -9
- package/docs/reference/doctor.md +53 -2
- package/docs/reference/errors.generated.md +41 -37
- package/docs/reference/errors.md +53 -41
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +157 -0
- package/docs/reference/tools.generated.md +8 -5
- package/docs/reference/tools.md +10 -7
- package/docs/zh/README.md +1 -1
- package/docs/zh/guide/hot-reload.md +4 -4
- package/docs/zh/guide/install-upgrade-migration.md +10 -0
- package/docs/zh/operations/security.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/config.md +9 -9
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +10 -8
- package/domain/batch.js +2 -2
- package/domain/components.js +641 -0
- package/domain/config/schema.js +62 -28
- package/domain/contracts/outputs.js +16 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +308 -43
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +17 -2
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/mission.js +12 -1
- package/domain/missions/completion.js +14 -0
- package/domain/missions/lifecycle.js +9 -3
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/read/views.js +3 -0
- package/domain/state/archive.js +1 -1
- package/domain/state.js +1 -1
- package/domain/surface.js +347 -0
- package/host/capability-batch.js +15 -0
- package/host/capability-hmr.js +30 -0
- package/host/capability-import.js +11 -0
- package/host/capability-leases.js +11 -0
- package/host/capability-registry.js +149 -0
- package/host/capability-tests.js +16 -0
- package/{lib → host}/doctor.js +157 -18
- package/{lib → host}/hmr.js +2 -4
- package/{lib → host}/modeStore.js +1 -1
- package/host/presets.js +77 -0
- package/{lib → host}/tool-registry.js +235 -44
- package/host/tower.js +32 -0
- package/index.js +40 -25
- package/locale/en.json +31 -11
- package/locale/zh.json +30 -10
- package/package.json +24 -4
- package/ports/archive.js +18 -0
- package/ports/caseSensitivity.js +15 -0
- package/ports/clock.js +10 -2
- package/ports/config.js +19 -0
- package/ports/contract.js +38 -0
- package/ports/events.js +24 -0
- package/ports/ids.js +10 -2
- package/ports/index.js +54 -2
- package/ports/ownership.js +17 -0
- package/ports/state.js +16 -0
- package/ports/store.js +52 -3
- package/ports/vcs.js +25 -4
- package/scripts/architecture-waivers.json +19 -0
- package/scripts/architecture.mjs +634 -23
- package/scripts/check-components.mjs +235 -0
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/component-surface.json +395 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-component-surface.mjs +59 -0
- package/scripts/generate-config-reference.mjs +14 -1
- package/scripts/generate-error-reference.mjs +68 -14
- package/scripts/generate-events-reference.mjs +83 -0
- package/scripts/generate-tools-reference.mjs +18 -25
- package/scripts/generate-types.mjs +127 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/lib/tool-surface.mjs +85 -0
- package/scripts/mount-smoke/fixtures.mjs +3 -3
- package/scripts/mount-smoke/surface.mjs +96 -27
- package/scripts/mount-smoke.mjs +3 -1
- package/scripts/pack-manifest.txt +75 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +52 -0
- package/scripts/surface-baseline.mjs +118 -0
- package/scripts/types-coverage.mjs +184 -0
- package/scripts/types-strict-manifest.txt +10 -0
- package/scripts/types-strict.mjs +141 -0
- package/skills.js +8 -1
- package/tools/adopt.js +2 -3
- package/tools/batch.js +2 -3
- package/tools/complete.js +2 -3
- package/tools/diff.js +3 -3
- package/tools/doctor.js +7 -10
- package/tools/finding.js +2 -3
- package/tools/findings.js +3 -3
- package/tools/import-legacy.js +3 -5
- package/tools/inbox.js +3 -3
- package/tools/index.js +9 -8
- package/tools/init.js +3 -6
- package/tools/interrupt.js +2 -3
- package/tools/legacy-map.js +3 -5
- package/tools/merge.js +3 -3
- package/tools/mission.js +2 -3
- package/tools/plan.js +6 -5
- package/tools/preflight.js +2 -3
- package/tools/rebase.js +3 -3
- package/tools/recover.js +38 -13
- package/tools/reload.js +36 -40
- package/tools/resource.js +3 -5
- package/tools/review.js +3 -4
- package/tools/roster.js +2 -3
- package/tools/send.js +2 -2
- package/tools/spawn.js +8 -5
- package/tools/status.js +11 -4
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +29 -13
- package/ui-bundle/README.md +37 -3
- package/ui-bundle/client.cards.js +356 -0
- package/ui-bundle/client.denied.js +142 -0
- package/ui-bundle/client.dock.js +333 -0
- package/ui-bundle/client.helpers.js +271 -0
- package/ui-bundle/client.js +79 -1747
- package/ui-bundle/client.locales.js +247 -0
- package/ui-bundle/client.mode.js +206 -0
- package/ui-bundle/client.overlay.js +64 -0
- package/ui-bundle/client.styles.js +151 -0
- package/ui-bundle/client.toolview.js +82 -0
- package/ui-bundle/package.json +1 -0
- package/lib/persistence.js +0 -356
- package/lib/presets.js +0 -35
- package/lib/store.js +0 -133
- /package/{lib → adapters}/stateFile.js +0 -0
- /package/{lib → app}/briefing.js +0 -0
- /package/{lib → app}/spawn-support.js +0 -0
- /package/{lib → host}/compatibility.js +0 -0
- /package/{lib → host}/config.js +0 -0
- /package/{lib → host}/modeState.js +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 0005 — The component surface: mandatory host, switchable optional rows
|
|
2
|
+
|
|
3
|
+
- Date: 2026-10-23
|
|
4
|
+
- Status: Accepted
|
|
5
|
+
- Deciders: maintainer
|
|
6
|
+
- Supersedes: none
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The plugin had no durable statement of what may and may not be a switch. The
|
|
11
|
+
policy that #65 was resolved against lived only in the issue body, so when the
|
|
12
|
+
issue closed the policy evaporated: the 2.0.4 surface (four top-level rows:
|
|
13
|
+
`tower`, `tower-skills`, `preset-tower-worker`, `preset-tower-reviewer`) became
|
|
14
|
+
one all-or-nothing `tower-bundle` group row plus seven `tools.expose*` config
|
|
15
|
+
keys. The user-controllable component count went 4 → 1 while nothing recorded
|
|
16
|
+
that decision or refused the next one.
|
|
17
|
+
|
|
18
|
+
Two failure modes are already visible in the 2.3.0 tree:
|
|
19
|
+
|
|
20
|
+
1. Optional capabilities have no component. Skills, the reviewer preset, the
|
|
21
|
+
batch/import/leases/test tools and hot reload can only be changed by editing
|
|
22
|
+
YAML under `tools:`, which is the discoverability problem components exist to
|
|
23
|
+
solve. A config key is still a switch, just a worse-labelled one.
|
|
24
|
+
2. The safety nets are switchable. `tools.exposeOperational:false` removes all
|
|
25
|
+
seven operational tools including `tower_recover` and `tower_doctor`, so the
|
|
26
|
+
equipment that diagnoses and repairs a stale `state.lock` can itself be
|
|
27
|
+
turned off, while the plugin stays mounted. That off state is worse than
|
|
28
|
+
uninstalling the plugin, because uninstalling would remove the lock problem
|
|
29
|
+
too.
|
|
30
|
+
|
|
31
|
+
`PRINCIPLES.md` already says an enforcement pointer that does not resolve is a
|
|
32
|
+
slogan; by that standard the component policy was a slogan.
|
|
33
|
+
|
|
34
|
+
## Decision
|
|
35
|
+
|
|
36
|
+
Adopt this shape and record it as principle 11:
|
|
37
|
+
|
|
38
|
+
- **The host is mandatory.** `tower` (and its worker preset dependency) is never
|
|
39
|
+
a switch, not as a row and not as a boolean that removes it.
|
|
40
|
+
- **Each optional capability is its own component row**, individually toggleable
|
|
41
|
+
from the plugin page, with `title`, `summary` and `disables` in both locales and
|
|
42
|
+
a documented fallback stating what still works while it is off. A capability
|
|
43
|
+
qualifies only when its off state leaves the rest of Tower intact and is not
|
|
44
|
+
worse than uninstalling.
|
|
45
|
+
- **The safety nets are never switches.** The health contract, the recovery
|
|
46
|
+
escape, the review contract and the merge gates are configuration or code; no
|
|
47
|
+
group, row or boolean key may gate them. Enforcement covers both routes: a
|
|
48
|
+
`cordis.patch.yml` row that defaults any key classified `forbidden-to-disable`
|
|
49
|
+
to `false` is refused, and every `tools.expose*` route in
|
|
50
|
+
`host/tool-registry.js` must match the `gateableTools` allowlist in the source
|
|
51
|
+
of truth, so a new route to a review, merge or host tool fails even when that
|
|
52
|
+
capability lists no tools of its own.
|
|
53
|
+
- **Config keys stay on the component axis.** Every config key is classified
|
|
54
|
+
`core`, `optional-component`, `optional-config` or `forbidden-to-disable` in
|
|
55
|
+
`docs/reference/component-surface.md`, generated from the single source of
|
|
56
|
+
truth `scripts/component-surface.json`, so "should this be a component?" is a
|
|
57
|
+
table lookup rather than a judgement call.
|
|
58
|
+
- **The component count is a baselined surface.** `scripts/surface-baseline.json`
|
|
59
|
+
gains `components` (seed 4, the 2.0.4 row count; value 1, today's group row) and
|
|
60
|
+
the 4 → 1 contraction is itself a dated decision. Unlike the other surfaces, the
|
|
61
|
+
component count is two-way: a contraction needs a decision record just like
|
|
62
|
+
growth does. `npm run surface:check` and `npm run docs:check:components` refuse a
|
|
63
|
+
count that moved without one. That is what makes the next contraction — or the
|
|
64
|
+
coming growth — visible in a diff.
|
|
65
|
+
|
|
66
|
+
This ADR records the policy. The individual instance fixes follow it: #121 turns
|
|
67
|
+
the qualifying capabilities into optional rows, and #122 deletes
|
|
68
|
+
`tools.exposeDoctor` / `tools.exposeRecovery`, removes the `exposeOperational`
|
|
69
|
+
master switch and the whole `tools` config group. While that migration was in
|
|
70
|
+
flight the two `tools.expose*` keys that gated the health and recovery contracts
|
|
71
|
+
were recorded as dated debt in `scripts/component-surface.json`, so a *new*
|
|
72
|
+
switch failed the gate immediately even before #122 landed.
|
|
73
|
+
|
|
74
|
+
## Migration (2.4.0): the #65 policy is restored from the carrier
|
|
75
|
+
|
|
76
|
+
- Date: 2026-10-23
|
|
77
|
+
- Status: implemented by #121 and #122
|
|
78
|
+
|
|
79
|
+
The one `tower-bundle` group is gone. `cordis.patch.yml` now ships the mandatory
|
|
80
|
+
`tower` host anchor (the Host plugin and the worker preset are its internal
|
|
81
|
+
sections) plus the individually switchable top-level rows `tower-skills`,
|
|
82
|
+
`tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-hmr`
|
|
83
|
+
(shipped off) and `preset-tower-reviewer` (conditional on
|
|
84
|
+
`review.requireReview`). The visible component count moved 1 → 8 and is recorded
|
|
85
|
+
as a dated decision in `scripts/surface-baseline.json`.
|
|
86
|
+
|
|
87
|
+
The capability switches became rows: the seven `tools.expose*` keys and
|
|
88
|
+
`dev.hotReload` were deleted from the schema and are now rejected as unknown
|
|
89
|
+
keys. `tower_doctor` and `tower_recover` are registered for every configuration
|
|
90
|
+
and have no switch; the `gateableTools` allowlist is empty. This is the
|
|
91
|
+
carrier's promised instance fix, not a new policy: #120 wrote the rule down, and
|
|
92
|
+
this wave makes the shipped surface obey it instead of carrying debt.
|
|
93
|
+
|
|
94
|
+
## Consequences
|
|
95
|
+
|
|
96
|
+
- The user can control every capability that legitimately has an off state, from
|
|
97
|
+
the place components are controlled, instead of editing YAML.
|
|
98
|
+
- The host, the diagnostics and the lock escape are always registered; a failure
|
|
99
|
+
message may safely point at `tower_recover`.
|
|
100
|
+
- A component count change now needs a written decision, so the 2.3.0 4 → 1
|
|
101
|
+
contraction can never recur silently; the 1 → 8 restoration is recorded the
|
|
102
|
+
same way.
|
|
103
|
+
- The 2.3.0 debt is paid: `tools.exposeDoctor`, `tools.exposeRecovery` and the
|
|
104
|
+
`exposeOperational` master switch are gone, so no configuration can remove the
|
|
105
|
+
health contract or the lock escape. The `gateableTools` allowlist stays in the
|
|
106
|
+
source of truth, empty, so the next `tools.expose*` route is refused rather
|
|
107
|
+
than legalised by default.
|
|
108
|
+
- Adding a component costs a locale row in two languages and a fallback sentence
|
|
109
|
+
in the source of truth; that is the intended marginal cost.
|
|
110
|
+
|
|
111
|
+
## Rejected alternatives
|
|
112
|
+
|
|
113
|
+
- **One all-or-nothing group row (the shipped 2.3.0 shape).** Simple, and it
|
|
114
|
+
removes the orphan-mount hazard, but it removes every user control as well:
|
|
115
|
+
the qualifying capabilities become unreachable from the plugin page. It also
|
|
116
|
+
makes the "off state" question vacuous, because the only off state is
|
|
117
|
+
"uninstalled".
|
|
118
|
+
- **Config-key switches instead of components.** `tools.expose*` and
|
|
119
|
+
`dev.hotReload` carry the same capabilities with none of the discoverability,
|
|
120
|
+
and they make safety nets disableable in a place where no label warns about it.
|
|
121
|
+
A config key is a switch with worse labelling, not an absence of a switch.
|
|
122
|
+
- **Per-tool components.** 29 rows is a worse surface than 29 tools, and it turns
|
|
123
|
+
every implementation detail of the tool registry into a user decision.
|
|
124
|
+
- **A denylist-only rule with no component count.** It would catch a forbidden
|
|
125
|
+
switch but not the 4 → 1 contraction, which is the failure that actually
|
|
126
|
+
happened; the count baseline is the part that keeps the policy alive across the
|
|
127
|
+
next refactor.
|
|
128
|
+
- **A GitHub issue label or a docs-only policy.** The brake must run offline in
|
|
129
|
+
`npm test` and in CI without a token, and the policy must survive the closure
|
|
130
|
+
of the issue that introduced it.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Architecture decision records
|
|
2
|
+
|
|
3
|
+
An ADR is a dated record of a decision that is already load-bearing. It is not a
|
|
4
|
+
proposal: the status says whether the decision is accepted, and the rejected
|
|
5
|
+
alternatives exist so a future reader can see what was actually weighed.
|
|
6
|
+
|
|
7
|
+
The engineering principles that these records serve are in
|
|
8
|
+
[../PRINCIPLES.md](../PRINCIPLES.md). `npm run docs:check:philosophy` requires at
|
|
9
|
+
least three numbered ADRs, each with Context, Decision, Consequences, Rejected
|
|
10
|
+
alternatives, Date and Status.
|
|
11
|
+
|
|
12
|
+
| ADR | Decision | Date |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| [0001](0001-ports-adapters-as-data.md) | Ports and adapters are data, not a dependency-injection container | 2026-10-23 |
|
|
15
|
+
| [0002](0002-single-state-json.md) | One `state.json` is the mission persistence contract | 2026-10-23 |
|
|
16
|
+
| [0003](0003-allow-agent-control-default-true.md) | `allowAgentControl` defaults to `true` | 2026-10-23 |
|
|
17
|
+
| [0004](0004-surface-budget.md) | The concept surface is frozen by a committed baseline | 2026-10-23 |
|
|
18
|
+
| [0005](0005-component-surface.md) | Mandatory host plus individually toggleable optional component rows | 2026-10-23 |
|
|
19
|
+
|
|
20
|
+
## Template
|
|
21
|
+
|
|
22
|
+
```markdown
|
|
23
|
+
# NNNN — Decision title
|
|
24
|
+
|
|
25
|
+
- Date: YYYY-MM-DD
|
|
26
|
+
- Status: Proposed | Accepted | Superseded by NNNN
|
|
27
|
+
- Deciders: <who>
|
|
28
|
+
|
|
29
|
+
## Context
|
|
30
|
+
|
|
31
|
+
What forced the decision.
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
What was decided, in one paragraph.
|
|
36
|
+
|
|
37
|
+
## Consequences
|
|
38
|
+
|
|
39
|
+
What becomes easier, what becomes harder, and what is now refused.
|
|
40
|
+
|
|
41
|
+
## Rejected alternatives
|
|
42
|
+
|
|
43
|
+
- **Alternative** — why it was not chosen.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Non-goals
|
|
47
|
+
|
|
48
|
+
These records do not form an RFC queue, an architecture review board, or a
|
|
49
|
+
process for approving ordinary changes.
|
|
50
|
+
|
|
51
|
+
Unowned. This is a documentation convention, not a gate; nothing in CI blocks a
|
|
52
|
+
new ADR or rewards one.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Execution workflow: Matt Pocock skills × Tower
|
|
2
|
+
|
|
3
|
+
This repository uses two workflows together. They are not alternatives: the **Matt Pocock skills** own thinking, planning, review discipline and retros; **Tower** owns delivery — isolated worktrees, scope fences, review gates, merges and durable mission state. **GitHub Issues** is the single source of truth for what is planned, in progress, blocked and shipped.
|
|
4
|
+
|
|
5
|
+
## Who owns what
|
|
6
|
+
|
|
7
|
+
| Stage | Owner | Artifact |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Intake | Matt Pocock `/triage` for raw external reports; milestones + `P*`/`area/*` labels for self-authored findings | issue with owner-facing state |
|
|
10
|
+
| Plan | `/to-spec` → `/to-tickets` (or an existing issue whose acceptance criteria are already ticket-shaped) | spec issue, tickets with native `blocked_by` edges |
|
|
11
|
+
| Execute | `/implement-spec` subagents for one-session waves; **Tower missions** for parallel or long-lived work | integration branch + PRs, or mission worktrees + merge gate |
|
|
12
|
+
| Build each unit | `/tdd` inside the ticket/mission | failing test first, then green |
|
|
13
|
+
| Verify | gates + required CI checks + `/code-review` (Standards + Spec) | green PR with two-axis review |
|
|
14
|
+
| Land | protected `main`, four-line PR body, `verified-by:`/`commit:` closure | merged PR, closed issues |
|
|
15
|
+
| Release | `release:check`, CHANGELOG, COMPATIBILITY, tag; publish only when planned | tag-only or published release |
|
|
16
|
+
| Learn | `/retro`; mechanical findings become checks, judgement becomes standards | CONTRIBUTING/AGENTS/scanner updates |
|
|
17
|
+
|
|
18
|
+
## The flow
|
|
19
|
+
|
|
20
|
+
1. **Intake.** Raw external bug reports and feature requests go through `/triage` (they are not ours). Findings we file ourselves (audits, retros) skip triage and get a priority, an area and a milestone; they are already acceptance-criteria-shaped.
|
|
21
|
+
2. **Plan.** Multi-session work goes `/to-spec` → `/to-tickets`. Tickets are tracer bullets with native blocking edges; the frontier is whatever has no open blockers. Small work may skip the spec when the issue already states acceptance criteria (e.g. a patch ticket).
|
|
22
|
+
3. **Choose the orchestrator.**
|
|
23
|
+
- **One-session wave (≤3 tickets, mostly independent):** `/implement-spec`-style: an integration branch, one implementer subagent per ticket in its own worktree, merger merges to the integration branch, one PR to `main`.
|
|
24
|
+
- **Parallel or long-lived wave (architecture epics, >3 tickets, review needed):** **Tower**. `tower_init` → `tower_plan` with one mission per ticket, `scope` = the ticket's write fence, `deps` = the ticket's blocking edges → `tower_spawn` a worker per mission → optional reviewer (`tower-reviewer`) → `tower_merge` (gate checks scope, branch freshness, review, tasks) → `tower_teardown`. Tower's persistent state makes the wave resumable after a restart.
|
|
25
|
+
- Never run both orchestrators over the same files at once.
|
|
26
|
+
4. **Build.** Each unit starts with `/tdd`: one failing test, then the implementation, then the next slice. Commit messages carry `Refs #n`; the completing commit uses `Fixes #n`. Workers do not merge; they hand off a branch.
|
|
27
|
+
5. **Verify before landing.** Run `npm test`, `npm run docs:check`, all five `docs:check:generated*` (including the generated type vocabulary), `npm run pack:check`, `npm run architecture:check`, `npm run types:coverage`, `npm run types:strict:check`, `npm run types:check` (both tsconfigs), `npm run domain:check`. Bundle changes also run `npm run smoke:mount`. CI must be green on the required checks (`test (22)`, `test (24)`, `test (22, windows)`, `mount`). Then `/code-review` runs both axes on the diff (Standards against CONTRIBUTING/architecture docs, Spec against the ticket); every finding is fixed in the same PR or explicitly deferred with a reason.
|
|
28
|
+
6. **Land.** PR body follows the four-line contract (failure mode → change → test that fails without it → blast radius). `main` is protected: PR + required checks, admins included, no force-push. A closure comment carries `verified-by: <test file>` and `commit: <sha>`; `npm run audit:issues` flags closures whose evidence is missing.
|
|
29
|
+
7. **Release.** `release:check` compares the version, the previous tag and the CHANGELOG; a tag push fails when the version did not move or the tag does not match. Releases are either **tag-only** (version + changelog + compatibility row + annotated tag; no npm publish, no GitHub Release) or **published** (adds the human `npm publish --tag latest` with 2FA, `dist-tags` verification, then the GitHub Release). The mode is recorded in the milestone/plan.
|
|
30
|
+
8. **Learn.** After a release (or a wave that went sideways) run `/retro`: mechanical mistakes become deterministic checks (scripts, CI jobs, preflight), judgement calls become written standards. Periodically run `/improve-codebase-architecture` for deepening opportunities and `/domain-modeling` to keep the vocabulary and ADRs (2.3.0) honest.
|
|
31
|
+
|
|
32
|
+
## Decision table
|
|
33
|
+
|
|
34
|
+
| Situation | Flow |
|
|
35
|
+
|---|---|
|
|
36
|
+
| Raw external issue | `/triage` → then plan |
|
|
37
|
+
| Design question you cannot settle on paper | `/grill-with-docs`, detour through `/prototype` if it needs a runnable answer |
|
|
38
|
+
| Multi-session feature | `/to-spec` → `/to-tickets` → `/implement-spec` |
|
|
39
|
+
| Small fix with acceptance criteria | ticket → `/implement` (+ `/tdd`) |
|
|
40
|
+
| Architecture epic / parallel missions | Tower plan → missions → review gate → merge |
|
|
41
|
+
| Something broken, cause unknown | `/diagnosing-bugs` (tight red loop first) |
|
|
42
|
+
| Repeated mistakes | `/retro` → checks/standards |
|
|
43
|
+
| Release | release plan: tag-only or publish |
|
|
44
|
+
|
|
45
|
+
## Invariants
|
|
46
|
+
|
|
47
|
+
- One ticket per PR series; a branch never touches two tickets.
|
|
48
|
+
- Tracked blocking edges, not prose: work only the frontier.
|
|
49
|
+
- Tower missions: `scope` is the write fence, a mission that outgrows it is re-scoped, not silently widened; the workspace lock keeps one Tower session per repository.
|
|
50
|
+
- Human-only gates: npm publish/2FA, branch-protection changes, anything behind a third-party credential. Stop and hand over rather than inventing a credential path.
|
|
51
|
+
- Context hygiene: keep planning in one window until tickets exist; start each implementation unit from the ticket, not from the previous unit's context.
|
|
@@ -2,36 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
## Components
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Since #121 the bundle ships a mandatory host anchor plus individually switchable
|
|
6
|
+
optional rows: `cordis.patch.yml` declares top-level `tower` (a `cordis:group`
|
|
7
|
+
that mounts the Host plugin and the mandatory worker preset), then
|
|
8
|
+
`tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`,
|
|
9
|
+
`tower-hmr` (shipped off) and `preset-tower-reviewer`. Turning one optional row
|
|
10
|
+
off removes exactly that capability; the host, the diagnostics and the recovery
|
|
11
|
+
escape keep working.
|
|
12
|
+
|
|
13
|
+
[Principle 11](../../PRINCIPLES.md) and
|
|
14
|
+
[ADR 0005](../adr/0005-component-surface.md) say a component may exist only for
|
|
15
|
+
a capability whose off state leaves the rest intact, and that the host, health,
|
|
16
|
+
recovery, review and merge-gate capabilities are never switches. Which keys are
|
|
17
|
+
allowed to be switches is a table lookup in
|
|
18
|
+
[component-surface.md](../reference/component-surface.md), generated from
|
|
19
|
+
`scripts/component-surface.json`; the component count is baselined and checked by
|
|
20
|
+
`npm run docs:check:components`. That gate also refuses a `tools.expose*` route
|
|
21
|
+
to a tool that is not on the recorded allowlist (empty since #122) and any
|
|
22
|
+
`cordis.patch.yml` row that defaults a forbidden key off.
|
|
9
23
|
|
|
10
24
|
```
|
|
11
25
|
Host plugin (index.js)
|
|
12
|
-
├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control
|
|
13
|
-
├─
|
|
14
|
-
│ (+
|
|
15
|
-
│ (+
|
|
26
|
+
├─ /tower command + Tower mode (guard, prompt section, tower_mode/tower_control)
|
|
27
|
+
├─ host/tool-registry.js -> registration aggregator, actor guards, shared helpers
|
|
28
|
+
│ (+ 21 define()-registered host tools)
|
|
29
|
+
│ (+ tower_mode/tower_control registered by the Host entry, 29 total
|
|
30
|
+
│ once every optional capability row is on)
|
|
31
|
+
│ (tower_doctor/tower_recover are host tools, never switches)
|
|
32
|
+
├─ host/capability-*.js -> optional capability entry points (batch/import/leases/tests/hmr)
|
|
16
33
|
├─ tools/index.js -> thin public wiring (re-exports the registry + briefings)
|
|
17
34
|
└─ app/tower.js -> orchestration surface; delegates to use-case modules
|
|
18
35
|
├─ app/init.js, provision.js, merge.js, rebase.js, teardown.js
|
|
19
36
|
├─ app/review.js, findings.js, batch.js, messages.js, roster.js
|
|
20
37
|
├─ app/plan.js, mission.js, checkCompletion.js, spawn.js, recording.js
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
├─ app/spawn-prechecks.js, spawn-support.js, briefing.js
|
|
39
|
+
└─ app/archive.js, gate.js, read.js, board.js, missionContext.js, enrichedMissions.js
|
|
40
|
+
host (composition root + process/session glue; the only layer that may hold ambient state):
|
|
41
|
+
host/tool-registry.js registration aggregator + shared guards/helpers
|
|
42
|
+
host/config.js profile/repo config layers over the pure schema
|
|
43
|
+
host/doctor.js read-only environment contract and health checks
|
|
44
|
+
host/capability-registry.js mounted-component report for status/doctor
|
|
45
|
+
host/hmr.js optional DSH HMR bridge (tower_reload / /tower reload)
|
|
46
|
+
host/modeStore.js per-agent Tower mode persistence
|
|
47
|
+
host/modeState.js owned in-memory mode state
|
|
48
|
+
host/presets.js cross-field preset validation before work starts
|
|
49
|
+
host/compatibility.js COMPATIBILITY.md provenance
|
|
50
|
+
host/tower.js default-deps Tower binding for tests/embedders
|
|
51
|
+
ports (explicit runtime contracts, no implementation):
|
|
52
|
+
ports/contract.js definePort/assertPort runtime contract primitives
|
|
53
|
+
ports/{store,vcs,clock,ids,config,state,ownership,caseSensitivity,archive}.js
|
|
54
|
+
ports/index.js PORTS table + assertPorts(deps) injection check
|
|
55
|
+
adapters (the only I/O; they implement the ports):
|
|
56
|
+
adapters/store-fs.js the only module importing node:fs; createStorePort()
|
|
57
|
+
adapters/vcs-git.js the only module importing node:child_process; createVcsPort()
|
|
58
|
+
adapters/git.js bounded git commands + timeout classification
|
|
59
|
+
adapters/store.js repo-scoped lock + atomic JSON + activity.jsonl
|
|
60
|
+
adapters/persistence.js shared atomic write / lock / heartbeat primitives
|
|
61
|
+
adapters/archive.js terminal-mission archive files
|
|
62
|
+
adapters/leases.js repo-scoped advisory lease ledger
|
|
63
|
+
adapters/workspaceLock.js optional one-Tower-session-per-repository lock
|
|
64
|
+
adapters/worktreeOwnership.js worktree identity verification
|
|
65
|
+
adapters/stateFile.js state-file load/migrate/persist
|
|
66
|
+
adapters/caseSensitivity.js filesystem case-folding probe
|
|
67
|
+
adapters/legacy.js read-only legacy snapshot validation + storage
|
|
68
|
+
adapters/tests.js test discovery execution + TAP/JSON parsing
|
|
69
|
+
adapters/default.js createDefaultDeps(): the one default port implementation set
|
|
35
70
|
pure domain modules (no fs/git):
|
|
36
71
|
domain/errors.js stable, structural tool error catalog
|
|
37
72
|
domain/batch.js batch admission create/close/summary validation
|
|
@@ -48,7 +83,6 @@ Host plugin (index.js)
|
|
|
48
83
|
domain/report.js board rendering
|
|
49
84
|
domain/roster.js roster predicates
|
|
50
85
|
tools layer:
|
|
51
|
-
lib/tool-registry.js registration aggregator + shared guards/helpers
|
|
52
86
|
tools/index.js thin public wiring (re-exports only)
|
|
53
87
|
tools/*.js one module per tool, handler + imported input schema
|
|
54
88
|
Subagents
|
|
@@ -61,6 +95,54 @@ The main checkout is the control-tower repo; each mission has a branch `tower/<i
|
|
|
61
95
|
worktree in `.dsh-tower-worktrees/<hash>/<missionId>`. The plugin never operates on the main checkout's
|
|
62
96
|
working tree except merge and the read-only diff.
|
|
63
97
|
|
|
98
|
+
## Ports and the composition root (#69)
|
|
99
|
+
|
|
100
|
+
The seam is real, not documented-only. `ports/` exports **runtime contracts**: each module defines a
|
|
101
|
+
frozen `{ name, methods }` descriptor plus an `assert*Port` helper, and `ports/index.js` exposes the
|
|
102
|
+
`PORTS` table and `assertPorts(deps)`.
|
|
103
|
+
|
|
104
|
+
`app/**` imports only app siblings, `domain/**`, `ports/**` and `node:path`. Every effectful call goes
|
|
105
|
+
through the `deps` object:
|
|
106
|
+
|
|
107
|
+
| deps member | contract | default implementation |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `store` | StorePort: `pathsFor/exists/ensureDir/readJson/writeJsonAtomic/appendJsonl/withLock/withGitLock/readActivityTail` plus the raw-file and lock primitives `readText/writeText/appendText/rename/renameWithRetry/stat/unlink/readGitLock/gitLockView` | `adapters/store-fs.js` `createStorePort()` |
|
|
110
|
+
| `vcs` | VcsPort: `git/tryGit/revParse/isClean/isAncestor/resolveRepo/currentBranch/countCommits/listWorktrees` | `adapters/vcs-git.js` `createVcsPort()` |
|
|
111
|
+
| `clock`, `ids` | ClockPort / IdsPort | `adapters/default.js` |
|
|
112
|
+
| `config` | ConfigPort: `runtimeConfig/resolveRepoConfig` | `host/config.js` |
|
|
113
|
+
| `state` | StatePort: `loadStateFile` | `adapters/stateFile.js` |
|
|
114
|
+
| `archive` | ArchivePort: `readIndex/readEntry/writeEntries` | `adapters/archive.js` |
|
|
115
|
+
| `ownership` | OwnershipPort: `assertOwnership/canonicalPath` | `adapters/worktreeOwnership.js` |
|
|
116
|
+
| `caseSensitivity` | CaseSensitivityPort: `detect` | `adapters/caseSensitivity.js` |
|
|
117
|
+
|
|
118
|
+
`index.js` is the composition root: it calls `createDefaultDeps()` once and passes the object to
|
|
119
|
+
`registerTowerTools`. The registry adopts and validates that object (`assertPorts`) instead of building
|
|
120
|
+
its own adapter set, hands every tool an `openTower(repoRoot)` factory bound to those ports, and
|
|
121
|
+
`Tower.open(repoRoot, deps)` / `new Tower(repo, config, deps)` validates the object again before use.
|
|
122
|
+
`app/tower.js` itself imports no adapter and no `host/` module. The one piece of lock policy the app and
|
|
123
|
+
the store share (the git-mutex budget, #93) is pure and lives in `domain/lockBudget.js`;
|
|
124
|
+
`adapters/store.js` re-exports it for the infrastructure callers.
|
|
125
|
+
|
|
126
|
+
`store.rename` and `store.renameWithRetry` are both declared because they are not interchangeable: the
|
|
127
|
+
retrying helper has temp-file semantics and removes its source when the rename finally fails, which is
|
|
128
|
+
right for a disposable temp file but would destroy the lock bytes `tower_recover` must keep auditable.
|
|
129
|
+
The audit rename therefore uses the plain `rename`. The contract test scans `app/**` **and** `tools/**`
|
|
130
|
+
for every `deps.<port>.<method>` call and fails if the named port does not declare it, so a tool that
|
|
131
|
+
reaches a new port method cannot slip past `assertPorts`.
|
|
132
|
+
|
|
133
|
+
`lib/` no longer exists (#70): every module was `git mv`-ed into the layer that owns it. The I/O
|
|
134
|
+
implementations now sit in `adapters/` (`store`, `persistence`, `git`, `archive`, `stateFile`,
|
|
135
|
+
`leases`, `workspaceLock`, `worktreeOwnership`, `caseSensitivity`, `legacy`, `tests`), the
|
|
136
|
+
composition root and process/session glue in `host/`, and spawn policy in `app/`. `host/**` is the
|
|
137
|
+
only source root, besides `adapters/**` itself, allowed to import an adapter; `app/**` and `domain/**`
|
|
138
|
+
may not import `lib/**` (rule `no-lib-from-core`) and may not hold ambient module state
|
|
139
|
+
(`host-scope`). Tools receive effectful helpers from the registry instead of importing an adapter.
|
|
140
|
+
|
|
141
|
+
A full mission lifecycle (init → plan → provision → spawn → complete → review → gate → merge →
|
|
142
|
+
teardown) runs through the real app use cases on an in-memory store and a scripted vcs:
|
|
143
|
+
`tests/ports-lifecycle.test.mjs`. The default adapter set and the in-memory doubles are both asserted
|
|
144
|
+
against the same port contracts in `tests/memory-adapters.contract.test.mjs`.
|
|
145
|
+
|
|
64
146
|
## Mission state machine
|
|
65
147
|
|
|
66
148
|
```
|
|
@@ -82,7 +164,7 @@ set by `tower_merge`/reconciliation.
|
|
|
82
164
|
against the merge base.
|
|
83
165
|
3. Survey missions must not change files; build missions must stay inside `scope`.
|
|
84
166
|
4. The main checkout must be clean for the files this merge would overwrite and on the base branch.
|
|
85
|
-
5. Dependency missions must be merged/abandoned.
|
|
167
|
+
5. Dependency missions must be merged/abandoned, except a completed survey dependency which is its terminal success state (#107).
|
|
86
168
|
6. If `review.requireReview:true` (default false), a review must exist, belong to this mission, be
|
|
87
169
|
clean, approve merge, and be bound to the tip (unless a clean rebase waived it). No worker may be
|
|
88
170
|
running.
|
|
@@ -105,7 +187,7 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
105
187
|
| `<git-common-dir>/dsh-tower/archive/index.json` | compact terminal-mission index (ids, status, branch, timestamps) |
|
|
106
188
|
| `<git-common-dir>/dsh-tower/archive/<year>.json` | full archived missions plus their linked reviews/findings |
|
|
107
189
|
| `<git-common-dir>/dsh-tower/activity.jsonl` | append-only activity |
|
|
108
|
-
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (
|
|
190
|
+
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (heartbeat reclaim; a recycled PID cannot pin it) |
|
|
109
191
|
| `<git-common-dir>/dsh-tower/git.lock` | repo-scoped serialization for git mutations outside the state lock |
|
|
110
192
|
| `<git-common-dir>/dsh-tower/lock-holds.jsonl` | state/git lock-hold telemetry |
|
|
111
193
|
| `<checkout parent>/.dsh-tower-worktrees/<hash>/<missionId>` | mission worktree |
|
|
@@ -113,8 +195,10 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
113
195
|
| `$DSH_HOME/tower-mode/modes.lock` | mode-store single-writer lock |
|
|
114
196
|
|
|
115
197
|
`state.json` and `modes.json` both use an atomic write (temp + rename), token-checked lock release,
|
|
116
|
-
|
|
117
|
-
|
|
198
|
+
heartbeat-based reclamation (a live PID with a silent heartbeat is reclaimed after byte verification;
|
|
199
|
+
a live holder that keeps heartbeating is never stolen), and bounded lock waits. `state.json` is the
|
|
200
|
+
mission persistence contract; `modes.json` is the per-agent mode contract; neither is written without
|
|
201
|
+
its own lock.
|
|
118
202
|
|
|
119
203
|
## Layering rules
|
|
120
204
|
|
|
@@ -123,17 +207,66 @@ dead-PID reclamation, and bounded lock waits. `state.json` is the mission persis
|
|
|
123
207
|
- `domain/**` must not import I/O or adapters (`domain-pure`).
|
|
124
208
|
- Only `adapters/vcs-git.js` may import `node:child_process`, and only
|
|
125
209
|
`adapters/store-fs.js` may import `node:fs` (`child-process-surface`, `fs-surface`).
|
|
126
|
-
The rule is enforced over the source roots — `
|
|
210
|
+
The rule is enforced over the source roots — `host/`, `app/`, `domain/`, `tools/`,
|
|
127
211
|
`adapters/`, `ports/` — and does not cover `scripts/**`, which holds maintenance
|
|
128
212
|
entry points (release check, mount smoke, docs generators) that may spawn git or npm.
|
|
213
|
+
The optional Web client (`ui-bundle/`) is its own tier with its own rules.
|
|
129
214
|
- `tools/**` must stay thin and free of status/type literals (`tools-thin`, `tools-vocabulary`);
|
|
130
|
-
`tools/index.js` is the thin public wiring module
|
|
131
|
-
while registration/shared machinery lives in `
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
215
|
+
`tools/index.js` is the thin public wiring module (it imports `host/tool-registry.js`, not a
|
|
216
|
+
`node:` module), while registration/shared machinery lives in `host/tool-registry.js`.
|
|
217
|
+
Tools receive effectful helpers (repository resolution, leases, recovery, legacy import) from
|
|
218
|
+
the registry instead of importing an adapter.
|
|
219
|
+
- Aggregate files have size ceilings; top-level mutable state is limited to `host/**`
|
|
220
|
+
(`aggregate-size`, `host-scope`). The per-tool ceiling is 200 lines; `tools/index.js`
|
|
221
|
+
currently sits inside the 40-200 range, so the tools allow-list is empty rather than silently
|
|
222
|
+
exempting the wiring module.
|
|
223
|
+
- `app/**` may import only app siblings, `domain/**`, `ports/**` and `node:path` (`app-ports-only`);
|
|
224
|
+
an app module that reaches an adapter, a `host/` module or `lib/` fails the gate.
|
|
225
|
+
- `app/**` and `domain/**` may not import the retired `lib/` layer (`no-lib-from-core`); with the
|
|
226
|
+
layer gone this keeps it from creeping back.
|
|
227
|
+
- `host/**` is the only source root that may hold ambient module state (`host-scope`);
|
|
228
|
+
`app/**` and `domain/**` must stay stateless at module scope. Only `host/**` and `adapters/**`
|
|
229
|
+
may import an adapter (`composition-root`); `app/`, `tools/`, `domain/` and `ports/` never may.
|
|
230
|
+
- Every `ports/**` module must export a runtime contract (`const`/`function`/`class`) and must not
|
|
231
|
+
import an implementation (`port-shape`); a typedef plus `export {}` is no longer accepted.
|
|
232
|
+
- `ui-bundle/**` is a browser tier, not host code (#71): no `node:*` import
|
|
233
|
+
(`client-no-node` — DOM globals are fine; host capabilities must come through the
|
|
234
|
+
plugin's registration surface), every client file stays at or below 800 lines
|
|
235
|
+
(`client-size`), and module-level mutable state is banned exactly as it is for
|
|
236
|
+
`app/**` (`client-ambient`); the split surfaces keep their state inside the
|
|
237
|
+
`create*` closure the entry composes. `ui-bundle/` is also excluded from
|
|
238
|
+
`aggregate-size`, which it would otherwise duplicate.
|
|
239
|
+
|
|
240
|
+
### Exemptions are justified data
|
|
241
|
+
|
|
242
|
+
The gate's only exemptions live in `scripts/architecture-waivers.json`; `scripts/architecture.mjs`
|
|
243
|
+
keeps no allow-lists of its own. Since #70 the file has exactly two entries, both I/O adapter
|
|
244
|
+
boundaries (`adapters/vcs-git.js` for `node:child_process` and `adapters/store-fs.js` for
|
|
245
|
+
`node:fs`). Each entry names the `rule` and `path` and must carry a non-empty `reason` plus an
|
|
246
|
+
`owner` or `ref`. An entry missing any of those is itself a blocking `waiver-contract` violation
|
|
247
|
+
that names the rule and the path, so a bare path cannot turn the gate green. The waiver file is a
|
|
248
|
+
two-key path in `CONTRIBUTING.md`; there is no `reviewBy`/expiry field in this release, so the
|
|
249
|
+
reason and the owner are the reviewed decision.
|
|
250
|
+
|
|
251
|
+
`host-scope` replaces the old `ambient-state` waivers (#70). It scans `app/**` and `domain/**` and
|
|
252
|
+
records every module-level `let`; `host/**` owns the process/session state and needs no waiver. The
|
|
253
|
+
scan is statement-aware rather than line-based: it walks the module's token stream and records every
|
|
254
|
+
binding of every top-level `let` declaration. A second declarator (`let a = 1, b = 2`), a second
|
|
255
|
+
statement on one line (`let a = 1; let b = 2`), and any new top-level `let` in `app/` or `domain/`
|
|
256
|
+
are each their own violation. Strings, comments, template literals and regex literals are skipped,
|
|
257
|
+
and `let` inside a function, block, class or `for (…)` header is not module-level state.
|
|
135
258
|
|
|
136
|
-
|
|
259
|
+
`domain-pure`, `host-scope`, `no-lib-from-core`, and anything under `domain/**` are load-bearing: no
|
|
260
|
+
waiver may target them, even a justified one. A `waiver-contract` violation rejects that entry and
|
|
261
|
+
the underlying rule still fails.
|
|
262
|
+
|
|
263
|
+
A green run prints `architecture: ok (waivers: N)`; N is the number of entries that actually
|
|
264
|
+
suppressed a violation, so a stale waiver is visible in the output and in the tests. The fixture
|
|
265
|
+
suite seeds an unjustified waiver (it fails), a justified one (it passes), a `host-scope` fixture
|
|
266
|
+
that puts ambient state in `host/` (allowed) and in `app/`/`domain/` (rejected), a `no-lib-from-core`
|
|
267
|
+
import from `app/` (rejected), a same-line second binding (rejected), and a new violation inside
|
|
268
|
+
each rule. A separate fixture seeds one violation per rule, so a rule that cannot fail is itself a
|
|
269
|
+
test failure.
|
|
137
270
|
|
|
138
271
|
## Mount smoke and host internals
|
|
139
272
|
|
|
@@ -149,24 +282,40 @@ probe adjustments in the same change.
|
|
|
149
282
|
|
|
150
283
|
All epic DoD items now have an executable gate:
|
|
151
284
|
|
|
152
|
-
- Pure domain share: **
|
|
153
|
-
`npm run domain:check` (threshold 35%).
|
|
154
|
-
|
|
285
|
+
- Pure domain share: **39.5%** (7,475 / 18,936 host source lines), enforced by
|
|
286
|
+
`npm run domain:check` (threshold 35%). `npm run domain:metrics` also prints a
|
|
287
|
+
separate `ui-bundle` line, so Web-client growth is visible in CI output while the
|
|
288
|
+
share itself still measures the Host architecture.
|
|
289
|
+
- `domain/**` imports no fs, child_process, adapters, app, tools, or host infrastructure;
|
|
155
290
|
`domain-pure` is a blocking architecture rule.
|
|
156
291
|
- Inside the source roots, only `adapters/vcs-git.js` imports `node:child_process`; only
|
|
157
|
-
`adapters/store-fs.js` imports `node:fs`; both are blocking rules
|
|
158
|
-
-
|
|
292
|
+
`adapters/store-fs.js` imports `node:fs`; both are blocking rules whose files are the only
|
|
293
|
+
`child-process-surface`/`fs-surface` entries in `scripts/architecture-waivers.json`.
|
|
294
|
+
- `host`, `app`, and `domain` files stay <= 800 lines; the same ceiling applies to
|
|
295
|
+
`ui-bundle/**` (`client-size`); every `tools/**` file stays
|
|
159
296
|
40-200 lines (one handler module per tool; `tools/index.js` is 40-line wiring).
|
|
160
297
|
- No `withLock` scope starts a process; the two-phase tests instrument every git call.
|
|
161
298
|
- `tools/**` has no status/type/enum literal; vocabulary is owned by
|
|
162
299
|
`domain/vocabulary.js`; the `tools-vocabulary` rule is blocking.
|
|
163
300
|
- `validateState` has a four-line body over `domain/state/schema.js` and
|
|
164
301
|
`domain/state/semantics.js`.
|
|
165
|
-
- `state.json` and `modes.json` share `
|
|
302
|
+
- `state.json` and `modes.json` share `adapters/persistence.js` atomic write, token-owned
|
|
166
303
|
lock, dead-PID quarantine, and bounded-wait primitives.
|
|
167
|
-
- The in-memory lifecycle
|
|
168
|
-
|
|
169
|
-
|
|
304
|
+
- The in-memory lifecycle runs the real app use cases (init → plan → provision → spawn → complete →
|
|
305
|
+
review → gate → merge → teardown) on an in-memory store and scripted vcs supplied through the
|
|
306
|
+
composition root, with no disk, no git and no `defaultDeps` (#69).
|
|
307
|
+
- The eleven host fitness rules are errors, each with a seeded failing fixture; the
|
|
308
|
+
three `client-*` rules form the Web-client tier with one fixture each, and the
|
|
309
|
+
`waiver-contract` rule makes an unjustified exemption blocking as well.
|
|
310
|
+
|
|
311
|
+
## Non-goals
|
|
312
|
+
|
|
313
|
+
This layer map is not a dependency-injection container, a service locator, or a
|
|
314
|
+
place to register classes by reflection. Ports stay runtime contracts and the
|
|
315
|
+
composition root stays the only place that assembles implementations.
|
|
316
|
+
|
|
317
|
+
Enforced by: `scripts/architecture.mjs` and `ports/index.js`; tests in
|
|
318
|
+
`tests/architecture.test.mjs`; gate: `npm run architecture:check`.
|
|
170
319
|
|
|
171
320
|
|
|
172
321
|
|
package/docs/guide/hot-reload.md
CHANGED
|
@@ -26,8 +26,8 @@ This page covers the development loop. It is not required to use Tower.
|
|
|
26
26
|
`plugin_manager`); it compares already-loaded modules with disk and replaces the changed ones even
|
|
27
27
|
with `root: []`. Watching only makes that happen automatically after a save.
|
|
28
28
|
- HMR is only mounted for profile boots (`disabled` when there is no `profileContext`). A process
|
|
29
|
-
booted without a profile reports reload as unavailable (`tower_reload` stays registered while
|
|
30
|
-
`
|
|
29
|
+
booted without a profile reports reload as unavailable (`tower_reload` stays registered while the
|
|
30
|
+
`tower-hmr` component row is on, so the tool returns `reload-unavailable` instead of disappearing).
|
|
31
31
|
- The profile patch layer is loaded when DSH boots. Editing it — in practice the hmr `root` list —
|
|
32
32
|
takes effect after **one DSH restart**. Nothing in the running process re-reads the HMR row's own
|
|
33
33
|
configuration.
|
|
@@ -86,7 +86,7 @@ To stop watching, run `npm run dev:hmr -- --remove` and restart DSH once.
|
|
|
86
86
|
|
|
87
87
|
## The edit loop
|
|
88
88
|
|
|
89
|
-
1. Edit `
|
|
89
|
+
1. Edit `host/*.js`, `adapters/*.js`, `app/*.js`, `domain/**/*.js`, `index.js`, `worker.js`, `reviewer.js` or `skills.js`.
|
|
90
90
|
2. Save the file. The watcher debounces (DSH default 100 ms) and reconciles the profile; changed
|
|
91
91
|
modules are replaced and the affected Loader entries restart.
|
|
92
92
|
3. Watch the DSH log for `reload plugin at <path>` / `restart required: …` messages.
|
|
@@ -99,9 +99,9 @@ The bridge behind the tool follows the `dev` config group:
|
|
|
99
99
|
|
|
100
100
|
| Config key | Meaning |
|
|
101
101
|
|---|---|
|
|
102
|
-
| `
|
|
102
|
+
| `tower-hmr` (component row) | The switch: on registers `tower_reload` and `/tower reload`, off reports "disabled by component tower-hmr". The row ships off; enable it from the plugin page for a linked dev profile. `dev.hotReload` was removed in 2.4.0 and is now an unknown key. |
|
|
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
|
|
104
|
+
| `dev.reloadRequiredIds` | Loader entry ids the profile must keep active (default `tower`, `tower-host`, `preset-tower-worker`). An inactive required entry is reported as a reload failure even if it was already inactive. Optional capability rows are deliberately not required, so a row switched off is not a reload failure. |
|
|
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
|
|
@@ -45,6 +45,19 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
|
|
|
45
45
|
overlapping and case-only mismatches are no longer refused by the out-of-scope gate. Stored scope
|
|
46
46
|
strings are never rewritten: the board keeps showing what the operator typed.
|
|
47
47
|
|
|
48
|
+
- **2.3.0 → 2.4.0 (unreleased)**: the single `tower-bundle` group row is gone. `cordis.patch.yml`
|
|
49
|
+
now ships the mandatory `tower` host anchor (the Host plugin and the worker preset are its internal
|
|
50
|
+
sections) plus individually switchable top-level rows: `tower-skills`, `tower-batch`, `tower-import`,
|
|
51
|
+
`tower-leases`, `tower-tests`, `tower-hmr` (shipped off) and `preset-tower-reviewer` (conditional on
|
|
52
|
+
`review.requireReview`). The `tools` config group and `dev.hotReload` were removed: a profile that
|
|
53
|
+
still carries them is now rejected as an unknown key (`config-unknown-key`) and the host stops
|
|
54
|
+
mounting until they are deleted, so no silent no-op switch remains. Move each capability to its row
|
|
55
|
+
in the plugin page instead; `tower-hmr` ships off, so `tower_reload` and `/tower reload` are reported
|
|
56
|
+
as "disabled by component tower-hmr" until you enable it for a linked dev profile. `tower_doctor` and
|
|
57
|
+
`tower_recover` are registered for every configuration and need no key. The reviewer row may be off
|
|
58
|
+
only while `review.requireReview` is false; with the gate on, planning and mode entry refuse the
|
|
59
|
+
combination with an explanatory error. No state migration is needed.
|
|
60
|
+
|
|
48
61
|
- **2.0.4 → 2.1.0-beta.1**: no config migration; the state file migrates automatically (v1/v2 → v3) as
|
|
49
62
|
described above. This is a **pre-release** published on the
|
|
50
63
|
`next` dist-tag, so `latest` still resolves to 2.0.4 — install it with
|
package/docs/guide/release.md
CHANGED
|
@@ -58,7 +58,7 @@ Put the **host build in cell 0** and the **tag in cell 1**:
|
|
|
58
58
|
| 0.2.1-rc.2 | v2.1.0-beta.1 | 2026-10-22 | `npm test`, `npm run docs:check`, mount smoke |
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
`
|
|
61
|
+
`host/compatibility.js#parseCompatibility` reads cells 0–3 only: never insert a column before
|
|
62
62
|
the tag and never add one before "what was run", or `tower_doctor` stops seeing the row. The
|
|
63
63
|
host pin in `scripts/mount-smoke/host.json` must equal cell 0 of the newest row (a unit test
|
|
64
64
|
enforces it).
|