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.
- package/CHANGELOG.md +154 -0
- package/COMPATIBILITY.md +6 -1
- package/CONTRIBUTING.md +71 -5
- package/PRINCIPLES.md +145 -0
- package/README.en.md +14 -5
- package/README.md +14 -5
- package/SECURITY.md +6 -0
- package/adapters/archive.js +279 -0
- package/adapters/caseSensitivity.js +66 -0
- package/adapters/default.js +51 -8
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +15 -6
- package/{lib → adapters}/legacy.js +20 -6
- package/adapters/persistence.js +543 -0
- package/adapters/stateFile.js +99 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +15 -7
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +18 -7
- package/app/archive.js +188 -0
- package/app/batch.js +9 -10
- package/app/board.js +4 -4
- package/app/checkCompletion.js +23 -11
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +32 -15
- package/app/init.js +59 -22
- package/app/merge.js +64 -51
- package/app/messages.js +7 -8
- package/app/mission.js +7 -5
- package/app/plan.js +20 -9
- package/app/provision.js +12 -14
- package/app/read.js +65 -6
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +17 -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 +28 -8
- package/app/tower.js +121 -39
- 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/domain.md +38 -0
- package/docs/agents/issue-tracker.md +46 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +170 -37
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/install-upgrade-migration.md +18 -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 +3 -3
- package/docs/reference/config.generated.md +5 -0
- package/docs/reference/config.md +4 -0
- package/docs/reference/doctor.md +104 -8
- package/docs/reference/errors.generated.md +41 -35
- package/docs/reference/errors.md +54 -40
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +128 -0
- package/docs/reference/tools.generated.md +9 -8
- package/docs/reference/tools.md +9 -8
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/guide/install-upgrade-migration.md +13 -1
- package/docs/zh/reference/blockers.md +3 -3
- package/docs/zh/reference/config.md +4 -0
- package/docs/zh/reference/doctor.md +63 -2
- package/docs/zh/reference/errors.md +4 -2
- package/docs/zh/reference/tools.md +6 -6
- package/domain/batch.js +2 -2
- package/domain/config/schema.js +51 -4
- package/domain/contracts/outputs.js +33 -3
- package/domain/contracts/tool-inputs/missions.js +2 -1
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +297 -40
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +41 -12
- package/domain/integration/init.js +40 -4
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/mission.js +1 -1
- package/domain/missions/completion.js +72 -5
- package/domain/missions/lifecycle.js +11 -4
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/planning.js +6 -3
- package/domain/read/views.js +17 -1
- package/domain/report.js +9 -1
- package/domain/scope.js +83 -11
- package/domain/state/archive.js +257 -0
- package/domain/state/migrations.js +145 -0
- package/domain/state/schema.js +6 -0
- package/domain/state.js +154 -4
- package/domain/summary.js +2 -0
- package/domain/surface.js +248 -0
- package/host/doctor.js +555 -0
- 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 +64 -19
- 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 +3 -4
- 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 +4 -7
- 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 +7 -6
- package/tools/preflight.js +3 -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 +16 -8
- package/tools/teardown.js +3 -4
- package/tools/test.js +2 -2
- package/types/index.d.ts +35 -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/doctor.js +0 -270
- package/lib/persistence.js +0 -349
- package/lib/store.js +0 -118
- /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,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 '../
|
|
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
|
+
}
|