dsh-tower 2.1.0 → 2.3.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 (170) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/COMPATIBILITY.md +5 -1
  3. package/CONTRIBUTING.md +64 -5
  4. package/PRINCIPLES.md +145 -0
  5. package/README.en.md +7 -4
  6. package/README.md +7 -4
  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/docs/README.md +6 -2
  44. package/docs/adr/0001-ports-adapters-as-data.md +49 -0
  45. package/docs/adr/0002-single-state-json.md +50 -0
  46. package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
  47. package/docs/adr/0004-surface-budget.md +50 -0
  48. package/docs/adr/README.md +51 -0
  49. package/docs/agents/workflow.md +51 -0
  50. package/docs/architecture/overview.md +167 -36
  51. package/docs/guide/hot-reload.md +1 -1
  52. package/docs/guide/release.md +1 -1
  53. package/docs/operations/incident.md +82 -0
  54. package/docs/operations/runbook.md +12 -3
  55. package/docs/reference/blockers.md +2 -2
  56. package/docs/reference/config.generated.md +1 -0
  57. package/docs/reference/doctor.md +48 -2
  58. package/docs/reference/errors.generated.md +41 -37
  59. package/docs/reference/errors.md +53 -41
  60. package/docs/reference/events.md +38 -6
  61. package/docs/reference/surface-budget.md +128 -0
  62. package/docs/reference/tools.generated.md +5 -4
  63. package/docs/reference/tools.md +4 -4
  64. package/docs/zh/guide/hot-reload.md +1 -1
  65. package/docs/zh/reference/blockers.md +2 -2
  66. package/docs/zh/reference/doctor.md +29 -1
  67. package/docs/zh/reference/tools.md +4 -4
  68. package/domain/batch.js +2 -2
  69. package/domain/config/schema.js +25 -1
  70. package/domain/contracts/outputs.js +6 -2
  71. package/domain/contracts/tool-inputs/ops.js +1 -0
  72. package/domain/errors.js +297 -42
  73. package/domain/events.js +207 -0
  74. package/domain/integration/gate.js +17 -2
  75. package/domain/integration/merge.js +35 -0
  76. package/domain/lockBudget.js +30 -0
  77. package/domain/missions/completion.js +14 -0
  78. package/domain/missions/lifecycle.js +9 -3
  79. package/domain/missions/plan.js +14 -0
  80. package/domain/missions/review.js +90 -7
  81. package/domain/read/views.js +3 -0
  82. package/domain/state/archive.js +1 -1
  83. package/domain/state.js +1 -1
  84. package/domain/surface.js +248 -0
  85. package/{lib → host}/doctor.js +116 -9
  86. package/{lib → host}/modeStore.js +1 -1
  87. package/{lib → host}/tool-registry.js +106 -31
  88. package/host/tower.js +32 -0
  89. package/index.js +12 -9
  90. package/package.json +10 -3
  91. package/ports/archive.js +18 -0
  92. package/ports/caseSensitivity.js +15 -0
  93. package/ports/clock.js +10 -2
  94. package/ports/config.js +19 -0
  95. package/ports/contract.js +38 -0
  96. package/ports/events.js +24 -0
  97. package/ports/ids.js +10 -2
  98. package/ports/index.js +54 -2
  99. package/ports/ownership.js +17 -0
  100. package/ports/state.js +16 -0
  101. package/ports/store.js +52 -3
  102. package/ports/vcs.js +25 -4
  103. package/scripts/architecture-waivers.json +19 -0
  104. package/scripts/architecture.mjs +634 -23
  105. package/scripts/check-docs.mjs +8 -35
  106. package/scripts/check-pack.mjs +68 -39
  107. package/scripts/check-philosophy.mjs +266 -0
  108. package/scripts/domain-metrics.mjs +13 -4
  109. package/scripts/generate-config-reference.mjs +14 -1
  110. package/scripts/generate-error-reference.mjs +68 -14
  111. package/scripts/generate-events-reference.mjs +83 -0
  112. package/scripts/generate-tools-reference.mjs +11 -0
  113. package/scripts/issue-audit.mjs +2 -2
  114. package/scripts/lib/surface.mjs +87 -0
  115. package/scripts/pack-manifest.txt +58 -22
  116. package/scripts/regression-audit.mjs +164 -0
  117. package/scripts/release-check.mjs +1 -1
  118. package/scripts/surface-baseline.json +21 -0
  119. package/scripts/surface-baseline.mjs +120 -0
  120. package/tools/adopt.js +2 -3
  121. package/tools/batch.js +2 -3
  122. package/tools/complete.js +2 -3
  123. package/tools/diff.js +3 -3
  124. package/tools/doctor.js +7 -10
  125. package/tools/finding.js +2 -3
  126. package/tools/findings.js +3 -3
  127. package/tools/import-legacy.js +3 -5
  128. package/tools/inbox.js +3 -3
  129. package/tools/index.js +7 -7
  130. package/tools/init.js +3 -6
  131. package/tools/interrupt.js +2 -3
  132. package/tools/legacy-map.js +3 -5
  133. package/tools/merge.js +3 -3
  134. package/tools/mission.js +2 -3
  135. package/tools/plan.js +6 -5
  136. package/tools/preflight.js +2 -3
  137. package/tools/rebase.js +3 -3
  138. package/tools/recover.js +38 -13
  139. package/tools/reload.js +3 -6
  140. package/tools/resource.js +3 -5
  141. package/tools/review.js +3 -4
  142. package/tools/roster.js +2 -3
  143. package/tools/send.js +2 -2
  144. package/tools/spawn.js +8 -5
  145. package/tools/status.js +3 -3
  146. package/tools/teardown.js +2 -3
  147. package/tools/test.js +2 -2
  148. package/types/index.d.ts +1 -1
  149. package/ui-bundle/README.md +37 -3
  150. package/ui-bundle/client.cards.js +356 -0
  151. package/ui-bundle/client.denied.js +142 -0
  152. package/ui-bundle/client.dock.js +333 -0
  153. package/ui-bundle/client.helpers.js +271 -0
  154. package/ui-bundle/client.js +79 -1747
  155. package/ui-bundle/client.locales.js +247 -0
  156. package/ui-bundle/client.mode.js +206 -0
  157. package/ui-bundle/client.overlay.js +64 -0
  158. package/ui-bundle/client.styles.js +151 -0
  159. package/ui-bundle/client.toolview.js +82 -0
  160. package/ui-bundle/package.json +1 -0
  161. package/lib/persistence.js +0 -356
  162. package/lib/store.js +0 -133
  163. /package/{lib → adapters}/stateFile.js +0 -0
  164. /package/{lib → app}/briefing.js +0 -0
  165. /package/{lib → app}/spawn-support.js +0 -0
  166. /package/{lib → host}/compatibility.js +0 -0
  167. /package/{lib → host}/config.js +0 -0
  168. /package/{lib → host}/hmr.js +0 -0
  169. /package/{lib → host}/modeState.js +0 -0
  170. /package/{lib → host}/presets.js +0 -0
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // Concept-budget gate (#84): compare the live surface with the committed
3
+ // baseline and require evidence for growth.
4
+ //
5
+ // The surface is three counts the user has to hold at once:
6
+ // config keys leaf keys in domain/config/schema.js (one level deep)
7
+ // sections object groups in that schema
8
+ // tools tool definitions registered by host/tool-registry.js
9
+ //
10
+ // `scripts/surface-baseline.json` freezes those counts. A PR that grows a count
11
+ // fails here until the baseline carries a decision with either an ADR under
12
+ // docs/adr/ or deletions that no longer exist in the live surface. The same
13
+ // file supplies the date and counts printed in the generated config/tools
14
+ // reference headers, so growth is visible in a diff.
15
+ //
16
+ // Usage:
17
+ // node scripts/surface-baseline.mjs [--root <dir>] [--json]
18
+ import fs from 'node:fs'
19
+ import path from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+ import { configSurface } from '../domain/config/schema.js'
22
+ import { SURFACE_CLASSIFICATION_FILE, SURFACE_BASELINE_FILE, surfaceLine } from '../domain/surface.js'
23
+ import { registerTowerTools } from '../tools/index.js'
24
+ import { definedToolNames } from '../host/tool-registry.js'
25
+ import { checkSurfaceBudget } from './lib/surface.mjs'
26
+ import { parseArgs, runMain } from './lib/cli.mjs'
27
+
28
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
29
+ const USAGE = `usage: node scripts/surface-baseline.mjs [--root <dir>] [--json]
30
+
31
+ --root repository root to check (default: this package)
32
+ --json print the report as JSON
33
+ --help print this text
34
+
35
+ Growth is refused by default. To grow a count, update ${SURFACE_BASELINE_FILE}
36
+ with a decision that names an existing docs/adr/ ADR or deletions that are no
37
+ longer in the live surface. The classification table in
38
+ ${SURFACE_CLASSIFICATION_FILE} must cover every key and tool exactly once.`
39
+
40
+ /** Register the real tool surface against a minimal mock context. */
41
+ function liveToolNames() {
42
+ const ctx = {
43
+ get() {
44
+ return undefined
45
+ },
46
+ effect(fn) {
47
+ const dispose = fn()
48
+ return typeof dispose === 'function' ? dispose : () => {}
49
+ },
50
+ systemPrompt: { section() {} },
51
+ tools: {
52
+ register() {
53
+ return () => {}
54
+ },
55
+ guard() {
56
+ return () => {}
57
+ },
58
+ restrict() {
59
+ return () => {}
60
+ },
61
+ },
62
+ }
63
+ registerTowerTools(ctx)
64
+ return definedToolNames()
65
+ }
66
+
67
+ /**
68
+ * The live surface used by the gate. Exported so tests can compare the counts
69
+ * with the generated references without spawning the CLI.
70
+ */
71
+ export function liveSurface() {
72
+ const config = configSurface()
73
+ const tools = liveToolNames()
74
+ return {
75
+ configKeys: config.count,
76
+ sections: config.sections.length,
77
+ tools: tools.length,
78
+ liveNames: {
79
+ configKeys: config.keys,
80
+ sections: config.sections,
81
+ tools,
82
+ },
83
+ }
84
+ }
85
+
86
+ function main(argv) {
87
+ const options = parseArgs(argv, {
88
+ initial: { root: PACKAGE_ROOT, json: false, help: false },
89
+ values: { '--root': { key: 'root', parse: path.resolve } },
90
+ flags: { '--json': 'json', '--help': 'help', '-h': 'help' },
91
+ })
92
+ if (options.help) {
93
+ console.log(USAGE)
94
+ return 0
95
+ }
96
+ const root = path.resolve(options.root)
97
+ if (!fs.existsSync(path.join(root, SURFACE_BASELINE_FILE))) {
98
+ console.error(`surface: ${SURFACE_BASELINE_FILE} is missing under ${root}`)
99
+ return 2
100
+ }
101
+ const current = liveSurface()
102
+ const result = checkSurfaceBudget({ root, current })
103
+ if (options.json) {
104
+ console.log(JSON.stringify({ root, current, ...result }, null, 2))
105
+ return result.ok ? 0 : 1
106
+ }
107
+ console.log(`surface: ${surfaceLine(current)}`)
108
+ if (result.baseline) console.log(`surface: baseline ${result.baseline.date} (${SURFACE_BASELINE_FILE})`)
109
+ if (result.classification) {
110
+ const configRows = result.classification.entries.filter((entry) => entry.kind === 'config').length
111
+ const toolRows = result.classification.entries.filter((entry) => entry.kind === 'tools').length
112
+ console.log(`surface: classified ${configRows} config keys and ${toolRows} tools (${SURFACE_CLASSIFICATION_FILE})`)
113
+ }
114
+ for (const finding of result.findings) console.log(`surface: ${finding.level} [${finding.code}] ${finding.message}`)
115
+ if (result.ok) console.log('surface: ok — counts match the committed baseline and every entry is classified')
116
+ else console.log(`surface: ${result.findings.length} problem(s); growth needs an ADR or a matching deletion`)
117
+ return result.ok ? 0 : 1
118
+ }
119
+
120
+ await runMain(import.meta.url, () => main(process.argv.slice(2)), { errorPrefix: 'surface', usage: USAGE })
package/tools/adopt.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // tower_adopt: reconcile the roster with the subagent catalog.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { TowerToolError } from '../domain/errors.js'
5
4
  import { AdoptInput } from '../domain/contracts/tool-inputs/index.js'
@@ -7,7 +6,7 @@ import { PARENT_STATUS_OK, ROSTER_ACTIVITY, ROSTER_STATE } from '../domain/vocab
7
6
 
8
7
  /**
9
8
  * @param {any} ctx
10
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any }} deps
9
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any }} deps
11
10
  */
12
11
  export function registerAdoptTool(ctx, deps) {
13
12
  const { define, actorFor, requireTowerActor } = deps
@@ -19,7 +18,7 @@ export function registerAdoptTool(ctx, deps) {
19
18
  aliases: { retire_missing: ['retireMissing'] },
20
19
  output: output(SCHEMAS.adopt),
21
20
  async execute(args, exec) {
22
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
23
22
  const actor = await actorFor(tower, exec)
24
23
  requireTowerActor(actor, 'adopt the roster')
25
24
  const view = await tower.rosterView()
package/tools/batch.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // tower_batch: finite evidence batches.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { TowerToolError } from '../domain/errors.js'
5
4
  import { BatchInput } from '../domain/contracts/tool-inputs/index.js'
@@ -7,7 +6,7 @@ import { BATCH_ACTION } from '../domain/vocabulary.js'
7
6
 
8
7
  /**
9
8
  * @param {any} ctx
10
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, summarizeBatch: any, verifyBatchReceiptFiles: any }} deps
9
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, summarizeBatch: any, verifyBatchReceiptFiles: any }} deps
11
10
  */
12
11
  export function registerBatchTool(ctx, deps) {
13
12
  const { define, actorFor, requireTowerActor, summarizeBatch, verifyBatchReceiptFiles } = deps
@@ -19,7 +18,7 @@ export function registerBatchTool(ctx, deps) {
19
18
  board: true,
20
19
  output: output(SCHEMAS.batch),
21
20
  async execute(args, exec) {
22
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
23
22
  const actor = await actorFor(tower, exec)
24
23
  if (args.action === BATCH_ACTION.status) {
25
24
  const state = await tower.load()
package/tools/complete.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // tower_complete: worker completion admission.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { TowerToolError } from '../domain/errors.js'
5
4
  import { CompleteInput } from '../domain/contracts/tool-inputs/index.js'
@@ -7,7 +6,7 @@ import { isCompleteAdmission } from '../domain/vocabulary.js'
7
6
 
8
7
  /**
9
8
  * @param {any} ctx
10
- * @param {{ define: import('../types/index.js').DefineTool, runMissionDiscovery: any }} deps
9
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, runMissionDiscovery: any }} deps
11
10
  */
12
11
  export function registerCompleteTool(ctx, deps) {
13
12
  const { define, runMissionDiscovery } = deps
@@ -21,7 +20,7 @@ export function registerCompleteTool(ctx, deps) {
21
20
  board: true,
22
21
  output: output(SCHEMAS.complete),
23
22
  async execute(args, exec) {
24
- const tower = await Tower.open(args.repoRoot)
23
+ const tower = await deps.openTower(args.repoRoot)
25
24
  // Validate authorization/state before executing repository-controlled
26
25
  // test code or writing a test receipt.
27
26
  await tower.checkCompletion({
package/tools/diff.js CHANGED
@@ -13,15 +13,15 @@
13
13
  // The patch cap is enforced by the pure read projection.
14
14
  // See docs/reference/tools.generated.md for the public shape.
15
15
  // tower_diff: read-only mission branch view.
16
- import { Tower } from '../app/tower.js'
17
16
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
18
17
  import { DiffInput } from '../domain/contracts/tool-inputs/index.js'
19
18
 
20
19
  /**
21
20
  * @param {any} ctx
22
- * @param {{ define: import('../types/index.js').DefineTool }} deps
21
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any }} deps
23
22
  */
24
23
  export function registerDiffTool(ctx, deps) {
24
+ // openTower is injected by the composition root (#69).
25
25
  const { define } = deps
26
26
  define(ctx, {
27
27
  name: 'tower_diff',
@@ -32,7 +32,7 @@ export function registerDiffTool(ctx, deps) {
32
32
  requiresMissionId: true,
33
33
  output: output(SCHEMAS.diff),
34
34
  async execute(args) {
35
- const tower = await Tower.open(args.repoRoot)
35
+ const tower = await deps.openTower(args.repoRoot)
36
36
  return tower.diff({ missionId: args.missionId, maxBytes: args.maxBytes })
37
37
  },
38
38
  })
package/tools/doctor.js CHANGED
@@ -1,36 +1,33 @@
1
1
  // tower_doctor: read-only health check.
2
- import { Tower } from '../app/tower.js'
3
- import { resolveRepo } from '../lib/git.js'
4
- import { runtimeConfig } from '../lib/config.js'
5
- import { runDoctor } from '../lib/doctor.js'
6
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
7
3
  import { DoctorInput } from '../domain/contracts/tool-inputs/index.js'
8
4
  import { DOCTOR_STATUS } from '../domain/vocabulary.js'
9
5
 
10
6
  /**
11
7
  * @param {any} ctx
12
- * @param {import('../types/index.js').DefineTool} define
8
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, createTower: any, resolveRepo: any, runtimeConfig: any, runDoctor: any, surface?: () => { tools?: number } }} deps
13
9
  */
14
- export function registerDoctorTool(ctx, define) {
10
+ export function registerDoctorTool(ctx, deps) {
11
+ const { define, createTower, resolveRepo, runtimeConfig, runDoctor } = deps
15
12
  define(ctx, {
16
13
  name: 'tower_doctor',
17
14
  description:
18
- 'Read-only\ environment\ and\ repository\ health\ check:\ git,\ state\ schema,\ case-sensitivity\ detection\ \(caseInsensitive\ and\ the\ active\ scope\ comparison\ mode\),\ config,\ presets/subagents/HMR,\ workspace\ lock,\ roster\ staleness,\ worktree\ ownership,\ test\ discovery,\ leases,\ and\ the\ terminal-mission\ archive\ \(count,\ bytes,\ years,\ unreadable\ diagnostics\ and\ the\ configured\ afterDays/maxMissions\ policy\)\.\ Returns\ checks\ with\ pass/warn/fail\ and\ an\ environment\ contract\.\ Never\ writes\ state\ and\ never\ probes\ the\ filesystem\.',
15
+ 'Read-only\ environment\ and\ repository\ health\ check:\ the\ concept-budget\ surface\ \(config\ keys/sections/tools\),\ git,\ state\ schema,\ case-sensitivity\ detection\ \(caseInsensitive\ and\ the\ active\ scope\ comparison\ mode\),\ config,\ presets/subagents/HMR,\ the\ state\ lock\ age/heartbeat\ freshness,\ the\ repo-scoped\ git\ lock\ holder\ \(gitLock:\ \{held,pid,phase,heldMs\}\),\ workspace\ lock,\ roster\ staleness,\ worktree\ ownership,\ test\ discovery,\ leases,\ and\ the\ terminal-mission\ archive\ \(count,\ bytes,\ years,\ unreadable\ diagnostics\ and\ the\ configured\ afterDays/maxMissions\ policy\)\.\ Returns\ checks\ with\ pass/warn/fail,\ state-lock\ and\ git-lock\ reports\ and\ an\ environment\ contract\.\ Never\ writes\ state\ and\ never\ probes\ the\ filesystem\.',
19
16
  parameters: DoctorInput,
20
17
  output: output(SCHEMAS.doctor),
21
18
  async execute(args, exec) {
22
19
  let tower
23
20
  let configError = null
24
21
  try {
25
- tower = await Tower.open(args.repoRoot)
22
+ tower = await deps.openTower(args.repoRoot)
26
23
  } catch (error) {
27
24
  const message = String(error?.message ?? error)
28
25
  if (error?.code !== 'config-invalid' && error?.code !== 'config-unknown-key') throw error
29
26
  configError = message
30
27
  const repo = await resolveRepo(args.repoRoot)
31
- tower = new Tower(repo, { ...runtimeConfig(), configPath: null, repoConfigPresent: true })
28
+ tower = deps.createTower(repo, { ...runtimeConfig(), configPath: null, repoConfigPresent: true })
32
29
  }
33
- const report = await runDoctor(/** @type {any} */ ({ tower, ctx, agent: exec.agent }))
30
+ const report = await runDoctor(/** @type {any} */ ({ tower, ctx, agent: exec.agent, surface: deps.surface?.() }))
34
31
  if (!configError) return report
35
32
  return {
36
33
  ...report,
package/tools/finding.js CHANGED
@@ -1,12 +1,11 @@
1
1
  // tower_finding: file one structured finding.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { FindingInput } from '../domain/contracts/tool-inputs/index.js'
5
4
  import { ROLE } from '../domain/vocabulary.js'
6
5
 
7
6
  /**
8
7
  * @param {any} ctx
9
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireKnownActor: any }} deps
8
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireKnownActor: any }} deps
10
9
  */
11
10
  export function registerFindingTool(ctx, deps) {
12
11
  const { define, actorFor, requireKnownActor } = deps
@@ -19,7 +18,7 @@ export function registerFindingTool(ctx, deps) {
19
18
  board: true,
20
19
  output: output(SCHEMAS.finding),
21
20
  async execute(args, exec) {
22
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
23
22
  const actor = await actorFor(tower, exec)
24
23
  requireKnownActor(actor, 'tower_finding')
25
24
  const view = await tower.rosterView()
package/tools/findings.js CHANGED
@@ -1,14 +1,14 @@
1
1
  // tower_findings: list, read, or update findings.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { FindingsInput } from '../domain/contracts/tool-inputs/index.js'
5
4
  import { ROLE } from '../domain/vocabulary.js'
6
5
 
7
6
  /**
8
7
  * @param {any} ctx
9
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireKnownActor: any }} deps
8
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireKnownActor: any }} deps
10
9
  */
11
10
  export function registerFindingsTool(ctx, deps) {
11
+ // openTower is injected by the composition root (#69).
12
12
  const { define, actorFor, requireKnownActor } = deps
13
13
  define(ctx, {
14
14
  name: 'tower_findings',
@@ -18,7 +18,7 @@ export function registerFindingsTool(ctx, deps) {
18
18
  aliases: {"missionId":["mission_id"]},
19
19
  output: output(SCHEMAS.findings),
20
20
  async execute(args, exec) {
21
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
22
22
  const actor = await actorFor(tower, exec)
23
23
  requireKnownActor(actor, 'tower_findings')
24
24
  const view = await tower.rosterView()
@@ -8,17 +8,15 @@
8
8
  // Pure legacy validation lives in domain and never runs fs.
9
9
  // See docs/reference/tools.generated.md for the public shape.
10
10
  // tower_import_legacy: read-only legacy snapshot import.
11
- import { Tower } from '../app/tower.js'
12
11
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
13
- import { importLegacySnapshot } from '../lib/legacy.js'
14
12
  import { ImportLegacyInput } from '../domain/contracts/tool-inputs/index.js'
15
13
 
16
14
  /**
17
15
  * @param {any} ctx
18
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any }} deps
16
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, importLegacySnapshot: any }} deps
19
17
  */
20
18
  export function registerImportLegacyTool(ctx, deps) {
21
- const { define, actorFor, requireTowerActor } = deps
19
+ const { define, actorFor, requireTowerActor, importLegacySnapshot } = deps
22
20
  define(ctx, {
23
21
  name: 'tower_import_legacy',
24
22
  description:
@@ -27,7 +25,7 @@ export function registerImportLegacyTool(ctx, deps) {
27
25
  board: true,
28
26
  output: output(SCHEMAS.importLegacy),
29
27
  async execute(args, exec) {
30
- const tower = await Tower.open(args.repoRoot)
28
+ const tower = await deps.openTower(args.repoRoot)
31
29
  const actor = await actorFor(tower, exec)
32
30
  requireTowerActor(actor, 'import a legacy snapshot')
33
31
  const imported = await importLegacySnapshot({
package/tools/inbox.js CHANGED
@@ -10,16 +10,16 @@
10
10
  // The unread calculation is a pure domain/collab projection.
11
11
  // See docs/reference/tools.generated.md for the public shape.
12
12
  // tower_inbox: read and acknowledge inbound messages.
13
- import { Tower } from '../app/tower.js'
14
13
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
15
14
  import { InboxInput } from '../domain/contracts/tool-inputs/index.js'
16
15
  import { ROLE } from '../domain/vocabulary.js'
17
16
 
18
17
  /**
19
18
  * @param {any} ctx
20
- * @param {{ define: import('../types/index.js').DefineTool }} deps
19
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any }} deps
21
20
  */
22
21
  export function registerInboxTool(ctx, deps) {
22
+ // openTower is injected by the composition root (#69).
23
23
  const { define } = deps
24
24
  define(ctx, {
25
25
  name: 'tower_inbox',
@@ -29,7 +29,7 @@ export function registerInboxTool(ctx, deps) {
29
29
  aliases: {"include_read":["includeRead"]},
30
30
  output: output(SCHEMAS.inbox),
31
31
  async execute(args, exec) {
32
- const tower = await Tower.open(args.repoRoot)
32
+ const tower = await deps.openTower(args.repoRoot)
33
33
  const view = await tower.rosterView()
34
34
  const self = view.roster.find((entry) => entry.agentId === String(exec.agent.id))
35
35
  const reader = self ? self.name : ROLE.tower
package/tools/index.js CHANGED
@@ -9,9 +9,9 @@
9
9
  // - tools must not import node:fs or node:child_process directly.
10
10
  //
11
11
  // Tests import this module for the public surface; they may also
12
- // import lib/tool-registry.js for low-level test hooks.
12
+ // import host/tool-registry.js for low-level test hooks.
13
13
  //
14
- // Packaging: package.json ships tools/*.js and lib/tool-registry.js.
14
+ // Packaging: package.json ships tools/*.js and host/tool-registry.js.
15
15
  // The file intentionally stays tiny so handler modules remain the
16
16
  // only place where a tool is defined.
17
17
  // Keep this comment and the re-export list in sync when adding tools.
@@ -20,7 +20,7 @@
20
20
  //
21
21
  // The registration aggregator, guards, rate limits, spawn backoff,
22
22
  // repository preparation, discovery helpers, and verb helpers live in
23
- // lib/tool-registry.js. This module only re-exports the public entry
23
+ // host/tool-registry.js. This module only re-exports the public entry
24
24
  // points consumed by the Host plugin, tests, and docs generators.
25
25
  //
26
26
  // Design rules:
@@ -30,10 +30,10 @@
30
30
  // - no business logic in this file;
31
31
  // - keep the surface stable for index.js and tests.
32
32
  //
33
- // See docs/architecture/overview.md and lib/tool-registry.js.
33
+ // See docs/architecture/overview.md and host/tool-registry.js.
34
34
  // Tower tool surface: thin public wiring (#56 P5).
35
- // Registration and shared machinery live in lib/tool-registry.js; one handler
35
+ // Registration and shared machinery live in host/tool-registry.js; one handler
36
36
  // module per tool lives under tools/. This file only exposes the public entry
37
37
  // points consumed by the Host plugin, tests, and documentation generators.
38
- export { registerTowerTools, __testSpawnBackoff } from '../lib/tool-registry.js'
39
- export { workerBriefing, reviewerBriefing } from '../lib/briefing.js'
38
+ export { registerTowerTools, __testSpawnBackoff } from '../host/tool-registry.js'
39
+ export { workerBriefing, reviewerBriefing } from '../app/briefing.js'
package/tools/init.js CHANGED
@@ -1,18 +1,15 @@
1
1
  // tower_init: repository initialization.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { TowerToolError } from '../domain/errors.js'
5
- import { resolveRepo } from '../lib/git.js'
6
- import { runtimeConfig } from '../lib/config.js'
7
4
  import { InitInput } from '../domain/contracts/tool-inputs/index.js'
8
5
  import { isTowerRole, isUnknownRole } from '../domain/vocabulary.js'
9
6
 
10
7
  /**
11
8
  * @param {any} ctx
12
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, workspaceHook: any, ensureRepository: any }} deps
9
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, workspaceHook: any, ensureRepository: any, resolveRepo: any, runtimeConfig: any }} deps
13
10
  */
14
11
  export function registerInitTool(ctx, deps) {
15
- const { define, actorFor, requireTowerActor, workspaceHook, ensureRepository } = deps
12
+ const { define, actorFor, requireTowerActor, workspaceHook, ensureRepository, resolveRepo, runtimeConfig } = deps
16
13
  define(ctx, {
17
14
  name: 'tower_init',
18
15
  description:
@@ -31,7 +28,7 @@ export function registerInitTool(ctx, deps) {
31
28
  args.autoGitInit === true || (args.autoGitInit === undefined && profileInit.autoGitInit === true)
32
29
  repo = await ensureRepository(args.repoRoot, autoGitInit)
33
30
  }
34
- const tower = await Tower.open(repo.root)
31
+ const tower = await deps.openTower(repo.root)
35
32
  const initConfig = tower.config.init ?? {}
36
33
  const snapshotDirty =
37
34
  args.snapshotDirty === true || (args.snapshotDirty === undefined && initConfig.snapshotDirty === true)
@@ -8,14 +8,13 @@
8
8
  // Roster state policy is pure and lives in domain/collab.
9
9
  // See docs/reference/tools.generated.md for the public shape.
10
10
  // tower_interrupt: interrupt one running roster agent.
11
- import { Tower } from '../app/tower.js'
12
11
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
13
12
  import { TowerToolError } from '../domain/errors.js'
14
13
  import { InterruptInput } from '../domain/contracts/tool-inputs/index.js'
15
14
 
16
15
  /**
17
16
  * @param {any} ctx
18
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, interruptRosterAgent: any }} deps
17
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, interruptRosterAgent: any }} deps
19
18
  */
20
19
  export function registerInterruptTool(ctx, deps) {
21
20
  const { define, actorFor, requireTowerActor, interruptRosterAgent } = deps
@@ -27,7 +26,7 @@ export function registerInterruptTool(ctx, deps) {
27
26
  aliases: { agentId: ['agent_id'] },
28
27
  output: output(SCHEMAS.interrupt),
29
28
  async execute(args, exec) {
30
- const tower = await Tower.open(args.repoRoot)
29
+ const tower = await deps.openTower(args.repoRoot)
31
30
  const actor = await actorFor(tower, exec)
32
31
  requireTowerActor(actor, 'interrupt tower agents')
33
32
  if (!args.to && !args.agentId) throw new TowerToolError('invalid-argument', 'tower_interrupt requires to (roster name) or agentId')
@@ -8,19 +8,17 @@
8
8
  // Pure mapping validation lives in the domain legacy layer.
9
9
  // See docs/reference/tools.generated.md for the public shape.
10
10
  // tower_legacy_map: audit-only legacy snapshot mapping.
11
- import { Tower } from '../app/tower.js'
12
11
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
13
12
  import { TowerToolError } from '../domain/errors.js'
14
- import { validateLegacyMap } from '../lib/legacy.js'
15
13
  import { LegacyMapInput } from '../domain/contracts/tool-inputs/index.js'
16
14
  import { LEGACY_MAP_ACTION } from '../domain/vocabulary.js'
17
15
 
18
16
  /**
19
17
  * @param {any} ctx
20
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any }} deps
18
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, validateLegacyMap: any }} deps
21
19
  */
22
20
  export function registerLegacyMapTool(ctx, deps) {
23
- const { define, actorFor, requireTowerActor } = deps
21
+ const { define, actorFor, requireTowerActor, validateLegacyMap } = deps
24
22
  define(ctx, {
25
23
  name: 'tower_legacy_map',
26
24
  description:
@@ -28,7 +26,7 @@ export function registerLegacyMapTool(ctx, deps) {
28
26
  parameters: LegacyMapInput,
29
27
  output: output(SCHEMAS.legacyMap),
30
28
  async execute(args, exec) {
31
- const tower = await Tower.open(args.repoRoot)
29
+ const tower = await deps.openTower(args.repoRoot)
32
30
  const actor = await actorFor(tower, exec)
33
31
  const state = await tower.load()
34
32
  if (args.action === LEGACY_MAP_ACTION.status) {
package/tools/merge.js CHANGED
@@ -9,15 +9,15 @@
9
9
  // The pure intent/shape decisions live in domain/integration.
10
10
  // See docs/reference/tools.generated.md for the public shape.
11
11
  // tower_merge: merge a mission branch into the recorded base.
12
- import { Tower } from '../app/tower.js'
13
12
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
14
13
  import { MergeInput } from '../domain/contracts/tool-inputs/index.js'
15
14
 
16
15
  /**
17
16
  * @param {any} ctx
18
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
17
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
19
18
  */
20
19
  export function registerMergeTool(ctx, deps) {
20
+ // openTower is injected by the composition root (#69).
21
21
  const { define, actorFor, requireTowerActor, observeActivity } = deps
22
22
  define(ctx, {
23
23
  name: 'tower_merge',
@@ -29,7 +29,7 @@ export function registerMergeTool(ctx, deps) {
29
29
  board: true,
30
30
  output: output(SCHEMAS.merge),
31
31
  async execute(args, exec) {
32
- const tower = await Tower.open(args.repoRoot)
32
+ const tower = await deps.openTower(args.repoRoot)
33
33
  const actor = await actorFor(tower, exec)
34
34
  requireTowerActor(actor, 'merge missions')
35
35
  const { activity, parentStatus } = await observeActivity(ctx, tower)
package/tools/mission.js CHANGED
@@ -1,12 +1,11 @@
1
1
  // tower_mission: mission lifecycle updates.
2
- import { Tower } from '../app/tower.js'
3
2
  import { TowerToolError } from '../domain/errors.js'
4
3
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
5
4
  import { MissionInput } from '../domain/contracts/tool-inputs/index.js'
6
5
 
7
6
  /**
8
7
  * @param {any} ctx
9
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireKnownActor: any }} deps
8
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireKnownActor: any }} deps
10
9
  */
11
10
  export function registerMissionTool(ctx, deps) {
12
11
  const { define, actorFor, requireKnownActor } = deps
@@ -19,7 +18,7 @@ export function registerMissionTool(ctx, deps) {
19
18
  board: true,
20
19
  output: output(SCHEMAS.mission),
21
20
  async execute(args, exec) {
22
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
23
22
  const actor = await actorFor(tower, exec)
24
23
  requireKnownActor(actor, 'tower_mission')
25
24
  return tower.mission({
package/tools/plan.js CHANGED
@@ -10,17 +10,18 @@
10
10
  // Pure normalization and DAG policy live in domain/missions.
11
11
  // See docs/reference/tools.generated.md for the public shape.
12
12
  // tower_plan: create missions.
13
- import { Tower } from '../app/tower.js'
14
13
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
15
- import { assertPresetsAvailable } from '../lib/presets.js'
16
14
  import { PlanInput } from '../domain/contracts/tool-inputs/index.js'
17
15
 
18
16
  /**
19
17
  * @param {any} ctx
20
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any }} deps
18
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, assertPresetsAvailable: any }} deps
21
19
  */
22
20
  export function registerPlanTool(ctx, deps) {
23
- const { define, actorFor, requireTowerActor } = deps
21
+ // #70: preset validation is injected by the composition root, so this tool
22
+ // never imports host/ or adapters/ directly.
23
+ // openTower is injected by the composition root (#69).
24
+ const { define, actorFor, requireTowerActor, assertPresetsAvailable } = deps
24
25
  define(ctx, {
25
26
  name: 'tower_plan',
26
27
  description:
@@ -29,7 +30,7 @@ export function registerPlanTool(ctx, deps) {
29
30
  board: true,
30
31
  output: output(SCHEMAS.plan),
31
32
  async execute(args, exec) {
32
- const tower = await Tower.open(args.repoRoot)
33
+ const tower = await deps.openTower(args.repoRoot)
33
34
  const actor = await actorFor(tower, exec)
34
35
  requireTowerActor(actor, 'plan missions')
35
36
  await assertPresetsAvailable(ctx, tower.config)
@@ -1,12 +1,11 @@
1
1
  // tower_preflight: read-only merge gate digest.
2
- import { Tower } from '../app/tower.js'
3
2
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
4
3
  import { classifyError } from '../domain/errors.js'
5
4
  import { PreflightInput } from '../domain/contracts/tool-inputs/index.js'
6
5
 
7
6
  /**
8
7
  * @param {any} ctx
9
- * @param {{ define: import('../types/index.js').DefineTool, observeActivity: any }} deps
8
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, observeActivity: any }} deps
10
9
  */
11
10
  export function registerPreflightTool(ctx, deps) {
12
11
  const { define, observeActivity } = deps
@@ -19,7 +18,7 @@ export function registerPreflightTool(ctx, deps) {
19
18
  requiresMissionId: true,
20
19
  output: output(SCHEMAS.preflight),
21
20
  async execute(args) {
22
- const tower = await Tower.open(args.repoRoot)
21
+ const tower = await deps.openTower(args.repoRoot)
23
22
  const { activity, parentStatus } = await observeActivity(ctx, tower)
24
23
  const gate = await tower.evaluateGate({ missionId: args.missionId, activity, parentStatus })
25
24
  return {
package/tools/rebase.js CHANGED
@@ -9,15 +9,15 @@
9
9
  // Pure intent/waiver decisions live in domain/integration.
10
10
  // See docs/reference/tools.generated.md for the public shape.
11
11
  // tower_rebase: rebase a mission branch inside its worktree.
12
- import { Tower } from '../app/tower.js'
13
12
  import { SCHEMAS, output } from '../domain/contracts/outputs.js'
14
13
  import { RebaseInput } from '../domain/contracts/tool-inputs/index.js'
15
14
 
16
15
  /**
17
16
  * @param {any} ctx
18
- * @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
17
+ * @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
19
18
  */
20
19
  export function registerRebaseTool(ctx, deps) {
20
+ // openTower is injected by the composition root (#69).
21
21
  const { define, actorFor, requireTowerActor, observeActivity } = deps
22
22
  define(ctx, {
23
23
  name: 'tower_rebase',
@@ -29,7 +29,7 @@ export function registerRebaseTool(ctx, deps) {
29
29
  board: true,
30
30
  output: output(SCHEMAS.rebase),
31
31
  async execute(args, exec) {
32
- const tower = await Tower.open(args.repoRoot)
32
+ const tower = await deps.openTower(args.repoRoot)
33
33
  const actor = await actorFor(tower, exec)
34
34
  requireTowerActor(actor, 'rebase missions')
35
35
  const { activity } = await observeActivity(ctx, tower)