dsh-tower 2.1.0-beta.1 → 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 (188) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMPATIBILITY.md +6 -1
  3. package/CONTRIBUTING.md +71 -5
  4. package/PRINCIPLES.md +145 -0
  5. package/README.en.md +14 -5
  6. package/README.md +14 -5
  7. package/SECURITY.md +6 -0
  8. package/adapters/archive.js +279 -0
  9. package/adapters/caseSensitivity.js +66 -0
  10. package/adapters/default.js +51 -8
  11. package/{lib → adapters}/git.js +1 -1
  12. package/{lib → adapters}/leases.js +15 -6
  13. package/{lib → adapters}/legacy.js +20 -6
  14. package/adapters/persistence.js +543 -0
  15. package/adapters/stateFile.js +99 -0
  16. package/adapters/store-fs.js +28 -1
  17. package/adapters/store.js +204 -0
  18. package/{lib → adapters}/tests.js +15 -7
  19. package/adapters/vcs-git.js +15 -1
  20. package/{lib → adapters}/workspaceLock.js +1 -1
  21. package/{lib → adapters}/worktreeOwnership.js +18 -7
  22. package/app/archive.js +188 -0
  23. package/app/batch.js +9 -10
  24. package/app/board.js +4 -4
  25. package/app/checkCompletion.js +23 -11
  26. package/app/enrichedMissions.js +2 -4
  27. package/app/findings.js +6 -7
  28. package/app/gate.js +32 -15
  29. package/app/init.js +59 -22
  30. package/app/merge.js +64 -51
  31. package/app/messages.js +7 -8
  32. package/app/mission.js +7 -5
  33. package/app/plan.js +20 -9
  34. package/app/provision.js +12 -14
  35. package/app/read.js +65 -6
  36. package/app/rebase.js +14 -16
  37. package/app/recording.js +7 -8
  38. package/app/review.js +17 -8
  39. package/app/roster.js +14 -15
  40. package/{lib → app}/spawn-prechecks.js +4 -3
  41. package/app/spawn.js +12 -13
  42. package/app/teardown.js +28 -8
  43. package/app/tower.js +121 -39
  44. package/docs/README.md +6 -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 +50 -0
  49. package/docs/adr/README.md +51 -0
  50. package/docs/agents/domain.md +38 -0
  51. package/docs/agents/issue-tracker.md +46 -0
  52. package/docs/agents/triage-labels.md +15 -0
  53. package/docs/agents/workflow.md +51 -0
  54. package/docs/architecture/overview.md +170 -37
  55. package/docs/guide/hot-reload.md +1 -1
  56. package/docs/guide/install-upgrade-migration.md +18 -1
  57. package/docs/guide/release.md +1 -1
  58. package/docs/operations/incident.md +82 -0
  59. package/docs/operations/runbook.md +12 -3
  60. package/docs/reference/blockers.md +3 -3
  61. package/docs/reference/config.generated.md +5 -0
  62. package/docs/reference/config.md +4 -0
  63. package/docs/reference/doctor.md +104 -8
  64. package/docs/reference/errors.generated.md +41 -35
  65. package/docs/reference/errors.md +54 -40
  66. package/docs/reference/events.md +38 -6
  67. package/docs/reference/surface-budget.md +128 -0
  68. package/docs/reference/tools.generated.md +9 -8
  69. package/docs/reference/tools.md +9 -8
  70. package/docs/zh/guide/hot-reload.md +1 -1
  71. package/docs/zh/guide/install-upgrade-migration.md +13 -1
  72. package/docs/zh/reference/blockers.md +3 -3
  73. package/docs/zh/reference/config.md +4 -0
  74. package/docs/zh/reference/doctor.md +63 -2
  75. package/docs/zh/reference/errors.md +4 -2
  76. package/docs/zh/reference/tools.md +6 -6
  77. package/domain/batch.js +2 -2
  78. package/domain/config/schema.js +51 -4
  79. package/domain/contracts/outputs.js +33 -3
  80. package/domain/contracts/tool-inputs/missions.js +2 -1
  81. package/domain/contracts/tool-inputs/ops.js +1 -0
  82. package/domain/errors.js +297 -40
  83. package/domain/events.js +207 -0
  84. package/domain/integration/gate.js +41 -12
  85. package/domain/integration/init.js +40 -4
  86. package/domain/integration/merge.js +35 -0
  87. package/domain/lockBudget.js +30 -0
  88. package/domain/mission.js +1 -1
  89. package/domain/missions/completion.js +72 -5
  90. package/domain/missions/lifecycle.js +11 -4
  91. package/domain/missions/plan.js +14 -0
  92. package/domain/missions/review.js +90 -7
  93. package/domain/planning.js +6 -3
  94. package/domain/read/views.js +17 -1
  95. package/domain/report.js +9 -1
  96. package/domain/scope.js +83 -11
  97. package/domain/state/archive.js +257 -0
  98. package/domain/state/migrations.js +145 -0
  99. package/domain/state/schema.js +6 -0
  100. package/domain/state.js +154 -4
  101. package/domain/summary.js +2 -0
  102. package/domain/surface.js +248 -0
  103. package/host/doctor.js +555 -0
  104. package/{lib → host}/modeStore.js +1 -1
  105. package/{lib → host}/tool-registry.js +106 -31
  106. package/host/tower.js +32 -0
  107. package/index.js +12 -9
  108. package/package.json +10 -3
  109. package/ports/archive.js +18 -0
  110. package/ports/caseSensitivity.js +15 -0
  111. package/ports/clock.js +10 -2
  112. package/ports/config.js +19 -0
  113. package/ports/contract.js +38 -0
  114. package/ports/events.js +24 -0
  115. package/ports/ids.js +10 -2
  116. package/ports/index.js +54 -2
  117. package/ports/ownership.js +17 -0
  118. package/ports/state.js +16 -0
  119. package/ports/store.js +52 -3
  120. package/ports/vcs.js +25 -4
  121. package/scripts/architecture-waivers.json +19 -0
  122. package/scripts/architecture.mjs +634 -23
  123. package/scripts/check-docs.mjs +8 -35
  124. package/scripts/check-pack.mjs +68 -39
  125. package/scripts/check-philosophy.mjs +266 -0
  126. package/scripts/domain-metrics.mjs +13 -4
  127. package/scripts/generate-config-reference.mjs +14 -1
  128. package/scripts/generate-error-reference.mjs +68 -14
  129. package/scripts/generate-events-reference.mjs +83 -0
  130. package/scripts/generate-tools-reference.mjs +11 -0
  131. package/scripts/issue-audit.mjs +2 -2
  132. package/scripts/lib/surface.mjs +87 -0
  133. package/scripts/pack-manifest.txt +64 -19
  134. package/scripts/regression-audit.mjs +164 -0
  135. package/scripts/release-check.mjs +1 -1
  136. package/scripts/surface-baseline.json +21 -0
  137. package/scripts/surface-baseline.mjs +120 -0
  138. package/tools/adopt.js +2 -3
  139. package/tools/batch.js +2 -3
  140. package/tools/complete.js +3 -4
  141. package/tools/diff.js +3 -3
  142. package/tools/doctor.js +7 -10
  143. package/tools/finding.js +2 -3
  144. package/tools/findings.js +3 -3
  145. package/tools/import-legacy.js +3 -5
  146. package/tools/inbox.js +3 -3
  147. package/tools/index.js +7 -7
  148. package/tools/init.js +4 -7
  149. package/tools/interrupt.js +2 -3
  150. package/tools/legacy-map.js +3 -5
  151. package/tools/merge.js +3 -3
  152. package/tools/mission.js +2 -3
  153. package/tools/plan.js +7 -6
  154. package/tools/preflight.js +3 -3
  155. package/tools/rebase.js +3 -3
  156. package/tools/recover.js +38 -13
  157. package/tools/reload.js +3 -6
  158. package/tools/resource.js +3 -5
  159. package/tools/review.js +3 -4
  160. package/tools/roster.js +2 -3
  161. package/tools/send.js +2 -2
  162. package/tools/spawn.js +8 -5
  163. package/tools/status.js +16 -8
  164. package/tools/teardown.js +3 -4
  165. package/tools/test.js +2 -2
  166. package/types/index.d.ts +35 -1
  167. package/ui-bundle/README.md +37 -3
  168. package/ui-bundle/client.cards.js +356 -0
  169. package/ui-bundle/client.denied.js +142 -0
  170. package/ui-bundle/client.dock.js +333 -0
  171. package/ui-bundle/client.helpers.js +271 -0
  172. package/ui-bundle/client.js +79 -1747
  173. package/ui-bundle/client.locales.js +247 -0
  174. package/ui-bundle/client.mode.js +206 -0
  175. package/ui-bundle/client.overlay.js +64 -0
  176. package/ui-bundle/client.styles.js +151 -0
  177. package/ui-bundle/client.toolview.js +82 -0
  178. package/ui-bundle/package.json +1 -0
  179. package/lib/doctor.js +0 -270
  180. package/lib/persistence.js +0 -349
  181. package/lib/store.js +0 -118
  182. /package/{lib → app}/briefing.js +0 -0
  183. /package/{lib → app}/spawn-support.js +0 -0
  184. /package/{lib → host}/compatibility.js +0 -0
  185. /package/{lib → host}/config.js +0 -0
  186. /package/{lib → host}/hmr.js +0 -0
  187. /package/{lib → host}/modeState.js +0 -0
  188. /package/{lib → host}/presets.js +0 -0
@@ -0,0 +1,83 @@
1
+ // Generate docs/reference/events.md from the event table (#75) so event names
2
+ // and payloads cannot drift from the runtime. Usage:
3
+ // node scripts/generate-events-reference.mjs [--check]
4
+ import fs from 'node:fs'
5
+ import path from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import {
8
+ EVENT_COUNT,
9
+ LEGACY_TOOL_EVENT_TABLE,
10
+ MISSION_EVENT_TABLE,
11
+ } from '../domain/events.js'
12
+
13
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
14
+ const target = path.join(root, 'docs', 'reference', 'events.md')
15
+
16
+ const payloadCell = (entry) => `\`{ ${entry.payload.join(', ')} }\``
17
+ const missionRows = MISSION_EVENT_TABLE.map(
18
+ (entry) => `| \`${entry.name}\` | ${payloadCell(entry)} | ${entry.description} |`,
19
+ ).join('\n')
20
+ const legacyRows = LEGACY_TOOL_EVENT_TABLE.map((entry) => `| \`${entry.name}\` | ${entry.description} |`).join('\n')
21
+
22
+ const expected = `# Tower events
23
+
24
+ > Generated by \`npm run docs:generate:events\` from \`domain/events.js\`. Do not edit by hand.
25
+
26
+ Tower emits process-local Cordis events after successful tool calls (mutations and read-only tools), so
27
+ other plugins can observe a repository without polling. This release defines ${EVENT_COUNT} process-local events:
28
+ ${MISSION_EVENT_TABLE.length} stable domain events and ${LEGACY_TOOL_EVENT_TABLE.length} deprecated tool-shaped
29
+ aliases. Events are **not** sent over the network and carry no telemetry.
30
+
31
+ ## Domain events (stable)
32
+
33
+ Domain events describe what changed, not which tool ran. They are emitted only after the state write
34
+ succeeds, so \`revision\` is the revision a reader can load back from \`state.json\`; a failed tool emits
35
+ no domain event. \`at\` is the transition timestamp in epoch milliseconds. \`state.json\` stays
36
+ authoritative: these events are an observation channel, not an event-sourced log, and are never
37
+ replayed.
38
+
39
+ | Event | Payload | Emitted after |
40
+ |---|---|---|
41
+ ${missionRows}
42
+
43
+ \`\`\`js
44
+ ctx.on('tower.mission.merged', ({ missionId, revision, at, commit }) => {
45
+ // the merge is already durable; commit is the merge commit that landed
46
+ })
47
+ \`\`\`
48
+
49
+ ## Tool-shaped aliases (deprecated)
50
+
51
+ The older \`tower/<tool>\` events are kept for one minor release as a **deprecated** alias for the
52
+ tool-shaped observation channel; prefer the domain events above and expect the alias to be removed in
53
+ the next minor. They carry \`{ tool, repoRoot, value }\`, fire after a successful tool call, and never
54
+ fail a tool when a listener throws. For \`tower_merge\` the alias also fires for a gate failure whose
55
+ \`value.ok\` is false.
56
+
57
+ | Event | Emitted after |
58
+ |---|---|
59
+ ${legacyRows}
60
+
61
+ \`\`\`js
62
+ ctx.on('tower/merge', (payload) => {
63
+ // deprecated: prefer tower.mission.merged
64
+ // payload.tool, payload.repoRoot, payload.value (the tool's JSON result)
65
+ })
66
+ \`\`\`
67
+
68
+ For a durable audit trail use \`<git-common-dir>/dsh-tower/activity.jsonl\` and \`MISSIONS.md\` instead;
69
+ events are process-local and are not replayed.
70
+ `
71
+
72
+ if (process.argv.includes('--check')) {
73
+ const actual = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
74
+ if (actual !== expected) {
75
+ console.error('events reference is stale; run npm run docs:generate:events')
76
+ process.exit(1)
77
+ }
78
+ console.log(`events reference: ${EVENT_COUNT} events in sync`)
79
+ } else {
80
+ fs.mkdirSync(path.dirname(target), { recursive: true })
81
+ fs.writeFileSync(target, expected)
82
+ console.log(`wrote ${path.relative(root, target)} (${EVENT_COUNT} events)`)
83
+ }
@@ -3,9 +3,12 @@ import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { registerTowerTools } from '../tools/index.js'
6
+ import { SURFACE_BASELINE_FILE, surfaceLine } from '../domain/surface.js'
7
+ import { readBaseline } from './lib/surface.mjs'
6
8
 
7
9
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
8
10
  const target = path.join(root, 'docs', 'reference', 'tools.generated.md')
11
+ const baseline = readBaseline(root)
9
12
  const tools = new Map()
10
13
  const ctx = {
11
14
  get() {
@@ -33,9 +36,17 @@ registerTowerTools(ctx)
33
36
  const rows = [...tools.values()]
34
37
  .sort((a, b) => a.name.localeCompare(b.name))
35
38
  .map((tool) => `| \`${tool.name}\` | ${String(tool.description ?? '').replace(/\s+/g, ' ').trim()} |`)
39
+ // Live tools count, baseline config counts: `npm run surface:check` keeps the
40
+ // two halves equal, so the generated header carries one decision record.
41
+ const counts = {
42
+ configKeys: baseline.surfaces.configKeys.value,
43
+ sections: baseline.surfaces.sections.value,
44
+ tools: tools.size,
45
+ }
36
46
  const expected = `# Tools overview (generated)
37
47
 
38
48
  > Generated by \`npm run docs:generate:tools\` from the registered tool definitions. Do not edit by hand.
49
+ > Surface: ${surfaceLine(counts)} · baseline ${baseline.date}. Generated from \`${SURFACE_BASELINE_FILE}\`; do not edit by hand.
39
50
 
40
51
  | Tool | Description |
41
52
  |---|---|
@@ -87,7 +87,7 @@ export function selectEvidence(issue) {
87
87
  return null
88
88
  }
89
89
 
90
- function labelsOf(issue) {
90
+ export function labelsOf(issue) {
91
91
  const labels = Array.isArray(issue.labels) ? issue.labels : []
92
92
  return labels.map((label) => (typeof label === 'string' ? label : label?.name ?? '')).filter(Boolean)
93
93
  }
@@ -116,7 +116,7 @@ function closedAtOf(issue) {
116
116
 
117
117
  // A pointer must stay inside the repository and name a real test file: an
118
118
  // existing script or doc is not evidence of a regression test.
119
- function inspectPointer(pointer, root) {
119
+ export function inspectPointer(pointer, root) {
120
120
  const normalized = String(pointer).replace(/\\/g, '/').trim()
121
121
  if (!normalized) return { code: 'invalid-pointer', detail: 'empty' }
122
122
  if (path.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized) || normalized.startsWith('/')) {
@@ -0,0 +1,87 @@
1
+ // File-reading wrapper around the pure concept-budget surface (#84).
2
+ //
3
+ // `domain/surface.js` owns the rules; this module owns the two files they read
4
+ // (`scripts/surface-baseline.json` and `docs/reference/surface-budget.md`) and
5
+ // the combined check used by the CLI gate and the tests.
6
+ import fs from 'node:fs'
7
+ import path from 'node:path'
8
+ import {
9
+ SURFACE_BASELINE_FILE,
10
+ SURFACE_CLASSIFICATION_FILE,
11
+ parseBaseline,
12
+ validateBaseline,
13
+ validateClassification,
14
+ } from '../../domain/surface.js'
15
+
16
+ /**
17
+ * @param {string} root repository root
18
+ * @returns {ReturnType<typeof parseBaseline>}
19
+ */
20
+ export function readBaseline(root) {
21
+ const file = path.join(root, SURFACE_BASELINE_FILE)
22
+ return parseBaseline(fs.readFileSync(file, 'utf8'))
23
+ }
24
+
25
+ /**
26
+ * @param {string} root repository root
27
+ */
28
+ export function readClassification(root) {
29
+ return fs.readFileSync(path.join(root, SURFACE_CLASSIFICATION_FILE), 'utf8')
30
+ }
31
+
32
+ /**
33
+ * Run both halves of the concept-budget gate against one repository root.
34
+ * `current` carries the live counts and the live key/tool names.
35
+ *
36
+ * @param {{ root: string, current: { configKeys: number, sections: number, tools: number, liveNames: { configKeys: string[], sections: string[], tools: string[] } } }} options
37
+ */
38
+ export function checkSurfaceBudget({ root, current }) {
39
+ let baseline
40
+ try {
41
+ baseline = readBaseline(root)
42
+ } catch (error) {
43
+ return {
44
+ ok: false,
45
+ baseline: null,
46
+ classification: null,
47
+ findings: [
48
+ {
49
+ code: 'baseline-unreadable',
50
+ level: 'error',
51
+ message: `${SURFACE_BASELINE_FILE} is unreadable: ${String(error?.message ?? error)}`,
52
+ },
53
+ ],
54
+ }
55
+ }
56
+ const findings = validateBaseline(baseline, current, {
57
+ exists: (relative) => fs.existsSync(path.join(root, relative)),
58
+ })
59
+ let classificationText
60
+ try {
61
+ classificationText = readClassification(root)
62
+ } catch (error) {
63
+ return {
64
+ ok: false,
65
+ baseline,
66
+ classification: null,
67
+ findings: [
68
+ ...findings,
69
+ {
70
+ code: 'classification-unreadable',
71
+ level: 'error',
72
+ message: `${SURFACE_CLASSIFICATION_FILE} is unreadable: ${String(error?.message ?? error)}`,
73
+ },
74
+ ],
75
+ }
76
+ }
77
+ const classification = validateClassification(classificationText, current.liveNames, {
78
+ baselineDate: baseline.date,
79
+ })
80
+ const all = [...findings, ...classification.findings]
81
+ return {
82
+ ok: all.length === 0,
83
+ baseline,
84
+ classification,
85
+ findings: all,
86
+ }
87
+ }
@@ -2,14 +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
23
+ app/archive.js
11
24
  app/batch.js
12
25
  app/board.js
26
+ app/briefing.js
13
27
  app/checkCompletion.js
14
28
  app/enrichedMissions.js
15
29
  app/findings.js
@@ -26,11 +40,22 @@ app/rebase.js
26
40
  app/recording.js
27
41
  app/review.js
28
42
  app/roster.js
43
+ app/spawn-prechecks.js
44
+ app/spawn-support.js
29
45
  app/spawn.js
30
46
  app/teardown.js
31
47
  app/tower.js
32
48
  cordis.patch.yml
33
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/README.md
55
+ docs/agents/domain.md
56
+ docs/agents/issue-tracker.md
57
+ docs/agents/triage-labels.md
58
+ docs/agents/workflow.md
34
59
  docs/architecture/overview.md
35
60
  docs/board.md
36
61
  docs/examples/first-mission.md
@@ -40,6 +65,7 @@ docs/guide/install-upgrade-migration.md
40
65
  docs/guide/quickstart.md
41
66
  docs/guide/release.md
42
67
  docs/guide/scenarios-and-philosophy.md
68
+ docs/operations/incident.md
43
69
  docs/operations/runbook.md
44
70
  docs/operations/security.md
45
71
  docs/reference/blockers.md
@@ -50,6 +76,7 @@ docs/reference/errors.generated.md
50
76
  docs/reference/errors.md
51
77
  docs/reference/events.md
52
78
  docs/reference/operations.md
79
+ docs/reference/surface-budget.md
53
80
  docs/reference/testing.md
54
81
  docs/reference/tools.generated.md
55
82
  docs/reference/tools.md
@@ -83,6 +110,7 @@ domain/contracts/tool-inputs/missions.js
83
110
  domain/contracts/tool-inputs/ops.js
84
111
  domain/contracts/tool-inputs/shared.js
85
112
  domain/errors.js
113
+ domain/events.js
86
114
  domain/gate.js
87
115
  domain/integration/gate.js
88
116
  domain/integration/init.js
@@ -92,6 +120,7 @@ domain/integration/provision.js
92
120
  domain/integration/rebase.js
93
121
  domain/integration/review.js
94
122
  domain/integration/teardown.js
123
+ domain/lockBudget.js
95
124
  domain/mission.js
96
125
  domain/missions/completion.js
97
126
  domain/missions/findings.js
@@ -106,50 +135,54 @@ domain/report.js
106
135
  domain/roster.js
107
136
  domain/scope.js
108
137
  domain/state.js
138
+ domain/state/archive.js
139
+ domain/state/migrations.js
109
140
  domain/state/schema.js
110
141
  domain/state/semantics.js
111
142
  domain/summary.js
143
+ domain/surface.js
112
144
  domain/vocabulary.js
145
+ host/compatibility.js
146
+ host/config.js
147
+ host/doctor.js
148
+ host/hmr.js
149
+ host/modeState.js
150
+ host/modeStore.js
151
+ host/presets.js
152
+ host/tool-registry.js
153
+ host/tower.js
113
154
  icon.svg
114
155
  index.js
115
- lib/briefing.js
116
- lib/compatibility.js
117
- lib/config.js
118
- lib/doctor.js
119
- lib/git.js
120
- lib/hmr.js
121
- lib/leases.js
122
- lib/legacy.js
123
- lib/modeState.js
124
- lib/modeStore.js
125
- lib/persistence.js
126
- lib/presets.js
127
- lib/spawn-prechecks.js
128
- lib/spawn-support.js
129
- lib/store.js
130
- lib/tests.js
131
- lib/tool-registry.js
132
- lib/workspaceLock.js
133
- lib/worktreeOwnership.js
134
156
  locale/en.json
135
157
  locale/zh.json
136
158
  package.json
159
+ ports/archive.js
160
+ ports/caseSensitivity.js
137
161
  ports/clock.js
162
+ ports/config.js
163
+ ports/contract.js
164
+ ports/events.js
138
165
  ports/ids.js
139
166
  ports/index.js
167
+ ports/ownership.js
168
+ ports/state.js
140
169
  ports/store.js
141
170
  ports/vcs.js
142
171
  reviewer.js
172
+ scripts/architecture-waivers.json
143
173
  scripts/architecture.mjs
144
174
  scripts/check-docs.mjs
145
175
  scripts/check-pack.mjs
176
+ scripts/check-philosophy.mjs
146
177
  scripts/domain-metrics.mjs
147
178
  scripts/enable-dev-hmr.mjs
148
179
  scripts/generate-config-reference.mjs
149
180
  scripts/generate-error-reference.mjs
181
+ scripts/generate-events-reference.mjs
150
182
  scripts/generate-tools-reference.mjs
151
183
  scripts/issue-audit.mjs
152
184
  scripts/lib/cli.mjs
185
+ scripts/lib/surface.mjs
153
186
  scripts/lib/tmp.mjs
154
187
  scripts/mount-smoke.mjs
155
188
  scripts/mount-smoke/fixtures.mjs
@@ -157,7 +190,10 @@ scripts/mount-smoke/host.json
157
190
  scripts/mount-smoke/probe.mjs
158
191
  scripts/mount-smoke/surface.mjs
159
192
  scripts/pack-manifest.txt
193
+ scripts/regression-audit.mjs
160
194
  scripts/release-check.mjs
195
+ scripts/surface-baseline.json
196
+ scripts/surface-baseline.mjs
161
197
  skills.js
162
198
  skills/tower-recovery.md
163
199
  skills/tower-review.md
@@ -193,7 +229,16 @@ tools/test.js
193
229
  types/cordis.d.ts
194
230
  types/index.d.ts
195
231
  ui-bundle/README.md
232
+ ui-bundle/client.cards.js
233
+ ui-bundle/client.denied.js
234
+ ui-bundle/client.dock.js
235
+ ui-bundle/client.helpers.js
196
236
  ui-bundle/client.js
237
+ ui-bundle/client.locales.js
238
+ ui-bundle/client.mode.js
239
+ ui-bundle/client.overlay.js
240
+ ui-bundle/client.styles.js
241
+ ui-bundle/client.toolview.js
197
242
  ui-bundle/cordis.patch.yml
198
243
  ui-bundle/icon.svg
199
244
  ui-bundle/index.js
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ // Regression closure audit (#85): a closed `regression` issue must name a
3
+ // `Regression test:` path that exists.
4
+ //
5
+ // A regression is "worked in a released version, then stopped". The project's
6
+ // own closure standard is a pointer, not prose: the incident template
7
+ // (docs/operations/incident.md) carries six fields and the regression test path
8
+ // is the one that can be checked. This script reads a local tracker export, the
9
+ // same format as `npm run audit:issues`, so it never touches the network:
10
+ //
11
+ // { "issues": [ { number, title, state, labels, closedAt, body, comments } ] }
12
+ //
13
+ // Usage:
14
+ // node scripts/regression-audit.mjs [--tracker <file>] [--root <dir>] [--json]
15
+ //
16
+ // Exit codes: 0 all checked regressions point at an existing test; 1 at least
17
+ // one is missing or points at a nonexistent/non-test path; 2 the export is
18
+ // missing or malformed.
19
+ import fs from 'node:fs'
20
+ import path from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
22
+ import { inspectPointer, isClosed, labelsOf, parseTracker } from './issue-audit.mjs'
23
+ import { findingRecorder, parseArgs, runMain } from './lib/cli.mjs'
24
+
25
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
26
+ const DEFAULT_TRACKER = path.join('tracker', 'issues.json')
27
+ const USAGE = `usage: node scripts/regression-audit.mjs [--tracker <file>] [--root <dir>] [--json]
28
+
29
+ --tracker local tracker export (default: tracker/issues.json)
30
+ --root repository the Regression test paths resolve against (default: this package)
31
+ --json print the report as JSON
32
+ --help print this text
33
+
34
+ A regression issue is closed when state is closed (or closedAt is set) and its
35
+ labels include "regression". Its body or newest comment must carry:
36
+
37
+ Regression test: tests/<file>.test.mjs
38
+
39
+ The path must exist under --root and must be a test file. Produce the export once
40
+ with gh (see CONTRIBUTING.md, "Regressions and incidents"); the audit itself is
41
+ offline.`
42
+
43
+ function cleanValue(value) {
44
+ return String(value)
45
+ .trim()
46
+ .replace(/^`+|`+$/g, '')
47
+ .trim()
48
+ .replace(/^["']+|["']+$/g, '')
49
+ .trim()
50
+ }
51
+
52
+ /**
53
+ * The newest text (body first, then comments oldest to newest) that carries a
54
+ * `Regression test:` line is the closure evidence. Returns `{ pointer }` or
55
+ * `{ error: 'missing' }`.
56
+ * @param {any} issue
57
+ */
58
+ export function selectRegressionField(issue) {
59
+ const sources = []
60
+ if (typeof issue.body === 'string' && issue.body.trim()) sources.push(issue.body)
61
+ const comments = Array.isArray(issue.comments) ? issue.comments : []
62
+ for (const comment of comments) {
63
+ const body = typeof comment === 'string' ? comment : comment?.body
64
+ if (typeof body === 'string' && body.trim()) sources.push(body)
65
+ }
66
+ for (let index = sources.length - 1; index >= 0; index -= 1) {
67
+ for (const rawLine of sources[index].split(/\r?\n/)) {
68
+ const line = rawLine.replace(/^\s*(?:[-*+]\s+|>\s*)+/, '')
69
+ const match = /^Regression test\s*:\s*(.+)$/i.exec(line)
70
+ if (!match) continue
71
+ const pointer = cleanValue(match[1])
72
+ if (pointer) return { pointer }
73
+ }
74
+ }
75
+ return { error: 'missing' }
76
+ }
77
+
78
+ /**
79
+ * Audit closed regression issues against the repository test tree.
80
+ * @param {any[]} issues parsed tracker issues
81
+ * @param {{ root?: string }} [options]
82
+ */
83
+ export function auditRegressions(issues, options = {}) {
84
+ const root = path.resolve(options.root ?? PACKAGE_ROOT)
85
+ const findings = []
86
+ let checked = 0
87
+ for (const issue of issues) {
88
+ if (!isClosed(issue)) continue
89
+ const labels = labelsOf(issue).map((label) => label.toLowerCase())
90
+ if (!labels.includes('regression')) continue
91
+ checked += 1
92
+ const label = `#${issue.number} ${String(issue.title ?? '').trim()}`
93
+ const record = findingRecorder(findings, { number: issue.number, title: issue.title })
94
+ const field = selectRegressionField(issue)
95
+ if (field.error) {
96
+ record(
97
+ 'missing-regression-test',
98
+ 'error',
99
+ `${label}: closed regression has no "Regression test:" field (see docs/operations/incident.md)`,
100
+ )
101
+ continue
102
+ }
103
+ const problem = inspectPointer(field.pointer, root)
104
+ if (!problem) continue
105
+ if (problem.code === 'missing-test') {
106
+ record('missing-regression-test', 'error', `${label}: Regression test names a missing file: ${field.pointer}`)
107
+ } else if (problem.code === 'not-a-test') {
108
+ record('not-a-test', 'error', `${label}: Regression test is not a test file: ${field.pointer} (${problem.detail})`)
109
+ } else {
110
+ record(
111
+ 'invalid-regression-test',
112
+ 'error',
113
+ `${label}: Regression test must be a repository-relative path: ${field.pointer} (${problem.detail})`,
114
+ )
115
+ }
116
+ }
117
+ const errors = findings.filter((item) => item.level === 'error')
118
+ return { ok: errors.length === 0, checked, findings, errors: errors.length }
119
+ }
120
+
121
+ function main(argv) {
122
+ const options = parseArgs(argv, {
123
+ initial: { tracker: null, root: PACKAGE_ROOT, json: false, help: false },
124
+ values: {
125
+ '--tracker': 'tracker',
126
+ '--root': { key: 'root', parse: path.resolve },
127
+ },
128
+ flags: { '--json': 'json', '--help': 'help', '-h': 'help' },
129
+ })
130
+ if (options.help) {
131
+ console.log(USAGE)
132
+ return 0
133
+ }
134
+ const tracker = options.tracker ? path.resolve(process.cwd(), options.tracker) : path.join(PACKAGE_ROOT, DEFAULT_TRACKER)
135
+ let text
136
+ try {
137
+ text = fs.readFileSync(tracker, 'utf8')
138
+ } catch (error) {
139
+ console.error(`regression-audit: cannot read tracker export ${tracker}: ${error?.message ?? error}`)
140
+ console.error('regression-audit: produce one with the gh command in CONTRIBUTING.md ("Regressions and incidents")')
141
+ return 2
142
+ }
143
+ let issues
144
+ let result
145
+ try {
146
+ issues = parseTracker(text)
147
+ result = auditRegressions(issues, { root: options.root })
148
+ } catch (error) {
149
+ console.error(`regression-audit: ${error.message}`)
150
+ return 2
151
+ }
152
+ if (options.json) {
153
+ console.log(JSON.stringify({ tracker, root: path.resolve(options.root), ...result }, null, 2))
154
+ return result.ok ? 0 : 1
155
+ }
156
+ console.log(`regression-audit: ${tracker}`)
157
+ console.log(`regression-audit: ${result.checked} closed regression(s) checked, ${result.errors} error(s)`)
158
+ for (const item of result.findings) console.log(`${item.level} [${item.code}] ${item.message}`)
159
+ if (result.ok) console.log('regression-audit: every closed regression names an existing test file')
160
+ else console.log('regression-audit: a closed regression has no verifiable test pointer; see docs/operations/incident.md')
161
+ return result.ok ? 0 : 1
162
+ }
163
+
164
+ await runMain(import.meta.url, () => main(process.argv.slice(2)), { errorPrefix: 'regression-audit', usage: USAGE })
@@ -27,7 +27,7 @@
27
27
  import fs from 'node:fs'
28
28
  import path from 'node:path'
29
29
  import { spawnSync } from 'node:child_process'
30
- import { compareVersions } from '../lib/compatibility.js'
30
+ import { compareVersions } from '../host/compatibility.js'
31
31
  import { findingRecorder, parseArgs, runMain, UsageError } from './lib/cli.mjs'
32
32
 
33
33
  const USAGE = `usage: node scripts/release-check.mjs [--mode pr|tag] [--tag <name>] [--json] [--help]
@@ -0,0 +1,21 @@
1
+ {
2
+ "schema": 1,
3
+ "date": "2026-10-23",
4
+ "surfaces": {
5
+ "configKeys": {
6
+ "seed": 64,
7
+ "value": 64,
8
+ "decisions": []
9
+ },
10
+ "sections": {
11
+ "seed": 15,
12
+ "value": 15,
13
+ "decisions": []
14
+ },
15
+ "tools": {
16
+ "seed": 27,
17
+ "value": 27,
18
+ "decisions": []
19
+ }
20
+ }
21
+ }