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.
Files changed (203) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/COMPATIBILITY.md +6 -1
  3. package/CONTRIBUTING.md +78 -5
  4. package/PRINCIPLES.md +163 -0
  5. package/README.en.md +35 -19
  6. package/README.md +28 -18
  7. package/SECURITY.md +6 -0
  8. package/{lib → adapters}/archive.js +1 -1
  9. package/{lib → adapters}/caseSensitivity.js +1 -1
  10. package/adapters/default.js +51 -10
  11. package/{lib → adapters}/git.js +1 -1
  12. package/{lib → adapters}/leases.js +1 -1
  13. package/{lib → adapters}/legacy.js +1 -1
  14. package/adapters/persistence.js +543 -0
  15. package/adapters/store-fs.js +28 -1
  16. package/adapters/store.js +204 -0
  17. package/{lib → adapters}/tests.js +9 -5
  18. package/adapters/vcs-git.js +15 -1
  19. package/{lib → adapters}/workspaceLock.js +1 -1
  20. package/{lib → adapters}/worktreeOwnership.js +1 -1
  21. package/app/archive.js +4 -6
  22. package/app/batch.js +9 -10
  23. package/app/board.js +2 -4
  24. package/app/checkCompletion.js +9 -10
  25. package/app/enrichedMissions.js +2 -4
  26. package/app/findings.js +6 -7
  27. package/app/gate.js +1 -2
  28. package/app/init.js +24 -29
  29. package/app/merge.js +58 -51
  30. package/app/messages.js +7 -8
  31. package/app/mission.js +5 -5
  32. package/app/plan.js +8 -5
  33. package/app/provision.js +12 -14
  34. package/app/read.js +6 -3
  35. package/app/rebase.js +14 -16
  36. package/app/recording.js +7 -8
  37. package/app/review.js +15 -8
  38. package/app/roster.js +14 -15
  39. package/{lib → app}/spawn-prechecks.js +4 -3
  40. package/app/spawn.js +12 -13
  41. package/app/teardown.js +8 -9
  42. package/app/tower.js +89 -35
  43. package/cordis.patch.yml +56 -28
  44. package/docs/README.md +7 -2
  45. package/docs/adr/0001-ports-adapters-as-data.md +49 -0
  46. package/docs/adr/0002-single-state-json.md +50 -0
  47. package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
  48. package/docs/adr/0004-surface-budget.md +70 -0
  49. package/docs/adr/0005-component-surface.md +130 -0
  50. package/docs/adr/README.md +52 -0
  51. package/docs/agents/workflow.md +51 -0
  52. package/docs/architecture/overview.md +192 -43
  53. package/docs/guide/hot-reload.md +5 -5
  54. package/docs/guide/install-upgrade-migration.md +13 -0
  55. package/docs/guide/release.md +1 -1
  56. package/docs/operations/incident.md +82 -0
  57. package/docs/operations/runbook.md +12 -3
  58. package/docs/operations/security.md +1 -1
  59. package/docs/reference/blockers.md +2 -2
  60. package/docs/reference/component-surface.md +135 -0
  61. package/docs/reference/config.generated.md +2 -9
  62. package/docs/reference/config.md +11 -9
  63. package/docs/reference/doctor.md +53 -2
  64. package/docs/reference/errors.generated.md +41 -37
  65. package/docs/reference/errors.md +53 -41
  66. package/docs/reference/events.md +38 -6
  67. package/docs/reference/surface-budget.md +157 -0
  68. package/docs/reference/tools.generated.md +8 -5
  69. package/docs/reference/tools.md +10 -7
  70. package/docs/zh/README.md +1 -1
  71. package/docs/zh/guide/hot-reload.md +4 -4
  72. package/docs/zh/guide/install-upgrade-migration.md +10 -0
  73. package/docs/zh/operations/security.md +1 -1
  74. package/docs/zh/reference/blockers.md +2 -2
  75. package/docs/zh/reference/config.md +9 -9
  76. package/docs/zh/reference/doctor.md +29 -1
  77. package/docs/zh/reference/tools.md +10 -8
  78. package/domain/batch.js +2 -2
  79. package/domain/components.js +641 -0
  80. package/domain/config/schema.js +62 -28
  81. package/domain/contracts/outputs.js +16 -2
  82. package/domain/contracts/tool-inputs/ops.js +1 -0
  83. package/domain/errors.js +308 -43
  84. package/domain/events.js +207 -0
  85. package/domain/integration/gate.js +17 -2
  86. package/domain/integration/merge.js +35 -0
  87. package/domain/lockBudget.js +30 -0
  88. package/domain/mission.js +12 -1
  89. package/domain/missions/completion.js +14 -0
  90. package/domain/missions/lifecycle.js +9 -3
  91. package/domain/missions/plan.js +14 -0
  92. package/domain/missions/review.js +90 -7
  93. package/domain/read/views.js +3 -0
  94. package/domain/state/archive.js +1 -1
  95. package/domain/state.js +1 -1
  96. package/domain/surface.js +347 -0
  97. package/host/capability-batch.js +15 -0
  98. package/host/capability-hmr.js +30 -0
  99. package/host/capability-import.js +11 -0
  100. package/host/capability-leases.js +11 -0
  101. package/host/capability-registry.js +149 -0
  102. package/host/capability-tests.js +16 -0
  103. package/{lib → host}/doctor.js +157 -18
  104. package/{lib → host}/hmr.js +2 -4
  105. package/{lib → host}/modeStore.js +1 -1
  106. package/host/presets.js +77 -0
  107. package/{lib → host}/tool-registry.js +235 -44
  108. package/host/tower.js +32 -0
  109. package/index.js +40 -25
  110. package/locale/en.json +31 -11
  111. package/locale/zh.json +30 -10
  112. package/package.json +24 -4
  113. package/ports/archive.js +18 -0
  114. package/ports/caseSensitivity.js +15 -0
  115. package/ports/clock.js +10 -2
  116. package/ports/config.js +19 -0
  117. package/ports/contract.js +38 -0
  118. package/ports/events.js +24 -0
  119. package/ports/ids.js +10 -2
  120. package/ports/index.js +54 -2
  121. package/ports/ownership.js +17 -0
  122. package/ports/state.js +16 -0
  123. package/ports/store.js +52 -3
  124. package/ports/vcs.js +25 -4
  125. package/scripts/architecture-waivers.json +19 -0
  126. package/scripts/architecture.mjs +634 -23
  127. package/scripts/check-components.mjs +235 -0
  128. package/scripts/check-docs.mjs +8 -35
  129. package/scripts/check-pack.mjs +68 -39
  130. package/scripts/check-philosophy.mjs +266 -0
  131. package/scripts/component-surface.json +395 -0
  132. package/scripts/domain-metrics.mjs +13 -4
  133. package/scripts/generate-component-surface.mjs +59 -0
  134. package/scripts/generate-config-reference.mjs +14 -1
  135. package/scripts/generate-error-reference.mjs +68 -14
  136. package/scripts/generate-events-reference.mjs +83 -0
  137. package/scripts/generate-tools-reference.mjs +18 -25
  138. package/scripts/generate-types.mjs +127 -0
  139. package/scripts/issue-audit.mjs +2 -2
  140. package/scripts/lib/surface.mjs +87 -0
  141. package/scripts/lib/tool-surface.mjs +85 -0
  142. package/scripts/mount-smoke/fixtures.mjs +3 -3
  143. package/scripts/mount-smoke/surface.mjs +96 -27
  144. package/scripts/mount-smoke.mjs +3 -1
  145. package/scripts/pack-manifest.txt +75 -22
  146. package/scripts/regression-audit.mjs +164 -0
  147. package/scripts/release-check.mjs +1 -1
  148. package/scripts/surface-baseline.json +52 -0
  149. package/scripts/surface-baseline.mjs +118 -0
  150. package/scripts/types-coverage.mjs +184 -0
  151. package/scripts/types-strict-manifest.txt +10 -0
  152. package/scripts/types-strict.mjs +141 -0
  153. package/skills.js +8 -1
  154. package/tools/adopt.js +2 -3
  155. package/tools/batch.js +2 -3
  156. package/tools/complete.js +2 -3
  157. package/tools/diff.js +3 -3
  158. package/tools/doctor.js +7 -10
  159. package/tools/finding.js +2 -3
  160. package/tools/findings.js +3 -3
  161. package/tools/import-legacy.js +3 -5
  162. package/tools/inbox.js +3 -3
  163. package/tools/index.js +9 -8
  164. package/tools/init.js +3 -6
  165. package/tools/interrupt.js +2 -3
  166. package/tools/legacy-map.js +3 -5
  167. package/tools/merge.js +3 -3
  168. package/tools/mission.js +2 -3
  169. package/tools/plan.js +6 -5
  170. package/tools/preflight.js +2 -3
  171. package/tools/rebase.js +3 -3
  172. package/tools/recover.js +38 -13
  173. package/tools/reload.js +36 -40
  174. package/tools/resource.js +3 -5
  175. package/tools/review.js +3 -4
  176. package/tools/roster.js +2 -3
  177. package/tools/send.js +2 -2
  178. package/tools/spawn.js +8 -5
  179. package/tools/status.js +11 -4
  180. package/tools/teardown.js +2 -3
  181. package/tools/test.js +2 -2
  182. package/types/index.d.ts +29 -13
  183. package/ui-bundle/README.md +37 -3
  184. package/ui-bundle/client.cards.js +356 -0
  185. package/ui-bundle/client.denied.js +142 -0
  186. package/ui-bundle/client.dock.js +333 -0
  187. package/ui-bundle/client.helpers.js +271 -0
  188. package/ui-bundle/client.js +79 -1747
  189. package/ui-bundle/client.locales.js +247 -0
  190. package/ui-bundle/client.mode.js +206 -0
  191. package/ui-bundle/client.overlay.js +64 -0
  192. package/ui-bundle/client.styles.js +151 -0
  193. package/ui-bundle/client.toolview.js +82 -0
  194. package/ui-bundle/package.json +1 -0
  195. package/lib/persistence.js +0 -356
  196. package/lib/presets.js +0 -35
  197. package/lib/store.js +0 -133
  198. /package/{lib → adapters}/stateFile.js +0 -0
  199. /package/{lib → app}/briefing.js +0 -0
  200. /package/{lib → app}/spawn-support.js +0 -0
  201. /package/{lib → host}/compatibility.js +0 -0
  202. /package/{lib → host}/config.js +0 -0
  203. /package/{lib → host}/modeState.js +0 -0
@@ -0,0 +1,85 @@
1
+ // The shipped tool surface, derived from the Host entry (#118).
2
+ //
3
+ // `scripts/surface-baseline.mjs` and `scripts/generate-tools-reference.mjs` must
4
+ // measure the tools the plugin actually registers — including `tower_mode` and
5
+ // `tower_control`, which `index.js` registers directly instead of through the
6
+ // `define()` path. Loading the real `apply()` with a minimal Cordis context
7
+ // means the measurement cannot drift from the shipped surface again.
8
+ import os from 'node:os'
9
+ import path from 'node:path'
10
+ import { apply } from '../../index.js'
11
+ import { definedToolNames, registerAllCapabilities, __testResetToolSurface } from '../../host/tool-registry.js'
12
+
13
+ /**
14
+ * Apply the real Host entry against a recording mock context.
15
+ *
16
+ * @returns {{
17
+ * names: string[],
18
+ * definitions: Map<string, any>,
19
+ * dispose: () => void,
20
+ * }}
21
+ * `names` is the recorded surface (the same source `tower_doctor` reads);
22
+ * `definitions` is every tool the default profile actually registered.
23
+ */
24
+ export function loadShippedToolSurface() {
25
+ const definitions = new Map()
26
+ const disposers = []
27
+ const ctx = {
28
+ effect(fn) {
29
+ const dispose = fn()
30
+ if (typeof dispose === 'function') disposers.push(dispose)
31
+ return () => {}
32
+ },
33
+ get() {
34
+ return undefined
35
+ },
36
+ provide() {},
37
+ inject(_deps, callback) {
38
+ callback(ctx)
39
+ },
40
+ on() {
41
+ return () => {}
42
+ },
43
+ logger: { warn() {} },
44
+ tools: {
45
+ register(definition) {
46
+ if (definition && typeof definition.name === 'string') definitions.set(definition.name, definition)
47
+ return () => {}
48
+ },
49
+ guard() {
50
+ return () => {}
51
+ },
52
+ restrict() {
53
+ return () => {}
54
+ },
55
+ },
56
+ systemPrompt: { section() {} },
57
+ commands: {
58
+ register() {
59
+ return () => {}
60
+ },
61
+ },
62
+ }
63
+ // Keep the mode store out of the operator's real $DSH_HOME. The path is only
64
+ // read (a missing modes.json is an empty mode set); nothing is written.
65
+ // #121: the host entry registers only the mandatory surface; the shipped
66
+ // profile mounts the optional capability rows. The concept budget measures the
67
+ // *build* surface, so the all-capabilities aggregate is applied on top.
68
+ __testResetToolSurface()
69
+ apply(ctx, { mode: { storePath: path.join(os.tmpdir(), 'dsh-tower-surface-check') } })
70
+ registerAllCapabilities(ctx)
71
+ const names = definedToolNames()
72
+ return {
73
+ names,
74
+ definitions,
75
+ dispose() {
76
+ for (const dispose of disposers.reverse()) {
77
+ try {
78
+ dispose()
79
+ } catch {
80
+ // a recorder fixture must still exit when a disposer fails
81
+ }
82
+ }
83
+ },
84
+ }
85
+ }
@@ -67,19 +67,19 @@ const fixtures = [
67
67
  },
68
68
  {
69
69
  name: 'dropped-skills-section',
70
- what: 'the tower-skills row is removed from the bundle patch',
70
+ what: 'the optional tower-skills row is removed from the bundle patch',
71
71
  seed(root) {
72
72
  const file = path.join(root, 'cordis.patch.yml')
73
73
  const text = fs.readFileSync(file, 'utf8')
74
74
  const next = replaceOnce(
75
75
  text,
76
- /\n {8}- id: tower-skills\n {10}name: 'dsh-tower\/skills'/,
76
+ /\n {4}- id: tower-skills\n {6}name: 'dsh-tower\/skills'/,
77
77
  '',
78
78
  'cordis.patch.yml tower-skills row',
79
79
  )
80
80
  fs.writeFileSync(file, next)
81
81
  },
82
- expect: ['skill: missing tower-worker-protocol', 'bundle: tower-bundle is missing child tower-skills'],
82
+ expect: ['skill: missing tower-worker-protocol', 'bundle: missing optional top-level row tower-skills'],
83
83
  },
84
84
  ]
85
85
 
@@ -1,4 +1,4 @@
1
- // The expected loaded surface of the dsh-tower bundle (#95).
1
+ // The expected loaded surface of the dsh-tower bundle (#95, #121).
2
2
  //
3
3
  // Pure derivation and comparison only: the mount smoke boots a throwaway
4
4
  // profile, and these helpers turn the checked-in source of truth into the ids
@@ -7,17 +7,53 @@
7
7
  //
8
8
  // The oracle is deliberately source-derived where the source is unambiguous:
9
9
  // one tool id per tool module, one skill id per bundled skill file. The bundle
10
- // children and presets come from `cordis.patch.yml` and are stated here as the
11
- // contract the #66 invariant protects.
10
+ // rows and presets come from `cordis.patch.yml` and are stated here as the
11
+ // contract the component policy protects: one mandatory host group plus
12
+ // individually switchable optional top-level rows (#121). Tools owned by a row
13
+ // that ships off (today only `tower-hmr` -> `tower_reload`) are trimmed from the
14
+ // expected surface using the same ownership table the runtime reports, so the
15
+ // shipped default profile (28 tools) and the all-rows-on build surface (29)
16
+ // cannot be confused again.
17
+
18
+ import { COMPONENT_TOOLS } from '../../host/capability-registry.js'
12
19
 
13
20
  /** Tools registered by index.js rather than by a handler module under tools/. */
14
21
  export const INDEX_TOOL_IDS = ['tower_control', 'tower_mode']
15
22
 
16
- /** The single top-level row the bundle is allowed to contribute (#66). */
17
- export const BUNDLE_ID = 'tower-bundle'
23
+ /** The mandatory host group row: off equals uninstalling. */
24
+ export const BUNDLE_ID = 'tower'
18
25
 
19
26
  /** Rows that must stay nested inside {@link BUNDLE_ID}. */
20
- export const BUNDLE_CHILD_IDS = ['preset-tower-reviewer', 'preset-tower-worker', 'tower', 'tower-skills']
27
+ export const BUNDLE_CHILD_IDS = ['preset-tower-worker', 'tower-host']
28
+
29
+ /** The optional capability rows that sit at the top level beside the host. */
30
+ export const OPTIONAL_ROW_IDS = [
31
+ 'preset-tower-reviewer',
32
+ 'tower-batch',
33
+ 'tower-hmr',
34
+ 'tower-import',
35
+ 'tower-leases',
36
+ 'tower-skills',
37
+ 'tower-tests',
38
+ ]
39
+
40
+ /** Rows shipped off: the mount smoke must not expect them to be active. */
41
+ export const DEFAULT_OFF_ROW_IDS = ['tower-hmr']
42
+
43
+ /**
44
+ * Tool ids owned by the rows in {@link DEFAULT_OFF_ROW_IDS}. The ownership table
45
+ * is the one `host/capability-registry.js` uses to report a disabled component,
46
+ * so the oracle cannot drift from the runtime: turning a row's default off in
47
+ * `cordis.patch.yml` automatically removes its tools from the expected surface.
48
+ * @returns {Set<string>} the tool ids a default profile does not register.
49
+ */
50
+ export function defaultOffToolIds() {
51
+ const ids = new Set()
52
+ for (const rowId of DEFAULT_OFF_ROW_IDS) {
53
+ for (const toolId of COMPONENT_TOOLS[rowId] ?? []) ids.add(toolId)
54
+ }
55
+ return ids
56
+ }
21
57
 
22
58
  /** Presets the bundle declares for its worker and reviewer roles. */
23
59
  export const PRESET_IDS = ['tower-reviewer', 'tower-worker']
@@ -25,7 +61,7 @@ export const PRESET_IDS = ['tower-reviewer', 'tower-worker']
25
61
  /** Cordis fiber state 2: mounted and active. */
26
62
  const FIBER_ACTIVE = 2
27
63
 
28
- const OWNED_ROW_IDS = new Set([BUNDLE_ID, ...BUNDLE_CHILD_IDS])
64
+ const OWNED_ROW_IDS = new Set([BUNDLE_ID, ...BUNDLE_CHILD_IDS, ...OPTIONAL_ROW_IDS])
29
65
 
30
66
  /**
31
67
  * Map one tools/ file name to the tool id its module must register.
@@ -42,14 +78,25 @@ export function toolIdForModule(fileName) {
42
78
 
43
79
  /**
44
80
  * @param {string[]} moduleFiles - file names under tools/.
81
+ * @param {{ enabledRows?: string[] }} [options] - rows enabled in the profile
82
+ * under test. Defaults to the shipped profile: every optional row except the
83
+ * ones in {@link DEFAULT_OFF_ROW_IDS}. Pass every row id to pin the full
84
+ * build surface (29 tools with the shipped sources).
45
85
  * @returns {string[]} sorted tool ids the loaded profile must register.
46
86
  */
47
- export function expectedToolIds(moduleFiles) {
87
+ export function expectedToolIds(moduleFiles, options = {}) {
88
+ const enabledRows = options.enabledRows === undefined ? null : new Set(options.enabledRows)
89
+ const skipped = new Set()
90
+ for (const rowId of DEFAULT_OFF_ROW_IDS) {
91
+ if (enabledRows !== null && enabledRows.has(rowId)) continue
92
+ for (const toolId of COMPONENT_TOOLS[rowId] ?? []) skipped.add(toolId)
93
+ }
48
94
  const ids = new Set(INDEX_TOOL_IDS)
49
95
  for (const file of moduleFiles) {
50
96
  const id = toolIdForModule(file)
51
97
  if (id !== null) ids.add(id)
52
98
  }
99
+ for (const id of skipped) ids.delete(id)
53
100
  return [...ids].sort()
54
101
  }
55
102
 
@@ -81,46 +128,55 @@ export function compareIds(label, expectedIds, loadedIds) {
81
128
  return failures
82
129
  }
83
130
 
131
+ /** Effective loader id for a nested row (`group:child`), falling back to the raw id. */
132
+ function stateKey(id, entryStates) {
133
+ if (entryStates[id] !== undefined) return id
134
+ const nested = `${BUNDLE_ID}:${id}`
135
+ if (entryStates[nested] !== undefined) return nested
136
+ return id
137
+ }
138
+
84
139
  /**
85
- * Assert the bundle contributes exactly one top-level group row (#66) and that
86
- * every composed section actually mounted.
87
- * @param {Array<{ id?: string, name?: string, group?: boolean, config?: unknown[] }>} topRows -
88
- * the composed rows at the root of the loaded profile tree.
140
+ * Assert the bundle ships one mandatory host group plus the optional rows and
141
+ * that every expected row actually mounted (the `tower-hmr` row is shipped off).
142
+ * @param {Array<{ id?: string, name?: string, group?: boolean, config?: unknown[], childIds?: unknown[] }>} topRows
89
143
  * @param {Record<string, number | undefined>} [entryStates] - fiber state per entry id.
90
144
  * @returns {string[]} one message per violated invariant.
91
145
  */
92
146
  export function checkBundleRow(topRows, entryStates = {}) {
93
147
  const rows = Array.isArray(topRows) ? topRows.filter(Boolean) : []
94
148
  const failures = []
95
- const bundleRows = rows.filter((row) => row.id === BUNDLE_ID)
96
- if (bundleRows.length !== 1) {
149
+ const hostRows = rows.filter((row) => row.id === BUNDLE_ID)
150
+ if (hostRows.length !== 1) {
97
151
  failures.push(
98
- bundleRows.length === 0
99
- ? `bundle: no top-level ${BUNDLE_ID} row; the bundle did not compose`
100
- : `bundle: found ${bundleRows.length} top-level ${BUNDLE_ID} rows, expected exactly 1`,
152
+ hostRows.length === 0
153
+ ? `bundle: no top-level ${BUNDLE_ID} host row; the bundle did not compose`
154
+ : `bundle: found ${hostRows.length} top-level ${BUNDLE_ID} rows, expected exactly 1`,
101
155
  )
102
- if (bundleRows.length === 0) return failures
156
+ if (hostRows.length === 0) return failures
103
157
  }
104
158
 
105
- const leaked = new Set()
159
+ const topIds = new Set(rows.map((row) => String(row.id)))
160
+ for (const id of OPTIONAL_ROW_IDS) {
161
+ if (!topIds.has(id)) failures.push(`bundle: missing optional top-level row ${id}`)
162
+ }
106
163
  for (const row of rows) {
107
164
  if (row.id === BUNDLE_ID) continue
108
165
  const name = typeof row.name === 'string' ? row.name : ''
109
166
  const ownedName = name === 'dsh-tower' || name.startsWith('dsh-tower/')
110
167
  if (!OWNED_ROW_IDS.has(String(row.id)) && !ownedName) continue
111
- if (leaked.has(String(row.id))) continue
112
- leaked.add(String(row.id))
168
+ if (OPTIONAL_ROW_IDS.includes(String(row.id))) continue
113
169
  failures.push(`bundle: ${row.id} is a top-level row; it must stay nested in ${BUNDLE_ID}`)
114
170
  }
115
171
 
116
- const bundleRow = bundleRows[0]
117
- if (bundleRow.name !== 'cordis:group' || bundleRow.group !== true) {
172
+ const hostRow = hostRows[0]
173
+ if (hostRow.name !== 'cordis:group' || hostRow.group !== true) {
118
174
  failures.push(
119
- `bundle: ${BUNDLE_ID} is not a cordis:group row (name ${JSON.stringify(bundleRow.name)}, group ${JSON.stringify(bundleRow.group)})`,
175
+ `bundle: ${BUNDLE_ID} is not a cordis:group row (name ${JSON.stringify(hostRow.name)}, group ${JSON.stringify(hostRow.group)})`,
120
176
  )
121
177
  }
122
178
 
123
- const rawChildren = Array.isArray(bundleRow.childIds) ? bundleRow.childIds : Array.isArray(bundleRow.config) ? bundleRow.config : []
179
+ const rawChildren = Array.isArray(hostRow.childIds) ? hostRow.childIds : Array.isArray(hostRow.config) ? hostRow.config : []
124
180
  const children = rawChildren
125
181
  .map((child) =>
126
182
  child && typeof child === 'object' && child.id !== undefined ? String(child.id) : typeof child === 'string' ? child : null,
@@ -131,8 +187,21 @@ export function checkBundleRow(topRows, entryStates = {}) {
131
187
  for (const id of children) if (!expectedChildren.includes(id)) failures.push(`bundle: ${BUNDLE_ID} has unexpected child ${id}`)
132
188
  if (new Set(children).size !== children.length) failures.push(`bundle: ${BUNDLE_ID} has duplicate child rows`)
133
189
 
134
- for (const id of [BUNDLE_ID, ...BUNDLE_CHILD_IDS]) {
135
- const state = entryStates?.[id]
190
+ for (const id of [BUNDLE_ID, ...OPTIONAL_ROW_IDS]) {
191
+ if (DEFAULT_OFF_ROW_IDS.includes(id)) {
192
+ const state = entryStates[id]
193
+ if (state === FIBER_ACTIVE) failures.push(`bundle: ${id} ships disabled but is active`)
194
+ continue
195
+ }
196
+ const key = stateKey(id, entryStates)
197
+ const state = entryStates[key]
198
+ if (state !== FIBER_ACTIVE) {
199
+ failures.push(`bundle: entry ${id} is not active (entry state ${state === undefined ? 'missing' : state})`)
200
+ }
201
+ }
202
+ for (const id of BUNDLE_CHILD_IDS) {
203
+ const key = stateKey(id, entryStates)
204
+ const state = entryStates[key]
136
205
  if (state !== FIBER_ACTIVE) {
137
206
  failures.push(`bundle: entry ${id} is not active (entry state ${state === undefined ? 'missing' : state})`)
138
207
  }
@@ -21,6 +21,7 @@ import path from 'node:path'
21
21
  import { fileURLToPath } from 'node:url'
22
22
  import {
23
23
  BUNDLE_ID,
24
+ DEFAULT_OFF_ROW_IDS,
24
25
  INDEX_TOOL_IDS,
25
26
  PRESET_IDS,
26
27
  checkBundleRow,
@@ -175,12 +176,13 @@ function verifySurface(surface, root) {
175
176
 
176
177
  function summarize(surface, root) {
177
178
  const modules = toolModuleNames(root).filter((name) => name !== 'index.js').length
179
+ const expected = expectedToolIds(toolModuleNames(root)).length
178
180
  const tools = Array.isArray(surface?.tools?.value) ? surface.tools.value.filter((name) => name.startsWith('tower_')).length : '?'
179
181
  const skills = Array.isArray(surface?.skills?.value?.tower) ? surface.skills.value.tower.length : '?'
180
182
  const presets = Array.isArray(surface?.presets?.value) ? surface.presets.value.length : '?'
181
183
  const commands = Array.isArray(surface?.commands?.value) ? surface.commands.value.filter((name) => name === 'tower').length : '?'
182
184
  const rows = Array.isArray(surface?.tree?.value?.top) ? surface.tree.value.top.filter((row) => row.id === BUNDLE_ID).length : '?'
183
- return `${tools} tower tools (${modules} modules + ${INDEX_TOOL_IDS.length} entry tools), ${skills} skills, ${presets} presets, /tower ${commands === 1 ? 'present' : 'missing'}, ${rows} top-level bundle row`
185
+ return `${tools} of ${expected} tower tools (${modules} modules + ${INDEX_TOOL_IDS.length} entry tools; ${DEFAULT_OFF_ROW_IDS.join(', ')} off), ${skills} skills, ${presets} presets, /tower ${commands === 1 ? 'present' : 'missing'}, ${rows} top-level bundle row`
184
186
  }
185
187
 
186
188
  function reportFailure(state, pin, options, error) {
@@ -2,15 +2,28 @@ CHANGELOG.md
2
2
  COMPATIBILITY.md
3
3
  CONTRIBUTING.md
4
4
  LICENSE
5
+ PRINCIPLES.md
5
6
  README.en.md
6
7
  README.md
7
8
  SECURITY.md
9
+ adapters/archive.js
10
+ adapters/caseSensitivity.js
8
11
  adapters/default.js
12
+ adapters/git.js
13
+ adapters/leases.js
14
+ adapters/legacy.js
15
+ adapters/persistence.js
16
+ adapters/stateFile.js
9
17
  adapters/store-fs.js
18
+ adapters/store.js
19
+ adapters/tests.js
10
20
  adapters/vcs-git.js
21
+ adapters/workspaceLock.js
22
+ adapters/worktreeOwnership.js
11
23
  app/archive.js
12
24
  app/batch.js
13
25
  app/board.js
26
+ app/briefing.js
14
27
  app/checkCompletion.js
15
28
  app/enrichedMissions.js
16
29
  app/findings.js
@@ -27,14 +40,23 @@ app/rebase.js
27
40
  app/recording.js
28
41
  app/review.js
29
42
  app/roster.js
43
+ app/spawn-prechecks.js
44
+ app/spawn-support.js
30
45
  app/spawn.js
31
46
  app/teardown.js
32
47
  app/tower.js
33
48
  cordis.patch.yml
34
49
  docs/README.md
50
+ docs/adr/0001-ports-adapters-as-data.md
51
+ docs/adr/0002-single-state-json.md
52
+ docs/adr/0003-allow-agent-control-default-true.md
53
+ docs/adr/0004-surface-budget.md
54
+ docs/adr/0005-component-surface.md
55
+ docs/adr/README.md
35
56
  docs/agents/domain.md
36
57
  docs/agents/issue-tracker.md
37
58
  docs/agents/triage-labels.md
59
+ docs/agents/workflow.md
38
60
  docs/architecture/overview.md
39
61
  docs/board.md
40
62
  docs/examples/first-mission.md
@@ -44,9 +66,11 @@ docs/guide/install-upgrade-migration.md
44
66
  docs/guide/quickstart.md
45
67
  docs/guide/release.md
46
68
  docs/guide/scenarios-and-philosophy.md
69
+ docs/operations/incident.md
47
70
  docs/operations/runbook.md
48
71
  docs/operations/security.md
49
72
  docs/reference/blockers.md
73
+ docs/reference/component-surface.md
50
74
  docs/reference/config.generated.md
51
75
  docs/reference/config.md
52
76
  docs/reference/doctor.md
@@ -54,6 +78,7 @@ docs/reference/errors.generated.md
54
78
  docs/reference/errors.md
55
79
  docs/reference/events.md
56
80
  docs/reference/operations.md
81
+ docs/reference/surface-budget.md
57
82
  docs/reference/testing.md
58
83
  docs/reference/tools.generated.md
59
84
  docs/reference/tools.md
@@ -77,6 +102,7 @@ domain/collab/batch.js
77
102
  domain/collab/messages.js
78
103
  domain/collab/roster.js
79
104
  domain/collab/spawn.js
105
+ domain/components.js
80
106
  domain/config/schema.js
81
107
  domain/contracts/outputs.js
82
108
  domain/contracts/tool-inputs/collaboration.js
@@ -87,6 +113,7 @@ domain/contracts/tool-inputs/missions.js
87
113
  domain/contracts/tool-inputs/ops.js
88
114
  domain/contracts/tool-inputs/shared.js
89
115
  domain/errors.js
116
+ domain/events.js
90
117
  domain/gate.js
91
118
  domain/integration/gate.js
92
119
  domain/integration/init.js
@@ -96,6 +123,7 @@ domain/integration/provision.js
96
123
  domain/integration/rebase.js
97
124
  domain/integration/review.js
98
125
  domain/integration/teardown.js
126
+ domain/lockBudget.js
99
127
  domain/mission.js
100
128
  domain/missions/completion.js
101
129
  domain/missions/findings.js
@@ -115,58 +143,74 @@ domain/state/migrations.js
115
143
  domain/state/schema.js
116
144
  domain/state/semantics.js
117
145
  domain/summary.js
146
+ domain/surface.js
118
147
  domain/vocabulary.js
148
+ host/capability-batch.js
149
+ host/capability-hmr.js
150
+ host/capability-import.js
151
+ host/capability-leases.js
152
+ host/capability-registry.js
153
+ host/capability-tests.js
154
+ host/compatibility.js
155
+ host/config.js
156
+ host/doctor.js
157
+ host/hmr.js
158
+ host/modeState.js
159
+ host/modeStore.js
160
+ host/presets.js
161
+ host/tool-registry.js
162
+ host/tower.js
119
163
  icon.svg
120
164
  index.js
121
- lib/archive.js
122
- lib/briefing.js
123
- lib/caseSensitivity.js
124
- lib/compatibility.js
125
- lib/config.js
126
- lib/doctor.js
127
- lib/git.js
128
- lib/hmr.js
129
- lib/leases.js
130
- lib/legacy.js
131
- lib/modeState.js
132
- lib/modeStore.js
133
- lib/persistence.js
134
- lib/presets.js
135
- lib/spawn-prechecks.js
136
- lib/spawn-support.js
137
- lib/stateFile.js
138
- lib/store.js
139
- lib/tests.js
140
- lib/tool-registry.js
141
- lib/workspaceLock.js
142
- lib/worktreeOwnership.js
143
165
  locale/en.json
144
166
  locale/zh.json
145
167
  package.json
168
+ ports/archive.js
169
+ ports/caseSensitivity.js
146
170
  ports/clock.js
171
+ ports/config.js
172
+ ports/contract.js
173
+ ports/events.js
147
174
  ports/ids.js
148
175
  ports/index.js
176
+ ports/ownership.js
177
+ ports/state.js
149
178
  ports/store.js
150
179
  ports/vcs.js
151
180
  reviewer.js
181
+ scripts/architecture-waivers.json
152
182
  scripts/architecture.mjs
183
+ scripts/check-components.mjs
153
184
  scripts/check-docs.mjs
154
185
  scripts/check-pack.mjs
186
+ scripts/check-philosophy.mjs
187
+ scripts/component-surface.json
155
188
  scripts/domain-metrics.mjs
156
189
  scripts/enable-dev-hmr.mjs
190
+ scripts/generate-component-surface.mjs
157
191
  scripts/generate-config-reference.mjs
158
192
  scripts/generate-error-reference.mjs
193
+ scripts/generate-events-reference.mjs
159
194
  scripts/generate-tools-reference.mjs
195
+ scripts/generate-types.mjs
160
196
  scripts/issue-audit.mjs
161
197
  scripts/lib/cli.mjs
198
+ scripts/lib/surface.mjs
162
199
  scripts/lib/tmp.mjs
200
+ scripts/lib/tool-surface.mjs
163
201
  scripts/mount-smoke.mjs
164
202
  scripts/mount-smoke/fixtures.mjs
165
203
  scripts/mount-smoke/host.json
166
204
  scripts/mount-smoke/probe.mjs
167
205
  scripts/mount-smoke/surface.mjs
168
206
  scripts/pack-manifest.txt
207
+ scripts/regression-audit.mjs
169
208
  scripts/release-check.mjs
209
+ scripts/surface-baseline.json
210
+ scripts/surface-baseline.mjs
211
+ scripts/types-coverage.mjs
212
+ scripts/types-strict-manifest.txt
213
+ scripts/types-strict.mjs
170
214
  skills.js
171
215
  skills/tower-recovery.md
172
216
  skills/tower-review.md
@@ -202,7 +246,16 @@ tools/test.js
202
246
  types/cordis.d.ts
203
247
  types/index.d.ts
204
248
  ui-bundle/README.md
249
+ ui-bundle/client.cards.js
250
+ ui-bundle/client.denied.js
251
+ ui-bundle/client.dock.js
252
+ ui-bundle/client.helpers.js
205
253
  ui-bundle/client.js
254
+ ui-bundle/client.locales.js
255
+ ui-bundle/client.mode.js
256
+ ui-bundle/client.overlay.js
257
+ ui-bundle/client.styles.js
258
+ ui-bundle/client.toolview.js
206
259
  ui-bundle/cordis.patch.yml
207
260
  ui-bundle/icon.svg
208
261
  ui-bundle/index.js