rvlib-shipkit 0.38.0 → 1.0.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 +371 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +6 -2
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +258 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +557 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +86 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +153 -189
- package/src/core/run-fix.ts +96 -0
- package/src/core/run-fixers.ts +53 -30
- package/src/core/self-update.ts +9 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/hooks/leak-guard.ts +10 -8
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +35 -23
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +155 -85
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +3 -2
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +216 -18
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
package/src/core/run-check.ts
CHANGED
|
@@ -1,35 +1,31 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the
|
|
3
|
-
//
|
|
1
|
+
// THE HEALTH PASS — `shipkit check`: every check this repo has except the
|
|
2
|
+
// build steps, through the one runner (core/check-runner.ts). `shipkit ci` asks
|
|
3
|
+
// the same runner for the gate's set instead (core/run-ci.ts), so the two
|
|
4
|
+
// commands differ by the QUESTION and never by the engine.
|
|
4
5
|
//
|
|
5
|
-
// NOTHING here decides what may be remembered
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import { CheckSuite, type CheckOutcome } from './check-suite.ts'
|
|
15
|
-
import { gateUnits } from '../ledger/gate-units.ts'
|
|
16
|
-
import { Ledger, stampReplayed } from '../ledger/ledger.ts'
|
|
6
|
+
// NOTHING here decides what may be remembered. A check is replayed when it
|
|
7
|
+
// DECLARED how ("inputs" makes it content-keyed and the ledger owns it, "ttl"
|
|
8
|
+
// makes it a world probe and the world cache owns it), and otherwise it is
|
|
9
|
+
// measured every run. `--fresh` (SHIPKIT_FRESH=1, which travels to children)
|
|
10
|
+
// busts both.
|
|
11
|
+
import { join } from 'path'
|
|
12
|
+
import { composeChecks, envTier, gateInventory, type GateCheck, type TestTier } from '../ledger/gate-checks.ts'
|
|
13
|
+
import { Ledger } from '../ledger/ledger.ts'
|
|
14
|
+
import { RunLog, type InvokedBy } from '../ledger/run-log.ts'
|
|
17
15
|
import { storeRoot } from '../ledger/store.ts'
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { spawnWithTimeout } from './spawn-timeout.ts'
|
|
16
|
+
import { gateBinary } from '../ledger/toolchain-identity.ts'
|
|
17
|
+
import { runChecks, type Audience, type CheckEvent, type CheckResult } from './check-runner.ts'
|
|
21
18
|
import { loadRepoConfig, type RepoConfig } from '../repo-config.ts'
|
|
22
19
|
import { reportToMotherhome } from './motherhome.ts'
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { formatLine, parseWireOutput, type Line } from './wire.ts'
|
|
20
|
+
import { unknownWhenIds } from './when.ts'
|
|
21
|
+
import { formatLine, type Line } from './wire.ts'
|
|
26
22
|
import { writeStatus } from './write-status.ts'
|
|
27
|
-
import {
|
|
28
|
-
import { UNIT_TABLE_FILE, writeUnitTable } from '../ledger/unit-table.ts'
|
|
23
|
+
import { CHECK_TABLE_FILE, writeCheckTable } from '../ledger/check-table.ts'
|
|
29
24
|
import { SHARE_ENV } from '../release/releases-share.ts'
|
|
30
25
|
import { rmSync } from 'fs'
|
|
26
|
+
import { CI_LOG_DIR } from './layout.ts'
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
export type { CheckEvent } from './check-runner.ts'
|
|
33
29
|
|
|
34
30
|
export type CheckOpts = {
|
|
35
31
|
json?: boolean
|
|
@@ -40,65 +36,64 @@ export type CheckOpts = {
|
|
|
40
36
|
status?: boolean
|
|
41
37
|
/** line sink — commands pass their context stream; defaults to console */
|
|
42
38
|
print?: (line: string) => void
|
|
43
|
-
/** per-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
|
|
39
|
+
/** per-CHECK completion tap: the real status, whether it replayed, what it
|
|
40
|
+
* cost. The demotion of a non-blocking failure happens in the LINES (a
|
|
41
|
+
* FAIL is rewritten to a warn before anything sees it), so `status` here
|
|
42
|
+
* is the check's REAL outcome and `advisory` says it cannot block. */
|
|
43
|
+
onCheck?: (e: CheckEvent) => void
|
|
44
|
+
/** the run's timings log, when the caller already opened one (runCi does),
|
|
45
|
+
* and the caller then owns the flush. null = record nothing. */
|
|
46
|
+
runLog?: RunLog | null
|
|
47
|
+
/** who started this run, as the run record files it: a terminal, the
|
|
48
|
+
* hub's buttons, or the hub's daily timer */
|
|
49
|
+
invokedBy?: InvokedBy
|
|
50
|
+
/** the already-resolved config: the caller that decides severity and the
|
|
51
|
+
* engine that runs the checks must read the same object */
|
|
48
52
|
config?: RepoConfig
|
|
49
|
-
/** run only the checks that can BLOCK
|
|
50
|
-
* cannot change the outcome, so running it is pure cost: a release does
|
|
51
|
-
* not need to know whether a DNS provider is reachable, and probing one
|
|
52
|
-
* spends a third-party API call per publish. Undeclared checks are still
|
|
53
|
-
* collected, because an unclassified check stays fatal. */
|
|
53
|
+
/** run only the checks that can BLOCK (the release path) */
|
|
54
54
|
onlyBlocking?: boolean
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** the caller narrowed the run to these ids (`ci --only`). Empty or absent
|
|
61
|
-
* means everything. A `--only` that narrowed TASKS while every check still
|
|
62
|
-
* ran made the flag actively harmful: `ci --only cycles`, a 0.03s task,
|
|
63
|
-
* probed every third-party service the repo knows about. One flag, one
|
|
64
|
-
* meaning, across all three surfaces. */
|
|
55
|
+
/** which test tier the caller is asking for */
|
|
56
|
+
tier?: TestTier
|
|
57
|
+
/** which question: the gate's set (`shipkit ci`) or the health pass's */
|
|
58
|
+
audience?: Audience
|
|
59
|
+
/** the caller narrowed the run to these ids (`--only`) */
|
|
65
60
|
only?: string[]
|
|
61
|
+
/** ids ADDED to a default run (`--with`) */
|
|
62
|
+
withIds?: string[]
|
|
63
|
+
/** a label several checks share, parked at once */
|
|
64
|
+
skipGroups?: string[]
|
|
65
|
+
/** stop at the first blocking failure */
|
|
66
|
+
bail?: boolean
|
|
67
|
+
/** the BUILD steps alone (the TUI's tasks panel) */
|
|
68
|
+
buildOnly?: boolean
|
|
69
|
+
/** the gate asks the proactive checks too (`release.requireHealthy`) */
|
|
70
|
+
includeProactive?: boolean
|
|
71
|
+
/** ids this caller does not need (a sub-unit release) */
|
|
72
|
+
exclude?: string[]
|
|
73
|
+
/** the already-composed check list, when the caller built one (runCi does,
|
|
74
|
+
* to open the ledger on it). One walk per run, the same contract the
|
|
75
|
+
* ledger and the run record follow. */
|
|
76
|
+
composed?: GateCheck[]
|
|
66
77
|
/** the run's ledger, when the caller already opened one (runCi does). A
|
|
67
78
|
* standalone `shipkit check` opens its own. */
|
|
68
79
|
ledger?: Ledger | null
|
|
80
|
+
/** the gate binary that own ledger is keyed on (default: the one running
|
|
81
|
+
* it). Ignored when `ledger` is passed, which carries its own. */
|
|
82
|
+
cliEntry?: string
|
|
69
83
|
/** refresh the motherhome registry row. runCi passes false and does it
|
|
70
84
|
* ITSELF, after the ledger has committed: that row is a TRACKED file under
|
|
71
85
|
* `wiki/`, so writing it before the post-run re-hash makes the gate look
|
|
72
86
|
* like an agent editing the tree and throws away every result the run just
|
|
73
|
-
* measured.
|
|
87
|
+
* measured. */
|
|
74
88
|
reportHome?: boolean
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
/** does this rule or check answer to one of the `--only` ids? Matched against
|
|
78
|
-
* every name the surface is known by (a rule's id AND its title, a discovered
|
|
79
|
-
* check's name AND its path), because the id an operator types is whichever
|
|
80
|
-
* one they last saw on the wire. */
|
|
81
|
-
export function matchesOnly(only: string[] | undefined, ...names: string[]): boolean {
|
|
82
|
-
if (!isNarrowed(only)) return true
|
|
83
|
-
return only.some((want) => names.some((n) => n.toLowerCase() === want.toLowerCase()))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
91
|
/** did the operator narrow this run? The ONE reading of `--only` everywhere: an
|
|
87
92
|
* empty list is not a selection, it is the absence of one. */
|
|
88
93
|
export function isNarrowed(only: string[] | undefined): only is string[] {
|
|
89
94
|
return only != null && only.length > 0
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
/** every id `--only` can name that is NOT a task: rule ids, rule titles,
|
|
93
|
-
* discovered check names and their repo-relative paths. `--only` addresses all
|
|
94
|
-
* three surfaces, so the task runner has to be told which ids belong to the
|
|
95
|
-
* other two, or it answers `unknown task id` for a check that does run. */
|
|
96
|
-
export function checkSideIds(root: string, c: RepoConfig): string[] {
|
|
97
|
-
const ids = [...RULES, ...(c.rules ?? [])].flatMap((r) => [r.id, r.title])
|
|
98
|
-
for (const d of discoverChecks(root)) ids.push(d.name, relative(root, d.path))
|
|
99
|
-
return ids
|
|
100
|
-
}
|
|
101
|
-
|
|
102
97
|
export type CheckRun = {
|
|
103
98
|
/** wire lines, with non-blocking failures already downgraded to warnings */
|
|
104
99
|
lines: Line[]
|
|
@@ -108,201 +103,148 @@ export type CheckRun = {
|
|
|
108
103
|
advisoryFailures: string[]
|
|
109
104
|
/** checks that never declared `blocking` — always fatal */
|
|
110
105
|
undeclared: string[]
|
|
106
|
+
/** `when` entries naming no check of this repo — a toggle that does
|
|
107
|
+
* nothing is worse than none, so it is fatal too */
|
|
108
|
+
unknownWhen: string[]
|
|
109
|
+
/** what every asked-for check did, in declaration order */
|
|
110
|
+
results: CheckResult[]
|
|
111
|
+
/** the run was interrupted or bailed out */
|
|
112
|
+
interrupted: boolean
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
/** run
|
|
114
|
-
*
|
|
115
|
-
|
|
115
|
+
/** THE run body: compose this repo's checks, run the set the audience asks
|
|
116
|
+
* for, and classify the outcome by what each check declared. `shipkit check`
|
|
117
|
+
* and `shipkit ci` both come through here, and nothing else decides
|
|
118
|
+
* severity. */
|
|
119
|
+
export async function runCheckPass(root: string, o: CheckOpts = {}): Promise<CheckRun> {
|
|
116
120
|
const c = o.config ?? (await loadRepoConfig(root))
|
|
121
|
+
const print = o.print ?? ((l: string) => console.log(l))
|
|
117
122
|
// env carries the flags to child checks — restored after the run so a
|
|
118
|
-
// long-lived caller (
|
|
123
|
+
// long-lived caller (the TUI, the hub) is never poisoned by one
|
|
119
124
|
// fresh/frozen invocation
|
|
120
125
|
const prevFresh = process.env.SHIPKIT_FRESH
|
|
121
126
|
const prevFrozen = process.env.SHIPKIT_FROZEN
|
|
122
127
|
if (o.fresh === true) process.env.SHIPKIT_FRESH = '1'
|
|
123
128
|
if (o.frozen === true) process.env.SHIPKIT_FROZEN = '1'
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
const tier: TestTier = o.tier ?? envTier()
|
|
130
|
+
const audience: Audience = o.audience ?? 'health'
|
|
131
|
+
const composed = o.composed ?? (await composeChecks(root, c, { tier }))
|
|
132
|
+
const checks = composed.map((g) => g.runnable)
|
|
133
|
+
// the caller's ledger, or this run's own. `shipkit check` is a full run of
|
|
134
|
+
// every check it owns, so it deserves the same replay the gate gets.
|
|
126
135
|
let ledger: Ledger | null = o.ledger ?? null
|
|
127
136
|
if (ledger === null && o.ledger === undefined) {
|
|
128
137
|
try {
|
|
129
|
-
ledger = Ledger.open(root,
|
|
138
|
+
ledger = Ledger.open(root, composed.map((g) => g.row.decl), { cliEntry: o.cliEntry ?? gateBinary(), fresh: o.fresh })
|
|
130
139
|
} catch { ledger = null }
|
|
131
140
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
try { code = ledger?.layer.keyFor(unitId).codeKey ?? '' } catch { code = '' }
|
|
141
|
-
return `tc=${ledger?.layer.toolchainId ?? ''}\0code=${code}\0${rotation ?? ''}`
|
|
142
|
-
}
|
|
143
|
-
const suite = new CheckSuite({
|
|
144
|
-
name: c.name,
|
|
145
|
-
storeDir: storeRoot(root),
|
|
146
|
-
onResult: (name, r) => {
|
|
147
|
-
o.onResult?.(name, r)
|
|
148
|
-
// a replay measured nothing, and a red is never stored. A check
|
|
149
|
-
// that cannot block had its FAIL rewritten to a warn before this
|
|
150
|
-
// fires, so the demotion list is what says it failed: a content
|
|
151
|
-
// key has no TTL, so storing that red would pin it until an
|
|
152
|
-
// unrelated edit moved the tree, and "the box is off" is fixed by
|
|
153
|
-
// turning the box on, which changes no file.
|
|
154
|
-
if (ledger == null || r.cached || replayed.has(name) || r.status === 'skip' || r.status === 'fail') return
|
|
155
|
-
if (advisoryFailures.includes(name)) return
|
|
156
|
-
const id = unitIdByName.get(name)
|
|
157
|
-
if (id == null) return
|
|
158
|
-
ledger.record(id, { status: r.status, lines: r.lines, measuredMs: r.ms })
|
|
141
|
+
// the caller's log, or this run's own: `shipkit check` measures checks too,
|
|
142
|
+
// and its timings are as honest as the gate's
|
|
143
|
+
const log: RunLog | null = o.runLog !== undefined ? o.runLog : RunLog.open(root, {
|
|
144
|
+
invocation: {
|
|
145
|
+
kind: audience === 'gate' ? 'ci' : 'check',
|
|
146
|
+
label: isNarrowed(o.only) ? `shipkit check --only ${o.only.join(',')}` : 'shipkit check',
|
|
147
|
+
by: o.invokedBy ?? 'cli',
|
|
148
|
+
narrowed: isNarrowed(o.only),
|
|
159
149
|
},
|
|
160
150
|
})
|
|
161
|
-
|
|
162
|
-
* the suite rather than splicing lines afterwards is what preserves
|
|
163
|
-
* declaration order, which the wire contract is frozen on. */
|
|
164
|
-
const addReplay = (unitId: string, name: string, blocking: boolean): boolean => {
|
|
165
|
-
const hit = ledger?.replay(unitId)
|
|
166
|
-
if (hit == null) return false
|
|
167
|
-
replayed.add(name)
|
|
168
|
-
unitIdByName.set(name, unitId)
|
|
169
|
-
suite.addCheck({
|
|
170
|
-
blocking,
|
|
171
|
-
name,
|
|
172
|
-
logic: (t) => {
|
|
173
|
-
for (const l of stampReplayed(hit.lines, hit.measuredMs)) t.lines.push(l)
|
|
174
|
-
if (t.lines.some((l) => l.kind === 'warn' || l.kind === 'fix-warn')) t.warned = true
|
|
175
|
-
},
|
|
176
|
-
})
|
|
177
|
-
return true
|
|
178
|
-
}
|
|
179
|
-
// built-in SK rules (rules/rule-registry.ts) THEN the repo's own
|
|
180
|
-
// (repo.config rules: []), id-tagged, crash-safe, fix-pointer floor
|
|
181
|
-
// (rules/rule.ts runRuleLint owns the semantics). `shipkit ci` runs this
|
|
182
|
-
// same engine, so a blocking rule blocks a commit as well as a doctor run
|
|
183
|
-
const undeclared: string[] = []
|
|
184
|
-
const advisoryFailures: string[] = []
|
|
185
|
-
// a non-blocking failure becomes a WARNING here, at the source. printing
|
|
186
|
-
// FAIL and exiting 0 would be the tool lying about its own severity, and
|
|
187
|
-
// demoting in the check means the suite's exit code already means exactly
|
|
188
|
-
// "something that blocks you failed".
|
|
189
|
-
const demote = (t: CheckCtx, blocking: boolean, name: string): void => {
|
|
190
|
-
if (blocking || !t.failed) return
|
|
191
|
-
if (!advisoryFailures.includes(name)) advisoryFailures.push(name)
|
|
192
|
-
t.lines = t.lines.map((l) => (l.kind === 'fail' ? { ...l, kind: 'warn' as const } : l.kind === 'fix-fail' ? { ...l, kind: 'fix-warn' as const } : l))
|
|
193
|
-
t.failed = false
|
|
194
|
-
t.warned = true
|
|
195
|
-
}
|
|
196
|
-
for (const rule of [...RULES, ...(c.rules ?? [])]) {
|
|
197
|
-
if (!matchesOnly(o.only, rule.id, rule.title)) continue
|
|
198
|
-
if (o.onlyBlocking === true && !rule.blocking) continue
|
|
199
|
-
unitIdByName.set(rule.title, rule.id)
|
|
200
|
-
if (addReplay(rule.id, rule.title, rule.blocking)) continue
|
|
201
|
-
suite.addCheck({
|
|
202
|
-
blocking: rule.blocking,
|
|
203
|
-
name: rule.title,
|
|
204
|
-
remainValidFor: rule.ttl,
|
|
205
|
-
cacheExtra: () => worldKey(rule.id, ruleCacheExtra(rule, { root, config: c })),
|
|
206
|
-
logic: async (t) => { await runRuleLint(rule, t, { root, config: c }); demote(t, rule.blocking, rule.title) },
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
// ONE release survey for the whole run: several discovered checks call
|
|
151
|
+
// ONE release survey for the whole run: several probes call
|
|
210
152
|
// collectReports, each in its own subprocess, and the survey is ~3s of
|
|
211
153
|
// network. The path is per RUN and never reused across runs, so nothing
|
|
212
154
|
// here can serve a stale answer to a later gate (releases-share.ts).
|
|
213
|
-
// This is the ONLY owner: nothing else in shipkit sets the variable, and an
|
|
214
|
-
// inherited one can only come from a caller outside the binary, which then
|
|
215
|
-
// owns its own cleanup.
|
|
216
155
|
const inherited = process.env[SHARE_ENV]
|
|
217
|
-
const sharePath = inherited != null && inherited !== '' ? inherited : join(root,
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (t.lines.some((l) => l.kind === 'warn')) t.warned = true
|
|
255
|
-
if (t.lines.some((l) => l.kind === 'fail')) t.failed = true
|
|
256
|
-
// exit code is truth — a non-zero exit with no FAIL line
|
|
257
|
-
// (crash, non-wire output, timeout) must still fail loud
|
|
258
|
-
if (r.code !== 0 && !t.failed)
|
|
259
|
-
t.fail(`${d.name} exited ${r.code}${r.timedOut ? ` (timeout after ${CHECK_TIMEOUT_MS}ms)` : ''} — ${r.out.trim().split('\n').at(-1)?.slice(0, 160) ?? ''}`, `bun ${rel}`)
|
|
260
|
-
demote(t, blocking ?? true, d.name)
|
|
261
|
-
},
|
|
262
|
-
})
|
|
156
|
+
const sharePath = inherited != null && inherited !== '' ? inherited : join(root, CI_LOG_DIR, `releases-${process.pid}-${Date.now()}.json`)
|
|
157
|
+
const collected: Line[] = []
|
|
158
|
+
const sink = (l: string): void => print(l)
|
|
159
|
+
const summary = await runChecks({
|
|
160
|
+
checks,
|
|
161
|
+
root,
|
|
162
|
+
config: c,
|
|
163
|
+
audience,
|
|
164
|
+
fullTier: tier === 'full',
|
|
165
|
+
logDir: c.logDir ?? join(root, CI_LOG_DIR),
|
|
166
|
+
cache: { storeDir: storeRoot(root), ns: c.name, now: Date.now },
|
|
167
|
+
ledger,
|
|
168
|
+
log,
|
|
169
|
+
childEnv: { [SHARE_ENV]: sharePath, ...(o.fresh === true ? { SHIPKIT_FRESH: '1' } : {}) },
|
|
170
|
+
bail: o.bail,
|
|
171
|
+
skipGroups: o.skipGroups,
|
|
172
|
+
onlyIds: o.only,
|
|
173
|
+
withIds: o.withIds,
|
|
174
|
+
onlyBlocking: o.onlyBlocking,
|
|
175
|
+
buildOnly: o.buildOnly,
|
|
176
|
+
includeProactive: o.includeProactive,
|
|
177
|
+
exclude: o.exclude,
|
|
178
|
+
print: sink,
|
|
179
|
+
onCheck: (e) => { collected.push(...e.lines); o.onCheck?.(e) },
|
|
180
|
+
})
|
|
181
|
+
const lines: Line[] = []
|
|
182
|
+
const say = (l: Line): void => { lines.push(l); print(formatLine(l, false)) }
|
|
183
|
+
// a declaration in a shape the check file replaced is reported by check
|
|
184
|
+
// SK016, which also carries the fixer that rewrites it: one owner, one
|
|
185
|
+
// line, one command, rather than a warning per surface.
|
|
186
|
+
//
|
|
187
|
+
// checks this run did not ask about: named here rather than printed one
|
|
188
|
+
// line each, because twenty four `skip:` lines is the wall of text the
|
|
189
|
+
// page just replaced
|
|
190
|
+
if (summary.parked.length > 0) {
|
|
191
|
+
const head = summary.parked.slice(0, 8).join(', ')
|
|
192
|
+
say({ kind: 'skip', text: `${summary.parked.length} check(s) not in this run (health only, release only at the quick tier, or a skipped group): ${head}${summary.parked.length > 8 ? `, and ${summary.parked.length - 8} more` : ''}` })
|
|
263
193
|
}
|
|
264
|
-
const r = await suite.execute()
|
|
265
194
|
// the survey dies with the run that made it, and only the run that made it
|
|
266
195
|
if (inherited == null || inherited === '') for (const f of [sharePath, `${sharePath}.lock`]) { try { rmSync(f, { force: true }) } catch {} }
|
|
267
196
|
if (prevFresh == null) delete process.env.SHIPKIT_FRESH; else process.env.SHIPKIT_FRESH = prevFresh
|
|
268
197
|
if (prevFrozen == null) delete process.env.SHIPKIT_FROZEN; else process.env.SHIPKIT_FROZEN = prevFrozen
|
|
269
198
|
// frozen = a release-shaped run: reporting would re-dirty the tree the
|
|
270
|
-
// caller is about to demand clean
|
|
199
|
+
// caller is about to demand clean
|
|
271
200
|
if (o.frozen !== true && o.reportHome !== false) reportToMotherhome(root, c)
|
|
272
201
|
// the caller that OPENED the ledger owns committing it: runCi commits once
|
|
273
|
-
// after
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
202
|
+
// after everything, so one post-run re-hash covers the whole gate
|
|
203
|
+
if (o.ledger === undefined && ledger != null) for (const l of ledger.header(ledger.commit())) say(l)
|
|
204
|
+
// the caller that OPENED the record owns writing it, the same rule
|
|
205
|
+
if (o.runLog === undefined && log != null) for (const l of log.flush()) say(l)
|
|
206
|
+
// a `when` id naming nothing is a switch wired to no check: the id is read
|
|
207
|
+
// against the WHOLE inventory this run composed, never its narrowed list
|
|
208
|
+
const unknownWhen = unknownWhenIds(composed.map((g) => g.row), c)
|
|
209
|
+
return {
|
|
210
|
+
lines: [...collected, ...lines],
|
|
211
|
+
blocked: !summary.ok,
|
|
212
|
+
advisoryFailures: summary.advisoryFailures,
|
|
213
|
+
undeclared: summary.undeclared,
|
|
214
|
+
unknownWhen,
|
|
215
|
+
results: summary.results,
|
|
216
|
+
interrupted: summary.interrupted,
|
|
217
|
+
}
|
|
277
218
|
}
|
|
278
219
|
|
|
279
|
-
/** `shipkit check` as a standalone command: the same
|
|
220
|
+
/** `shipkit check` as a standalone command: the same runner, printed. */
|
|
280
221
|
export async function runCheck(root: string, o: CheckOpts = {}): Promise<number> {
|
|
281
222
|
const print = o.print ?? ((l: string) => console.log(l))
|
|
282
223
|
const c = o.config ?? (await loadRepoConfig(root))
|
|
283
|
-
const run = await
|
|
284
|
-
const code = run.blocked || run.undeclared.length > 0 ? 1 : 0
|
|
224
|
+
const run = await runCheckPass(root, { ...o, config: c })
|
|
225
|
+
const code = run.blocked || run.undeclared.length > 0 || run.unknownWhen.length > 0 ? 1 : 0
|
|
285
226
|
if (o.json === true) { print(JSON.stringify(run)); return code }
|
|
286
|
-
const color = process.stdout.isTTY && process.env.NO_COLOR == null
|
|
287
|
-
for (const l of run.lines) print(formatLine(l, color))
|
|
288
227
|
for (const u of run.undeclared) {
|
|
289
228
|
print(`FAIL: ${u} does not declare whether it blocks`)
|
|
290
|
-
print(` fix: add \`
|
|
229
|
+
print(` fix: add \`blocking: true\` (about this repo) or \`false\` (about the world) to that check`)
|
|
230
|
+
}
|
|
231
|
+
for (const id of run.unknownWhen) {
|
|
232
|
+
print(`FAIL: when.${id} names no check of this repo`)
|
|
233
|
+
print(` fix: \`shipkit checks\` lists every id; drop the entry or correct it`)
|
|
291
234
|
}
|
|
292
235
|
if (o.status !== false) {
|
|
293
|
-
// the producing command is named honestly: a
|
|
236
|
+
// the producing command is named honestly: a health run must not
|
|
294
237
|
// overwrite the gate verdict with a green nobody gated
|
|
295
238
|
const st = writeStatus(root, {
|
|
296
239
|
name: c.name,
|
|
297
240
|
command: 'shipkit check',
|
|
298
241
|
lines: run.lines.map((l) => formatLine(l, false)),
|
|
299
242
|
exitCode: code,
|
|
300
|
-
sidecar: { file:
|
|
243
|
+
sidecar: { file: CHECK_TABLE_FILE, says: 'Every check this repo can run, and whether it blocks you' },
|
|
301
244
|
})
|
|
302
245
|
if (!st.written) print(`WARN ${st.warn}`)
|
|
303
|
-
const
|
|
304
|
-
if (!
|
|
305
|
-
|
|
246
|
+
const checks = writeCheckTable(root, { name: c.name, rows: await gateInventory(root, c) })
|
|
247
|
+
if (!checks.written) print(`WARN ${checks.warn}`)
|
|
306
248
|
}
|
|
307
249
|
return code
|
|
308
250
|
}
|