rvlib-shipkit 0.38.1 → 1.0.1
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 +379 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/derived.ts +1 -1
- 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} +49 -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 +16 -3
- 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 +257 -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 +616 -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 +88 -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 +152 -189
- package/src/core/run-fix.ts +99 -0
- package/src/core/run-fixers.ts +64 -33
- package/src/core/self-update.ts +11 -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/fleet/scan-fleet.ts +16 -0
- package/src/hooks/leak-guard.ts +20 -9
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +38 -26
- 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 +157 -88
- 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 +9 -4
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +209 -15
- 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/scan-md.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
|
@@ -12,13 +12,11 @@
|
|
|
12
12
|
// Both are generated from ONE table (core/one-way-in-policy.ts). The file is
|
|
13
13
|
// TRACKED on purpose: `.claude/settings.local.json` is gitignored by the house
|
|
14
14
|
// scaffold, so a policy written there would bind exactly one checkout.
|
|
15
|
-
import {
|
|
16
|
-
import { dirname, isAbsolute, join } from 'path'
|
|
15
|
+
import { isAbsolute, join } from 'path'
|
|
17
16
|
import { inferBin } from '../core/canonical-scripts.ts'
|
|
18
17
|
import { denyRules, RAW_RUNNERS } from '../core/one-way-in-policy.ts'
|
|
19
18
|
import type { RepoConfig } from '../repo-config.ts'
|
|
20
|
-
import
|
|
21
|
-
import { atomicWrite } from '../core/locked-write.ts'
|
|
19
|
+
import { check, type CheckCtx, type FixResult } from '../check.ts'
|
|
22
20
|
|
|
23
21
|
const OWNER = 'build/shipkit.md'
|
|
24
22
|
export const SETTINGS_PATH = '.claude/settings.json'
|
|
@@ -46,12 +44,23 @@ export function guardCommand(bin?: string): string {
|
|
|
46
44
|
return isAbsolute(path) ? `bun "${path}"` : `bun "$CLAUDE_PROJECT_DIR/${path}"`
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
/** the helpers below see the repo through the check context. A guard at an
|
|
48
|
+
* absolute path lives outside the repo, so probing it goes through `c.exec`,
|
|
49
|
+
* which keeps that answer out of the cache */
|
|
50
|
+
type Disk = Pick<CheckCtx, 'read' | 'exists' | 'exec'>
|
|
51
|
+
|
|
52
|
+
const existsOutside = async (d: Disk, abs: string): Promise<boolean> => (await d.exec(['test', '-e', abs])).code === 0
|
|
53
|
+
|
|
54
|
+
const parsed = <T>(text: string | null): T | null => {
|
|
55
|
+
if (text == null) return null
|
|
56
|
+
try { return JSON.parse(text) as T } catch { return null }
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
type Pkg = { scripts?: Record<string, string>; dependencies?: Record<string, string>; devDependencies?: Record<string, string> }
|
|
60
|
+
const readPkg = (d: Disk): Pkg | null => parsed<Pkg>(d.read('package.json'))
|
|
61
|
+
|
|
62
|
+
const declaresShipkit = (d: Disk): boolean => {
|
|
63
|
+
const pkg = readPkg(d)
|
|
55
64
|
return pkg?.dependencies?.['rvlib-shipkit'] != null || pkg?.devDependencies?.['rvlib-shipkit'] != null
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -59,13 +68,9 @@ const declaresShipkit = (root: string): boolean => {
|
|
|
59
68
|
* declares rvlib-shipkit, says the dependency is not installed, which
|
|
60
69
|
* `bun install` fixes and no edit to this repo can. Without the declaration
|
|
61
70
|
* no install ever brings it, so it is a path this repo got wrong like any other. */
|
|
62
|
-
const isUninstalledDep = (
|
|
71
|
+
const isUninstalledDep = (d: Disk, rel: string): boolean => rel.startsWith('node_modules/') && declaresShipkit(d)
|
|
63
72
|
|
|
64
|
-
const readSettings = (
|
|
65
|
-
const p = join(root, SETTINGS_PATH)
|
|
66
|
-
if (!existsSync(p)) return null
|
|
67
|
-
try { return JSON.parse(readFileSync(p, 'utf8')) as Settings } catch { return null }
|
|
68
|
-
}
|
|
73
|
+
const readSettings = (d: Disk): Settings | null => parsed<Settings>(d.read(SETTINGS_PATH))
|
|
69
74
|
|
|
70
75
|
const guardHookCommand = (s: Settings): string | null => {
|
|
71
76
|
for (const m of s.hooks?.['PreToolUse'] ?? [])
|
|
@@ -82,11 +87,11 @@ const hasGuardHook = (s: Settings): boolean => guardHookCommand(s) != null
|
|
|
82
87
|
* nothing, so the agent sees only the bare deny, which is the one failure
|
|
83
88
|
* this rule exists to prevent. Declaring it and never running it is worse
|
|
84
89
|
* than not declaring it, so it FAILs. */
|
|
85
|
-
function guardScriptMissing(
|
|
90
|
+
async function guardScriptMissing(d: Disk, command: string): Promise<string | null> {
|
|
86
91
|
const rel = /["']?\$(?:\{)?CLAUDE_PROJECT_DIR(?:\})?\/([^"'\s]+)["']?/.exec(command)?.[1]
|
|
87
|
-
if (rel != null) return
|
|
92
|
+
if (rel != null) return d.exists(rel) ? null : rel
|
|
88
93
|
const abs = /^bun\s+["']?(\/[^"'\s]+)["']?/.exec(command.trim())?.[1]
|
|
89
|
-
if (abs != null) return
|
|
94
|
+
if (abs != null) return (await existsOutside(d, abs)) ? null : abs
|
|
90
95
|
return null // a form this rule cannot resolve is not a form it may condemn
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -94,42 +99,44 @@ function guardScriptMissing(root: string, command: string): string | null {
|
|
|
94
99
|
* beside it (a repo's own `src/cli.ts` matches the same shape), else the
|
|
95
100
|
* repo's own dependency, else the copy of shipkit doing the fixing, which is
|
|
96
101
|
* the one the launcher falls back to in a repo that has none */
|
|
97
|
-
export function guardCommandFor(
|
|
98
|
-
const bin = inferBin(readPkg(
|
|
102
|
+
export async function guardCommandFor(d: Disk): Promise<string> {
|
|
103
|
+
const bin = inferBin(readPkg(d)?.scripts ?? {})
|
|
99
104
|
if (bin != null) {
|
|
100
105
|
const fromBin = guardCommand(bin)
|
|
101
|
-
if (guardScriptMissing(
|
|
106
|
+
if ((await guardScriptMissing(d, fromBin)) == null) return fromBin
|
|
102
107
|
}
|
|
103
|
-
if (declaresShipkit(
|
|
108
|
+
if (declaresShipkit(d)) return guardCommand()
|
|
104
109
|
return guardCommand(join(import.meta.dir, '..', 'cli.ts'))
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
export const oneWayInPolicy
|
|
112
|
+
export const oneWayInPolicy = check({
|
|
108
113
|
id: 'SK013',
|
|
109
114
|
blocking: true,
|
|
110
115
|
title: 'one way in policy',
|
|
116
|
+
description: 'the agent settings deny every raw runner this repo governs and name the gate',
|
|
111
117
|
owner: OWNER,
|
|
112
|
-
// .claude/settings.json, package.json, and whether
|
|
113
|
-
// script resolves
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
// keyed on what it reads: .claude/settings.json, package.json, and whether
|
|
119
|
+
// the declared guard script resolves. A guard at an absolute path lives
|
|
120
|
+
// outside the repo, so an answer that probed one is never stored
|
|
121
|
+
run: async (c) => {
|
|
122
|
+
const d = c
|
|
123
|
+
const want = denyRules(exemptSlots(c.config))
|
|
124
|
+
if (want.length === 0) return c.ok('every governed slot is exempt (repo.config oneWayIn/defaults) — nothing to deny')
|
|
125
|
+
const s = readSettings(d)
|
|
119
126
|
if (s == null) {
|
|
120
|
-
|
|
127
|
+
c.fail(`${SETTINGS_PATH} ${d.exists(SETTINGS_PATH) ? 'is unparseable' : 'missing'} — nothing stops an agent typing \`bun test\`, and a package.json script cannot intercept a bun builtin`)
|
|
121
128
|
return
|
|
122
129
|
}
|
|
123
130
|
const have = new Set(s.permissions?.deny ?? [])
|
|
124
131
|
const missing = want.filter((r) => !have.has(r))
|
|
125
132
|
if (missing.length > 0)
|
|
126
|
-
|
|
133
|
+
c.fail(`${SETTINGS_PATH}: ${missing.length} raw runner(s) still permitted (${missing.slice(0, 3).join(', ')}${missing.length > 3 ? ', …' : ''}) — those calls never reach shipkit, so nothing is cached and no run can be attached to`)
|
|
127
134
|
const guard = guardHookCommand(s)
|
|
128
135
|
let guardRuns = false
|
|
129
136
|
if (guard == null)
|
|
130
|
-
|
|
137
|
+
c.fail(`${SETTINGS_PATH}: no PreToolUse guard — a bare deny tells the model only that it was denied, so it goes looking for another runner instead of the shipkit command`)
|
|
131
138
|
else {
|
|
132
|
-
const missing = guardScriptMissing(
|
|
139
|
+
const missing = await guardScriptMissing(d, guard)
|
|
133
140
|
guardRuns = missing == null
|
|
134
141
|
// the hook is DECLARED either way, so it starts working the moment
|
|
135
142
|
// the dependency lands: no second fix run, nothing to remember. An
|
|
@@ -137,13 +144,13 @@ export const oneWayInPolicy: Rule = {
|
|
|
137
144
|
// that fixes it, not a red gate on a repo nobody has installed yet,
|
|
138
145
|
// and the deny list is the floor throughout. A path this repo chose
|
|
139
146
|
// and got wrong is still a FAIL: it will never resolve on its own.
|
|
140
|
-
if (missing != null && isUninstalledDep(
|
|
141
|
-
|
|
147
|
+
if (missing != null && isUninstalledDep(d, missing))
|
|
148
|
+
c.warn(
|
|
142
149
|
`${SETTINGS_PATH}: the PreToolUse guard points at ${missing}, which is not installed yet — until then a refusal names no replacement, and the deny list is the whole floor`,
|
|
143
150
|
`bun install (or bun add -d rvlib-shipkit)`,
|
|
144
151
|
)
|
|
145
152
|
else if (missing != null)
|
|
146
|
-
|
|
153
|
+
c.fail(
|
|
147
154
|
`${SETTINGS_PATH}: the PreToolUse guard points at ${missing}, which does not exist — every Bash call errors and no refusal ever names the shipkit command`,
|
|
148
155
|
`point it at this repo's own copy of hooks/one-way-in-guard.ts, or delete the hook and let the deny list stand alone`,
|
|
149
156
|
)
|
|
@@ -151,18 +158,18 @@ export const oneWayInPolicy: Rule = {
|
|
|
151
158
|
// the summary states only what the file actually delivers: claiming the
|
|
152
159
|
// replacement is named while the guard cannot run is the green that
|
|
153
160
|
// measured nothing, one line under the WARN that says otherwise
|
|
154
|
-
if (!
|
|
155
|
-
|
|
161
|
+
if (!c.failed)
|
|
162
|
+
c.ok(`${SETTINGS_PATH} closes ${want.length}/${RAW_RUNNERS.length} raw runner(s)${guardRuns ? ', with the replacement named' : ' (the guard that names the replacement is declared, not yet runnable)'}`)
|
|
156
163
|
},
|
|
157
164
|
// MERGES, never rewrites: this file also carries the attribution silencer,
|
|
158
165
|
// a repo's own allow list and its own hooks, and clobbering those to fix a
|
|
159
166
|
// deny list would cost more than the finding it repairs
|
|
160
|
-
fix: (
|
|
161
|
-
const
|
|
167
|
+
fix: async (c): Promise<FixResult> => {
|
|
168
|
+
const d = c
|
|
169
|
+
const want = denyRules(exemptSlots(c.config))
|
|
162
170
|
if (want.length === 0) return { changed: false, summary: 'every governed slot is exempt — nothing to write' }
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
if (existing == null && existsSync(p)) return { changed: false, summary: `${SETTINGS_PATH} is unparseable — repair it by hand first, a merge would destroy it` }
|
|
171
|
+
const existing = readSettings(d)
|
|
172
|
+
if (existing == null && c.exists(SETTINGS_PATH)) return { changed: false, summary: `${SETTINGS_PATH} is unparseable — repair it by hand first, a merge would destroy it` }
|
|
166
173
|
const s: Settings = existing ?? {}
|
|
167
174
|
const changes: string[] = []
|
|
168
175
|
const deny = s.permissions?.deny ?? []
|
|
@@ -171,7 +178,7 @@ export const oneWayInPolicy: Rule = {
|
|
|
171
178
|
s.permissions = { ...s.permissions, deny: [...deny, ...added] }
|
|
172
179
|
changes.push(`${added.length} raw runner(s) denied`)
|
|
173
180
|
}
|
|
174
|
-
const wantGuard = guardCommandFor(
|
|
181
|
+
const wantGuard = await guardCommandFor(d)
|
|
175
182
|
if (!hasGuardHook(s)) {
|
|
176
183
|
const pre = s.hooks?.['PreToolUse'] ?? []
|
|
177
184
|
s.hooks = { ...s.hooks, PreToolUse: [...pre, { matcher: 'Bash', hooks: [{ type: 'command', command: wantGuard }] }] }
|
|
@@ -183,16 +190,17 @@ export const oneWayInPolicy: Rule = {
|
|
|
183
190
|
for (const m of s.hooks?.['PreToolUse'] ?? [])
|
|
184
191
|
for (const h of m.hooks ?? []) {
|
|
185
192
|
if (h.command?.includes('one-way-in-guard') !== true) continue
|
|
186
|
-
const missing = guardScriptMissing(
|
|
187
|
-
if (missing == null || isUninstalledDep(
|
|
188
|
-
if (guardScriptMissing(
|
|
193
|
+
const missing = await guardScriptMissing(d, h.command)
|
|
194
|
+
if (missing == null || isUninstalledDep(d, missing) || h.command === wantGuard) continue
|
|
195
|
+
if ((await guardScriptMissing(d, wantGuard)) != null) continue
|
|
189
196
|
changes.push(`PreToolUse guard repointed from ${missing}, which does not exist`)
|
|
190
197
|
h.command = wantGuard
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
if (changes.length === 0) return { changed: false, summary: `${SETTINGS_PATH} already closes every raw runner` }
|
|
194
|
-
|
|
195
|
-
atomicWrite(p, JSON.stringify(s, null, 2) + '\n')
|
|
201
|
+
c.write(SETTINGS_PATH, JSON.stringify(s, null, 2) + '\n')
|
|
196
202
|
return { changed: true, summary: `${SETTINGS_PATH}: ${changes.join(', ')}` }
|
|
197
203
|
},
|
|
198
|
-
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
export default oneWayInPolicy
|
|
@@ -17,34 +17,48 @@
|
|
|
17
17
|
// sentence is content. The rewrite lives behind an explicit `shipkit prose
|
|
18
18
|
// --fix` that a human ran and can read the diff of.
|
|
19
19
|
import { classSummary, proseLines, runProseCheck } from '../prose/run-prose.ts'
|
|
20
|
-
import
|
|
20
|
+
import { check } from '../check.ts'
|
|
21
21
|
|
|
22
22
|
const OWNER = 'identity/writing-voice.md'
|
|
23
23
|
|
|
24
|
-
export const proseSte
|
|
24
|
+
export const proseSte = check({
|
|
25
25
|
id: 'SK014',
|
|
26
26
|
blocking: true,
|
|
27
27
|
title: 'STE prose',
|
|
28
|
+
description: 'house prose passes the mechanical STE rules',
|
|
28
29
|
owner: OWNER,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// keyed on what it reads: each include glob's listing and every file in
|
|
31
|
+
// scope, so an edit outside the declared prose never reruns it
|
|
32
|
+
run: (c) => {
|
|
33
|
+
const cfg = c.config.prose
|
|
32
34
|
if (cfg == null || (cfg.include ?? []).length === 0) {
|
|
33
|
-
|
|
35
|
+
c.ok(`no prose scope declared — add \`prose: { include: [...] }\` to repo.config.ts to gate house prose on STE`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const r = runProseCheck({ c, config: cfg })
|
|
39
|
+
// a scope that resolves to zero files reads exactly like a clean repo,
|
|
40
|
+
// and it is the opposite: the repo asked for the gate and the gate
|
|
41
|
+
// inspects nothing. A green here is coverage nobody has.
|
|
42
|
+
if (r.files === 0) {
|
|
43
|
+
c.warn(
|
|
44
|
+
`prose scope matches no file: ${(cfg.include ?? []).join(', ')} — declared and inspecting nothing`,
|
|
45
|
+
'fix the globs in repo.config.ts prose.include (or its exempt list), or drop the key to opt out',
|
|
46
|
+
)
|
|
34
47
|
return
|
|
35
48
|
}
|
|
36
|
-
const r = runProseCheck({ root: env.root, config: cfg })
|
|
37
49
|
const blocking = r.byTier.fix + r.byTier.hard
|
|
38
|
-
for (const l of proseLines(r, { cap: 12, tiers: ['fix', 'hard'] }))
|
|
50
|
+
for (const l of proseLines(r, { cap: 12, tiers: ['fix', 'hard'] })) c.note(l)
|
|
39
51
|
if (blocking > 0)
|
|
40
|
-
|
|
52
|
+
c.fail(
|
|
41
53
|
`${blocking} STE violation(s) across ${r.byFile.length}/${r.files} file(s) — ${classSummary(r)}`,
|
|
42
54
|
r.fixable > 0 ? `shipkit prose --fix rewrites ${r.fixable} of them` : undefined,
|
|
43
55
|
)
|
|
44
56
|
if (cfg.baseline != null && r.byTier.soft > cfg.baseline)
|
|
45
|
-
|
|
57
|
+
c.fail(`${r.byTier.soft} soft hits (sentence length, passive voice), above the declared baseline of ${cfg.baseline} — the ratchet only goes down`)
|
|
46
58
|
else if (r.byTier.soft > 0)
|
|
47
|
-
|
|
48
|
-
if (!
|
|
59
|
+
c.warn(`${r.byTier.soft} soft hit(s): sentence length and passive voice need a human read${cfg.baseline != null ? ` (baseline ${cfg.baseline})` : ''}`)
|
|
60
|
+
if (!c.failed && blocking === 0) c.ok(`${r.files} file(s) STE-clean on the mechanical rules`)
|
|
49
61
|
}
|
|
50
|
-
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export default proseSte
|
|
@@ -3,27 +3,30 @@
|
|
|
3
3
|
// sites that write history refuse on the disk fact either way (core/nesting.ts),
|
|
4
4
|
// so this rule is about the declaration telling the truth to the next reader.
|
|
5
5
|
import { nestedIn } from '../core/nesting.ts'
|
|
6
|
-
import
|
|
6
|
+
import { check } from '../check.ts'
|
|
7
7
|
|
|
8
|
-
export const nestedRepo
|
|
8
|
+
export const nestedRepo = check({
|
|
9
9
|
id: 'SK015',
|
|
10
10
|
blocking: true,
|
|
11
11
|
title: 'nested repo',
|
|
12
|
-
|
|
12
|
+
description: 'a config inside a larger work tree declares `nested: true`, and only there',
|
|
13
|
+
owner: 'build/shipkit/config-keys.md',
|
|
13
14
|
never: 'reads where the git work tree root sits, which no content inside the repo describes',
|
|
14
|
-
|
|
15
|
-
const top = nestedIn(
|
|
16
|
-
const declared =
|
|
15
|
+
run: (c) => {
|
|
16
|
+
const top = nestedIn(c.root)
|
|
17
|
+
const declared = c.config.nested === true
|
|
17
18
|
if (top != null && !declared) {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
c.fail(`repo.config.ts sits inside the git repo at ${top} and does not declare it`)
|
|
20
|
+
c.lines.push({ kind: 'fix-fail', text: 'add `nested: true` to repo.config.ts: shipkit then owns this folder only, and never writes the parent history' })
|
|
20
21
|
return
|
|
21
22
|
}
|
|
22
23
|
if (top == null && declared) {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
c.fail('repo.config.ts declares `nested: true`, but this folder is its own git work tree root (or sits outside git)')
|
|
25
|
+
c.lines.push({ kind: 'fix-fail', text: 'delete `nested: true` from repo.config.ts' })
|
|
25
26
|
return
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
c.ok(top == null ? 'not nested, and not declared nested' : `nested in ${top}, declared`)
|
|
28
29
|
},
|
|
29
|
-
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export default nestedRepo
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// SK016 — the shapes the check file replaced, and the machine that
|
|
2
|
+
// rewrites them (build/shipkit/writing-a-check.md owns the shape itself).
|
|
3
|
+
//
|
|
4
|
+
// The window: every shape below is READ through 0.40, named once per run, and
|
|
5
|
+
// refused at the first release after `shipkit fleet` shows no repo below 0.40.
|
|
6
|
+
// So a repo on an old declaration keeps working, learns what to write instead
|
|
7
|
+
// on every gate, and is carried across by one command, save what only a
|
|
8
|
+
// reader can port: a check script's body, a rule, a task whose body is code.
|
|
9
|
+
//
|
|
10
|
+
// It WARNs rather than FAILs for the whole window, because the repo is not
|
|
11
|
+
// broken: it is behind. The day the shapes are refused, `defineRepo` and
|
|
12
|
+
// discovery say so at load, which is a louder place than a lint row.
|
|
13
|
+
//
|
|
14
|
+
// It reads and writes through `c` alone. The config half of the rewrite loads
|
|
15
|
+
// repo.config.ts as a module, which no read can stand for, so it runs in its
|
|
16
|
+
// own process (core/config-rewrite.ts) through `c.exec`.
|
|
17
|
+
import { join } from 'path'
|
|
18
|
+
import { checkFileRunBy, dropConfigKey, legacyShapes, scriptPort, taskCheckSource, checkFileName } from '../core/legacy-shapes.ts'
|
|
19
|
+
import type { ConfigPlan, ConfigRewrite } from '../core/config-rewrite.ts'
|
|
20
|
+
import { check, type CheckCtx, type FixResult, type TaskDef } from '../check.ts'
|
|
21
|
+
|
|
22
|
+
/** the config half of the rewrite, run as its own process */
|
|
23
|
+
const CONFIG_REWRITE = join(import.meta.dir, '../core/config-rewrite.ts')
|
|
24
|
+
|
|
25
|
+
/** what applies the config half of a plan and says what it did */
|
|
26
|
+
export type ConfigRewriter = (plan: ConfigPlan) => Promise<ConfigRewrite>
|
|
27
|
+
|
|
28
|
+
/** the fixer's rewriter: core/config-rewrite.ts in its own process, since
|
|
29
|
+
* loading repo.config.ts as a module is not a read the context records */
|
|
30
|
+
const inOwnProcess = (c: CheckCtx): ConfigRewriter => async (plan) => {
|
|
31
|
+
const r = await c.exec([process.execPath, CONFIG_REWRITE, c.root, JSON.stringify(plan)], { cwd: c.root, timeoutMs: 60_000 })
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(r.out.trim().split('\n').at(-1) ?? '') as ConfigRewrite
|
|
34
|
+
} catch {
|
|
35
|
+
return { did: [], hand: [`the repo.config.ts rewrite did not answer (exit ${r.code}${r.timedOut ? ', timed out' : ''}: ${r.out.trim().slice(0, 160)}) — apply ${JSON.stringify(plan)} by hand`] }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const OWNER = 'build/shipkit/writing-a-check.md'
|
|
40
|
+
|
|
41
|
+
export const legacyCheckShapes = check({
|
|
42
|
+
id: 'SK016',
|
|
43
|
+
title: 'legacy check shapes',
|
|
44
|
+
description: 'every check declaration uses the current check file shape',
|
|
45
|
+
owner: OWNER,
|
|
46
|
+
// about THIS repo's own declarations, so it is a repo fact. It only ever
|
|
47
|
+
// warns while the window is open, which is the severity, not the axis
|
|
48
|
+
blocking: true,
|
|
49
|
+
// keyed on what it reads: the listing of check files, their sources, and
|
|
50
|
+
// the config. A gitignored check file is no declaration of this repo
|
|
51
|
+
run: (c) => {
|
|
52
|
+
const s = legacyShapes(c, c.config)
|
|
53
|
+
if (s.count === 0 && s.placeholders.length === 0) {
|
|
54
|
+
c.ok('no legacy declaration: every check in this repo is a check file')
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
// the one thing the migration cannot write: a sentence only the
|
|
58
|
+
// author knows. The check loads and runs meanwhile, so it is a warning
|
|
59
|
+
// that names each file, never a refusal
|
|
60
|
+
if (s.placeholders.length > 0)
|
|
61
|
+
c.warn(
|
|
62
|
+
`${s.placeholders.length} check file(s) still open with the TODO description the migration wrote: ${s.placeholders.join(', ')}`,
|
|
63
|
+
'replace each TODO with one line stating what must be true',
|
|
64
|
+
)
|
|
65
|
+
if (s.count === 0) return
|
|
66
|
+
// one WARN per shape, never a bare line: the gate promises every line
|
|
67
|
+
// it prints carries a wire prefix, and this check runs in every gate
|
|
68
|
+
if (s.checkFiles.length > 0) {
|
|
69
|
+
const head = s.checkFiles.slice(0, 3).map((f) => f.rel).join(', ')
|
|
70
|
+
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` : ''}`)
|
|
71
|
+
const off = s.checkFiles.filter((f) => !f.read.gate).length
|
|
72
|
+
if (off > 0) c.warn(`${off} of them carry \`export const gate = false\`, which is \`when: 'health'\``)
|
|
73
|
+
}
|
|
74
|
+
if (s.checkDefaults) c.warn(`repo.config.ts checkDefaults lets a file forget \`blocking\` — the value belongs in each file`)
|
|
75
|
+
if (s.tasks.length > 0) {
|
|
76
|
+
const data = s.tasks.filter((x) => x.data).length
|
|
77
|
+
c.warn(`${s.tasks.length} \`tasks: []\` entr(ies), ${data} of them pure data — each one is a *.check.ts file with \`build: true\``)
|
|
78
|
+
}
|
|
79
|
+
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\``)
|
|
80
|
+
if (s.ruleExempt.length > 0) c.warn(`ruleExempt names ${s.ruleExempt.join(', ')} — the spelling is \`when: { id: 'off:<why>' }\``)
|
|
81
|
+
c.warn(
|
|
82
|
+
`${s.count} declaration(s) in a shape the check file replaced — read through 0.40, refused after`,
|
|
83
|
+
'shipkit check --fix SK016 rewrites what is mechanical and names the rest',
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
fix: async (c) => await fixLegacyShapes(c),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
export default legacyCheckShapes
|
|
90
|
+
|
|
91
|
+
/** the rewrite. Everything it cannot do without guessing is NAMED in the
|
|
92
|
+
* summary instead of attempted, so a repo is never left half migrated.
|
|
93
|
+
* `rewrite` applies the config half, in its own process unless a caller
|
|
94
|
+
* that may load the config in process hands `rewriteConfig` itself */
|
|
95
|
+
export async function fixLegacyShapes(c: CheckCtx, rewrite: ConfigRewriter = inOwnProcess(c)): Promise<FixResult> {
|
|
96
|
+
const { root } = c
|
|
97
|
+
const config = c.config
|
|
98
|
+
const s = legacyShapes(c, config)
|
|
99
|
+
const todo = (rel: string): string => `${rel}: replace the TODO with one line stating what must be true`
|
|
100
|
+
if (s.count === 0)
|
|
101
|
+
return { changed: false, summary: s.placeholders.length === 0 ? 'nothing legacy left to rewrite' : `nothing mechanical left. ${s.placeholders.length} by hand: ${s.placeholders.map(todo).join(' | ')}` }
|
|
102
|
+
|
|
103
|
+
const did: string[] = []
|
|
104
|
+
const hand: string[] = s.placeholders.map(todo)
|
|
105
|
+
const source = (rel: string): string => c.read(rel) ?? ''
|
|
106
|
+
|
|
107
|
+
// THE PLAN, before any write. A check script is never rewritten: its body
|
|
108
|
+
// prints and exits in a process of its own, and the one body a check file
|
|
109
|
+
// has is `run: (c) => …` (SK020 refuses a check file that spawns itself).
|
|
110
|
+
// Moving prints onto `c` is a hand pass, so each script is listed with
|
|
111
|
+
// where it goes, and the static reader keeps running it meanwhile. The
|
|
112
|
+
// tasks move all together or not at all: the `tasks` key is dropped
|
|
113
|
+
// whole, and a task that runs a script waits for that script's port
|
|
114
|
+
const tasks = config.tasks ?? []
|
|
115
|
+
const checkRels = new Set(s.checkFiles.map((f) => f.rel))
|
|
116
|
+
const runsOf = new Map<TaskDef, { rel: string; rest: string[] }>()
|
|
117
|
+
for (const t of tasks) {
|
|
118
|
+
const r = t.logic == null ? checkFileRunBy(t, root) : null
|
|
119
|
+
if (r != null && checkRels.has(r.rel)) runsOf.set(t, r)
|
|
120
|
+
}
|
|
121
|
+
const fileIds = new Set(s.checkFiles.map((f) => f.read.name))
|
|
122
|
+
const configText = source('repo.config.ts')
|
|
123
|
+
const taskBlock: string[] = []
|
|
124
|
+
if (tasks.length > 0 && dropConfigKey(configText, 'tasks') == null) taskBlock.push('the `tasks` key could not be read mechanically')
|
|
125
|
+
for (const t of tasks) {
|
|
126
|
+
const runs = runsOf.get(t)
|
|
127
|
+
if (t.logic != null || t.cmd == null) taskBlock.push(`task ${t.id}: its body is \`logic\`, a function — move it into ${checkFileName(t.id)} beside repo.config.ts as \`run\``)
|
|
128
|
+
else if (runs != null) taskBlock.push(`task ${t.id}: it runs ${runs.rel}, a check script — port that file first, then drop the task: the check file is the check`)
|
|
129
|
+
else if (fileIds.has(t.id)) taskBlock.push(`task ${t.id}: a check file answers to the same id, and the task does not simply run that file, so the two are different checks — rename one of them`)
|
|
130
|
+
else if (c.exists(checkFileName(t.id))) taskBlock.push(`task ${t.id}: ${checkFileName(t.id)} already exists beside repo.config.ts, so its check file needs another name`)
|
|
131
|
+
}
|
|
132
|
+
const tasksMove = tasks.length > 0 && taskBlock.length === 0
|
|
133
|
+
if (!tasksMove) {
|
|
134
|
+
for (const l of taskBlock) hand.push(l)
|
|
135
|
+
if (tasks.length > 0) hand.push(`the ${tasks.length} task(s) stay in repo.config.ts until the line(s) above are done, then they move together`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 1. the check scripts, each listed with where it goes. Its declarations
|
|
139
|
+
// are what the static reader read, so the port loses none of them
|
|
140
|
+
for (const f of s.checkFiles) hand.push(`${f.rel}: ${scriptPort(f.read, config.checkDefaults?.blocking)}`)
|
|
141
|
+
const filesLeft = s.checkFiles.length
|
|
142
|
+
|
|
143
|
+
// 2. the tasks that are DATA, each as its own check file beside the
|
|
144
|
+
// config (the plan refused a name another file already holds)
|
|
145
|
+
for (const t of tasksMove ? tasks : []) {
|
|
146
|
+
const name = checkFileName(t.id)
|
|
147
|
+
c.write(name, taskCheckSource(t, root))
|
|
148
|
+
did.push(`task ${t.id} → ${name}`)
|
|
149
|
+
// a config entry carries no prose, so nothing can be lifted: the file
|
|
150
|
+
// passes the shape and a human owes it the line
|
|
151
|
+
hand.push(`${name}: a task declaration carried no prose — replace the TODO with one line stating what must be true`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 3. a rule is a function too, and the file it was written in owns the
|
|
155
|
+
// names it closes over. The fixer says where it goes, never guesses
|
|
156
|
+
for (const r of config.rules ?? [])
|
|
157
|
+
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)`)
|
|
158
|
+
|
|
159
|
+
// 4. the exemptions that were their own key, as `when` entries (the
|
|
160
|
+
// managed block is the one writer), then 5. the config keys, last: a
|
|
161
|
+
// key is only dropped once every declaration that leaned on it has a
|
|
162
|
+
// home. Both load the config, so they run in their own process
|
|
163
|
+
const cfgPlan: ConfigPlan = { when: {}, drop: [], name: config.name }
|
|
164
|
+
for (const [id, why] of Object.entries(config.ruleExempt ?? {})) cfgPlan.when[id] = `off:${why}`
|
|
165
|
+
if (s.checkDefaults && filesLeft === 0) cfgPlan.drop.push({ key: 'checkDefaults', why: 'every file now declares its own blocking' })
|
|
166
|
+
else if (s.checkDefaults) hand.push(`repo.config.ts \`checkDefaults\` stays until the ${filesLeft} file(s) above declare their own \`blocking\``)
|
|
167
|
+
if (tasksMove) cfgPlan.drop.push({ key: 'tasks', why: 'every task is a check file now' })
|
|
168
|
+
if (s.ruleExempt.length > 0) cfgPlan.drop.push({ key: 'ruleExempt', why: 'the exemptions are `when` entries now', afterWhen: true })
|
|
169
|
+
if (Object.keys(cfgPlan.when).length > 0 || cfgPlan.drop.length > 0) {
|
|
170
|
+
const out = await rewrite(cfgPlan)
|
|
171
|
+
did.push(...out.did)
|
|
172
|
+
hand.push(...out.hand)
|
|
173
|
+
if (out.restored != null)
|
|
174
|
+
return { changed: did.length > 0, summary: `repo.config.ts was left as it was, its rewrite did not load back (${out.restored}). Done: ${did.join('; ') || 'nothing'}` }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const summary = [
|
|
178
|
+
did.length > 0 ? did.join('; ') : 'nothing mechanical left',
|
|
179
|
+
...(hand.length > 0 ? [`${hand.length} by hand: ${hand.join(' | ')}`] : []),
|
|
180
|
+
].join('. ')
|
|
181
|
+
return { changed: did.length > 0, summary }
|
|
182
|
+
}
|