rvlib-shipkit 1.8.0 → 2.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 +66 -0
- package/package.json +1 -1
- package/src/assets/gen/comfyts-gen.ts +31 -14
- package/src/assets/gen/generate-roll.ts +20 -7
- package/src/assets/kit-config.ts +9 -0
- package/src/assets/kits/identity-kit.ts +7 -1
- package/src/assets/run-assets.ts +3 -3
- package/src/assets/server/studio-server.ts +3 -3
- package/src/check.ts +5 -2
- package/src/checks/SK001-package-json-shape.check.ts +47 -12
- package/src/checks/SK013-one-way-in-policy.check.ts +8 -7
- package/src/checks/SK017-vscode-hub-task.check.ts +109 -106
- package/src/checks/SK019-tests-run-in-units.check.ts +2 -1
- package/src/checks/SK027-agent-wiki-links.check.ts +5 -2
- package/src/checks/SK029-explicit-check-files.check.ts +25 -8
- package/src/checks/SK034-commit-gates-armed.check.ts +90 -0
- package/src/cli/cmd_Ci.ts +6 -5
- package/src/cli/cmd_Commands.ts +21 -5
- package/src/cli/cmd_Hooks.ts +6 -2
- package/src/cli/cmd_Init.ts +1 -1
- package/src/cli/cmd_Serve.ts +1 -2
- package/src/cli/cmd_Start.ts +1 -1
- package/src/cli.ts +2 -4
- package/src/collections/providers.ts +1 -1
- package/src/core/budget.ts +6 -8
- package/src/core/bun-builtins.ts +36 -0
- package/src/core/bun-tool-cli.ts +23 -0
- package/src/core/canonical-scripts.ts +6 -4
- package/src/core/check-ctx.ts +104 -10
- package/src/core/check-file-names.ts +2 -3
- package/src/core/check-pass.ts +17 -1
- package/src/core/check-runner.ts +32 -10
- package/src/core/config-rewrite.ts +27 -6
- package/src/core/discover-checks.ts +64 -15
- package/src/core/fix-scope.ts +5 -0
- package/src/core/floor-resolve.ts +18 -8
- package/src/core/git-root.ts +22 -12
- package/src/core/job-run.ts +2 -2
- package/src/core/layout.ts +4 -1
- package/src/core/legacy-shapes.ts +40 -0
- package/src/core/one-lib.ts +5 -3
- package/src/core/one-way-in-policy.ts +106 -13
- package/src/core/pre-commit-discovery.ts +106 -0
- package/src/core/retired-checks.ts +2 -0
- package/src/core/run-ci.ts +23 -16
- package/src/core/run-fix.ts +9 -0
- package/src/core/run-fixers.ts +91 -35
- package/src/core/runs-matrix.ts +7 -0
- package/src/core/slots.ts +36 -6
- package/src/core/vendored-read.ts +39 -0
- package/src/core/vendored.ts +82 -0
- package/src/core/waivers.ts +125 -0
- package/src/core/when-block.ts +25 -185
- package/src/core/when.ts +30 -13
- package/src/core/wire.ts +17 -1
- package/src/git-bootstrap.ts +1 -1
- package/src/home/attach.ts +3 -5
- package/src/home/gui-app/ShipkitGui.swift +96 -0
- package/src/home/insights.ts +6 -1
- package/src/home/pm-config.ts +2 -2
- package/src/home/resources.ts +3 -0
- package/src/home/run-daemon.ts +2 -2
- package/src/home/run-unit-action.ts +2 -2
- package/src/hooks/commit-message.ts +2 -14
- package/src/hooks/hook-gates.ts +38 -0
- package/src/hooks/leak-guard.ts +8 -3
- package/src/hooks/one-way-in-guard.ts +8 -2
- package/src/hooks/root-identity.ts +9 -6
- package/src/hooks/run-hooks.ts +72 -41
- package/src/hooks/staged.ts +49 -0
- package/src/hooks/tree-shrink.ts +41 -0
- package/src/init-repo.ts +0 -42
- package/src/ledger/check-table.ts +1 -1
- package/src/ledger/gate-checks.ts +45 -9
- package/src/ledger/ledger.ts +6 -1
- package/src/ledger/repo-view.ts +14 -4
- package/src/ledger/toolchain-identity.ts +4 -2
- package/src/mv/mv-md.ts +6 -1
- package/src/plan/planner.ts +21 -4
- package/src/plan/repo-source.ts +7 -3
- package/src/problems/extract.ts +27 -5
- package/src/problems/problem-line.ts +2 -3
- package/src/release/run-release.ts +30 -16
- package/src/release/unit-gate.ts +12 -5
- package/src/repo-config.ts +51 -27
- package/src/scaffold/files/bunfig.toml +11 -0
- package/src/scaffold/files/vscode-settings.json +1 -2
- package/src/scaffold/jsonc-spans.ts +16 -0
- package/src/scaffold/project-scaffold.ts +43 -77
- package/src/serve/hub-app/check-view.tsx +4 -4
- package/src/serve/hub-app/check-words.ts +1 -0
- package/src/serve/hub-app/checks-svc.ts +1 -2
- package/src/serve/hub-app/home.html +0 -1
- package/src/serve/hub-app/hub-types.ts +0 -2
- package/src/serve/hub-app/index.html +0 -1
- package/src/serve/hub-app/project-list.css +1 -2
- package/src/serve/hub-app/tree-build.ts +3 -2
- package/src/serve/hub-app/tree-svc.ts +2 -2
- package/src/serve/hub-port.ts +3 -3
- package/src/serve/hub-runtime.ts +3 -5
- package/src/serve/project-gutter.ts +5 -2
- package/src/serve/run-lanes.ts +7 -3
- package/src/serve/serve-hub.ts +1 -16
- package/src/serve/tracked-watch.ts +1 -1
- package/src/smoke/run-smoke.ts +13 -6
- package/src/start-repo.ts +15 -3
- package/src/suite/judge-suites.ts +81 -32
- package/src/suite/select.ts +2 -0
- package/src/suite/suite-cli.ts +27 -16
- package/src/suite/suite-cpu-preload.ts +7 -11
- package/src/suite/test-units.ts +7 -4
- package/src/ts/config.ts +12 -5
- package/src/ts/imports.ts +3 -1
- package/src/ts/typecheck-cache.ts +11 -0
- package/src/ts/typecheck-cli.ts +2 -1
- package/src/tui/build-steps-panel.ts +1 -1
- package/src/wiki/scan-md.ts +16 -9
- package/src/checks/SK009-commit-ai-tells.check.ts +0 -131
- package/src/checks/SK014-prose-ste.check.ts +0 -78
- package/src/cli/cmd_Prose.ts +0 -74
- package/src/prose/prose-view.ts +0 -90
- package/src/prose/run-prose.ts +0 -187
- package/src/prose/ste.ts +0 -552
- package/src/scaffold/files/vscode-tasks.json +0 -24
- package/src/serve/hub-app/prose-hits-st.ts +0 -125
- package/src/serve/hub-app/prose-hits-view.tsx +0 -176
- package/src/serve/hub-app/prose-hits.css +0 -21
|
@@ -12,7 +12,7 @@ import { runCi } from '../core/run-ci.ts'
|
|
|
12
12
|
import { extractChangelogSection } from './changelog.ts'
|
|
13
13
|
import { deriveRemote, dirtyPaths, git, remoteTagState, tagExistsLocally, type RemoteTagState } from './git-facts.ts'
|
|
14
14
|
import { releaseScope } from './release-scope.ts'
|
|
15
|
-
import {
|
|
15
|
+
import { publishCommand, unitBlockers, type Publisher } from './unit-gate.ts'
|
|
16
16
|
import { formatUnitError, resolveUnits, unitById, unknownUnit, type ReleaseUnit } from './units.ts'
|
|
17
17
|
import type { RepoConfig } from '../repo-config.ts'
|
|
18
18
|
import { nestedIn, nestedRefusal } from '../core/nesting.ts'
|
|
@@ -37,6 +37,10 @@ export type ReleaseConfig = {
|
|
|
37
37
|
* Absent = rely on ambient npm auth. Never put a token in config.
|
|
38
38
|
* Repo-wide: one account publishes every unit. */
|
|
39
39
|
npmTokenCommand?: string[]
|
|
40
|
+
/** the tool that packs and publishes every npm unit (default 'npm'). A
|
|
41
|
+
* repo that tests its tarball with `bun pm pack` publishes with 'bun', so
|
|
42
|
+
* the packer it tests is the packer that ships. */
|
|
43
|
+
publisher?: Publisher
|
|
40
44
|
/** cut a GitHub release for the ROOT unit with `gh` (default true) */
|
|
41
45
|
githubRelease?: boolean
|
|
42
46
|
/** make a NON-blocking check failure block the release too. Blocking
|
|
@@ -113,7 +117,7 @@ export async function runReleaseGate(root: string, config: RepoConfig, print: (l
|
|
|
113
117
|
return await runCi(root, {
|
|
114
118
|
fix: o.fix ?? false,
|
|
115
119
|
// a check's repair never runs on the release path (runs.onRelease):
|
|
116
|
-
// only the mechanical slots (the
|
|
120
|
+
// only the mechanical slots (the import fixer) may, when bump asks
|
|
117
121
|
fixRules: false,
|
|
118
122
|
// the rows declaring onRelease, whatever the tier
|
|
119
123
|
release: true,
|
|
@@ -239,13 +243,7 @@ export async function releaseUnit(root: string, config: RepoConfig, unit: Releas
|
|
|
239
243
|
const remoteName0 = 'remote' in remote ? remote.remote : null
|
|
240
244
|
const tagExists = (): boolean => tagExistsLocally(root, tag)
|
|
241
245
|
const remoteState: RemoteTagState = remoteName0 == null ? { state: 'unreadable', error: 'no remote' } : remoteTagState(root, remoteName0, tag)
|
|
242
|
-
|
|
243
|
-
blockers.push(`cannot read tags on '${remoteName0}' (${remoteState.error}) — the push would be a guess`)
|
|
244
|
-
// a tag NAME the remote already carries at another commit is not a
|
|
245
|
-
// resumable release: this version would describe a tree that shipped as
|
|
246
|
-
// something else, and git refuses the push anyway
|
|
247
|
-
if (remoteState.state === 'different')
|
|
248
|
-
blockers.push(`tag ${tag} already on '${remoteName0 ?? ''}' at ${remoteState.remoteOid.slice(0, 8)}, ${remoteState.localOid == null ? 'and not here at all' : `not ${remoteState.localOid.slice(0, 8)} as here`} — that version number is burnt, bump a new one`)
|
|
246
|
+
blockers.push(...tagBlockers(remoteName0, remoteState, tag))
|
|
249
247
|
const ghReleaseExists = (): boolean =>
|
|
250
248
|
wantGh && Bun.which('gh') != null && Bun.spawnSync(['gh', 'release', 'view', tag], { cwd: root }).exitCode === 0
|
|
251
249
|
const fullyShipped = tagExists() && remoteState.state === 'same' && (!wantGh || ghReleaseExists()) && (!wantNpm || alreadyPublished)
|
|
@@ -301,18 +299,23 @@ export async function releaseUnit(root: string, config: RepoConfig, unit: Releas
|
|
|
301
299
|
if (wantNpm && !alreadyPublished) {
|
|
302
300
|
const env: Record<string, string> = { ...process.env as Record<string, string> }
|
|
303
301
|
const argv = rc.npmTokenCommand
|
|
302
|
+
const publisher = rc.publisher ?? 'npm'
|
|
304
303
|
let npmrc: string | null = null
|
|
304
|
+
let token: string | null = null
|
|
305
305
|
if (argv != null && argv[0] != null) {
|
|
306
306
|
const t = Bun.spawnSync(argv)
|
|
307
|
-
|
|
307
|
+
token = t.exitCode === 0 ? t.stdout.toString().trim() : ''
|
|
308
308
|
if (token === '') { print(`FAIL: release.npmTokenCommand produced no token (exit ${t.exitCode})`); return 1 }
|
|
309
|
-
// token via a throwaway userconfig
|
|
310
|
-
// committed .npmrc, never a line in a log
|
|
311
|
-
|
|
312
|
-
|
|
309
|
+
// token via a throwaway userconfig (npm) or the env (bun),
|
|
310
|
+
// never argv, never a committed .npmrc, never a line in a log
|
|
311
|
+
if (publisher === 'npm') {
|
|
312
|
+
npmrc = join(work, 'npmrc')
|
|
313
|
+
writeFileSync(npmrc, `//registry.npmjs.org/:_authToken=${token}\n`, { mode: 0o600 })
|
|
314
|
+
}
|
|
313
315
|
}
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
+
const cmd = publishCommand({ publisher, npmrc, token })
|
|
317
|
+
const p = Bun.spawnSync(cmd.argv, { cwd: unit.dir, env: { ...env, ...cmd.env }, stdout: 'inherit', stderr: 'inherit' })
|
|
318
|
+
if (p.exitCode !== 0) { print(`FAIL: ${publisher} publish failed for ${unit.id} (exit ${p.exitCode})`); return 1 }
|
|
316
319
|
print(`ok: published ${name}@${version}`)
|
|
317
320
|
}
|
|
318
321
|
|
|
@@ -356,3 +359,14 @@ export async function releaseUnit(root: string, config: RepoConfig, unit: Releas
|
|
|
356
359
|
rmSync(work, { recursive: true, force: true })
|
|
357
360
|
}
|
|
358
361
|
}
|
|
362
|
+
|
|
363
|
+
/** what the remote's view of the tag forbids: a remote nobody can read makes
|
|
364
|
+
* the push a guess, and a tag NAME it already carries at another commit is not
|
|
365
|
+
* a resumable release (this version would describe a tree that shipped as
|
|
366
|
+
* something else, and git refuses the push anyway) */
|
|
367
|
+
export function tagBlockers(remote: string | null, state: RemoteTagState, tag: string): string[] {
|
|
368
|
+
if (remote != null && state.state === 'unreadable') return [`cannot read tags on '${remote}' (${state.error}) — the push would be a guess`]
|
|
369
|
+
if (state.state === 'different')
|
|
370
|
+
return [`tag ${tag} already on '${remote ?? ''}' at ${state.remoteOid.slice(0, 8)}, ${state.localOid == null ? 'and not here at all' : `not ${state.localOid.slice(0, 8)} as here`} — that version number is burnt, bump a new one`]
|
|
371
|
+
return []
|
|
372
|
+
}
|
package/src/release/unit-gate.ts
CHANGED
|
@@ -9,11 +9,18 @@ import { dirtyPaths } from './git-facts.ts'
|
|
|
9
9
|
import type { ReleaseUnit } from './units.ts'
|
|
10
10
|
import type { RepoConfig } from '../repo-config.ts'
|
|
11
11
|
|
|
12
|
-
/** the
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
/** the tool that packs and publishes an npm unit (`release.publisher`) */
|
|
13
|
+
export const PUBLISHERS = ['npm', 'bun'] as const
|
|
14
|
+
export type Publisher = (typeof PUBLISHERS)[number]
|
|
15
|
+
|
|
16
|
+
/** the publish argv and the env its token rides in. `--access public` is not
|
|
17
|
+
* a default: npm restricts a scoped package unless it is asked, and a
|
|
18
|
+
* restricted publish looks successful right up until a consumer cannot
|
|
19
|
+
* install it. npm reads the token from a throwaway userconfig; `bun publish`
|
|
20
|
+
* has no `--userconfig` and reads NPM_CONFIG_TOKEN instead. */
|
|
21
|
+
export function publishCommand(o: { publisher?: Publisher; npmrc?: string | null; token?: string | null } = {}): { argv: string[]; env: Record<string, string> } {
|
|
22
|
+
if (o.publisher === 'bun') return { argv: ['bun', 'publish', '--access', 'public'], env: o.token != null ? { NPM_CONFIG_TOKEN: o.token } : {} }
|
|
23
|
+
return { argv: ['npm', 'publish', '--access', 'public', ...(o.npmrc != null ? ['--userconfig', o.npmrc] : [])], env: {} }
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/** everything that must hold for this unit, checked before anything is
|
package/src/repo-config.ts
CHANGED
|
@@ -5,26 +5,27 @@
|
|
|
5
5
|
// (lint/typecheck/test/imports:check/cycles): a package.json script wins;
|
|
6
6
|
// with no script, imports:check/cycles fill from shipkit's own
|
|
7
7
|
// rvlib-ts-tools dep and typecheck from the repo's tsc (O6 zero-config);
|
|
8
|
-
// a slot that can't fill emits a visible reason,
|
|
9
|
-
// `
|
|
8
|
+
// a slot that can't fill emits a visible reason, and one turned off with
|
|
9
|
+
// `off: { x: '<why>' }` prints a skipped-by-config line, never silence.
|
|
10
10
|
// The `contracts` task is always present when any contract applies.
|
|
11
|
-
import { existsSync } from 'fs'
|
|
11
|
+
import { existsSync, readFileSync } from 'fs'
|
|
12
12
|
import { isAbsolute, join, normalize } from 'path'
|
|
13
13
|
import { ROUTED_SCRIPTS } from './core/canonical-scripts.ts'
|
|
14
14
|
import { FLOOR_IDS } from './core/floor.ts'
|
|
15
15
|
export { resolveFloor, type FloorResolution, type FloorSource } from './core/floor.ts'
|
|
16
16
|
import type { ContractManifest } from './contracts/contract-types.ts'
|
|
17
|
-
import type { ProseConfig } from './prose/run-prose.ts'
|
|
18
|
-
import { proseWordsProblems } from './prose/ste.ts'
|
|
19
17
|
import { validateSmokeConfig, type SmokeConfig } from './smoke/run-smoke.ts'
|
|
20
18
|
import { RULE_ID_RE, validateRules } from './rules/rule.ts'
|
|
21
19
|
import { builtinCheckIds } from './core/discover-checks.ts'
|
|
20
|
+
import { RETIRED_CHECK_IDS } from './core/retired-checks.ts'
|
|
22
21
|
import { parseWhen } from './core/when.ts'
|
|
22
|
+
import { normalizeVendored, vendoredOfText, vendoredProblem } from './core/vendored.ts'
|
|
23
23
|
import { budgetValueProblem } from './core/budget.ts'
|
|
24
24
|
import type { Rule } from './check.ts'
|
|
25
25
|
import { leakAllowProblems } from './hooks/leak-guard.ts'
|
|
26
26
|
import type { HooksConfig } from './hooks/run-hooks.ts'
|
|
27
27
|
import type { ReleaseConfig } from './release/run-release.ts'
|
|
28
|
+
import { PUBLISHERS } from './release/unit-gate.ts'
|
|
28
29
|
import { TEST_ONLY_UNIT_KEYS, UNIT_DECL_KEYS, type UnitDecl } from './release/unit-resolve.ts'
|
|
29
30
|
import { TASK_DEF_KEYS, type TaskDef } from './check.ts'
|
|
30
31
|
import { testsConfigProblems, type TestsConfig } from './suite/judge-suites.ts'
|
|
@@ -41,6 +42,10 @@ export type ServeConfig = {
|
|
|
41
42
|
* its own port — house range 5800+, default = the hub port plus 1000
|
|
42
43
|
* (build/comfyui.md owns that surface) */
|
|
43
44
|
comfyPort?: number
|
|
45
|
+
/** false hides the asset kit from the hub page (no asset rows, an empty
|
|
46
|
+
* studio) and from `shipkit needs`, for a repo whose own tool mounts
|
|
47
|
+
* `studioRoutes` and owns the picking. Default true */
|
|
48
|
+
assets?: boolean
|
|
44
49
|
/** custom wiki-graph provider for repos with their own link semantics.
|
|
45
50
|
* Default: a generic md scan of the repo root. */
|
|
46
51
|
wikiGraph?: {
|
|
@@ -75,12 +80,13 @@ export type RepoConfig = {
|
|
|
75
80
|
social?: string[]
|
|
76
81
|
/** prefix for contract fix: pointers (docs checkout or URL) — written by init, never baked in */
|
|
77
82
|
docsBase?: string
|
|
78
|
-
/** a floor slot: `
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
83
|
+
/** a floor slot: a def with a `cmd` or `logic` REPLACES it, and a def
|
|
84
|
+
* with neither DECORATES whatever fills it. The decoration is how a repo
|
|
85
|
+
* declares what its own `test` script reads (`{ test: { inputs:
|
|
86
|
+
* ['tests', 'src'] } }`) without restating the command: the read set is
|
|
87
|
+
* repo knowledge, the command is already in package.json, and restating
|
|
88
|
+
* it is a second source of truth. A slot set to `false` is RETIRED, read
|
|
89
|
+
* as `off` while SK029 ports it there: a slot turns off like any check */
|
|
84
90
|
defaults?: Record<string, Partial<TaskDef> | false>
|
|
85
91
|
/** DEPRECATED, read through 0.40 and rewritten by `shipkit check --fix
|
|
86
92
|
* SK016`: a build step is a `<name>.check.ts` file with `build: true` */
|
|
@@ -162,10 +168,10 @@ export type RepoConfig = {
|
|
|
162
168
|
* folder only: it never commits, untracks, tags or pushes on the parent's
|
|
163
169
|
* history. Rule SK015 fails when this and the disk disagree (core/nesting.ts) */
|
|
164
170
|
nested?: boolean
|
|
165
|
-
/** THE CHECKS THIS REPO TURNS OFF, check id → why
|
|
166
|
-
*
|
|
167
|
-
* declaration (build/shipkit/runs.md), and an
|
|
168
|
-
* context. `defineRepo` throws on a blank reason and on a rule id no
|
|
171
|
+
/** THE CHECKS THIS REPO TURNS OFF, check id → why, for a whole check it
|
|
172
|
+
* does not own (one false finding is waived at its site instead): where
|
|
173
|
+
* a check runs is its own declaration (build/shipkit/runs.md), and an
|
|
174
|
+
* off check runs in no context. `defineRepo` throws on a blank reason and on a rule id no
|
|
169
175
|
* check carries. The hub writes this map through the managed block in
|
|
170
176
|
* this file (core/when-block.ts). */
|
|
171
177
|
off?: Record<string, string>
|
|
@@ -246,6 +252,18 @@ export type RepoConfig = {
|
|
|
246
252
|
* repo root is refused outright: a single line that excuses everything
|
|
247
253
|
* would turn a blocking rule into a false green. */
|
|
248
254
|
notReleasable?: string[]
|
|
255
|
+
/** repo-relative dirs of THIRD PARTY code (a git subtree, a copied
|
|
256
|
+
* library) that must stay byte identical to upstream. Every tool that
|
|
257
|
+
* treats the tree as the repo's own source leaves them out: the import
|
|
258
|
+
* fixer and its package discovery, imports:check, cycles, the fix pass
|
|
259
|
+
* (a fixer that rewrites a file under one gets it put back, with a
|
|
260
|
+
* WARN naming it), bun lint, the checks' file
|
|
261
|
+
* listings, check discovery, the md scan and `shipkit mv`. Cache keys
|
|
262
|
+
* and watchers still read them, because they are still inputs, and the
|
|
263
|
+
* leak guard still scans what a commit stages there. Write it
|
|
264
|
+
* as a literal array of strings: the tools that never load this file
|
|
265
|
+
* read it as text. One entry is one dir, never a glob */
|
|
266
|
+
vendored?: string[]
|
|
249
267
|
/** git-hook gates: built-in leak guard + commit-message predicate, plus the
|
|
250
268
|
* repo's own commands. `shipkit hooks list` enumerates them, so what blocks
|
|
251
269
|
* a commit is answerable without reading shell. hooks/run-hooks.ts */
|
|
@@ -258,12 +276,6 @@ export type RepoConfig = {
|
|
|
258
276
|
/** what a `*.check.ts` with no `export const blocking` means here */
|
|
259
277
|
blocking?: boolean
|
|
260
278
|
}
|
|
261
|
-
/** house prose in STE (identity/writing-voice.md), gated by rule SK014.
|
|
262
|
-
* `include` is what makes the rule run at all: a repo that declares no
|
|
263
|
-
* scope is skipped rather than failed, because STE binds the prose an
|
|
264
|
-
* agent WRITES and no repo can be opted in from the outside.
|
|
265
|
-
* prose/run-prose.ts */
|
|
266
|
-
prose?: ProseConfig
|
|
267
279
|
/** `shipkit smoke`: the deployed url, the routes a read only browser pass
|
|
268
280
|
* visits after a deploy, and how it logs in. The password is a secret
|
|
269
281
|
* NAME: the value never sits in config, argv or env. smoke/run-smoke.ts */
|
|
@@ -295,7 +307,7 @@ export type RepoConfig = {
|
|
|
295
307
|
logDir?: string
|
|
296
308
|
}
|
|
297
309
|
|
|
298
|
-
const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'credsReadCommand', 'notifyCommand', 'contracts', 'gitignore', 'ignoredInputs', 'untrackGenerated', 'nested', 'off', 'when', 'budgets', 'ruleExempt', 'visibility', 'archived', 'reviewer', 'oneWayIn', 'rules', 'release', 'units', 'notReleasable', 'hooks', 'checkDefaults', 'tui', 'serve', 'identity', 'logDir', '
|
|
310
|
+
const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'credsReadCommand', 'notifyCommand', 'contracts', 'gitignore', 'ignoredInputs', 'untrackGenerated', 'nested', 'off', 'when', 'budgets', 'ruleExempt', 'visibility', 'archived', 'reviewer', 'oneWayIn', 'rules', 'release', 'units', 'notReleasable', 'vendored', 'hooks', 'checkDefaults', 'tui', 'serve', 'identity', 'logDir', 'smoke', 'tests'])
|
|
299
311
|
|
|
300
312
|
export function defineRepo(c: RepoConfig): RepoConfig {
|
|
301
313
|
if (typeof c.name !== 'string' || c.name === '') throw new Error('defineRepo: name is required')
|
|
@@ -319,7 +331,9 @@ export function defineRepo(c: RepoConfig): RepoConfig {
|
|
|
319
331
|
// gate is exactly the never-silence failure mode (A23)
|
|
320
332
|
for (const k of Object.keys(c))
|
|
321
333
|
if (!KNOWN_KEYS.has(k))
|
|
322
|
-
throw new Error(`defineRepo: unknown key '${k}'${k === 'checks' ? ` — checks:[] was removed; drop a <name>.check.ts file instead ('shipkit check' auto-discovers it)` : k === 'home' ? ` — home.resources was removed; declare each one in a <name>.shipkit.ts file as \`export default globalProcess({...})\`` : ''}`)
|
|
334
|
+
throw new Error(`defineRepo: unknown key '${k}'${k === 'checks' ? ` — checks:[] was removed; drop a <name>.check.ts file instead ('shipkit check' auto-discovers it)` : k === 'home' ? ` — home.resources was removed; declare each one in a <name>.shipkit.ts file as \`export default globalProcess({...})\`` : k === 'prose' ? ` — prose was removed with rule SK014: delete the key` : ''}`)
|
|
335
|
+
if (c.release?.publisher != null && !(PUBLISHERS as readonly string[]).includes(c.release.publisher))
|
|
336
|
+
throw new Error(`defineRepo: release.publisher '${String(c.release.publisher)}' is not a publisher (${PUBLISHERS.join(', ')})`)
|
|
323
337
|
// a typo'd unit key would silently drop a target and release the wrong shape
|
|
324
338
|
for (const u of c.units ?? []) {
|
|
325
339
|
if (typeof u.dir !== 'string' || u.dir === '') throw new Error(`defineRepo: every units[] entry needs a 'dir'`)
|
|
@@ -341,6 +355,11 @@ export function defineRepo(c: RepoConfig): RepoConfig {
|
|
|
341
355
|
if (isAbsolute(d) || rel === '.' || rel === '' || rel === '..' || rel.startsWith('../'))
|
|
342
356
|
throw new Error(`defineRepo: notReleasable '${d}' is the repo root or outside it, and would excuse every package at once — name one package dir per entry`)
|
|
343
357
|
}
|
|
358
|
+
if (c.vendored != null && !Array.isArray(c.vendored)) throw new Error(`defineRepo: vendored is a list of repo-relative dirs`)
|
|
359
|
+
for (const d of c.vendored ?? []) {
|
|
360
|
+
const bad = vendoredProblem(d)
|
|
361
|
+
if (bad != null) throw new Error(`defineRepo: vendored '${String(d)}' ${bad}`)
|
|
362
|
+
}
|
|
344
363
|
for (const [k, slot] of Object.entries(c.defaults ?? {})) {
|
|
345
364
|
if (!(FLOOR_IDS as readonly string[]).includes(k))
|
|
346
365
|
throw new Error(`defineRepo: defaults.${k} is not a floor task (floor: ${FLOOR_IDS.join(', ')})`)
|
|
@@ -378,6 +397,7 @@ export function defineRepo(c: RepoConfig): RepoConfig {
|
|
|
378
397
|
for (const [id, reason] of Object.entries(c.off ?? {})) {
|
|
379
398
|
if (id.trim() === '') throw new Error(`defineRepo: off has an entry with no check id`)
|
|
380
399
|
if (typeof reason !== 'string' || reason.trim() === '') throw new Error(`defineRepo: off.${id} needs a reason: a check nobody can explain is a silenced gate`)
|
|
400
|
+
if (RETIRED_CHECK_IDS[id] != null) throw new Error(`defineRepo: off.${id} names a retired rule (${RETIRED_CHECK_IDS[id]}): delete the entry`)
|
|
381
401
|
if (RULE_ID_RE.test(id) && !builtinCheckIds().includes(id) && !(c.rules ?? []).some((r) => r.id === id))
|
|
382
402
|
throw new Error(`defineRepo: off.${id} names no rule, the ids are in \`shipkit checks\``)
|
|
383
403
|
}
|
|
@@ -396,15 +416,12 @@ export function defineRepo(c: RepoConfig): RepoConfig {
|
|
|
396
416
|
}
|
|
397
417
|
// an exemption silences a gate, so it names a rule that exists and says why
|
|
398
418
|
for (const [id, reason] of Object.entries(c.ruleExempt ?? {})) {
|
|
419
|
+
if (RETIRED_CHECK_IDS[id] != null) throw new Error(`defineRepo: ruleExempt.${id} names a retired rule (${RETIRED_CHECK_IDS[id]}): delete the entry`)
|
|
399
420
|
if (!builtinCheckIds().includes(id) && !(c.rules ?? []).some((r) => r.id === id))
|
|
400
421
|
throw new Error(`defineRepo: ruleExempt.${id} names no rule, the ids are in \`shipkit checks\``)
|
|
401
422
|
if (typeof reason !== 'string' || reason.trim() === '')
|
|
402
423
|
throw new Error(`defineRepo: ruleExempt.${id} needs a reason, an exemption nobody can explain is a silenced gate`)
|
|
403
424
|
}
|
|
404
|
-
// a word list entry silences or adds gate hits, so a malformed one dies
|
|
405
|
-
// here rather than matching nothing
|
|
406
|
-
const words = proseWordsProblems(c.prose?.words)
|
|
407
|
-
if (words.length > 0) throw new Error(`defineRepo: ${words[0]}`)
|
|
408
425
|
// a smoke typo would land as a run that visits nothing and exits 0
|
|
409
426
|
if (c.smoke != null) validateSmokeConfig(c.smoke)
|
|
410
427
|
// a leak-guard waiver is the one config row that can make a gate quieter,
|
|
@@ -432,5 +449,12 @@ export async function loadRepoConfig(root: string, o: { fresh?: boolean } = {}):
|
|
|
432
449
|
if (!existsSync(path)) throw new Error(`no repo.config.ts in ${root} — run: shipkit init`)
|
|
433
450
|
const mod = (await import(o.fresh === true ? `${path}?v=${Date.now()}-${Math.random().toString(36).slice(2)}` : path)) as { default?: RepoConfig }
|
|
434
451
|
if (mod.default == null) throw new Error(`${path}: must default-export defineRepo({...})`)
|
|
452
|
+
// the tools that never load the config read `vendored` off the text, so
|
|
453
|
+
// a value the text cannot show would omit one set of dirs here and
|
|
454
|
+
// another there
|
|
455
|
+
const loaded = (mod.default.vendored ?? []).map(normalizeVendored)
|
|
456
|
+
const read = vendoredOfText(readFileSync(path, 'utf8'))
|
|
457
|
+
if (loaded.join('\0') !== read.join('\0'))
|
|
458
|
+
throw new Error(`${path}: \`vendored\` must be a literal array of strings, the tools that never load the config read it as text (they read [${read.join(', ')}], the config says [${loaded.join(', ')}])`)
|
|
435
459
|
return mod.default
|
|
436
460
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# `bun lint` is the house linter, and `sk ci` runs it once [lint] is declared.
|
|
2
|
+
# [fmt] is the house style for a manual `bun fmt`: nothing formats in the gate.
|
|
3
|
+
[fmt]
|
|
4
|
+
semi = false
|
|
5
|
+
singleQuote = true
|
|
6
|
+
printWidth = 140
|
|
7
|
+
tabWidth = 4
|
|
8
|
+
# code only: prose keeps its own shape, and package.json belongs to its fixers
|
|
9
|
+
ignorePatterns = ["*.md", "*.json", "*.jsonc", "*.yaml", "*.yml", "*.toml"]
|
|
10
|
+
|
|
11
|
+
[lint]
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"js/ts.tsserver.automaticTypeAcquisition.enabled": false,
|
|
34
34
|
"js/ts.tsserver.checkNpmIsInstalled": false,
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
"oxc.enable": true,
|
|
36
|
+
// eslint off, and no formatter on save: formatting is a manual `bun fmt`
|
|
38
37
|
"eslint.enable": false,
|
|
39
38
|
"js/ts.format.enabled": false
|
|
40
39
|
}
|
|
@@ -111,3 +111,19 @@ export function removalSpan(text: string, arr: Extract<Node, { kind: 'array' }>,
|
|
|
111
111
|
}
|
|
112
112
|
return [item.start, item.end]
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
/** the spans that take several items out of one array at once, disjoint so
|
|
116
|
+
* they apply in any order. An item with a kept one after it goes with its own
|
|
117
|
+
* comma, and the removed run after the last kept item goes as one span from
|
|
118
|
+
* that item's end, so no comma is left dangling */
|
|
119
|
+
export function removalSpans(text: string, arr: Extract<Node, { kind: 'array' }>, idxs: Iterable<number>): [number, number][] {
|
|
120
|
+
const gone = new Set(idxs)
|
|
121
|
+
if (gone.size === 0) return []
|
|
122
|
+
const kept = arr.items.flatMap((_, i) => (gone.has(i) ? [] : [i]))
|
|
123
|
+
const lastKept = kept.at(-1) ?? -1
|
|
124
|
+
const spans: [number, number][] = []
|
|
125
|
+
for (const i of [...gone].sort((a, b) => a - b)) if (i < lastKept) spans.push(removalSpan(text, arr, i))
|
|
126
|
+
const tail = [...gone].filter((i) => i > lastKept).sort((a, b) => a - b)
|
|
127
|
+
if (tail.length > 0) spans.push([lastKept < 0 ? arr.items[tail[0]!]!.start : arr.items[lastKept]!.end, arr.items[tail.at(-1)!]!.end])
|
|
128
|
+
return spans
|
|
129
|
+
}
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
// package.json rewrite reformats the file it edits.
|
|
11
11
|
// Templates live beside this file in files/ and ship in the npm tarball, so
|
|
12
12
|
// none of them may name a machine-local tool or a private path.
|
|
13
|
-
import { existsSync,
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
14
14
|
import { dirname, join } from 'path'
|
|
15
15
|
import { defaultHubPort } from '../serve/hub-port.ts'
|
|
16
|
-
import { hubProblemMatcher } from '../problems/problem-line.ts'
|
|
17
16
|
|
|
18
17
|
const FILES = join(import.meta.dir, 'files')
|
|
19
18
|
|
|
@@ -36,15 +35,14 @@ export type ScaffoldOpts = {
|
|
|
36
35
|
claudePolicy?: boolean
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
/** every dep the house toolchain needs for the scripts below to run
|
|
40
|
-
|
|
38
|
+
/** every dep the house toolchain needs for the scripts below to run. The
|
|
39
|
+
* linter and the formatter are bun's own, declared in bunfig.toml */
|
|
40
|
+
export const TOOLCHAIN = ['typescript', '@types/bun'] as const
|
|
41
41
|
|
|
42
42
|
/** script bodies for a fresh repo whose only source dir is src/. A stack
|
|
43
|
-
* scaffold (vite, hono) adds dev/build on top
|
|
44
|
-
*
|
|
43
|
+
* scaffold (vite, hono) adds dev/build on top. No `lint` or `fmt`: a script
|
|
44
|
+
* of either name would shadow `bun lint` and `bun fmt` */
|
|
45
45
|
const SCRIPTS: Record<string, string> = {
|
|
46
|
-
lint: 'oxlint src/',
|
|
47
|
-
fmt: 'oxfmt src/',
|
|
48
46
|
typecheck: 'tsc --noEmit',
|
|
49
47
|
}
|
|
50
48
|
|
|
@@ -104,64 +102,12 @@ function put(root: string, rel: string, body: string): boolean {
|
|
|
104
102
|
return true
|
|
105
103
|
}
|
|
106
104
|
|
|
107
|
-
export type ErgonomicsOpts = {
|
|
108
|
-
/** repo name, substituted into the templates */
|
|
109
|
-
name: string
|
|
110
|
-
/** the port the hub actually listens on, resolved by the caller from the
|
|
111
|
-
* repo config (`serve.port`, else the name-derived default), so the task
|
|
112
|
-
* advertises a url something answers on */
|
|
113
|
-
port: number
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** the folder-open story, and the ONE file of it a repo shipkit did not create
|
|
117
|
-
* can be handed unasked.
|
|
118
|
-
*
|
|
119
|
-
* What lands is `tasks.json` alone. `settings.json` turns eslint off and hands
|
|
120
|
-
* formatting to an oxc extension, so writing it into a repo that lints with
|
|
121
|
-
* eslint silences the linter that repo chose: that is an answer about what the
|
|
122
|
-
* repo IS, and a repo with a history of its own already gave it. A tasks file
|
|
123
|
-
* is the opposite: with no `folderOpen` task nothing starts at all, so there
|
|
124
|
-
* is no existing answer to overrule.
|
|
125
|
-
*
|
|
126
|
-
* What lands INSIDE it is only what is true of every wired repo: the hub,
|
|
127
|
-
* whose script `init` writes, carrying the problem matcher that turns every
|
|
128
|
-
* check's findings into the problems pane. There is no type checker task, the
|
|
129
|
-
* hub runs `typecheck` like any other check. Every watcher a stack earns (a
|
|
130
|
-
* dev server, css) stays the analysis step `build/vscode-tasks.md` describes,
|
|
131
|
-
* and the caller says so out loud.
|
|
132
|
-
*
|
|
133
|
-
* Create-only: an adopted repo often carries a `.vscode/` from whatever it was
|
|
134
|
-
* forked from, and that one wins byte for byte (rule SK017 then repairs its
|
|
135
|
-
* hub task in place). Returns the paths it wrote. */
|
|
136
|
-
export function editorErgonomics(root: string, opts: ErgonomicsOpts): string[] {
|
|
137
|
-
// a symlinked .vscode is a dotfiles pattern pointing at a SHARED dir, and
|
|
138
|
-
// writing through it lands files outside the repo being wired
|
|
139
|
-
if (isLink(join(root, '.vscode'))) return []
|
|
140
|
-
const body = tasksTemplate({ NAME: opts.name, PORT: String(opts.port) })
|
|
141
|
-
return put(root, '.vscode/tasks.json', body) ? ['.vscode/tasks.json'] : []
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const isLink = (path: string): boolean => {
|
|
145
|
-
try {
|
|
146
|
-
return lstatSync(path).isSymbolicLink()
|
|
147
|
-
} catch {
|
|
148
|
-
return false
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** the tasks template, the hub's problem matcher rendered in from the one
|
|
153
|
-
* module the hub prints through, so the two can never disagree */
|
|
154
|
-
export function tasksTemplate(vars: Record<string, string>): string {
|
|
155
|
-
return template('vscode-tasks.json', vars).replace(/^(\s*)(.*)\{\{HUB_PROBLEM_MATCHER\}\}/m, (_all, indent: string, head: string) =>
|
|
156
|
-
`${indent}${head}${JSON.stringify(hubProblemMatcher(), null, 3).split('\n').join(`\n${indent}`)}`)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
105
|
export function scaffoldProject(root: string, opts: ScaffoldOpts): string[] {
|
|
160
106
|
const p = opts.print ?? console.log
|
|
161
107
|
// the hub resolves its port from repo.config, whose name `init` takes from
|
|
162
108
|
// the CALLER first and the manifest only after (init-repo.ts repoName). A
|
|
163
|
-
// dir that arrived holding a forked package.json would otherwise get
|
|
164
|
-
//
|
|
109
|
+
// dir that arrived holding a forked package.json would otherwise get an
|
|
110
|
+
// agent index advertising a port nothing listens on, so the caller that
|
|
165
111
|
// knows the name it is about to wire passes the port it resolves
|
|
166
112
|
const port = opts.port ?? defaultHubPort(packageName(root) ?? opts.name)
|
|
167
113
|
const vars = {
|
|
@@ -175,15 +121,32 @@ export function scaffoldProject(root: string, opts: ScaffoldOpts): string[] {
|
|
|
175
121
|
|
|
176
122
|
write('src/index.ts', 'export {}\n')
|
|
177
123
|
write('tsconfig.json', template('tsconfig.json', vars))
|
|
178
|
-
|
|
179
|
-
// settings.json only here: it turns eslint off and
|
|
180
|
-
//
|
|
124
|
+
write('bunfig.toml', template('bunfig.toml', vars))
|
|
125
|
+
// settings.json only here: it turns eslint off and keeps tsserver from
|
|
126
|
+
// formatting, which is a decision only the repo's creator gets to make
|
|
181
127
|
write('.vscode/settings.json', template('vscode-settings.json', vars))
|
|
182
128
|
write('.claude/skills/verify/SKILL.md', template('verify-skill.md', vars))
|
|
129
|
+
writeHouseFiles(write, vars, opts.claudePolicy === true, written, p)
|
|
130
|
+
|
|
131
|
+
addScripts(root, written, p)
|
|
132
|
+
addIgnoreLines(root, written)
|
|
133
|
+
|
|
134
|
+
if (written.length > 0) p(`ok: scaffolded ${written.length} file(s): ${written.slice(0, 6).join(', ')}${written.length > 6 ? `, +${written.length - 6} more` : ''}`)
|
|
135
|
+
// the skips are the interesting half on a half-built dir: a pre-existing
|
|
136
|
+
// .claude/settings.json means no attribution silencer, and that one only
|
|
137
|
+
// counts from the first commit on
|
|
138
|
+
if (skipped.length > 0) p(`skip: ${skipped.length} file(s) already present, left untouched: ${skipped.join(', ')}`)
|
|
139
|
+
return written
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** the half of the day-one shape a house brings, whatever the repo already
|
|
143
|
+
* is: the agent wiki its CLAUDE.md routes at, the reflections folder, and
|
|
144
|
+
* for a caller that declared a house the .claude/ policy files. Create-only */
|
|
145
|
+
function writeHouseFiles(write: (rel: string, body: string) => void, vars: Record<string, string>, claudePolicy: boolean, written: string[], p: (line: string) => void): void {
|
|
183
146
|
// a permission grant and a git-history policy are decisions about someone
|
|
184
147
|
// else's repo, so they land only for a caller that declared a house, and
|
|
185
148
|
// the grant is named out loud the one time it is written
|
|
186
|
-
if (
|
|
149
|
+
if (claudePolicy) {
|
|
187
150
|
write('.claude/settings.json', template('claude-settings.json', vars))
|
|
188
151
|
write('.claude/settings.local.json', template('claude-settings-local.json', vars))
|
|
189
152
|
if (written.includes('.claude/settings.local.json')) p(`ok: .claude/ policy written — agents are pre-approved for ${grantSummary()} in this repo, and the harness writes no attribution trailer`)
|
|
@@ -197,15 +160,18 @@ export function scaffoldProject(root: string, opts: ScaffoldOpts): string[] {
|
|
|
197
160
|
// gitignored by policy, so it never ships: it exists so an agent hitting
|
|
198
161
|
// friction with the toolchain finds the place to write it down
|
|
199
162
|
write('.shipkit/reflections/README.md', REFLECTIONS_README)
|
|
163
|
+
}
|
|
200
164
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
165
|
+
/** an ADOPTED repo (one with history) whose start declares a house: the
|
|
166
|
+
* include lines pull it into the house contract, so it gets the house half
|
|
167
|
+
* of the day-one shape and nothing that says what the repo IS. Create-only,
|
|
168
|
+
* so every file the repo already holds wins. Returns the paths it wrote */
|
|
169
|
+
export function adoptHouse(root: string, opts: { name: string; today?: string; print?: (line: string) => void }): string[] {
|
|
170
|
+
const p = opts.print ?? console.log
|
|
171
|
+
const vars = { NAME: opts.name, PORT: String(defaultHubPort(opts.name)), DATE: opts.today ?? new Date().toISOString().slice(0, 10) }
|
|
172
|
+
const written: string[] = []
|
|
173
|
+
writeHouseFiles((rel, body) => { if (put(root, rel, body)) written.push(rel) }, vars, true, written, p)
|
|
174
|
+
if (written.length > 0) p(`ok: adopted into the house, ${written.length} file(s) seeded: ${written.join(', ')}`)
|
|
209
175
|
return written
|
|
210
176
|
}
|
|
211
177
|
|
|
@@ -227,14 +193,14 @@ What broke, which page or lib, what you expected. Gitignored on purpose,
|
|
|
227
193
|
transient by design: it is harvested, then deleted.
|
|
228
194
|
`
|
|
229
195
|
|
|
230
|
-
/** the
|
|
196
|
+
/** the baseline scripts, added only where the key is free */
|
|
231
197
|
function addScripts(root: string, written: string[], p: (line: string) => void): void {
|
|
232
198
|
const path = join(root, 'package.json')
|
|
233
|
-
if (!existsSync(path)) return void p('WARN no package.json here — the
|
|
199
|
+
if (!existsSync(path)) return void p('WARN no package.json here — the typecheck script was not written')
|
|
234
200
|
const raw = readFileSync(path, 'utf8')
|
|
235
201
|
let pkg: { scripts?: Record<string, string> }
|
|
236
202
|
try { pkg = JSON.parse(raw) as { scripts?: Record<string, string> } }
|
|
237
|
-
catch (e) { return void p(`WARN package.json is unparseable (${String(e).slice(0, 80)}) — the
|
|
203
|
+
catch (e) { return void p(`WARN package.json is unparseable (${String(e).slice(0, 80)}) — the typecheck script was not written, repair it then re-run`) }
|
|
238
204
|
pkg.scripts ??= {}
|
|
239
205
|
let added = 0
|
|
240
206
|
for (const [k, v] of Object.entries(SCRIPTS)) if (pkg.scripts[k] == null) { pkg.scripts[k] = v; added++ }
|
|
@@ -13,7 +13,6 @@ import type { RepoSt } from './repo-st.ts'
|
|
|
13
13
|
import type { SlowSuite, CheckFacts, RecentRun } from './hub-types.ts'
|
|
14
14
|
import { renderMd, renderMdInline } from './md.ts'
|
|
15
15
|
import { nextRun } from './next-run.ts'
|
|
16
|
-
import { ProseHits } from './prose-hits-view.tsx'
|
|
17
16
|
import { stampWords, TimeAgo, TimeAhead } from './time-ago.tsx'
|
|
18
17
|
import { checkName, commandOf, docPath, editorHref, lastRun, rememberedFact, repoPath, whatFacts, type ShortPath } from './check-words.ts'
|
|
19
18
|
|
|
@@ -23,8 +22,9 @@ const SOURCE_CHIP: Record<CheckFacts['whenSource'], string> = {
|
|
|
23
22
|
off: 'repo.config.ts',
|
|
24
23
|
when: 'when (retired)',
|
|
25
24
|
ruleExempt: 'ruleExempt (retired)',
|
|
25
|
+
'hooks.disable': 'hooks.disable (retired)',
|
|
26
26
|
'check-file': 'the check file',
|
|
27
|
-
defaults: 'defaults',
|
|
27
|
+
defaults: 'defaults (retired)',
|
|
28
28
|
default: 'derived',
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -33,7 +33,8 @@ const SOURCE_WORD: Record<CheckFacts['whenSource'], string> = {
|
|
|
33
33
|
when: 'turned off by the retired `when` key, which `shipkit ci` ports to `off` (SK029)',
|
|
34
34
|
ruleExempt: 'turned off by the retired `ruleExempt` key, which `shipkit ci` ports to `off` (SK029)',
|
|
35
35
|
'check-file': 'declared by the check file itself: every context it runs in, no default',
|
|
36
|
-
|
|
36
|
+
'hooks.disable': 'turned off by the retired `hooks.disable` key, which `shipkit ci` ports to `off` (SK029)',
|
|
37
|
+
defaults: 'turned off by the retired `defaults: { <slot>: false }`, which `shipkit ci` ports to `off` (SK029)',
|
|
37
38
|
default: 'a row with no file of its own: the code that derives it writes every context',
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -359,7 +360,6 @@ export const CheckCard = observer(({ app, id }: { app: RepoSt; id: string }) =>
|
|
|
359
360
|
</div>
|
|
360
361
|
<WhereItRuns app={app} u={u} />
|
|
361
362
|
{u.build && u.checkId === 'test' ? <SlowSuites app={app} /> : null}
|
|
362
|
-
{u.checkId === 'SK014' ? <ProseHits app={app} /> : null}
|
|
363
363
|
<LastOutput app={app} lines={u.lastLines} />
|
|
364
364
|
</div>
|
|
365
365
|
)
|
|
@@ -100,8 +100,7 @@ export class ChecksSvc {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/** ONE write path for the switch
|
|
104
|
-
* lists alike: it edits repo.config.ts in place and leaves it dirty to be
|
|
103
|
+
/** ONE write path for the switch and the slow suite form alike: it edits repo.config.ts in place and leaves it dirty to be
|
|
105
104
|
* reviewed. Resolves true when the server took the edit */
|
|
106
105
|
async writeGate(edit: Record<string, unknown>, said: string): Promise<boolean> {
|
|
107
106
|
if (this.gateBusy) return false
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
<title>home</title>
|
|
7
7
|
<link rel="stylesheet" href="./hub.css" />
|
|
8
8
|
<link rel="stylesheet" href="./project-list.css" />
|
|
9
|
-
<link rel="stylesheet" href="./prose-hits.css" />
|
|
10
9
|
<link rel="stylesheet" href="./home-strip.css" />
|
|
11
10
|
<link rel="stylesheet" href="./planned.css" />
|
|
12
11
|
<link rel="stylesheet" href="./when.css" />
|
|
@@ -145,8 +145,6 @@ export type GateBlock = {
|
|
|
145
145
|
tests: { budgetMs?: number; slow?: SlowSuite[]; serial?: { file: string; why: string }[] }
|
|
146
146
|
declaredOutside: boolean
|
|
147
147
|
dirty: boolean
|
|
148
|
-
/** the prose words block inside `prose: {` (core/when-block.ts) */
|
|
149
|
-
wordsState?: 'missing-file' | 'missing-block' | 'stale' | 'current' | 'no-prose'
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
/** one check finishing, as the run stream sends it */
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
<title>hub</title>
|
|
7
7
|
<link rel="stylesheet" href="./hub.css" />
|
|
8
8
|
<link rel="stylesheet" href="./project-list.css" />
|
|
9
|
-
<link rel="stylesheet" href="./prose-hits.css" />
|
|
10
9
|
<link rel="stylesheet" href="./home-strip.css" />
|
|
11
10
|
<link rel="stylesheet" href="./planned.css" />
|
|
12
11
|
<link rel="stylesheet" href="./when.css" />
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/* the project list, the page's left panel. Its classes are pj
|
|
2
|
-
sheet of the page already styles .plist (prose-hits.css). It fills its
|
|
1
|
+
/* the project list, the page's left panel. Its classes are pj*. It fills its
|
|
3
2
|
panel (hub-app.tsx, react-resizable-panels), whose width is the only one:
|
|
4
3
|
this sheet sizes the rows and never the list. Colours are hub.css tokens only, spacing is its scale:
|
|
5
4
|
tests/hub-css-invariants.test.ts reads this sheet too. Every icon is 18px
|