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.
- package/CHANGELOG.md +103 -0
- package/COMPATIBILITY.md +5 -1
- package/CONTRIBUTING.md +64 -5
- package/PRINCIPLES.md +145 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/SECURITY.md +6 -0
- package/{lib → adapters}/archive.js +1 -1
- package/{lib → adapters}/caseSensitivity.js +1 -1
- package/adapters/default.js +51 -10
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +1 -1
- package/{lib → adapters}/legacy.js +1 -1
- package/adapters/persistence.js +543 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +9 -5
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +1 -1
- package/app/archive.js +4 -6
- package/app/batch.js +9 -10
- package/app/board.js +2 -4
- package/app/checkCompletion.js +9 -10
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +1 -2
- package/app/init.js +24 -29
- package/app/merge.js +58 -51
- package/app/messages.js +7 -8
- package/app/mission.js +5 -5
- package/app/plan.js +8 -5
- package/app/provision.js +12 -14
- package/app/read.js +6 -3
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +15 -8
- package/app/roster.js +14 -15
- package/{lib → app}/spawn-prechecks.js +4 -3
- package/app/spawn.js +12 -13
- package/app/teardown.js +8 -9
- package/app/tower.js +89 -35
- package/docs/README.md +6 -2
- package/docs/adr/0001-ports-adapters-as-data.md +49 -0
- package/docs/adr/0002-single-state-json.md +50 -0
- package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
- package/docs/adr/0004-surface-budget.md +50 -0
- package/docs/adr/README.md +51 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +167 -36
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/config.generated.md +1 -0
- package/docs/reference/doctor.md +48 -2
- package/docs/reference/errors.generated.md +41 -37
- package/docs/reference/errors.md +53 -41
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +128 -0
- package/docs/reference/tools.generated.md +5 -4
- package/docs/reference/tools.md +4 -4
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +4 -4
- package/domain/batch.js +2 -2
- package/domain/config/schema.js +25 -1
- package/domain/contracts/outputs.js +6 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +297 -42
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +17 -2
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/missions/completion.js +14 -0
- package/domain/missions/lifecycle.js +9 -3
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/read/views.js +3 -0
- package/domain/state/archive.js +1 -1
- package/domain/state.js +1 -1
- package/domain/surface.js +248 -0
- package/{lib → host}/doctor.js +116 -9
- package/{lib → host}/modeStore.js +1 -1
- package/{lib → host}/tool-registry.js +106 -31
- package/host/tower.js +32 -0
- package/index.js +12 -9
- package/package.json +10 -3
- package/ports/archive.js +18 -0
- package/ports/caseSensitivity.js +15 -0
- package/ports/clock.js +10 -2
- package/ports/config.js +19 -0
- package/ports/contract.js +38 -0
- package/ports/events.js +24 -0
- package/ports/ids.js +10 -2
- package/ports/index.js +54 -2
- package/ports/ownership.js +17 -0
- package/ports/state.js +16 -0
- package/ports/store.js +52 -3
- package/ports/vcs.js +25 -4
- package/scripts/architecture-waivers.json +19 -0
- package/scripts/architecture.mjs +634 -23
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-config-reference.mjs +14 -1
- package/scripts/generate-error-reference.mjs +68 -14
- package/scripts/generate-events-reference.mjs +83 -0
- package/scripts/generate-tools-reference.mjs +11 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/pack-manifest.txt +58 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +21 -0
- package/scripts/surface-baseline.mjs +120 -0
- package/tools/adopt.js +2 -3
- package/tools/batch.js +2 -3
- package/tools/complete.js +2 -3
- package/tools/diff.js +3 -3
- package/tools/doctor.js +7 -10
- package/tools/finding.js +2 -3
- package/tools/findings.js +3 -3
- package/tools/import-legacy.js +3 -5
- package/tools/inbox.js +3 -3
- package/tools/index.js +7 -7
- package/tools/init.js +3 -6
- package/tools/interrupt.js +2 -3
- package/tools/legacy-map.js +3 -5
- package/tools/merge.js +3 -3
- package/tools/mission.js +2 -3
- package/tools/plan.js +6 -5
- package/tools/preflight.js +2 -3
- package/tools/rebase.js +3 -3
- package/tools/recover.js +38 -13
- package/tools/reload.js +3 -6
- package/tools/resource.js +3 -5
- package/tools/review.js +3 -4
- package/tools/roster.js +2 -3
- package/tools/send.js +2 -2
- package/tools/spawn.js +8 -5
- package/tools/status.js +3 -3
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +1 -1
- package/ui-bundle/README.md +37 -3
- package/ui-bundle/client.cards.js +356 -0
- package/ui-bundle/client.denied.js +142 -0
- package/ui-bundle/client.dock.js +333 -0
- package/ui-bundle/client.helpers.js +271 -0
- package/ui-bundle/client.js +79 -1747
- package/ui-bundle/client.locales.js +247 -0
- package/ui-bundle/client.mode.js +206 -0
- package/ui-bundle/client.overlay.js +64 -0
- package/ui-bundle/client.styles.js +151 -0
- package/ui-bundle/client.toolview.js +82 -0
- package/ui-bundle/package.json +1 -0
- package/lib/persistence.js +0 -356
- package/lib/store.js +0 -133
- /package/{lib → adapters}/stateFile.js +0 -0
- /package/{lib → app}/briefing.js +0 -0
- /package/{lib → app}/spawn-support.js +0 -0
- /package/{lib → host}/compatibility.js +0 -0
- /package/{lib → host}/config.js +0 -0
- /package/{lib → host}/hmr.js +0 -0
- /package/{lib → host}/modeState.js +0 -0
- /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
|
|---|---|
|
package/scripts/issue-audit.mjs
CHANGED
|
@@ -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,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,22 @@ 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/README.md
|
|
35
55
|
docs/agents/domain.md
|
|
36
56
|
docs/agents/issue-tracker.md
|
|
37
57
|
docs/agents/triage-labels.md
|
|
58
|
+
docs/agents/workflow.md
|
|
38
59
|
docs/architecture/overview.md
|
|
39
60
|
docs/board.md
|
|
40
61
|
docs/examples/first-mission.md
|
|
@@ -44,6 +65,7 @@ docs/guide/install-upgrade-migration.md
|
|
|
44
65
|
docs/guide/quickstart.md
|
|
45
66
|
docs/guide/release.md
|
|
46
67
|
docs/guide/scenarios-and-philosophy.md
|
|
68
|
+
docs/operations/incident.md
|
|
47
69
|
docs/operations/runbook.md
|
|
48
70
|
docs/operations/security.md
|
|
49
71
|
docs/reference/blockers.md
|
|
@@ -54,6 +76,7 @@ docs/reference/errors.generated.md
|
|
|
54
76
|
docs/reference/errors.md
|
|
55
77
|
docs/reference/events.md
|
|
56
78
|
docs/reference/operations.md
|
|
79
|
+
docs/reference/surface-budget.md
|
|
57
80
|
docs/reference/testing.md
|
|
58
81
|
docs/reference/tools.generated.md
|
|
59
82
|
docs/reference/tools.md
|
|
@@ -87,6 +110,7 @@ domain/contracts/tool-inputs/missions.js
|
|
|
87
110
|
domain/contracts/tool-inputs/ops.js
|
|
88
111
|
domain/contracts/tool-inputs/shared.js
|
|
89
112
|
domain/errors.js
|
|
113
|
+
domain/events.js
|
|
90
114
|
domain/gate.js
|
|
91
115
|
domain/integration/gate.js
|
|
92
116
|
domain/integration/init.js
|
|
@@ -96,6 +120,7 @@ domain/integration/provision.js
|
|
|
96
120
|
domain/integration/rebase.js
|
|
97
121
|
domain/integration/review.js
|
|
98
122
|
domain/integration/teardown.js
|
|
123
|
+
domain/lockBudget.js
|
|
99
124
|
domain/mission.js
|
|
100
125
|
domain/missions/completion.js
|
|
101
126
|
domain/missions/findings.js
|
|
@@ -115,50 +140,49 @@ domain/state/migrations.js
|
|
|
115
140
|
domain/state/schema.js
|
|
116
141
|
domain/state/semantics.js
|
|
117
142
|
domain/summary.js
|
|
143
|
+
domain/surface.js
|
|
118
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
|
|
119
154
|
icon.svg
|
|
120
155
|
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
156
|
locale/en.json
|
|
144
157
|
locale/zh.json
|
|
145
158
|
package.json
|
|
159
|
+
ports/archive.js
|
|
160
|
+
ports/caseSensitivity.js
|
|
146
161
|
ports/clock.js
|
|
162
|
+
ports/config.js
|
|
163
|
+
ports/contract.js
|
|
164
|
+
ports/events.js
|
|
147
165
|
ports/ids.js
|
|
148
166
|
ports/index.js
|
|
167
|
+
ports/ownership.js
|
|
168
|
+
ports/state.js
|
|
149
169
|
ports/store.js
|
|
150
170
|
ports/vcs.js
|
|
151
171
|
reviewer.js
|
|
172
|
+
scripts/architecture-waivers.json
|
|
152
173
|
scripts/architecture.mjs
|
|
153
174
|
scripts/check-docs.mjs
|
|
154
175
|
scripts/check-pack.mjs
|
|
176
|
+
scripts/check-philosophy.mjs
|
|
155
177
|
scripts/domain-metrics.mjs
|
|
156
178
|
scripts/enable-dev-hmr.mjs
|
|
157
179
|
scripts/generate-config-reference.mjs
|
|
158
180
|
scripts/generate-error-reference.mjs
|
|
181
|
+
scripts/generate-events-reference.mjs
|
|
159
182
|
scripts/generate-tools-reference.mjs
|
|
160
183
|
scripts/issue-audit.mjs
|
|
161
184
|
scripts/lib/cli.mjs
|
|
185
|
+
scripts/lib/surface.mjs
|
|
162
186
|
scripts/lib/tmp.mjs
|
|
163
187
|
scripts/mount-smoke.mjs
|
|
164
188
|
scripts/mount-smoke/fixtures.mjs
|
|
@@ -166,7 +190,10 @@ scripts/mount-smoke/host.json
|
|
|
166
190
|
scripts/mount-smoke/probe.mjs
|
|
167
191
|
scripts/mount-smoke/surface.mjs
|
|
168
192
|
scripts/pack-manifest.txt
|
|
193
|
+
scripts/regression-audit.mjs
|
|
169
194
|
scripts/release-check.mjs
|
|
195
|
+
scripts/surface-baseline.json
|
|
196
|
+
scripts/surface-baseline.mjs
|
|
170
197
|
skills.js
|
|
171
198
|
skills/tower-recovery.md
|
|
172
199
|
skills/tower-review.md
|
|
@@ -202,7 +229,16 @@ tools/test.js
|
|
|
202
229
|
types/cordis.d.ts
|
|
203
230
|
types/index.d.ts
|
|
204
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
|
|
205
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
|
|
206
242
|
ui-bundle/cordis.patch.yml
|
|
207
243
|
ui-bundle/icon.svg
|
|
208
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 '../
|
|
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
|
+
}
|