rvlib-shipkit 1.4.0 → 1.5.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 +68 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/check.ts +68 -95
- package/src/checks/SK001-package-json-shape.check.ts +6 -2
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +5 -1
- package/src/checks/SK004-gitignore-policy.check.ts +7 -4
- package/src/checks/SK005-rvlib-versions.check.ts +53 -21
- package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
- package/src/checks/SK007-github-visibility.check.ts +4 -2
- package/src/checks/SK008-reviewer-agent.check.ts +6 -4
- package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
- package/src/checks/SK010-undeclared-unit.check.ts +4 -0
- package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
- package/src/checks/SK012-config-barrel-import.check.ts +5 -1
- package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
- package/src/checks/SK014-prose-ste.check.ts +4 -0
- package/src/checks/SK015-nested-repo.check.ts +4 -2
- package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
- package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
- package/src/checks/SK018-test-files-run.check.ts +6 -2
- package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
- package/src/checks/SK020-self-spawning-check.check.ts +4 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
- package/src/checks/SK024-identity-colors.check.ts +5 -3
- package/src/checks/SK025-release-drift.check.ts +4 -5
- package/src/checks/SK026-asks-autoclose.check.ts +4 -4
- package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
- package/src/checks/SK028-readme.check.ts +5 -1
- package/src/checks/SK029-explicit-check-files.check.ts +95 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +7 -7
- package/src/cli/cmd_Bump.ts +12 -11
- package/src/cli/cmd_Check.ts +8 -7
- package/src/cli/cmd_Checks.ts +7 -6
- package/src/cli/cmd_Ci.ts +12 -11
- package/src/cli/cmd_Commands.ts +16 -13
- package/src/cli/cmd_ConfigKeys.ts +7 -6
- package/src/cli/cmd_Contracts.ts +3 -3
- package/src/cli/cmd_CrossRepoActivity.ts +6 -5
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +116 -4
- package/src/cli/cmd_Home.ts +23 -17
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +8 -7
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +4 -3
- package/src/cli/cmd_Pb.ts +2 -2
- package/src/cli/cmd_Planned.ts +6 -5
- package/src/cli/cmd_Prose.ts +5 -5
- package/src/cli/cmd_Reflect.ts +7 -7
- package/src/cli/cmd_Release.ts +6 -5
- package/src/cli/cmd_Releases.ts +5 -4
- package/src/cli/cmd_Serve.ts +4 -3
- package/src/cli/cmd_Smoke.ts +5 -4
- package/src/cli/cmd_Start.ts +7 -6
- package/src/cli/cmd_Status.ts +4 -3
- package/src/cli/cmd_Tui.ts +3 -2
- package/src/cli/cmd_When.ts +71 -0
- package/src/cli/cmd_Wiki.ts +4 -4
- package/src/cli.ts +7 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +9 -4
- package/src/core/canonical-scripts.ts +1 -1
- package/src/core/check-runner.ts +37 -20
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +23 -2
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/one-way-in-policy.ts +2 -2
- package/src/core/outcome-words.ts +18 -2
- package/src/core/port-check-cli.ts +52 -0
- package/src/core/port-check-file.ts +295 -0
- package/src/core/port-check-files.ts +31 -0
- package/src/core/retention.ts +0 -14
- package/src/core/run-check.ts +12 -9
- package/src/core/run-ci.ts +8 -4
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +42 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -0
- package/src/core/update-policy.ts +20 -0
- package/src/core/when-block.ts +29 -30
- package/src/core/when.ts +41 -103
- package/src/core/wire.ts +10 -14
- package/src/fleet/scan-fleet.ts +20 -4
- package/src/fleet/update-fleet.ts +6 -6
- package/src/home/home-launchd.ts +20 -4
- package/src/home/home-routes.ts +12 -1
- package/src/home/hubs.ts +5 -5
- package/src/home/pm.ts +8 -0
- package/src/home/run-pm.ts +1 -0
- package/src/home/serve-web.ts +2 -2
- package/src/home/supervise.ts +24 -1
- package/src/hooks/run-hooks.ts +48 -4
- package/src/init-repo.ts +6 -6
- package/src/ledger/gate-checks.ts +113 -59
- package/src/ledger/git-key.ts +5 -1
- package/src/ledger/ledger.ts +5 -5
- package/src/ledger/toolchain-identity.ts +45 -27
- package/src/plan/repo-source.ts +18 -39
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +30 -55
- package/src/rules/rule.ts +2 -2
- package/src/scaffold/files/agent-coding.md +1 -1
- package/src/scaffold/files/agent-index.md +1 -1
- package/src/scaffold/files/verify-skill.md +2 -2
- package/src/serve/hub-app/activity-st.ts +5 -4
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/admin-view.tsx +13 -11
- package/src/serve/hub-app/check-view.tsx +66 -46
- package/src/serve/hub-app/check-words.ts +1 -11
- package/src/serve/hub-app/checks-svc.ts +14 -35
- package/src/serve/hub-app/col-tables-view.tsx +13 -6
- package/src/serve/hub-app/cols-svc.ts +5 -4
- package/src/serve/hub-app/dock-st.ts +3 -2
- package/src/serve/hub-app/dock-view.tsx +36 -0
- package/src/serve/hub-app/dock.css +6 -0
- package/src/serve/hub-app/float-cards.tsx +30 -0
- package/src/serve/hub-app/floating.tsx +41 -4
- package/src/serve/hub-app/git-st.ts +42 -0
- package/src/serve/hub-app/git-view.tsx +61 -0
- package/src/serve/hub-app/hash-route.ts +45 -1
- package/src/serve/hub-app/header-bar.tsx +62 -72
- package/src/serve/hub-app/health-svc.ts +3 -2
- package/src/serve/hub-app/home-st.ts +14 -2
- package/src/serve/hub-app/home-strip.css +43 -8
- package/src/serve/hub-app/home-strip.tsx +111 -47
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +14 -4
- package/src/serve/hub-app/hub-types.ts +5 -2
- package/src/serve/hub-app/hub.css +65 -43
- package/src/serve/hub-app/icons.tsx +11 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/keys.ts +5 -2
- package/src/serve/hub-app/live-svc.ts +3 -2
- package/src/serve/hub-app/machine-st.ts +18 -22
- package/src/serve/hub-app/plan-view.tsx +51 -0
- package/src/serve/hub-app/planned-st.ts +9 -4
- package/src/serve/hub-app/processes-view.tsx +38 -0
- package/src/serve/hub-app/projects-svc.ts +15 -10
- package/src/serve/hub-app/prose-hits-st.ts +3 -2
- package/src/serve/hub-app/reach.ts +36 -0
- package/src/serve/hub-app/readme-svc.ts +3 -2
- package/src/serve/hub-app/repo-sections.ts +117 -0
- package/src/serve/hub-app/repo-st.ts +46 -9
- package/src/serve/hub-app/sidebar.tsx +12 -37
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/tree-build.ts +149 -155
- package/src/serve/hub-app/tree-row.tsx +39 -9
- package/src/serve/hub-app/tree-svc.ts +70 -49
- package/src/serve/hub-app/when-view.tsx +89 -0
- package/src/serve/hub-app/when.css +17 -0
- package/src/serve/hub-runtime.ts +3 -16
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/serve/hub-app/processes-svc.ts +0 -63
- package/src/serve/hub-app/repo-sections.tsx +0 -61
- package/src/serve/repo-processes.ts +0 -89
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
// Per rvlib-* dep:
|
|
11
11
|
// link: → the symlink must resolve (a broken link dies cryptically at
|
|
12
12
|
// import time otherwise) — nothing to update, code is live
|
|
13
|
-
// semver → installed vs npm latest
|
|
13
|
+
// semver → installed vs npm latest. A semver pin whose node_modules
|
|
14
|
+
// entry is a `bun link` symlink is judged by the PIN's floor
|
|
15
|
+
// instead: the link carries the source, always the newest, while
|
|
16
|
+
// a fresh clone installs the pin. Behind, the pin moves through
|
|
17
|
+
// the same guarded update, and `bun link <name>` restores the
|
|
18
|
+
// link after it (a link that does not come back FAILs). A BREAKING jump (a new major, or a
|
|
14
19
|
// new 0.x minor) is never taken by itself
|
|
15
20
|
// (src/core/update-policy.ts: WARN naming `fleet --update --major`,
|
|
16
21
|
// package.json untouched); the rvlib-shipkit tree running this
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
import { existsSync, readFileSync } from 'fs'
|
|
47
52
|
import { basename, join } from 'path'
|
|
48
53
|
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
49
|
-
import { allowMajor, breakingAdvice, isBreaking } from '../core/update-policy.ts'
|
|
54
|
+
import { allowMajor, breakingAdvice, isBreaking, isLinkedInstall, pinFloor } from '../core/update-policy.ts'
|
|
50
55
|
import { isRunningCopy, rememberLatest } from '../core/self-update.ts'
|
|
51
56
|
import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
52
57
|
import { mutateLease, withMutateLease } from '../core/locked-write.ts'
|
|
@@ -61,6 +66,13 @@ const installedVersion = (root: string, name: string): string | null => {
|
|
|
61
66
|
try { return (JSON.parse(readFileSync(p, 'utf8')) as Pkg).version ?? null } catch { return null }
|
|
62
67
|
}
|
|
63
68
|
|
|
69
|
+
const readPin = (root: string, name: string): string | null => {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as Pkg
|
|
72
|
+
return pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? null
|
|
73
|
+
} catch { return null }
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
async function latestVersion(t: CheckCtx, name: string): Promise<string> {
|
|
65
77
|
const res = await t.fetch(`https://registry.npmjs.org/${name}/latest`)
|
|
66
78
|
if (!res.ok) throw new Error(`registry ${res.status}`)
|
|
@@ -74,23 +86,24 @@ export const rvlibVersions = check({
|
|
|
74
86
|
blocking: true,
|
|
75
87
|
title: 'rvlib versions',
|
|
76
88
|
description: 'the rvlib packages installed here are as new as the published ones',
|
|
77
|
-
owner: 'build/shipkit.md',
|
|
78
|
-
ttl: '24h',
|
|
89
|
+
owner: 'build/shipkit/consuming.md',
|
|
79
90
|
// it updates the rvlib deps unless the run is frozen
|
|
80
91
|
writes: true,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
try { pkg = JSON.parse(readFileSync(join(env.root, 'package.json'), 'utf8')) as Pkg } catch { return 'no readable package.json' }
|
|
84
|
-
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).some((n) => n.startsWith('rvlib-')) ? null : 'no rvlib-* dependency'
|
|
85
|
-
},
|
|
86
|
-
cacheKey: (env) => {
|
|
92
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: false, onClock: { every: '24h', inactive: 'wait' } },
|
|
93
|
+
cache: { by: 'ttl', span: '24h', key: (env) => {
|
|
87
94
|
let pkg: Pkg
|
|
88
95
|
try { pkg = JSON.parse(readFileSync(join(env.root, 'package.json'), 'utf8')) as Pkg } catch { return 'unreadable' }
|
|
89
96
|
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })
|
|
90
97
|
.filter((n) => n.startsWith('rvlib-'))
|
|
91
98
|
.sort()
|
|
92
|
-
.map((n) => `${n}@${installedVersion(env.root, n) ?? 'absent'}`)
|
|
99
|
+
.map((n) => `${n}@${installedVersion(env.root, n) ?? 'absent'}${isLinkedInstall(env.root, n) ? ` pin ${readPin(env.root, n)}` : ''}`)
|
|
93
100
|
.join(',') + (rootPublishesNpm(env.config) ? ' npm' : '')
|
|
101
|
+
} },
|
|
102
|
+
fix: 'none',
|
|
103
|
+
applies: (env) => {
|
|
104
|
+
let pkg: Pkg
|
|
105
|
+
try { pkg = JSON.parse(readFileSync(join(env.root, 'package.json'), 'utf8')) as Pkg } catch { return 'no readable package.json' }
|
|
106
|
+
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).some((n) => n.startsWith('rvlib-')) ? null : 'no rvlib-* dependency'
|
|
94
107
|
},
|
|
95
108
|
run: async (c) => {
|
|
96
109
|
let pkg: Pkg
|
|
@@ -100,39 +113,47 @@ export const rvlibVersions = check({
|
|
|
100
113
|
const pm = detectPackageManager(c.root)
|
|
101
114
|
const manifest = ['package.json', ...(isRefusal(pm) ? LOCK_FILES : pm.locks)]
|
|
102
115
|
|
|
103
|
-
const stale: Array<{ name: string; installed: string; latest: string }> = []
|
|
116
|
+
const stale: Array<{ name: string; installed: string; latest: string; linked: boolean }> = []
|
|
104
117
|
for (const [name, spec] of deps) {
|
|
105
118
|
if (spec.startsWith('link:')) {
|
|
106
119
|
if (installedVersion(c.root, name) == null) c.fail(`${name} is link: but the symlink does not resolve — re-run \`bun link ${name}\` (a broken link dies cryptically at import time)`)
|
|
107
120
|
else c.ok(`${name} (linked, live)`)
|
|
108
121
|
continue
|
|
109
122
|
}
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
123
|
+
const onDisk = installedVersion(c.root, name)
|
|
124
|
+
if (onDisk == null) { c.fail(`${name} declared (${spec}) but not installed — ${isRefusal(pm) ? pm.refused : `run: ${pm.install.join(' ')}`}`); continue }
|
|
125
|
+
const linked = isLinkedInstall(c.root, name)
|
|
126
|
+
const floor = linked ? pinFloor(spec) : null
|
|
127
|
+
if (linked && floor == null) { c.ok(`${name} (linked to its source, pin ${spec} names no version to judge)`); continue }
|
|
128
|
+
const installed = floor ?? onDisk
|
|
129
|
+
// how the lines below name it: a linked dep is its pin, never the source it links
|
|
130
|
+
const label = linked ? `${name} pin ${installed}` : `${name} ${installed}`
|
|
112
131
|
let latest: string
|
|
113
132
|
try { latest = await latestVersion(c, name) } catch (e) {
|
|
114
|
-
c.lines.push({ kind: 'skip', text: `${
|
|
133
|
+
c.lines.push({ kind: 'skip', text: `${label} — registry unreachable (${String(e).slice(0, 80)}), version drift unknown` })
|
|
115
134
|
continue
|
|
116
135
|
}
|
|
117
136
|
// real semver order, not string difference — a dist-tag pointing
|
|
118
137
|
// BEHIND the installed version (prerelease, unpublish window)
|
|
119
138
|
// must never trigger a silent downgrade
|
|
120
139
|
const cmp = Bun.semver.order(latest, installed)
|
|
121
|
-
if (cmp === 0) c.ok(`${
|
|
122
|
-
else if (cmp < 0) c.ok(`${
|
|
140
|
+
if (cmp === 0) c.ok(`${label} (latest${linked ? ', linked to its source' : ''})`)
|
|
141
|
+
else if (cmp < 0) c.ok(`${label} ahead of npm latest ${latest} (prerelease or dist-tag lag) — not touching`)
|
|
123
142
|
else if (isBreaking(installed, latest) && !allowMajor()) c.warn(breakingAdvice(name, latest, basename(c.root)))
|
|
124
|
-
|
|
143
|
+
// a linked toolkit moves only its pin: the source tree this process
|
|
144
|
+
// runs from is never written, so the swap below cannot happen
|
|
145
|
+
else if (name === 'rvlib-shipkit' && !linked && isRunningCopy(c.root)) {
|
|
125
146
|
// swapping this tree under the running process is how a run
|
|
126
147
|
// meets modules of two versions. The startup self-update owns
|
|
127
148
|
// it: handing it the answer makes the next run update and
|
|
128
149
|
// restart before any check loads
|
|
129
150
|
rememberLatest(latest)
|
|
130
151
|
c.warn(`${name} ${installed} → ${latest} is the toolkit running this check, so it is not swapped mid run: the next shipkit run updates it and restarts before any check`)
|
|
131
|
-
} else stale.push({ name, installed, latest })
|
|
152
|
+
} else stale.push({ name, installed, latest, linked })
|
|
132
153
|
}
|
|
133
154
|
if (stale.length === 0) return
|
|
134
155
|
|
|
135
|
-
const drift = stale.map((s) => `${s.name} ${s.installed} → ${s.latest}`).join(', ')
|
|
156
|
+
const drift = stale.map((s) => `${s.name}${s.linked ? ' pin' : ''} ${s.installed} → ${s.latest}`).join(', ')
|
|
136
157
|
const names = stale.map((s) => s.name)
|
|
137
158
|
if (isRefusal(pm)) {
|
|
138
159
|
c.warn(`newer rvlib live: ${drift}, not updating: ${pm.refused}`)
|
|
@@ -164,7 +185,11 @@ export const rvlibVersions = check({
|
|
|
164
185
|
const leased = await withMutateLease(mutateLease(c.root, 'fix'), { label: 'self-update', print: () => {}, escape: 'SHIPKIT_FROZEN=1' }, async () => {
|
|
165
186
|
const again = await c.exec(['git', 'status', '--porcelain', '--', ...manifest], { cwd: c.root })
|
|
166
187
|
if (again.code === 0 && again.out.trim() !== '') return null
|
|
167
|
-
|
|
188
|
+
const up = await c.exec(cmd, { cwd: c.root, timeoutMs: 120_000 })
|
|
189
|
+
// the update may swap a link for the tarball: put every link back,
|
|
190
|
+
// whatever the update did, inside the same lease
|
|
191
|
+
for (const s of stale) if (s.linked) await c.exec(['bun', 'link', s.name], { cwd: c.root, timeoutMs: 60_000 })
|
|
192
|
+
return up
|
|
168
193
|
})
|
|
169
194
|
if (!leased.ok) {
|
|
170
195
|
c.warn(`newer rvlib live: ${drift} — self-update deferred (${leased.warn}); re-run check once that run finishes`)
|
|
@@ -188,6 +213,13 @@ export const rvlibVersions = check({
|
|
|
188
213
|
return
|
|
189
214
|
}
|
|
190
215
|
for (const s of stale) {
|
|
216
|
+
if (s.linked) {
|
|
217
|
+
const now = pinFloor(readPin(c.root, s.name) ?? '')
|
|
218
|
+
if (!isLinkedInstall(c.root, s.name)) c.fail(`${s.name} pin ${s.installed} → ${now ?? 'unreadable'} but node_modules/${s.name} is no longer linked to its source — run: bun link ${s.name}`)
|
|
219
|
+
else if (now === s.latest) c.warn(`${s.name} pin ${s.installed} → ${now} self-updated, link to the source restored — commit the bump`)
|
|
220
|
+
else c.warn(`${s.name} updated but its pin reads ${now ?? 'unreadable'} (wanted ${s.latest}) — check package.json`)
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
191
223
|
const now = installedVersion(c.root, s.name)
|
|
192
224
|
// WARN on purpose: the run MUTATED the repo and a follow-up
|
|
193
225
|
// (restart watchers, commit) is owed — an ok here would be a
|
|
@@ -40,6 +40,10 @@ export const npmPublishSafety = check({
|
|
|
40
40
|
owner: 'meta/rv-conventions/package-json.md',
|
|
41
41
|
// keyed on what it reads: the root package.json and every declared release
|
|
42
42
|
// unit's, at the dirs the config picks
|
|
43
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
44
|
+
cache: { by: 'reads' },
|
|
45
|
+
fix: 'none',
|
|
46
|
+
applies: 'always',
|
|
43
47
|
run: (c) => {
|
|
44
48
|
const json = (rel: string): Pkg | null | 'unparseable' => {
|
|
45
49
|
try { return c.json<Pkg>(rel) } catch { return 'unparseable' }
|
|
@@ -27,8 +27,10 @@ export const githubVisibility = check({
|
|
|
27
27
|
title: 'github visibility',
|
|
28
28
|
description: 'the github visibility of this repo matches the one it declares',
|
|
29
29
|
owner: 'meta/contracts.md',
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: 'moved', onClock: 'once' },
|
|
31
|
+
cache: { by: 'fact', key: (env) => declaredVisibility(env.config) },
|
|
32
|
+
fix: 'none',
|
|
33
|
+
applies: 'always',
|
|
32
34
|
run: async (c) => {
|
|
33
35
|
const declared = declaredVisibility(c.config)
|
|
34
36
|
const skip = (why: string): void => void c.lines.push({ kind: 'skip', text: `github visibility unverified — ${why} (declared: ${declared})` })
|
|
@@ -86,15 +86,17 @@ export const reviewerAgent = check({
|
|
|
86
86
|
title: 'reviewer definition',
|
|
87
87
|
description: 'the declared reviewer agent has its brief, its definition and read only tools',
|
|
88
88
|
owner: 'identity/how-we-work.md',
|
|
89
|
+
runs: { onPreCommit: { budgetMs: 20 }, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
89
90
|
// the answer is the config, the definition file and the brief. The brief
|
|
90
91
|
// is a CONFIG path that may sit outside the repo, so both files enter the
|
|
91
92
|
// key by content, wherever they live
|
|
92
|
-
inputs: [CONFIG_FILE],
|
|
93
|
-
cacheKey: (env) => {
|
|
93
|
+
cache: { by: 'files', inputs: [CONFIG_FILE], key: (env) => {
|
|
94
94
|
const r = env.config.reviewer
|
|
95
95
|
if (r == null) return 'no reviewer'
|
|
96
96
|
return `${r.name}|${(r.tools ?? REVIEWER_TOOLS).join(',')}|def=${fileStamp(join(env.root, reviewerAgentPath(r.name)))}|${r.brief}=${fileStamp(configPath(env.root, r.brief))}`
|
|
97
|
-
},
|
|
97
|
+
} },
|
|
98
|
+
fix: 'ci',
|
|
99
|
+
applies: 'always',
|
|
98
100
|
run: (c) => {
|
|
99
101
|
const r = c.config.reviewer
|
|
100
102
|
if (r == null) return c.ok('no reviewer agent declared (repo.config reviewer:) — skipped')
|
|
@@ -109,7 +111,7 @@ export const reviewerAgent = check({
|
|
|
109
111
|
// create-if-missing only: an existing definition may carry a repo's own
|
|
110
112
|
// tuned prompt, and silently rewriting that would cost more than the
|
|
111
113
|
// finding it repairs. Violations in a present file stay a loud FAIL.
|
|
112
|
-
|
|
114
|
+
repair: (c): FixResult => {
|
|
113
115
|
const r = c.config.reviewer
|
|
114
116
|
if (r == null) return { changed: false, summary: 'no reviewer declared — nothing to write' }
|
|
115
117
|
const rel = reviewerAgentPath(r.name)
|
|
@@ -81,9 +81,11 @@ export const commitAiTells = check({
|
|
|
81
81
|
title: 'commit ai tells',
|
|
82
82
|
description: 'the commit-msg hook that refuses AI attribution is armed, and no ref name tells',
|
|
83
83
|
owner: 'identity/how-we-commit.md',
|
|
84
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
84
85
|
// git config and ref names, which no tree id describes, so they are the key
|
|
85
|
-
inputs: [CONFIG_FILE],
|
|
86
|
-
|
|
86
|
+
cache: { by: 'files', inputs: [CONFIG_FILE], key: historyKey },
|
|
87
|
+
fix: 'none',
|
|
88
|
+
applies: 'always',
|
|
87
89
|
run: async (c) => {
|
|
88
90
|
const nested = nestedIn(c.root)
|
|
89
91
|
if (nested != null) return void c.lines.push({ kind: 'skip', text: `commit gates not checked, nested in ${nested}: the parent repo owns them` })
|
|
@@ -40,6 +40,10 @@ export const undeclaredUnit = check({
|
|
|
40
40
|
owner: 'build/shipkit/units.md',
|
|
41
41
|
// keyed on what it reads: the listing of every package.json, each one it
|
|
42
42
|
// opens, and the declared units' package.json
|
|
43
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
44
|
+
cache: { by: 'reads' },
|
|
45
|
+
fix: 'none',
|
|
46
|
+
applies: 'always',
|
|
43
47
|
run: (c) => {
|
|
44
48
|
const { units, errors, testOnly } = resolveUnits(c.root, c.config, checkUnitReader(c))
|
|
45
49
|
// a DECLARED unit that does not resolve is a declaration bug and
|
|
@@ -61,6 +61,10 @@ export const siblingUnitRanges = check({
|
|
|
61
61
|
owner: 'build/shipkit/built-in-checks.md',
|
|
62
62
|
// keyed on what it reads: every declared unit's package.json, at the dirs
|
|
63
63
|
// the config picks
|
|
64
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
65
|
+
cache: { by: 'reads' },
|
|
66
|
+
fix: 'none',
|
|
67
|
+
applies: 'always',
|
|
64
68
|
run: (c) => {
|
|
65
69
|
const { units } = resolveUnits(c.root, c.config, checkUnitReader(c))
|
|
66
70
|
const version = new Map(units.map((u) => [u.name, u.version]))
|
|
@@ -30,6 +30,10 @@ export const configBarrelImport = check({
|
|
|
30
30
|
title: 'config import path',
|
|
31
31
|
description: 'repo.config.ts imports `defineRepo` from the narrow module, never the barrel',
|
|
32
32
|
owner: 'build/shipkit/config-keys.md',
|
|
33
|
+
runs: { onPreCommit: { budgetMs: 20 }, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
34
|
+
cache: { by: 'reads' },
|
|
35
|
+
fix: 'ci',
|
|
36
|
+
applies: 'always',
|
|
33
37
|
run: (c) => {
|
|
34
38
|
const src = c.read(CONFIG)
|
|
35
39
|
if (src == null) return void c.ok(`${CONFIG} absent — nothing to narrow`)
|
|
@@ -43,7 +47,7 @@ export const configBarrelImport = check({
|
|
|
43
47
|
}
|
|
44
48
|
c.fail(`${CONFIG} imports from the package barrel (~400ms of module load on every shipkit run, the pre-commit hook included)`)
|
|
45
49
|
},
|
|
46
|
-
|
|
50
|
+
repair: (c): FixResult => {
|
|
47
51
|
const src = c.read(CONFIG)
|
|
48
52
|
if (src == null) return { changed: false, summary: `${CONFIG} absent` }
|
|
49
53
|
if (!BARREL.test(src)) return { changed: false, summary: 'already narrow' }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SK013 — the harness half of ONE WAY IN (build/shipkit.md). Rule SK001 routes
|
|
1
|
+
// SK013 — the harness half of ONE WAY IN (build/shipkit/one-way-in.md). Rule SK001 routes
|
|
2
2
|
// the package.json scripts, and that alone is not enough: `bun test` is a bun
|
|
3
3
|
// BUILTIN, so no script can intercept it and the reflex stays open. This rule
|
|
4
4
|
// makes the repo carry the two things that CAN close it, and repairs them:
|
|
@@ -19,7 +19,7 @@ import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
|
19
19
|
import type { RepoConfig } from '../repo-config.ts'
|
|
20
20
|
import { check, type CheckCtx, type FixResult } from '../check.ts'
|
|
21
21
|
|
|
22
|
-
const OWNER = 'build/shipkit.md'
|
|
22
|
+
const OWNER = 'build/shipkit/one-way-in.md'
|
|
23
23
|
export const SETTINGS_PATH = '.claude/settings.json'
|
|
24
24
|
|
|
25
25
|
type Settings = {
|
|
@@ -124,6 +124,10 @@ export const oneWayInPolicy = check({
|
|
|
124
124
|
// keyed on what it reads: .claude/settings.json, package.json, and whether
|
|
125
125
|
// the declared guard script resolves. A guard at an absolute path lives
|
|
126
126
|
// outside the repo, so an answer that probed one is never stored
|
|
127
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
128
|
+
cache: { by: 'reads' },
|
|
129
|
+
fix: 'ci',
|
|
130
|
+
applies: 'always',
|
|
127
131
|
run: async (c) => {
|
|
128
132
|
if (rootPublishesNpm(c.config)) return c.ok(PUBLIC_SKIP)
|
|
129
133
|
const d = c
|
|
@@ -171,7 +175,7 @@ export const oneWayInPolicy = check({
|
|
|
171
175
|
// MERGES, never rewrites: this file also carries the attribution silencer,
|
|
172
176
|
// a repo's own allow list and its own hooks, and clobbering those to fix a
|
|
173
177
|
// deny list would cost more than the finding it repairs
|
|
174
|
-
|
|
178
|
+
repair: async (c): Promise<FixResult> => {
|
|
175
179
|
if (rootPublishesNpm(c.config)) return { changed: false, summary: PUBLIC_SKIP }
|
|
176
180
|
const d = c
|
|
177
181
|
const want = denyRules(exemptSlots(c.config))
|
|
@@ -30,6 +30,10 @@ export const proseSte = check({
|
|
|
30
30
|
owner: OWNER,
|
|
31
31
|
// keyed on what it reads: each include glob's listing and every file in
|
|
32
32
|
// scope, so an edit outside the declared prose never reruns it
|
|
33
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
34
|
+
cache: { by: 'reads' },
|
|
35
|
+
fix: 'none',
|
|
36
|
+
applies: 'always',
|
|
33
37
|
run: (c) => {
|
|
34
38
|
const cfg = c.config.prose
|
|
35
39
|
if (cfg == null || (cfg.include ?? []).length === 0) {
|
|
@@ -12,10 +12,12 @@ export const nestedRepo = check({
|
|
|
12
12
|
title: 'nested repo',
|
|
13
13
|
description: 'a config inside a larger work tree declares `nested: true`, and only there',
|
|
14
14
|
owner: 'build/shipkit/config-keys.md',
|
|
15
|
+
runs: { onPreCommit: { budgetMs: 20 }, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
15
16
|
// the declaration, and where the git work tree root sits, which no tree
|
|
16
17
|
// id describes, so its path is the rest of the key
|
|
17
|
-
inputs: [CONFIG_FILE],
|
|
18
|
-
|
|
18
|
+
cache: { by: 'files', inputs: [CONFIG_FILE], key: (env) => `nested=${env.config.nested === true}|top=${nestedIn(env.root) ?? 'root'}` },
|
|
19
|
+
fix: 'none',
|
|
20
|
+
applies: 'always',
|
|
19
21
|
run: (c) => {
|
|
20
22
|
const top = nestedIn(c.root)
|
|
21
23
|
const declared = c.config.nested === true
|
|
@@ -49,6 +49,10 @@ export const legacyCheckShapes = check({
|
|
|
49
49
|
blocking: true,
|
|
50
50
|
// keyed on what it reads: the listing of check files, their sources, and
|
|
51
51
|
// the config. A gitignored check file is no declaration of this repo
|
|
52
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
53
|
+
cache: { by: 'reads' },
|
|
54
|
+
fix: 'manual',
|
|
55
|
+
applies: 'always',
|
|
52
56
|
run: (c) => {
|
|
53
57
|
const s = legacyShapes(c, c.config)
|
|
54
58
|
if (s.count === 0 && s.placeholders.length === 0) {
|
|
@@ -70,7 +74,7 @@ export const legacyCheckShapes = check({
|
|
|
70
74
|
const head = s.checkFiles.slice(0, 3).map((f) => f.rel).join(', ')
|
|
71
75
|
c.warn(`${s.checkFiles.length} legacy check script(s) read by the static reader: ${head}${s.checkFiles.length > 3 ? `, and ${s.checkFiles.length - 3} more` : ''}`)
|
|
72
76
|
const off = s.checkFiles.filter((f) => !f.read.gate).length
|
|
73
|
-
if (off > 0) c.warn(`${off} of them carry \`export const gate = false\`, which is
|
|
77
|
+
if (off > 0) c.warn(`${off} of them carry \`export const gate = false\`, which is every gate axis false and onHealth true`)
|
|
74
78
|
}
|
|
75
79
|
if (s.checkDefaults) c.warn(`repo.config.ts checkDefaults lets a file forget \`blocking\` — the value belongs in each file`)
|
|
76
80
|
if (s.tasks.length > 0) {
|
|
@@ -78,10 +82,10 @@ export const legacyCheckShapes = check({
|
|
|
78
82
|
c.warn(`${s.tasks.length} \`tasks: []\` entr(ies), ${data} of them pure data — each one is a *.check.ts file with \`build: true\``)
|
|
79
83
|
}
|
|
80
84
|
if (s.rules.length > 0) c.warn(`${s.rules.length} \`rules: []\` entr(ies): ${s.rules.join(', ')} — each one is a *.check.ts file whose \`run\` is its \`lint\``)
|
|
81
|
-
if (s.ruleExempt.length > 0) c.warn(`ruleExempt names ${s.ruleExempt.join(', ')} — the spelling is \`
|
|
85
|
+
if (s.ruleExempt.length > 0) c.warn(`ruleExempt names ${s.ruleExempt.join(', ')} — the spelling is \`off: { id: '<why>' }\`, and \`shipkit ci\` ports it (SK029)`)
|
|
82
86
|
// what the fixer rewrites without guessing: the exemptions, the tasks
|
|
83
87
|
// once all of them are data, checkDefaults once no script leans on it
|
|
84
|
-
const mechanical =
|
|
88
|
+
const mechanical = (s.tasks.every((x) => x.data) ? s.tasks.length : 0) + (s.checkDefaults && s.checkFiles.length === 0 ? 1 : 0)
|
|
85
89
|
c.warn(
|
|
86
90
|
`${s.count} declaration(s) in a shape the check file replaced: still read, and refused by the first shipkit release after \`shipkit fleet\` shows no repo below 0.40 and none still carrying a check script`,
|
|
87
91
|
mechanical > 0
|
|
@@ -89,7 +93,7 @@ export const legacyCheckShapes = check({
|
|
|
89
93
|
: `nothing left is mechanical, so the port is by hand: each line above becomes a *.check.ts file (${OWNER})`,
|
|
90
94
|
)
|
|
91
95
|
},
|
|
92
|
-
|
|
96
|
+
repair: async (c) => await fixLegacyShapes(c),
|
|
93
97
|
})
|
|
94
98
|
|
|
95
99
|
export default legacyCheckShapes
|
|
@@ -162,17 +166,14 @@ export async function fixLegacyShapes(c: CheckCtx, rewrite: ConfigRewriter = inO
|
|
|
162
166
|
for (const r of config.rules ?? [])
|
|
163
167
|
hand.push(`rule ${r.id}: move it into ${checkFileName(r.id)} beside repo.config.ts, \`lint\` becomes \`run\` and \`code\` goes (the file's own path is its closure)`)
|
|
164
168
|
|
|
165
|
-
// 4. the
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
const cfgPlan: ConfigPlan = { when: {}, drop: [], name: config.name }
|
|
170
|
-
for (const [id, why] of Object.entries(config.ruleExempt ?? {})) cfgPlan.when[id] = `off:${why}`
|
|
169
|
+
// 4. the config keys, last: a key is only dropped once every declaration
|
|
170
|
+
// that leaned on it has a home. It loads the config, so it runs in its
|
|
171
|
+
// own process. `ruleExempt` is SK029's: it ports it into `off`
|
|
172
|
+
const cfgPlan: ConfigPlan = { off: {}, drop: [], name: config.name }
|
|
171
173
|
if (s.checkDefaults && filesLeft === 0) cfgPlan.drop.push({ key: 'checkDefaults', why: 'every file now declares its own blocking' })
|
|
172
174
|
else if (s.checkDefaults) hand.push(`repo.config.ts \`checkDefaults\` stays until the ${filesLeft} file(s) above declare their own \`blocking\``)
|
|
173
175
|
if (tasksMove) cfgPlan.drop.push({ key: 'tasks', why: 'every task is a check file now' })
|
|
174
|
-
if (
|
|
175
|
-
if (Object.keys(cfgPlan.when).length > 0 || cfgPlan.drop.length > 0) {
|
|
176
|
+
if (cfgPlan.drop.length > 0) {
|
|
176
177
|
const out = await rewrite(cfgPlan)
|
|
177
178
|
did.push(...out.did)
|
|
178
179
|
hand.push(...out.hand)
|
|
@@ -12,7 +12,7 @@ import { check } from '../check.ts'
|
|
|
12
12
|
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
13
13
|
import { checkFloorReader, resolveFloor } from '../core/floor-resolve.ts'
|
|
14
14
|
import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
15
|
-
import {
|
|
15
|
+
import { repoOff } from '../core/when.ts'
|
|
16
16
|
import { hubProblemMatcher } from '../problems/problem-line.ts'
|
|
17
17
|
import { indentAt, parseSpans, plain, removalSpan, type Node } from '../scaffold/jsonc-spans.ts'
|
|
18
18
|
|
|
@@ -24,12 +24,15 @@ type Arr = Extract<Node, { kind: 'array' }>
|
|
|
24
24
|
|
|
25
25
|
const prop = (o: Obj, key: string): Node | undefined => o.props.find((p) => p.key === key)?.value
|
|
26
26
|
|
|
27
|
+
/** the type checker binaries: tsc, and tsgo, its native port */
|
|
28
|
+
const TSC_BINS = new Set(['tsc', 'tsgo'])
|
|
29
|
+
|
|
27
30
|
/** the house type checker task and nothing that only looks like it: the exact
|
|
28
|
-
* label, a tsc binary, in watch mode */
|
|
31
|
+
* label, a tsc or tsgo binary, in watch mode */
|
|
29
32
|
function isTscWatcher(t: Record<string, unknown>): boolean {
|
|
30
33
|
const cmd = typeof t['command'] === 'string' ? t['command'] : ''
|
|
31
34
|
const args = Array.isArray(t['args']) ? t['args'] : []
|
|
32
|
-
return t['label'] === TSC_LABEL && basename(cmd)
|
|
35
|
+
return t['label'] === TSC_LABEL && TSC_BINS.has(basename(cmd)) && args.includes('--watch')
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/** the hub task: `bun run hub`, or the house label */
|
|
@@ -90,9 +93,17 @@ export function repairTasks(text: string, o: RepairOpts): { text: string; change
|
|
|
90
93
|
if (/\/\/.*(master task|subtask)/i.test(text)) changes.push('a comment still describes a master task with subtasks: rewrite it by hand')
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
// a wrapper that started only the TSC task starts nothing once it goes
|
|
97
|
+
const emptied = !dropTsc ? [] : tasks.items.flatMap((n, i) => (n !== wrapper && n.kind === 'object' && isWrapper(record(n)) && depsOf(n).length === 0 ? [i] : []))
|
|
98
|
+
for (const i of emptied) {
|
|
99
|
+
const [start, end] = removalSpan(text, tasks, i)
|
|
100
|
+
edits.push({ start, end, body: '' })
|
|
101
|
+
changes.push(`removed the '${labelOf(tasks.items[i]!)}' wrapper, which started nothing else`)
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
if (dropTsc) {
|
|
94
105
|
for (const t of items) {
|
|
95
|
-
if (t === wrapper) continue
|
|
106
|
+
if (t === wrapper || emptied.some((i) => tasks.items[i] === t)) continue
|
|
96
107
|
const d = prop(t, 'dependsOn')
|
|
97
108
|
if (d?.kind === 'array') {
|
|
98
109
|
const idx = d.items.findIndex((x) => x.kind === 'scalar' && x.value === TSC_LABEL)
|
|
@@ -148,13 +159,13 @@ export function repairTasks(text: string, o: RepairOpts): { text: string; change
|
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
/** whether this repo's gate runs a typecheck: a floor slot or a task that
|
|
151
|
-
* resolves, and no `
|
|
162
|
+
* resolves, and no `off` entry turning it off */
|
|
152
163
|
export function typecheckOn(c: CheckCtx): boolean {
|
|
153
164
|
const config = c.config
|
|
154
165
|
let floor: ReturnType<typeof resolveFloor>
|
|
155
166
|
try { floor = resolveFloor(config, c.root, checkFloorReader(c)) } catch { return false }
|
|
156
167
|
const t = [...floor.tasks, ...(config.tasks ?? [])].find((x) => x.id === 'typecheck')
|
|
157
|
-
return t != null &&
|
|
168
|
+
return t != null && repoOff(config, t.id) == null
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
/** the tasks file this check may touch: none, or left alone because .vscode/
|
|
@@ -178,6 +189,10 @@ export const vscodeHubTask = check({
|
|
|
178
189
|
title: 'vscode hub task',
|
|
179
190
|
description: 'the vscode hub task carries the problem matcher and no extra tsc watcher runs',
|
|
180
191
|
owner: 'build/vscode-tasks.md',
|
|
192
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: 'moved', onClock: false },
|
|
193
|
+
cache: { by: 'reads' },
|
|
194
|
+
fix: 'manual',
|
|
195
|
+
applies: 'always',
|
|
181
196
|
run: (c) => {
|
|
182
197
|
if (rootPublishesNpm(c.config)) return c.ok(PUBLIC_SKIP)
|
|
183
198
|
const own = ownTasks(c)
|
|
@@ -190,7 +205,7 @@ export const vscodeHubTask = check({
|
|
|
190
205
|
if (r.changes.length === 0) return c.ok(`${TASKS}: the hub task feeds the problems pane`)
|
|
191
206
|
for (const ch of r.changes) c.fail(`${TASKS}: ${ch.replace(/^removed/, 'should remove').replace(/^dropped/, 'should drop').replace(/^wrote/, 'should write').replace(/^folded/, 'should fold')}`)
|
|
192
207
|
},
|
|
193
|
-
|
|
208
|
+
repair: (c) => {
|
|
194
209
|
if (rootPublishesNpm(c.config)) return { changed: false, summary: PUBLIC_SKIP }
|
|
195
210
|
const own = ownTasks(c)
|
|
196
211
|
if (typeof own === 'string') return { changed: false, summary: `no ${TASKS} to repair` }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SK018 — every test file runs in a check (build/shipkit.md
|
|
1
|
+
// SK018 — every test file runs in a check (build/shipkit/test-runner.md).
|
|
2
2
|
// A `*.test.ts` that no check runs is a green nobody measured: the file says
|
|
3
3
|
// something is proved, and nothing proves it. Each file belongs to the release
|
|
4
4
|
// unit whose dir holds it (the root takes the rest), and the unit's own
|
|
@@ -12,7 +12,11 @@ export const testFilesRun = check({
|
|
|
12
12
|
blocking: true,
|
|
13
13
|
title: 'test files run',
|
|
14
14
|
description: 'every test file in the repo runs in a test check or is ignored on purpose',
|
|
15
|
-
owner: 'build/shipkit.md',
|
|
15
|
+
owner: 'build/shipkit/test-runner.md',
|
|
16
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
17
|
+
cache: { by: 'reads' },
|
|
18
|
+
fix: 'none',
|
|
19
|
+
applies: 'always',
|
|
16
20
|
run: (c) => {
|
|
17
21
|
const files = c.list(TEST_GLOB).filter(isTestFile)
|
|
18
22
|
if (files.length === 0) return c.ok('no test file in this repo')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// SK019 — tests run through the generated test checks only (build/shipkit.md
|
|
2
|
-
//
|
|
1
|
+
// SK019 — tests run through the generated test checks only (build/shipkit/test-runner.md).
|
|
2
|
+
// The floor `test` slot and one `test:<unit>` per declared
|
|
3
3
|
// unit are the ONE way tests run: in the unit's dir with its bunfig and
|
|
4
4
|
// preload, parallel, timed against the budget, and planned file by file by
|
|
5
5
|
// SK018. A hand written check that runs `bun test` itself is a second runner
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Its own id rather than a line of SK018: SK018 answers "does every test file
|
|
10
10
|
// run", this one "does anything else run them", and each is read from
|
|
11
11
|
// different files (test files and bunfigs there, check sources and the config
|
|
12
|
-
// here), so each keeps its own key, row and `
|
|
12
|
+
// here), so each keeps its own key, row and `off` switch.
|
|
13
13
|
import { dirname, posix, relative } from 'path'
|
|
14
14
|
import { check } from '../check.ts'
|
|
15
15
|
import { repoCheckSources } from '../core/legacy-shapes.ts'
|
|
@@ -36,7 +36,11 @@ export const testsRunInUnits = check({
|
|
|
36
36
|
blocking: true,
|
|
37
37
|
title: 'tests run in unit checks',
|
|
38
38
|
description: 'no check runs the test runner itself: tests run only in the generated test checks',
|
|
39
|
-
owner: 'build/shipkit.md',
|
|
39
|
+
owner: 'build/shipkit/test-runner.md',
|
|
40
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
41
|
+
cache: { by: 'reads' },
|
|
42
|
+
fix: 'none',
|
|
43
|
+
applies: 'always',
|
|
40
44
|
run: (c) => {
|
|
41
45
|
// the toolkit's own checks (this file's dir) live inside the repo that
|
|
42
46
|
// hosts it, and a built-in that WRITES a test script (SK001) is not a
|
|
@@ -132,6 +132,10 @@ export const selfSpawningCheck = check({
|
|
|
132
132
|
description: 'no check file runs its own body by spawning itself',
|
|
133
133
|
owner: OWNER,
|
|
134
134
|
blocking: true,
|
|
135
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
136
|
+
cache: { by: 'reads' },
|
|
137
|
+
fix: 'none',
|
|
138
|
+
applies: 'always',
|
|
135
139
|
run: (c) => {
|
|
136
140
|
const found = repoCheckSources(c).flatMap((f) => {
|
|
137
141
|
if (f.src == null || isLegacyCheckSource(f.src)) return []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SK022 machine runs the pin (build/shipkit.md): on a machine with a house
|
|
1
|
+
// SK022 machine runs the pin (build/shipkit/home.md): on a machine with a house
|
|
2
2
|
// every shipkit runs the house copy, whatever a repo installed, so a repo's
|
|
3
3
|
// rvlib-shipkit pin is a minimum. It warns when the repo asks for a version
|
|
4
4
|
// newer than the one this process runs: a rule or an api that pin relies on
|
|
@@ -24,11 +24,13 @@ export const machineRunsThePin = check({
|
|
|
24
24
|
blocking: false,
|
|
25
25
|
title: 'machine runs the pin',
|
|
26
26
|
description: 'the shipkit this machine runs is at least the version this repo pins',
|
|
27
|
-
owner: 'build/shipkit.md',
|
|
27
|
+
owner: 'build/shipkit/home.md',
|
|
28
|
+
runs: { onPreCommit: false, onCi: false, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
28
29
|
// the pin, the install (gitignored) and the version this machine runs,
|
|
29
30
|
// which is no file of the repo
|
|
30
|
-
inputs: ['package.json'],
|
|
31
|
-
|
|
31
|
+
cache: { by: 'files', inputs: ['package.json'], key: (env) => `${LIB_VERSION}|installed=${fileStamp(join(env.root, 'node_modules', NAME, 'package.json'))}` },
|
|
32
|
+
fix: 'none',
|
|
33
|
+
applies: 'always',
|
|
32
34
|
run: (c) => {
|
|
33
35
|
const pkg = c.json<Pkg>('package.json')
|
|
34
36
|
const spec = pkg?.devDependencies?.[NAME] ?? pkg?.dependencies?.[NAME]
|
|
@@ -18,9 +18,11 @@ export const identityColors = check({
|
|
|
18
18
|
title: 'identity colors',
|
|
19
19
|
description: 'repo.config.ts declares identity colours: colorLight and colorDark, two hex',
|
|
20
20
|
owner: 'build/shipkit/built-in-checks.md',
|
|
21
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: 'moved', onClock: false },
|
|
21
22
|
// the config, and the installed lib that says whether it knows the key
|
|
22
|
-
inputs: [CONFIG_FILE],
|
|
23
|
-
|
|
23
|
+
cache: { by: 'files', inputs: [CONFIG_FILE], key: (env) => `installed=${fileStamp(join(env.root, INSTALLED_CONFIG_SRC))}` },
|
|
24
|
+
fix: 'manual',
|
|
25
|
+
applies: 'always',
|
|
24
26
|
run: (c) => {
|
|
25
27
|
const text = c.read(CONFIG_FILE)
|
|
26
28
|
if (text == null) return c.note(`no ${CONFIG_FILE}`)
|
|
@@ -32,7 +34,7 @@ export const identityColors = check({
|
|
|
32
34
|
if (why != null) return c.warn(`${CONFIG_FILE}: ${why}`, FIX)
|
|
33
35
|
c.ok(`identity ${id!.colorLight} on light, ${id!.colorDark} on dark`)
|
|
34
36
|
},
|
|
35
|
-
|
|
37
|
+
repair: async (c) => {
|
|
36
38
|
const text = c.read(CONFIG_FILE)
|
|
37
39
|
if (text == null) return { changed: false, summary: `no ${CONFIG_FILE}` }
|
|
38
40
|
const have = identityOfText(text)
|
|
@@ -41,11 +41,10 @@ export const releaseDrift = check({
|
|
|
41
41
|
title: 'release drift',
|
|
42
42
|
description: 'every unit this repo releases matches npm and its push remote',
|
|
43
43
|
owner: 'build/shipkit/built-in-checks.md',
|
|
44
|
-
|
|
45
|
-
ttl: '20h',
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
cacheKey: (env) => releasedUnits(env.root, env.config).map((u) => `${u.id}@${u.version}`).join(','),
|
|
44
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: false, onClock: { every: (last) => (last?.status === 'pass' ? ['20h', '28h'] : '4h'), inactive: 'wait' } },
|
|
45
|
+
cache: { by: 'ttl', span: '20h', key: (env) => releasedUnits(env.root, env.config).map((u) => `${u.id}@${u.version}`).join(',') },
|
|
46
|
+
fix: 'none',
|
|
47
|
+
applies: (env) => (releasedUnits(env.root, env.config).length === 0 ? 'this repo releases nothing' : null),
|
|
49
48
|
run: async (c) => {
|
|
50
49
|
const ids = new Set(releasedUnits(c.root, c.config).map((u) => u.id))
|
|
51
50
|
if (ids.size === 0) return void c.lines.push({ kind: 'skip', text: 'this repo releases nothing: no npm root, no release config, no units' })
|