rvlib-shipkit 1.4.0 → 1.6.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 +114 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/assets/gen/comfyts-gen.ts +12 -8
- package/src/assets/server/studio-server.ts +14 -2
- package/src/check.ts +73 -103
- package/src/checks/SK001-package-json-shape.check.ts +11 -6
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +7 -3
- 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/checks/SK030-cache-audit.check.ts +34 -0
- package/src/checks/SK031-trace-audit.check.ts +39 -0
- package/src/checks/SK032-ignored-inputs.check.ts +79 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +17 -13
- package/src/cli/cmd_Bump.ts +12 -11
- package/src/cli/cmd_CacheAudit.ts +43 -0
- 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 +8 -6
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +120 -7
- package/src/cli/cmd_Home.ts +26 -19
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +20 -8
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +5 -4
- 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 +6 -4
- 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_TraceAudit.ts +62 -0
- 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 +11 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +12 -4
- package/src/collections/providers.ts +5 -3
- package/src/core/app-icon.ts +154 -0
- package/src/core/audit-report.ts +21 -0
- package/src/core/cache-audit.ts +72 -0
- package/src/core/canonical-scripts.ts +2 -2
- package/src/core/check-ctx.ts +14 -5
- package/src/core/check-runner.ts +43 -23
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +23 -2
- package/src/core/floor-resolve.ts +5 -7
- package/src/core/git-root.ts +13 -0
- package/src/core/ignored-block.ts +94 -0
- package/src/core/ignored-inputs.ts +47 -0
- package/src/core/jobs.ts +9 -2
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/one-lib.ts +59 -4
- 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/read-set.ts +97 -9
- package/src/core/retention.ts +0 -14
- package/src/core/run-check.ts +12 -9
- package/src/core/run-ci.ts +14 -5
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +48 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -0
- package/src/core/trace-audit.ts +120 -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/gui-app.ts +24 -27
- package/src/home/home-launchd.ts +20 -4
- package/src/home/home-routes.ts +12 -1
- package/src/home/home-start.ts +11 -5
- package/src/home/hubs.ts +5 -5
- package/src/home/open-app.ts +2 -2
- 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/home/via-hub.ts +14 -0
- package/src/hooks/run-hooks.ts +48 -4
- package/src/init-repo.ts +6 -6
- package/src/ledger/db.ts +2 -13
- package/src/ledger/gate-checks.ts +133 -61
- package/src/ledger/git-key.ts +153 -31
- package/src/ledger/git-scan.ts +5 -2
- package/src/ledger/ledger.ts +136 -21
- package/src/ledger/store.ts +14 -0
- package/src/ledger/toolchain-identity.ts +248 -45
- package/src/plan/repo-source.ts +18 -39
- package/src/problems/extract.ts +3 -0
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +46 -54
- 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-svc.ts +36 -3
- package/src/serve/hub-app/admin-view.tsx +46 -12
- 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-card.tsx +1 -44
- package/src/serve/hub-app/col-tables-view.tsx +84 -11
- package/src/serve/hub-app/col-tables.ts +27 -0
- 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 -12
- package/src/serve/hub-app/home-strip.tsx +115 -64
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +32 -8
- package/src/serve/hub-app/hub-types.ts +5 -2
- package/src/serve/hub-app/hub.css +89 -63
- 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 +15 -4
- 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 +45 -0
- package/src/serve/hub-app/project-list.css +4 -1
- package/src/serve/hub-app/project-list.tsx +6 -3
- package/src/serve/hub-app/projects-svc.ts +25 -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 +131 -0
- package/src/serve/hub-app/repo-st.ts +70 -14
- package/src/serve/hub-app/sidebar.tsx +17 -42
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/tree-build.ts +151 -155
- package/src/serve/hub-app/tree-row.tsx +55 -12
- package/src/serve/hub-app/tree-svc.ts +97 -56
- 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 +7 -18
- package/src/serve/project-gutter-route.ts +22 -1
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/suite/judge-suites.ts +6 -0
- package/src/trace/interpose.c +655 -0
- package/src/trace/read-trace.ts +485 -0
- package/src/trace/tracer.ts +282 -0
- 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
|
@@ -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' })
|
|
@@ -14,11 +14,11 @@ export const asksAutoclose = check({
|
|
|
14
14
|
title: 'asks auto close',
|
|
15
15
|
description: 'no open ask whose own probe passes stays in the queue',
|
|
16
16
|
owner: 'build/shipkit/built-in-checks.md',
|
|
17
|
-
|
|
18
|
-
never: "runs each open ask's own probe, a shell command that reads the world",
|
|
19
|
-
|
|
17
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: false, onClock: { every: ['20h', '28h'], inactive: 'wait' } },
|
|
18
|
+
cache: { by: 'never', why: "runs each open ask's own probe, a shell command that reads the world" },
|
|
19
|
+
fix: 'none',
|
|
20
20
|
// it only probes an ask that carries one, and archives one marked done
|
|
21
|
-
|
|
21
|
+
applies: (env) => {
|
|
22
22
|
const dir = join(env.root, env.config.asks?.dir ?? LAYOUT.asks)
|
|
23
23
|
if (!existsSync(dir)) return 'no asks queue'
|
|
24
24
|
return listAsks(dir).some((a) => a.check != null || a.status === 'done') ? null : 'no open ask carries a probe'
|
|
@@ -14,8 +14,11 @@ export const agentWikiLinks = check({
|
|
|
14
14
|
title: 'agent wiki links',
|
|
15
15
|
description: 'every agent/ page is linked from somewhere, and every agent/ link resolves',
|
|
16
16
|
owner: 'meta/agent-folder.md',
|
|
17
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
17
18
|
// the md lint walks agent/ on disk rather than through `c`: agent/ is all it reads
|
|
18
|
-
inputs: ['agent'],
|
|
19
|
+
cache: { by: 'files', inputs: ['agent'], key: null },
|
|
20
|
+
fix: 'none',
|
|
21
|
+
applies: 'always',
|
|
19
22
|
run: (c) => {
|
|
20
23
|
if (!existsSync(join(c.root, 'agent/index.md'))) return void c.lines.push({ kind: 'skip', text: 'no agent/ wiki in this repo' })
|
|
21
24
|
const lint = lintMd({ root: join(c.root, 'agent'), index: 'index.md', boundary: c.root })
|
|
@@ -60,7 +60,7 @@ const REQUIREMENTS: Requirement[] = [
|
|
|
60
60
|
|
|
61
61
|
/** the other way out, for a repo that cannot show one: the rule off, with the
|
|
62
62
|
* reason `defineRepo` requires (core/when.ts) */
|
|
63
|
-
const OFF = `or, when this repo cannot show one, turn the rule off with the reason:
|
|
63
|
+
const OFF = `or, when this repo cannot show one, turn the rule off with the reason: off: { SK028: '<why this repo shows no screenshot>' } in repo.config.ts, inside its shipkit gate block`
|
|
64
64
|
|
|
65
65
|
export const readme = check({
|
|
66
66
|
id: 'SK028',
|
|
@@ -68,6 +68,10 @@ export const readme = check({
|
|
|
68
68
|
title: 'readme',
|
|
69
69
|
description: 'README.md exists and shows at least one screenshot',
|
|
70
70
|
owner: 'build/shipkit/built-in-checks.md',
|
|
71
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: 'moved', onClock: false },
|
|
72
|
+
cache: { by: 'reads' },
|
|
73
|
+
fix: 'none',
|
|
74
|
+
applies: 'always',
|
|
71
75
|
run: (c) => {
|
|
72
76
|
const md = c.read(README)
|
|
73
77
|
if (md == null) return c.warn(`${README}: exists: no ${README} at the repo root`, `write README.md: what the repo is, and a screenshot of it; ${OFF}`)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SK029 explicit check files (build/shipkit/runs.md owns the declaration):
|
|
2
|
+
// every check file of the repo declares runs, cache, fix and applies, with
|
|
3
|
+
// no retired key. A file that does not does not load, and discovery shows
|
|
4
|
+
// it as one failing row. The repair ports it with the values the retired
|
|
5
|
+
// keys resolved to (core/port-check-file.ts), so the check keeps running
|
|
6
|
+
// where it ran, and the ci fix pass applies it before the gate reads the
|
|
7
|
+
// files: a repo carried across by its first `shipkit ci`. The same holds for
|
|
8
|
+
// the config: a retired `when` (only its `off:<why>` entries still load) and
|
|
9
|
+
// `ruleExempt` become `off` entries in the managed block.
|
|
10
|
+
import { join } from 'path'
|
|
11
|
+
import { check, type FixResult } from '../check.ts'
|
|
12
|
+
import { unportedCheckFiles } from '../core/port-check-files.ts'
|
|
13
|
+
import type { PortRun } from '../core/port-check-cli.ts'
|
|
14
|
+
import type { ConfigPlan, ConfigRewrite } from '../core/config-rewrite.ts'
|
|
15
|
+
import type { RepoConfig } from '../repo-config.ts'
|
|
16
|
+
|
|
17
|
+
/** the write half, run as its own process: it walks import closures on disk */
|
|
18
|
+
const PORT_CLI = join(import.meta.dir, '../core/port-check-cli.ts')
|
|
19
|
+
/** the config half, run as its own process: it loads repo.config.ts */
|
|
20
|
+
const CONFIG_REWRITE = join(import.meta.dir, '../core/config-rewrite.ts')
|
|
21
|
+
|
|
22
|
+
/** the retired config keys this repo still carries, and the `off` entries
|
|
23
|
+
* they become. defineRepo already refused every `when` word but `off:<why>` */
|
|
24
|
+
export function retiredOff(c: RepoConfig): { keys: string[]; off: Record<string, string> } {
|
|
25
|
+
const off: Record<string, string> = {}
|
|
26
|
+
for (const [id, raw] of Object.entries(c.when ?? {})) off[id] = raw.trim().replace(/^off:?/, '').trim()
|
|
27
|
+
for (const [id, why] of Object.entries(c.ruleExempt ?? {})) off[id] ??= why
|
|
28
|
+
const keys = [...(c.when != null ? ['when'] : []), ...(c.ruleExempt != null ? ['ruleExempt'] : [])]
|
|
29
|
+
return { keys, off }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const explicitCheckFiles = check({
|
|
33
|
+
id: 'SK029',
|
|
34
|
+
title: 'explicit check files',
|
|
35
|
+
description: 'every check file declares runs, cache, fix and applies, and the config says only off',
|
|
36
|
+
owner: 'build/shipkit/runs.md',
|
|
37
|
+
blocking: true,
|
|
38
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: true, onHealth: true, onSave: 'moved', onClock: false },
|
|
39
|
+
// the listing of check files and their sources, read through `c`
|
|
40
|
+
cache: { by: 'reads' },
|
|
41
|
+
// the port is mechanical: every value it writes is what the retired keys
|
|
42
|
+
// resolved to, so the fix pass applies it without asking
|
|
43
|
+
fix: 'ci',
|
|
44
|
+
applies: 'always',
|
|
45
|
+
run: (c) => {
|
|
46
|
+
const files = unportedCheckFiles(c)
|
|
47
|
+
const cfg = retiredOff(c.config)
|
|
48
|
+
if (files.length === 0 && cfg.keys.length === 0) return c.ok('every check file declares runs, cache, fix and applies, and the config says only off')
|
|
49
|
+
for (const f of files) c.fail(`${f.rel} still declares the retired shape (${f.keys.join(', ')}), so it does not load`)
|
|
50
|
+
if (cfg.keys.length > 0) c.fail(`repo.config.ts still carries ${cfg.keys.join(' and ')}: a repo only turns a check off, as off: { <id>: '<why>' }`)
|
|
51
|
+
c.fail(`${files.length + (cfg.keys.length > 0 ? 1 : 0)} file(s) to port`, 'shipkit check --fix SK029 ports each one with the values it ran on, and `shipkit ci` does it in its fix pass')
|
|
52
|
+
},
|
|
53
|
+
repair: async (c): Promise<FixResult> => {
|
|
54
|
+
const files = unportedCheckFiles(c)
|
|
55
|
+
const cfg = retiredOff(c.config)
|
|
56
|
+
if (files.length === 0 && cfg.keys.length === 0) return { changed: false, summary: 'every check file is explicit already, and the config says only off' }
|
|
57
|
+
const did: string[] = []
|
|
58
|
+
const hand: string[] = []
|
|
59
|
+
if (files.length > 0) {
|
|
60
|
+
const r = await c.exec([process.execPath, PORT_CLI, c.root, ...files.map((f) => f.rel)], { cwd: c.root, timeoutMs: 60_000 })
|
|
61
|
+
try {
|
|
62
|
+
const run = JSON.parse(r.out.trim().split('\n').at(-1) ?? '') as PortRun
|
|
63
|
+
if (run.did.length > 0) did.push(`ported ${run.did.join('; ')}`)
|
|
64
|
+
hand.push(...run.hand)
|
|
65
|
+
} catch {
|
|
66
|
+
hand.push(`the port did not answer (exit ${r.code}: ${r.out.trim().slice(0, 160)}): port ${files.map((f) => f.rel).join(', ')} by hand (build/shipkit/runs.md)`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (cfg.keys.length > 0) {
|
|
70
|
+
// `when` sits inside the managed block, so it goes first and the
|
|
71
|
+
// block describes the config again before the entries land
|
|
72
|
+
const plan: ConfigPlan = {
|
|
73
|
+
off: cfg.off,
|
|
74
|
+
drop: [
|
|
75
|
+
...(c.config.when != null ? [{ key: 'when', why: 'its off entries are `off` now', phase: 'first' as const }] : []),
|
|
76
|
+
...(c.config.ruleExempt != null ? [{ key: 'ruleExempt', why: 'its exemptions are `off` now', phase: 'afterOff' as const }] : []),
|
|
77
|
+
],
|
|
78
|
+
name: c.config.name,
|
|
79
|
+
}
|
|
80
|
+
const r = await c.exec([process.execPath, CONFIG_REWRITE, c.root, JSON.stringify(plan)], { cwd: c.root, timeoutMs: 60_000 })
|
|
81
|
+
try {
|
|
82
|
+
const out = JSON.parse(r.out.trim().split('\n').at(-1) ?? '') as ConfigRewrite
|
|
83
|
+
did.push(...out.did)
|
|
84
|
+
hand.push(...out.hand)
|
|
85
|
+
if (out.restored != null) hand.push(`repo.config.ts was left as it was: ${out.restored}`)
|
|
86
|
+
} catch {
|
|
87
|
+
hand.push(`the repo.config.ts rewrite did not answer (exit ${r.code}): write off: ${JSON.stringify(cfg.off)} by hand and drop ${cfg.keys.join(', ')}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const summary = [did.length > 0 ? did.join('; ') : 'nothing ported', ...(hand.length > 0 ? [`${hand.length} by hand: ${hand.join(' | ')}`] : [])].join('. ')
|
|
91
|
+
return { changed: did.length > 0, summary }
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
export default explicitCheckFiles
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// SK030 cache audit (build/shipkit/runs.md owns the cache): a sample of the
|
|
2
|
+
// checks the cache answers is re-run fresh about once a day, and a cached
|
|
3
|
+
// green a fresh run disagrees with fails, naming the check whose key misses a
|
|
4
|
+
// path. The work is `sk cache-audit` (core/cache-audit.ts), in its own
|
|
5
|
+
// process, because it runs other checks.
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
import { check } from '../check.ts'
|
|
8
|
+
|
|
9
|
+
const CLI = join(import.meta.dir, '../cli.ts')
|
|
10
|
+
|
|
11
|
+
export const cacheAudit = check({
|
|
12
|
+
id: 'SK030',
|
|
13
|
+
title: 'cache audit',
|
|
14
|
+
description: 'a sample of cached answers matches what a fresh run of each check says',
|
|
15
|
+
owner: 'build/shipkit/runs.md',
|
|
16
|
+
blocking: true,
|
|
17
|
+
// never a gate: it re-runs other checks. The daemon asks it about once a
|
|
18
|
+
// day, and only while somebody works in the repo
|
|
19
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: false, onClock: { every: ['20h', '28h'], inactive: 'wait' } },
|
|
20
|
+
cache: { by: 'never', why: 'it runs other checks fresh, so its answer is that run itself' },
|
|
21
|
+
fix: 'none',
|
|
22
|
+
applies: 'always',
|
|
23
|
+
run: async (c) => {
|
|
24
|
+
const r = await c.exec([process.execPath, CLI, 'cache-audit', '--json'], { cwd: c.root, timeoutMs: 600_000 })
|
|
25
|
+
let out: { pool: number; rows: { id: string; cached: string; fresh: string; keyedOn: string }[]; stale: { id: string; cached: string; fresh: string; keyedOn: string }[] }
|
|
26
|
+
try { out = JSON.parse(r.out.trim().split('\n').at(-1) ?? '') } catch { return c.fail(`sk cache-audit did not answer (exit ${r.code}: ${r.out.trim().slice(0, 160)})`) }
|
|
27
|
+
if (out.rows.length === 0) return c.note(`nothing to audit: the cache answers none of this repo's checks right now`)
|
|
28
|
+
for (const s of out.stale)
|
|
29
|
+
c.fail(`${s.id}: the cache said ${s.cached}, a fresh run says ${s.fresh}, so its key misses something it reads`, `${s.keyedOn === 'files' ? 'add the path to its cache.inputs' : 'move the read through c'}, then sk keys --why ${s.id}`)
|
|
30
|
+
if (out.stale.length === 0) c.ok(`${out.rows.map((x) => x.id).join(', ')}: every cached answer is what a fresh run says (${out.rows.length} of ${out.pool} cached)`)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export default cacheAudit
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SK031 traced audit (build/shipkit/cache.md owns the key): about once a day
|
|
2
|
+
// each gate task runs under the tracer, and a gitignored path it reads that
|
|
3
|
+
// its key misses joins that task's key from the kept answer. SK032 carries the
|
|
4
|
+
// kept proposals into repo.config.ts in the gate's fix pass. The work is
|
|
5
|
+
// `sk trace-audit` (core/trace-audit.ts), in its own process, because it runs
|
|
6
|
+
// the gate's tasks. On wherever the tracer runs, unless the machine opted out.
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { check } from '../check.ts'
|
|
9
|
+
import { traceUnavailable } from '../trace/tracer.ts'
|
|
10
|
+
|
|
11
|
+
const CLI = join(import.meta.dir, '../cli.ts')
|
|
12
|
+
|
|
13
|
+
export const traceAuditCheck = check({
|
|
14
|
+
id: 'SK031',
|
|
15
|
+
title: 'traced audit',
|
|
16
|
+
description: 'every gitignored path a gate task reads is in its key',
|
|
17
|
+
owner: 'build/shipkit/cache.md',
|
|
18
|
+
blocking: false,
|
|
19
|
+
// never a gate: it runs the gate's tasks. The daemon asks it about once a
|
|
20
|
+
// day, and only while somebody works in the repo
|
|
21
|
+
runs: { onPreCommit: false, onCi: false, onFull: false, onRelease: false, onHealth: true, onSave: false, onClock: { every: ['20h', '28h'], inactive: 'wait' } },
|
|
22
|
+
cache: { by: 'never', why: 'it runs the gate tasks under the tracer, so its answer is that run itself' },
|
|
23
|
+
fix: 'none',
|
|
24
|
+
applies: () => traceUnavailable(),
|
|
25
|
+
run: async (c) => {
|
|
26
|
+
const r = await c.exec([process.execPath, CLI, 'trace-audit', '--json'], { cwd: c.root, timeoutMs: 1_200_000 })
|
|
27
|
+
let out: { skipped?: string; tasks: { id: string; status: string; why?: string; uncovered: string[]; propose: string[] }[] }
|
|
28
|
+
try { out = JSON.parse(r.out.trim().split('\n').at(-1) ?? '') } catch { return c.fail(`sk trace-audit did not answer (exit ${r.code}: ${r.out.trim().slice(0, 160)})`) }
|
|
29
|
+
if (out.skipped != null) return c.note(`nothing traced: ${out.skipped}`)
|
|
30
|
+
for (const t of out.tasks.filter((x) => x.status === 'unaudited')) c.warn(`${t.id} could not be audited: ${t.why}`)
|
|
31
|
+
const missing = out.tasks.filter((x) => x.uncovered.length > 0)
|
|
32
|
+
// a finding joins its task's key at once (core/audit-report.ts), so it
|
|
33
|
+
// is news, never a failure: SK032 carries it into repo.config.ts
|
|
34
|
+
for (const t of missing) c.ok(`${t.id} reads ${t.uncovered.length} gitignored path(s) the config does not name, keyed from this audit: ${t.propose.join(', ')}`)
|
|
35
|
+
if (missing.length === 0) c.ok(`${out.tasks.filter((x) => x.status === 'audited').length} gate task(s) audited: every gitignored path they read is in their key`)
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default traceAuditCheck
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SK032 ignored inputs current (build/shipkit/cache.md owns the key): what the
|
|
2
|
+
// last traced audit (SK031) found a task reading outside its key is in
|
|
3
|
+
// repo.config.ts ignoredInputs, so other machines and clones key it too. The repair writes the missing entries into
|
|
4
|
+
// the managed block, so the gate's fix pass carries each finding into the
|
|
5
|
+
// config. Nothing is traced here: it reads the audit SK031 kept.
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
import { check } from '../check.ts'
|
|
8
|
+
import { describeByTask, pendingByTask, type AuditReport } from '../core/ignored-inputs.ts'
|
|
9
|
+
|
|
10
|
+
const CLI = join(import.meta.dir, '../cli.ts')
|
|
11
|
+
|
|
12
|
+
/** the first rvlib-shipkit release whose `defineRepo` knows `ignoredInputs`.
|
|
13
|
+
* A repo whose pin allows an older one loads its config elsewhere (a CI job
|
|
14
|
+
* installing from npm, a fresh clone) with that older copy, which refuses
|
|
15
|
+
* the unknown key: so nothing is written until the pin says at least this */
|
|
16
|
+
export const IGNORED_INPUTS_SINCE = '1.6.0'
|
|
17
|
+
|
|
18
|
+
const parts = (v: string): number[] => v.split('.').map((n) => Number.parseInt(n, 10) || 0)
|
|
19
|
+
const atLeast = (v: string, min: string): boolean => {
|
|
20
|
+
const a = parts(v), b = parts(min)
|
|
21
|
+
for (let i = 0; i < 3; i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0)
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** why the repo's pin cannot carry the key yet, or null. No pin (the repo
|
|
26
|
+
* that hosts the toolkit, or one running it from source) can carry it */
|
|
27
|
+
function pinTooOld(pkg: string | null): string | null {
|
|
28
|
+
if (pkg == null) return null
|
|
29
|
+
let range: string | undefined
|
|
30
|
+
try {
|
|
31
|
+
const j = JSON.parse(pkg) as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }
|
|
32
|
+
range = j.devDependencies?.['rvlib-shipkit'] ?? j.dependencies?.['rvlib-shipkit']
|
|
33
|
+
} catch {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
const min = range == null ? null : /(\d+\.\d+\.\d+)/.exec(range)?.[1] ?? null
|
|
37
|
+
if (min == null || range!.startsWith('link:') || range!.startsWith('file:') || range!.startsWith('workspace:')) return null
|
|
38
|
+
return atLeast(min, IGNORED_INPUTS_SINCE) ? null : `package.json pins rvlib-shipkit ${range}, which allows a release before ${IGNORED_INPUTS_SINCE} that refuses ignoredInputs: raise the pin to ^${IGNORED_INPUTS_SINCE} first`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** the kept audit, read through c so the reads key follows it */
|
|
42
|
+
function kept(text: string | null): AuditReport | null {
|
|
43
|
+
try { return text == null ? null : (JSON.parse(text) as AuditReport) } catch { return null }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const ignoredInputsCheck = check({
|
|
47
|
+
id: 'SK032',
|
|
48
|
+
title: 'ignored inputs current',
|
|
49
|
+
description: 'every gitignored path the traced audit saw a task read is in ignoredInputs',
|
|
50
|
+
owner: 'build/shipkit/cache.md',
|
|
51
|
+
blocking: false,
|
|
52
|
+
runs: { onPreCommit: false, onCi: true, onFull: true, onRelease: false, onHealth: true, onSave: false, onClock: false },
|
|
53
|
+
cache: { by: 'reads' },
|
|
54
|
+
fix: 'ci',
|
|
55
|
+
applies: 'always',
|
|
56
|
+
run: (c) => {
|
|
57
|
+
const missing = pendingByTask(kept(c.state('trace-audit.json')), c.config.ignoredInputs ?? {})
|
|
58
|
+
const n = Object.values(missing).flat().length
|
|
59
|
+
if (n === 0) return c.ok('repo.config.ts ignoredInputs covers what the last traced audit found')
|
|
60
|
+
// the keys on this machine already read the kept audit (core/audit-report.ts),
|
|
61
|
+
// so what is missing here is the copy other machines and clones see
|
|
62
|
+
const old = pinTooOld(c.read('package.json'))
|
|
63
|
+
if (old != null) return c.ok(`${n} path(s) the traced audit found are keyed on this machine only: ${old}`)
|
|
64
|
+
c.warn(`the traced audit found ${describeByTask(missing)}: keyed on this machine, not yet in repo.config.ts ignoredInputs`, 'sk ci writes them, and sk trace-audit --fix says why when it cannot')
|
|
65
|
+
},
|
|
66
|
+
repair: async (c) => {
|
|
67
|
+
const missing = pendingByTask(kept(c.state('trace-audit.json')), c.config.ignoredInputs ?? {})
|
|
68
|
+
if (Object.keys(missing).length === 0) return { changed: false, summary: 'nothing to add' }
|
|
69
|
+
const old = pinTooOld(c.read('package.json'))
|
|
70
|
+
if (old != null) return { changed: false, summary: old }
|
|
71
|
+
// the managed block writer loads repo.config.ts, which no read through
|
|
72
|
+
// c can stand for, so it runs in its own process
|
|
73
|
+
const r = await c.exec([process.execPath, CLI, 'trace-audit', '--fix'], { cwd: c.root, timeoutMs: 60_000 })
|
|
74
|
+
if (r.code !== 0) return { changed: false, summary: `sk trace-audit --fix failed: ${r.out.trim().split('\n').at(-1) ?? `exit ${r.code}`}` }
|
|
75
|
+
return { changed: true, summary: `repo.config.ts ignoredInputs gained ${describeByTask(missing)}` }
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
export default ignoredInputsCheck
|
package/src/cli/cmd_Asks.ts
CHANGED
|
@@ -15,7 +15,7 @@ export class AsksCommand extends Command {
|
|
|
15
15
|
static override usage = Command.Usage({
|
|
16
16
|
category: 'Asks',
|
|
17
17
|
description: 'list open asks — the per-repo human action queue',
|
|
18
|
-
examples: [['list open asks', '
|
|
18
|
+
examples: [['list open asks', 'sk asks']],
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
async execute(): Promise<number> {
|
|
@@ -37,7 +37,7 @@ export class AsksCheckCommand extends Command {
|
|
|
37
37
|
static override usage = Command.Usage({
|
|
38
38
|
category: 'Asks',
|
|
39
39
|
description: 'probe every open ask and archive the ones whose check now passes',
|
|
40
|
-
examples: [['probe + auto-close', '
|
|
40
|
+
examples: [['probe + auto-close', 'sk asks check']],
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
async execute(): Promise<number> {
|
|
@@ -59,9 +59,9 @@ export class AskNewCommand extends Command {
|
|
|
59
59
|
category: 'Asks',
|
|
60
60
|
description: 'create a new ask file in the queue',
|
|
61
61
|
examples: [
|
|
62
|
-
['minimal', '
|
|
63
|
-
['with a reason', '
|
|
64
|
-
['with metadata', '
|
|
62
|
+
['minimal', 'sk ask "rotate the API token"'],
|
|
63
|
+
['with a reason', 'sk ask "buy the domain" --why "landing page is blocked on it"'],
|
|
64
|
+
['with metadata', 'sk ask "buy the domain" --kind action --blocks deploy --check "dig +short example.com"'],
|
|
65
65
|
],
|
|
66
66
|
})
|
|
67
67
|
|
package/src/cli/cmd_Assets.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
// (ComfyUI or procedural canvas), curate in the studio, ship with metadata
|
|
3
3
|
// write-through. Thin clipanion surfaces; logic in assets/run-assets.ts.
|
|
4
4
|
import { Command, Option } from 'clipanion'
|
|
5
|
-
import {
|
|
5
|
+
import type { AssetsIo } from '../assets/run-assets.ts'
|
|
6
|
+
|
|
7
|
+
// loaded when an assets command RUNS: it pulls in canvas, sharp and comfy-ts,
|
|
8
|
+
// which every other command would otherwise pay for at startup
|
|
9
|
+
const assets = (): Promise<typeof import('../assets/run-assets.ts')> => import('../assets/run-assets.ts')
|
|
6
10
|
|
|
7
11
|
const ioOf = (cmd: Command): AssetsIo => ({
|
|
8
12
|
print: (l) => cmd.context.stdout.write(`${l}\n`),
|
|
@@ -19,8 +23,8 @@ export class AssetsFillCommand extends Command {
|
|
|
19
23
|
category: 'Assets',
|
|
20
24
|
description: 'top rolls up to N per asset (canvas or comfy), sparsest-first',
|
|
21
25
|
examples: [
|
|
22
|
-
['every asset up to 8 rolls', '
|
|
23
|
-
['only two categories', '
|
|
26
|
+
['every asset up to 8 rolls', 'sk assets fill 8'],
|
|
27
|
+
['only two categories', 'sk assets fill 8 icons sprites'],
|
|
24
28
|
],
|
|
25
29
|
})
|
|
26
30
|
|
|
@@ -33,7 +37,7 @@ export class AssetsFillCommand extends Command {
|
|
|
33
37
|
this.context.stderr.write(`FAIL: '${this.n}' is not a positive roll count — usage: shipkit assets fill <N> [category…]\n`)
|
|
34
38
|
return 1
|
|
35
39
|
}
|
|
36
|
-
return runAssetsTopUp(process.cwd(), ioOf(this), { mode: 'fill', n, categories: this.categories })
|
|
40
|
+
return (await assets()).runAssetsTopUp(process.cwd(), ioOf(this), { mode: 'fill', n, categories: this.categories })
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -43,7 +47,7 @@ export class AssetsAddCommand extends Command {
|
|
|
43
47
|
static override usage = Command.Usage({
|
|
44
48
|
category: 'Assets',
|
|
45
49
|
description: 'N more rolls per asset (canvas or comfy), regardless of what it has',
|
|
46
|
-
examples: [['3 fresh rolls everywhere', '
|
|
50
|
+
examples: [['3 fresh rolls everywhere', 'sk assets add 3']],
|
|
47
51
|
})
|
|
48
52
|
|
|
49
53
|
n = Option.String({ required: true })
|
|
@@ -55,7 +59,7 @@ export class AssetsAddCommand extends Command {
|
|
|
55
59
|
this.context.stderr.write(`FAIL: '${this.n}' is not a positive roll count — usage: shipkit assets add <N> [category…]\n`)
|
|
56
60
|
return 1
|
|
57
61
|
}
|
|
58
|
-
return runAssetsTopUp(process.cwd(), ioOf(this), { mode: 'add', n, categories: this.categories })
|
|
62
|
+
return (await assets()).runAssetsTopUp(process.cwd(), ioOf(this), { mode: 'add', n, categories: this.categories })
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -66,8 +70,8 @@ export class AssetsGenCommand extends Command {
|
|
|
66
70
|
category: 'Assets',
|
|
67
71
|
description: 'one-shot rolls for a single asset (canvas or comfy)',
|
|
68
72
|
examples: [
|
|
69
|
-
['one roll', '
|
|
70
|
-
['five rolls', '
|
|
73
|
+
['one roll', 'sk assets gen hero'],
|
|
74
|
+
['five rolls', 'sk assets gen hero --n 5'],
|
|
71
75
|
],
|
|
72
76
|
})
|
|
73
77
|
|
|
@@ -76,7 +80,7 @@ export class AssetsGenCommand extends Command {
|
|
|
76
80
|
|
|
77
81
|
async execute(): Promise<number> {
|
|
78
82
|
const n = Math.max(1, Number(this.n) || 1)
|
|
79
|
-
return runAssetsGen(process.cwd(), ioOf(this), { assetId: this.assetId, n })
|
|
83
|
+
return (await assets()).runAssetsGen(process.cwd(), ioOf(this), { assetId: this.assetId, n })
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -86,11 +90,11 @@ export class AssetsIdentityCommand extends Command {
|
|
|
86
90
|
static override usage = Command.Usage({
|
|
87
91
|
category: 'Assets',
|
|
88
92
|
description: 'render favicon/logo/OG from .shipkit/identity/brand.ts into public/',
|
|
89
|
-
examples: [['regenerate the identity outputs', '
|
|
93
|
+
examples: [['regenerate the identity outputs', 'sk assets identity']],
|
|
90
94
|
})
|
|
91
95
|
|
|
92
96
|
async execute(): Promise<number> {
|
|
93
|
-
return runAssetsIdentity(process.cwd(), ioOf(this))
|
|
97
|
+
return (await assets()).runAssetsIdentity(process.cwd(), ioOf(this))
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
|
|
@@ -100,10 +104,10 @@ export class AssetsCheckCommand extends Command {
|
|
|
100
104
|
static override usage = Command.Usage({
|
|
101
105
|
category: 'Assets',
|
|
102
106
|
description: 'kit loads, kinds valid, cflow workflows load, drafts present, identity outputs present',
|
|
103
|
-
examples: [['health-check the asset pipeline', '
|
|
107
|
+
examples: [['health-check the asset pipeline', 'sk assets check']],
|
|
104
108
|
})
|
|
105
109
|
|
|
106
110
|
async execute(): Promise<number> {
|
|
107
|
-
return runAssetsCheck(process.cwd(), ioOf(this))
|
|
111
|
+
return (await assets()).runAssetsCheck(process.cwd(), ioOf(this))
|
|
108
112
|
}
|
|
109
113
|
}
|
package/src/cli/cmd_Bump.ts
CHANGED
|
@@ -9,18 +9,19 @@ export class BumpCommand extends Command {
|
|
|
9
9
|
|
|
10
10
|
static override usage = Command.Usage({
|
|
11
11
|
category: 'Gate',
|
|
12
|
-
description: 'ship a version
|
|
12
|
+
description: 'ship a version: merge, changelog, bump, gate, tag, push, release',
|
|
13
|
+
details: 'ship a version in one word: bumps a unit (default the repo root), run from a feature branch it merges into the release branch first, then promotes the CHANGELOG Unreleased section, bumps package.json, runs the gate and every prepublish script, commits, tags, pushes, and cuts the release. Any failure before the version commit rolls the merge back and returns you to your branch.',
|
|
13
14
|
examples: [
|
|
14
|
-
['a bugfix release of the repo itself', '
|
|
15
|
-
['a feature release', '
|
|
16
|
-
['a breaking release', '
|
|
17
|
-
['one sub-unit (ids: shipkit releases)', '
|
|
18
|
-
['a whole mixed batch in ONE gate, each unit at the kind its heading declares', '
|
|
19
|
-
['every unit carrying Unreleased notes, all at the same kind', '
|
|
20
|
-
['one unit, kind from its own heading', '
|
|
21
|
-
['rehearse the whole thing', '
|
|
22
|
-
['bump and commit, release later', '
|
|
23
|
-
['from a feature branch, refuse rather than merge', '
|
|
15
|
+
['a bugfix release of the repo itself', 'sk bump patch'],
|
|
16
|
+
['a feature release', 'sk bump minor'],
|
|
17
|
+
['a breaking release', 'sk bump major'],
|
|
18
|
+
['one sub-unit (ids: shipkit releases)', 'sk bump minor rvlib-shipkit'],
|
|
19
|
+
['a whole mixed batch in ONE gate, each unit at the kind its heading declares', 'sk bump --all'],
|
|
20
|
+
['every unit carrying Unreleased notes, all at the same kind', 'sk bump patch --all'],
|
|
21
|
+
['one unit, kind from its own heading', 'sk bump rvlib-shipkit'],
|
|
22
|
+
['rehearse the whole thing', 'sk bump minor --dry-run'],
|
|
23
|
+
['bump and commit, release later', 'sk bump patch --no-release'],
|
|
24
|
+
['from a feature branch, refuse rather than merge', 'sk bump patch --no-merge'],
|
|
24
25
|
],
|
|
25
26
|
})
|
|
26
27
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// sk cache-audit — re-runs a sample of the checks the cache would answer right
|
|
2
|
+
// now, fresh, and compares: a cached green a fresh run disagrees with is a
|
|
3
|
+
// key that misses something its check reads (core/cache-audit.ts).
|
|
4
|
+
import { Command, Option } from 'clipanion'
|
|
5
|
+
import { loadRepoConfig } from '../repo-config.ts'
|
|
6
|
+
import { auditCache } from '../core/cache-audit.ts'
|
|
7
|
+
|
|
8
|
+
export class CacheAuditCommand extends Command {
|
|
9
|
+
static override paths = [['cache-audit']]
|
|
10
|
+
|
|
11
|
+
static override usage = Command.Usage({
|
|
12
|
+
category: 'Health',
|
|
13
|
+
description: 'is a cached green still true: re-run a sample of cached checks fresh and compare',
|
|
14
|
+
details: 'Picks checks the cache would answer right now (keyed on files or on reads), a different stable sample each day, runs each one fresh and compares its verdict with the cached one. A mismatch means the check key misses a path its body reads: the fresh answer replaces the stale one, and the line names the check. Exit 1 on a mismatch.',
|
|
15
|
+
examples: [
|
|
16
|
+
['audit three cached checks', 'sk cache-audit'],
|
|
17
|
+
['a bigger sample', 'sk cache-audit --sample 10'],
|
|
18
|
+
['one check, by id', 'sk cache-audit --only SK004'],
|
|
19
|
+
['machine-readable', 'sk cache-audit --json'],
|
|
20
|
+
],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
sample = Option.String('--sample', '3', { description: 'how many cached checks to re-run fresh (default 3)' })
|
|
24
|
+
only = Option.Array('--only', { description: 'audit these check ids instead of a sample' })
|
|
25
|
+
json = Option.Boolean('--json', false, { description: 'machine-readable { pool, rows, stale }' })
|
|
26
|
+
|
|
27
|
+
async execute(): Promise<number> {
|
|
28
|
+
const root = process.cwd()
|
|
29
|
+
const n = Number(this.sample)
|
|
30
|
+
if (!Number.isInteger(n) || n < 1) { this.context.stdout.write(`FAIL: --sample takes a whole number above 0, got '${this.sample}'\n`); return 1 }
|
|
31
|
+
const r = await auditCache(root, await loadRepoConfig(root), { sample: n, only: this.only })
|
|
32
|
+
if (this.json) { this.context.stdout.write(`${JSON.stringify(r)}\n`); return r.stale.length > 0 ? 1 : 0 }
|
|
33
|
+
if (r.rows.length === 0) { this.context.stdout.write(`skip: nothing to audit, the cache answers none of ${this.only?.join(', ') ?? 'the checks'} right now\n`); return 0 }
|
|
34
|
+
for (const row of r.rows) {
|
|
35
|
+
const same = row.fresh === row.cached || row.fresh === 'skip'
|
|
36
|
+
this.context.stdout.write(same
|
|
37
|
+
? `ok: ${row.id} cached ${row.cached}, fresh ${row.fresh} (${row.ms}ms)\n`
|
|
38
|
+
: `FAIL: ${row.id} cached ${row.cached}, but a fresh run says ${row.fresh}: its key misses something it reads\n fix: ${row.keyedOn === 'files' ? `add the path its body reads to its cache.inputs` : 'move the read through c, or declare cache files or never'}, then sk keys --why ${row.id}\n`)
|
|
39
|
+
}
|
|
40
|
+
this.context.stdout.write(r.stale.length === 0 ? `ok: ${r.rows.length} of ${r.pool} cached check(s) audited, every cached answer is what a fresh run says\n` : `FAIL: ${r.stale.length} of ${r.rows.length} audited check(s) served a stale answer\n`)
|
|
41
|
+
return r.stale.length > 0 ? 1 : 0
|
|
42
|
+
}
|
|
43
|
+
}
|