rvlib-shipkit 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +17 -17
  3. package/package.json +3 -2
  4. package/src/assets/gen/comfyts-gen.ts +12 -8
  5. package/src/assets/server/studio-server.ts +14 -2
  6. package/src/check.ts +73 -103
  7. package/src/checks/SK001-package-json-shape.check.ts +11 -6
  8. package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
  9. package/src/checks/SK003-claude-md-shape.check.ts +7 -3
  10. package/src/checks/SK004-gitignore-policy.check.ts +7 -4
  11. package/src/checks/SK005-rvlib-versions.check.ts +53 -21
  12. package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
  13. package/src/checks/SK007-github-visibility.check.ts +4 -2
  14. package/src/checks/SK008-reviewer-agent.check.ts +6 -4
  15. package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
  16. package/src/checks/SK010-undeclared-unit.check.ts +4 -0
  17. package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
  18. package/src/checks/SK012-config-barrel-import.check.ts +5 -1
  19. package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
  20. package/src/checks/SK014-prose-ste.check.ts +4 -0
  21. package/src/checks/SK015-nested-repo.check.ts +4 -2
  22. package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
  23. package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
  24. package/src/checks/SK018-test-files-run.check.ts +6 -2
  25. package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
  26. package/src/checks/SK020-self-spawning-check.check.ts +4 -0
  27. package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
  28. package/src/checks/SK024-identity-colors.check.ts +5 -3
  29. package/src/checks/SK025-release-drift.check.ts +4 -5
  30. package/src/checks/SK026-asks-autoclose.check.ts +4 -4
  31. package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
  32. package/src/checks/SK028-readme.check.ts +5 -1
  33. package/src/checks/SK029-explicit-check-files.check.ts +95 -0
  34. package/src/checks/SK030-cache-audit.check.ts +34 -0
  35. package/src/checks/SK031-trace-audit.check.ts +39 -0
  36. package/src/checks/SK032-ignored-inputs.check.ts +79 -0
  37. package/src/cli/cmd_Asks.ts +5 -5
  38. package/src/cli/cmd_Assets.ts +17 -13
  39. package/src/cli/cmd_Bump.ts +12 -11
  40. package/src/cli/cmd_CacheAudit.ts +43 -0
  41. package/src/cli/cmd_Check.ts +8 -7
  42. package/src/cli/cmd_Checks.ts +7 -6
  43. package/src/cli/cmd_Ci.ts +12 -11
  44. package/src/cli/cmd_Commands.ts +16 -13
  45. package/src/cli/cmd_ConfigKeys.ts +7 -6
  46. package/src/cli/cmd_Contracts.ts +3 -3
  47. package/src/cli/cmd_CrossRepoActivity.ts +8 -6
  48. package/src/cli/cmd_Fleet.ts +9 -7
  49. package/src/cli/cmd_GlobalProcesses.ts +120 -7
  50. package/src/cli/cmd_Home.ts +26 -19
  51. package/src/cli/cmd_Hooks.ts +6 -5
  52. package/src/cli/cmd_Init.ts +5 -4
  53. package/src/cli/cmd_Keys.ts +20 -8
  54. package/src/cli/cmd_Mv.ts +7 -6
  55. package/src/cli/cmd_Needs.ts +4 -3
  56. package/src/cli/cmd_Overview.ts +5 -4
  57. package/src/cli/cmd_Pb.ts +2 -2
  58. package/src/cli/cmd_Planned.ts +6 -5
  59. package/src/cli/cmd_Prose.ts +5 -5
  60. package/src/cli/cmd_Reflect.ts +7 -7
  61. package/src/cli/cmd_Release.ts +6 -5
  62. package/src/cli/cmd_Releases.ts +5 -4
  63. package/src/cli/cmd_Serve.ts +6 -4
  64. package/src/cli/cmd_Smoke.ts +5 -4
  65. package/src/cli/cmd_Start.ts +7 -6
  66. package/src/cli/cmd_Status.ts +4 -3
  67. package/src/cli/cmd_TraceAudit.ts +62 -0
  68. package/src/cli/cmd_Tui.ts +3 -2
  69. package/src/cli/cmd_When.ts +71 -0
  70. package/src/cli/cmd_Wiki.ts +4 -4
  71. package/src/cli.ts +11 -2
  72. package/src/collections/checks.ts +2 -0
  73. package/src/collections/collection-types.ts +12 -4
  74. package/src/collections/providers.ts +5 -3
  75. package/src/core/app-icon.ts +154 -0
  76. package/src/core/audit-report.ts +21 -0
  77. package/src/core/cache-audit.ts +72 -0
  78. package/src/core/canonical-scripts.ts +2 -2
  79. package/src/core/check-ctx.ts +14 -5
  80. package/src/core/check-runner.ts +43 -23
  81. package/src/core/config-rewrite.ts +37 -24
  82. package/src/core/discover-checks.ts +23 -2
  83. package/src/core/floor-resolve.ts +5 -7
  84. package/src/core/git-root.ts +13 -0
  85. package/src/core/ignored-block.ts +94 -0
  86. package/src/core/ignored-inputs.ts +47 -0
  87. package/src/core/jobs.ts +9 -2
  88. package/src/core/legacy-shapes.ts +39 -10
  89. package/src/core/one-lib.ts +59 -4
  90. package/src/core/one-way-in-policy.ts +2 -2
  91. package/src/core/outcome-words.ts +18 -2
  92. package/src/core/port-check-cli.ts +52 -0
  93. package/src/core/port-check-file.ts +295 -0
  94. package/src/core/port-check-files.ts +31 -0
  95. package/src/core/read-set.ts +97 -9
  96. package/src/core/retention.ts +0 -14
  97. package/src/core/run-check.ts +12 -9
  98. package/src/core/run-ci.ts +14 -5
  99. package/src/core/run-fix.ts +30 -23
  100. package/src/core/run-fixers.ts +48 -5
  101. package/src/core/runs-matrix.ts +179 -0
  102. package/src/core/runs.ts +242 -0
  103. package/src/core/trace-audit.ts +120 -0
  104. package/src/core/update-policy.ts +20 -0
  105. package/src/core/when-block.ts +29 -30
  106. package/src/core/when.ts +41 -103
  107. package/src/core/wire.ts +10 -14
  108. package/src/fleet/scan-fleet.ts +20 -4
  109. package/src/fleet/update-fleet.ts +6 -6
  110. package/src/home/gui-app.ts +24 -27
  111. package/src/home/home-launchd.ts +20 -4
  112. package/src/home/home-routes.ts +12 -1
  113. package/src/home/home-start.ts +11 -5
  114. package/src/home/hubs.ts +5 -5
  115. package/src/home/open-app.ts +2 -2
  116. package/src/home/pm.ts +8 -0
  117. package/src/home/run-pm.ts +1 -0
  118. package/src/home/serve-web.ts +2 -2
  119. package/src/home/supervise.ts +24 -1
  120. package/src/home/via-hub.ts +14 -0
  121. package/src/hooks/run-hooks.ts +48 -4
  122. package/src/init-repo.ts +6 -6
  123. package/src/ledger/db.ts +2 -13
  124. package/src/ledger/gate-checks.ts +133 -61
  125. package/src/ledger/git-key.ts +153 -31
  126. package/src/ledger/git-scan.ts +5 -2
  127. package/src/ledger/ledger.ts +136 -21
  128. package/src/ledger/store.ts +14 -0
  129. package/src/ledger/toolchain-identity.ts +248 -45
  130. package/src/plan/repo-source.ts +18 -39
  131. package/src/problems/extract.ts +3 -0
  132. package/src/release/run-release.ts +5 -0
  133. package/src/repo-config.ts +46 -54
  134. package/src/rules/rule.ts +2 -2
  135. package/src/scaffold/files/agent-coding.md +1 -1
  136. package/src/scaffold/files/agent-index.md +1 -1
  137. package/src/scaffold/files/verify-skill.md +2 -2
  138. package/src/serve/hub-app/activity-st.ts +5 -4
  139. package/src/serve/hub-app/activity-view.tsx +1 -1
  140. package/src/serve/hub-app/admin-svc.ts +36 -3
  141. package/src/serve/hub-app/admin-view.tsx +46 -12
  142. package/src/serve/hub-app/check-view.tsx +66 -46
  143. package/src/serve/hub-app/check-words.ts +1 -11
  144. package/src/serve/hub-app/checks-svc.ts +14 -35
  145. package/src/serve/hub-app/col-card.tsx +1 -44
  146. package/src/serve/hub-app/col-tables-view.tsx +84 -11
  147. package/src/serve/hub-app/col-tables.ts +27 -0
  148. package/src/serve/hub-app/cols-svc.ts +5 -4
  149. package/src/serve/hub-app/dock-st.ts +3 -2
  150. package/src/serve/hub-app/dock-view.tsx +36 -0
  151. package/src/serve/hub-app/dock.css +6 -0
  152. package/src/serve/hub-app/float-cards.tsx +30 -0
  153. package/src/serve/hub-app/floating.tsx +41 -4
  154. package/src/serve/hub-app/git-st.ts +42 -0
  155. package/src/serve/hub-app/git-view.tsx +61 -0
  156. package/src/serve/hub-app/hash-route.ts +45 -1
  157. package/src/serve/hub-app/header-bar.tsx +62 -72
  158. package/src/serve/hub-app/health-svc.ts +3 -2
  159. package/src/serve/hub-app/home-st.ts +14 -2
  160. package/src/serve/hub-app/home-strip.css +43 -12
  161. package/src/serve/hub-app/home-strip.tsx +115 -64
  162. package/src/serve/hub-app/home.html +1 -0
  163. package/src/serve/hub-app/hub-app.tsx +32 -8
  164. package/src/serve/hub-app/hub-types.ts +5 -2
  165. package/src/serve/hub-app/hub.css +89 -63
  166. package/src/serve/hub-app/icons.tsx +11 -0
  167. package/src/serve/hub-app/index.html +1 -0
  168. package/src/serve/hub-app/keys.ts +15 -4
  169. package/src/serve/hub-app/live-svc.ts +3 -2
  170. package/src/serve/hub-app/machine-st.ts +18 -22
  171. package/src/serve/hub-app/plan-view.tsx +51 -0
  172. package/src/serve/hub-app/planned-st.ts +9 -4
  173. package/src/serve/hub-app/processes-view.tsx +45 -0
  174. package/src/serve/hub-app/project-list.css +4 -1
  175. package/src/serve/hub-app/project-list.tsx +6 -3
  176. package/src/serve/hub-app/projects-svc.ts +25 -10
  177. package/src/serve/hub-app/prose-hits-st.ts +3 -2
  178. package/src/serve/hub-app/reach.ts +36 -0
  179. package/src/serve/hub-app/readme-svc.ts +3 -2
  180. package/src/serve/hub-app/repo-sections.ts +131 -0
  181. package/src/serve/hub-app/repo-st.ts +70 -14
  182. package/src/serve/hub-app/sidebar.tsx +17 -42
  183. package/src/serve/hub-app/timeline-svc.ts +3 -2
  184. package/src/serve/hub-app/tree-build.ts +151 -155
  185. package/src/serve/hub-app/tree-row.tsx +55 -12
  186. package/src/serve/hub-app/tree-svc.ts +97 -56
  187. package/src/serve/hub-app/when-view.tsx +89 -0
  188. package/src/serve/hub-app/when.css +17 -0
  189. package/src/serve/hub-runtime.ts +7 -18
  190. package/src/serve/project-gutter-route.ts +22 -1
  191. package/src/serve/run-serve.ts +1 -1
  192. package/src/serve/serve-hub.ts +4 -14
  193. package/src/suite/judge-suites.ts +6 -0
  194. package/src/trace/interpose.c +655 -0
  195. package/src/trace/read-trace.ts +485 -0
  196. package/src/trace/tracer.ts +282 -0
  197. package/src/serve/hub-app/processes-svc.ts +0 -63
  198. package/src/serve/hub-app/repo-sections.tsx +0 -61
  199. package/src/serve/repo-processes.ts +0 -89
package/src/ledger/db.ts CHANGED
@@ -26,20 +26,9 @@
26
26
  import { Database } from 'bun:sqlite'
27
27
  import { existsSync, mkdirSync } from 'fs'
28
28
  import { dirname, join } from 'path'
29
- import { gitRoot } from '../core/git-root.ts'
30
- import { LAYOUT } from '../core/layout.ts'
31
29
 
32
- /** the one dir everything this repo remembers hangs off.
33
- *
34
- * `--git-common-dir` rather than `--git-dir`: a linked worktree has its own
35
- * git dir but shares the common one, and that sharing is safe because every
36
- * key component is content or a version string. A tree that is not a git
37
- * repo at all still gets one, under `.shipkit/cache/`, because a scaffold in
38
- * progress must not crash on it. */
39
- export function shipkitRoot(root: string): string {
40
- const dir = gitRoot(root)?.commonDir
41
- return dir == null ? join(root, LAYOUT.cache) : join(dir, 'shipkit')
42
- }
30
+ import { shipkitRoot } from '../core/git-root.ts'
31
+ export { shipkitRoot }
43
32
 
44
33
  export const DB_FILE = 'shipkit.sqlite'
45
34
 
@@ -11,27 +11,29 @@
11
11
  // check script, a `*.check.ts` with no check object). The declaration always lives WITH the check, never in a table
12
12
  // over here: a table beside the registry is a second source of truth that
13
13
  // drifts the first time somebody adds one.
14
- import { relative, resolve } from 'path'
14
+ import { existsSync } from 'fs'
15
+ import { join, relative, resolve } from 'path'
15
16
  import type { CheckCtx } from '../core/check-ctx.ts'
16
17
  import { type RepoConfig } from '../repo-config.ts'
18
+ import { auditedExtras, ignoredFor, readAuditReport } from '../core/audit-report.ts'
17
19
  import { listRepoFiles, resolveFloor, slotDescription, type FloorResolution } from '../core/floor.ts'
18
20
  import { discoverLegacyChecks } from '../core/discover-legacy-checks.ts'
19
21
  import { checkFileRunBy } from '../core/legacy-shapes.ts'
20
22
  import { ruleCacheExtra } from '../rules/rule.ts'
21
23
  import { discoverChecks } from '../core/discover-checks.ts'
22
- import { checkCacheExtra, describeFirst, type TaskDef, type CheckFrom, type CheckEnv } from '../check.ts'
24
+ import { checkCacheExtra, describeFirst, type TaskDef, type CheckFrom, type CheckEnv, type CheckDef } from '../check.ts'
23
25
  import { CHECK_TIMEOUT_MS, type RunnableCheck, type CheckBody } from '../core/check-runner.ts'
24
- import { ttlMsOf } from '../core/wire.ts'
26
+ import { FOREVER, ttlMsOf } from '../core/wire.ts'
25
27
  import { budgetOf } from '../core/budget.ts'
26
- import { whenOfLegacyCheck, whenOfDisabledSlot, whenOfRule, whenOfTask, whenOfCheck, type WhenDecl } from '../core/when.ts'
27
- import type { CheckDecl } from './git-key.ts'
28
+ import { repoOff, withRepoOff, whenSummary, type WhenDecl } from '../core/when.ts'
29
+ import { cacheText, filesText, NOWHERE, sourced, type CacheDecl, type ResolvedRuns, type Runs } from '../core/runs.ts'
30
+ import { isNamesInput, type CheckDecl } from './git-key.ts'
28
31
  import { populationOf } from '../problems/problem-line.ts'
29
32
  import { rawAccessInClosure, rawAccessLine } from '../core/raw-access.ts'
30
33
  import type { RvtConfig } from '../ts/config.ts'
31
34
  import { LIB_ROOT } from '../core/lib-root.ts'
32
35
  import type { DiscoveredCheck } from '../core/discover-checks.ts'
33
36
  import { declaredTestUnits, inputsOutsideUnits, isTestFile } from '../suite/test-units.ts'
34
- import type { PlanDecl } from '../plan/cadence.ts'
35
37
 
36
38
  /** why an undeclared `run` check cannot be keyed on its reads, or null when it
37
39
  * can. A built-in is walked inside the toolkit, a repo check inside the repo
@@ -74,18 +76,6 @@ export async function allTasks(c: RepoConfig, root: string): Promise<FloorResolu
74
76
  /** one gate check with everything a reader asks about it, not only what the
75
77
  * key layer asks. The cache half stays in `decl` so `CheckDecl` never grows
76
78
  * fields the key layer must then decide to ignore. */
77
- /** a check's `planOff`, read now; one that throws stays planned, and says so */
78
- function planOffOf(id: string, fn: ((env: CheckEnv) => string | null) | undefined, env: CheckEnv): { planOff?: string } {
79
- if (fn == null) return {}
80
- try {
81
- const why = fn(env)
82
- return why == null ? {} : { planOff: why }
83
- } catch (e) {
84
- console.error(`WARN ${id} planOff threw, so it stays planned: ${(e as Error).message}`)
85
- return {}
86
- }
87
- }
88
-
89
79
  export type CheckRow = {
90
80
  decl: CheckDecl
91
81
  /** the display name an operator reads (its title, else the id) */
@@ -126,16 +116,16 @@ export type CheckRow = {
126
116
  * then re-runs the check. `shipkit checks --fixable` is the catalog of
127
117
  * everything in this repo that repairs itself. */
128
118
  fixable: boolean
129
- /** WHERE IT RUNS, the one reading of the `when` key, of the three
130
- * spellings it replaces, and of a floor slot turned off (core/when.ts) */
119
+ /** WHERE IT RUNS, every context resolved: what the check file declared,
120
+ * or what the code deriving the row wrote, with the repo's `off`
121
+ * override laid over it (core/runs.ts, core/when.ts) */
122
+ runs: ResolvedRuns
123
+ /** how its answer is kept, as one short cell (core/runs.ts cacheText) */
124
+ cache: string
125
+ /** the coarse word the runs read as, for the surfaces that show one */
131
126
  when: WhenDecl
132
127
  /** what it waits on, null when it waits on nothing */
133
128
  deps: string[] | null
134
- /** when the planner runs it on its own (check.ts `every`, `cron`,
135
- * `whenInactive`). Absent: nothing declared, the planner's defaults */
136
- plan?: PlanDecl
137
- /** why the plan leaves this check out in this repo (check.ts `planOff`) */
138
- planOff?: string
139
129
  /** whose check it is: `shipkit` = it ships with the toolkit (a built-in
140
130
  * SK check, the contracts pass, a floor slot nothing in the repo filled),
141
131
  * `repo` = this repo declared it (a package.json script behind a floor
@@ -178,7 +168,7 @@ function runnableOf(row: CheckRow, body: CheckBody, o: { fix?: unknown; pointer?
178
168
  owner: row.owner ?? row.decl.id,
179
169
  blocking: row.blocking,
180
170
  build: row.build,
181
- when: row.when,
171
+ runs: row.runs,
182
172
  deps: row.deps ?? [],
183
173
  group: row.group,
184
174
  timeoutMs: row.timeoutMs ?? (row.build ? BUILD_TIMEOUT_MS : CHECK_TIMEOUT_MS),
@@ -211,6 +201,59 @@ function rootTestKey(root: string, c: RepoConfig, floor: FloorResolution): { inp
211
201
  const taskDescription = (t: TaskDef): string =>
212
202
  describeFirst(t.description, t.cmd == null ? null : `\`${t.cmd.join(' ')}\` exits 0`, `the ${t.id} task ${t.cmd == null ? 'passes' : 'exits 0'}`)
213
203
 
204
+ /** the default range a repo row nothing can key runs on, so it is asked
205
+ * about once a day and never in step with its siblings */
206
+ const DAILY: [string, string] = ['20h', '28h']
207
+
208
+ /** the runs of a row with no file to declare them in (a floor slot, a config
209
+ * task, a legacy rule or script), written out in full with the reason. It
210
+ * keeps what the planner and the gate did with such a row before the
211
+ * declaration was explicit: a ttl is the cadence ('once' for a forever
212
+ * fact), a save moves a keyed row or a fact, a gate build step nothing keys
213
+ * reruns after every save burst, and any other row nothing keys runs about
214
+ * once a day */
215
+ function derivedRuns(o: { gate: boolean; build: boolean; inputs?: string[]; ttl?: string; kind: string; repair: boolean }): ResolvedRuns {
216
+ const keyed = o.inputs != null
217
+ const ttl = o.ttl != null && o.ttl !== '' ? o.ttl : null
218
+ const fact = ttl === FOREVER
219
+ const parked = o.build && !o.gate
220
+ const onClock: Runs['onClock'] = parked ? false : fact ? 'once' : ttl != null ? { every: ttl, inactive: 'wait' } : o.build || keyed ? false : { every: DAILY, inactive: 'wait' }
221
+ const onSave: Runs['onSave'] = parked ? false : fact || (keyed && ttl == null) ? 'moved' : ttl == null && o.build ? 'burst' : false
222
+ const r: Runs = { ...NOWHERE, onCi: o.gate, onFull: o.gate, onRelease: o.gate, onHealth: !o.build, onSave, onClock }
223
+ const fix = !o.repair ? 'none' : o.gate ? 'ci' : 'manual'
224
+ const clock = onClock === false ? '' : onClock === 'once' ? ', once while its fact is unknown' : `, on the clock every ${ttl ?? DAILY.join('..')}`
225
+ const save = onSave === 'moved' ? ', the daemon when its key moves' : onSave === 'burst' ? ', the daemon after every save burst since nothing keys it' : ''
226
+ const where = parked ? 'parked: only --with or --only names it' : `${o.gate ? 'every gate' : 'no gate'}${o.build ? ', never the health pass' : ' and the health pass'}${save}${clock}`
227
+ return sourced(r, fix, 'derived', `${o.kind}: ${where}`)
228
+ }
229
+
230
+ /** the cache half of a check file, as the key layer reads it */
231
+ function declOfCache(cache: CacheDecl, env: CheckEnv): { inputs?: string[]; ttl?: string; never?: string; key?: string; reads?: boolean } {
232
+ switch (cache.by) {
233
+ case 'files': return { inputs: cache.inputs, key: cache.key?.(env) }
234
+ case 'reads': return { reads: true }
235
+ case 'ttl': return { ttl: cache.span, key: cache.key?.(env) }
236
+ case 'fact': return { ttl: FOREVER, key: cache.key(env) }
237
+ case 'never': return { never: cache.why }
238
+ }
239
+ }
240
+
241
+ /** a legacy row's cache, as one cell */
242
+ const legacyCacheText = (d: { inputs?: string[]; ttl?: string; never?: string }): string =>
243
+ d.inputs != null ? filesText(d.inputs) : d.ttl != null && d.ttl !== '' ? (d.ttl === FOREVER ? 'fact' : `ttl ${d.ttl}`) : d.never != null ? `never: ${d.never}` : 'never: nothing declared'
244
+
245
+ /** a check file's own runs, resolved: its declaration, n/a where it does not
246
+ * apply, then the repo's override */
247
+ function fileRuns(def: CheckDef, id: string, c: RepoConfig, env: CheckEnv): ResolvedRuns {
248
+ const own = sourced(def.runs, def.fix, 'check file')
249
+ let na: string | null = null
250
+ if (typeof def.applies === 'function') {
251
+ try { na = def.applies(env) } catch (e) { console.error(`WARN ${id} applies threw, so it applies: ${(e as Error).message}`) }
252
+ }
253
+ const base = na == null ? own : { ...own, off: `n/a here: ${na}` }
254
+ return withRepoOff(base, repoOff(c, id))
255
+ }
256
+
214
257
  /** THE enumeration. Everything below reads it, nothing else walks. */
215
258
  export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts = {}): Promise<GateCheck[]> {
216
259
  const out: GateCheck[] = []
@@ -249,13 +292,29 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
249
292
  // what it likes, and a gate that guesses which one is the tests will miss
250
293
  // one.
251
294
  const rootTest = rootTestKey(root, c, floor)
295
+ // a task that declares nothing is keyed on the whole work tree, plus the
296
+ // gitignored paths it reads. The work tree sees no ignored file, so those
297
+ // are named: by the config, and by the kept traced audit (SK031), whose
298
+ // findings join the key of the task that read them before a config write
299
+ // carries them to other clones. One missing from disk is left out, and
300
+ // its appearing moves the key
301
+ const onDisk = (p: string): boolean => existsSync(join(root, isNamesInput(p) ? p.slice(0, -3) : p))
302
+ const audit = readAuditReport(root)
303
+ const extrasOf = (id: string): string[] => [...new Set([...ignoredFor(c.ignoredInputs, id), ...auditedExtras(audit, id)])].filter(onDisk).sort()
252
304
  for (const raw of [...floor.tasks, ...(c.tasks ?? []).filter((t) => foldedInto.get(t.id) !== t)]) {
253
305
  const t: TaskDef = full ? { ...raw, env: { ...raw.env, [FULL_TESTS_ENV]: '1' } } : raw
254
306
  const own = t.id === 'test' && floor.tasks.includes(raw) ? rootTest : null
307
+ const declared = own?.inputs ?? t.inputs
308
+ // the declared extras join a declared list too: a list names what the
309
+ // task reads in the tree, and the audit found these outside it
310
+ const extras = extrasOf(t.id)
311
+ const inputs = declared == null && t.ttl == null && t.never == null && t.cmd != null ? ['.', ...extras] : declared == null ? declared : [...declared, ...extras]
312
+ const kind = floor.tasks.includes(raw) ? 'a floor slot' : 'a repo.config.ts task'
313
+ const runs = withRepoOff(derivedRuns({ gate: t.optIn !== true, build: true, inputs, ttl: t.ttl, kind, repair: false }), repoOff(c, t.id))
255
314
  const row: CheckRow = {
256
315
  decl: {
257
316
  id: t.id,
258
- inputs: own?.inputs ?? t.inputs,
317
+ inputs,
259
318
  ttl: t.ttl,
260
319
  never: t.never,
261
320
  argv: t.cmd,
@@ -266,7 +325,7 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
266
325
  description: taskDescription(t),
267
326
  // an optIn task is off the default run, which is the same shape as
268
327
  // a check that opted out of the gate
269
- gate: t.optIn !== true,
328
+ gate: runs.off == null && runs.onCi,
270
329
  build: true,
271
330
  // a build step IS the build: it has no non-blocking mode
272
331
  blocking: true,
@@ -278,7 +337,9 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
278
337
  // a build step runs a TOOL, and repairing what a tool reports is
279
338
  // that tool's own job (a formatter, a codemod), never the gate's
280
339
  fixable: false,
281
- when: whenOfTask(c, t),
340
+ runs,
341
+ cache: legacyCacheText({ inputs, ttl: t.ttl, never: t.never }),
342
+ when: whenSummary(runs, repoOff(c, t.id), false),
282
343
  deps: t.deps ?? null,
283
344
  from: taskFrom(t.id),
284
345
  }
@@ -293,6 +354,7 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
293
354
  // no command, which is why it declares `never` rather than joining the
294
355
  // burn-down of checks nobody has keyed yet.
295
356
  for (const id of floor.disabled) {
357
+ const runs = sourced(NOWHERE, 'none', 'repo.config.ts', null, `defaults.${id}: false`)
296
358
  const row: CheckRow = {
297
359
  decl: { id, never: `turned off by defaults.${id}: false, so there is no command to key` },
298
360
  title: id,
@@ -306,7 +368,9 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
306
368
  budgetMs: budgetOf(id, undefined, { build: true, cmd: false, world: false }, c.budgets),
307
369
  optIn: false,
308
370
  fixable: false,
309
- when: whenOfDisabledSlot(id),
371
+ runs,
372
+ cache: `never: turned off by defaults.${id}: false`,
373
+ when: whenSummary(runs, { reason: `defaults.${id}: false`, source: 'defaults' }, false),
310
374
  deps: null,
311
375
  // the repo wrote the line that turned it off
312
376
  from: 'repo',
@@ -319,46 +383,47 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
319
383
  const configs = new Map<string, RvtConfig>()
320
384
  const texts = new Map<string, string>()
321
385
  for (const u of checks) {
322
- const w = whenOfCheck(c, u)
323
- // an in process body declaring nothing is keyed on what it reads
324
- // through its context (git-key.ts READS). The tier is part of that
325
- // question: a body can read the env, and a quick answer must never
326
- // stand in for a full one
327
- const undeclared = u.def.run != null && u.def.inputs == null && u.def.ttl == null && u.def.never == null
328
- // conservative: any module its body can reach that touches the disk or
329
- // a process around `c` keeps it off its reads (core/raw-access.ts)
330
- const raw = undeclared ? rawAccessOf(root, u, configs, texts) : null
331
- const reads = undeclared && raw == null
332
- const extra = checkCacheExtra({ id: u.id, cacheKey: u.def.cacheKey }, env, w)
386
+ const runs = fileRuns(u.def, u.id, c, env)
387
+ const cache = declOfCache(u.def.cache, env)
388
+ // a body keyed on what it reads through its context (git-key.ts
389
+ // READS). The tier is part of that question: a body can read the env,
390
+ // and a quick answer must never stand in for a full one. Any module
391
+ // its body can reach that touches the disk or a process around `c`
392
+ // contradicts the declaration (core/raw-access.ts), and says so loud
393
+ const raw = cache.reads === true && u.def.run != null ? rawAccessOf(root, u, configs, texts) : null
394
+ const extra = checkCacheExtra(cache.key, runs.off)
333
395
  const row: CheckRow = {
334
396
  decl: {
335
397
  id: u.id,
336
- inputs: u.def.inputs,
398
+ inputs: cache.inputs,
337
399
  code: u.rel == null ? undefined : [u.rel],
338
- ttl: u.def.ttl,
339
- never: u.def.never,
400
+ // a built-in's code is the toolkit's: keyed on its own closure
401
+ // there, never on the toolkit as a whole
402
+ libCode: u.rel == null ? [u.path] : undefined,
403
+ ttl: cache.ttl,
404
+ never: cache.never,
340
405
  argv: u.def.cmd,
341
- extra: reads ? `${extra ?? ''}|tier=${full ? 'full' : 'quick'}` : extra,
406
+ extra: cache.reads === true ? `${extra ?? ''}|tier=${full ? 'full' : 'quick'}` : extra,
342
407
  writes: u.def.writes,
343
- ...(reads ? { reads: true } : {}),
344
- ...(raw != null ? { uncacheable: raw } : {}),
408
+ ...(cache.reads === true && raw == null ? { reads: true } : {}),
409
+ ...(raw != null ? { declError: `declares cache reads, but ${raw}` } : {}),
345
410
  },
346
411
  title: u.title,
347
412
  description: u.description,
348
- gate: w.at !== 'health',
413
+ gate: runs.off == null && runs.onCi,
349
414
  build: u.def.build === true,
350
415
  blocking: u.def.blocking,
351
416
  owner: u.owner,
352
417
  group: u.def.group ?? null,
353
418
  timeoutMs: u.def.timeoutMs ?? null,
354
- budgetMs: budgetOf(u.id, u.def.budgetMs, { build: u.def.build === true, cmd: u.def.cmd != null, world: u.def.ttl != null || u.def.never != null }, c.budgets),
419
+ budgetMs: budgetOf(u.id, u.def.budgetMs, { build: u.def.build === true, cmd: u.def.cmd != null, world: cache.ttl != null || cache.never != null }, c.budgets),
355
420
  optIn: false,
356
- fixable: u.def.fix != null,
357
- when: w,
421
+ fixable: u.def.repair != null,
422
+ runs,
423
+ cache: cacheText(u.def.cache),
424
+ when: whenSummary(runs, repoOff(c, u.id), true),
358
425
  deps: u.def.deps ?? null,
359
426
  from: u.from,
360
- ...(u.def.every !== undefined || u.def.cron != null || u.def.whenInactive != null ? { plan: { every: u.def.every, cron: u.def.cron, whenInactive: u.def.whenInactive } } : {}),
361
- ...planOffOf(u.id, u.def.planOff, env),
362
427
  }
363
428
  const body: CheckBody = {
364
429
  ...(u.def.cmd == null ? {} : { cmd: u.def.cmd }),
@@ -367,11 +432,12 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
367
432
  // relative to the root, and an absolute one stands as written
368
433
  ...(u.def.cwd == null ? {} : { cwd: resolve(root, u.def.cwd) }),
369
434
  }
370
- out.push({ row, runnable: runnableOf(row, body, { fix: u.def.fix, pointer: u.def.cmd?.join(' ') ?? null }) })
435
+ out.push({ row, runnable: runnableOf(row, body, { fix: u.def.repair, pointer: u.def.cmd?.join(' ') ?? null }) })
371
436
  }
372
437
  // LEGACY: `rules: []`, read as a check whose body is its lint. Dies with
373
438
  // that config key once `shipkit fleet` shows no repo below 0.40.
374
439
  for (const r of c.rules ?? []) {
440
+ const runs = withRepoOff(derivedRuns({ gate: true, build: false, inputs: r.inputs, ttl: r.ttl, kind: 'a legacy repo.config.ts rule', repair: r.fix != null }), repoOff(c, r.id))
375
441
  const row: CheckRow = {
376
442
  decl: {
377
443
  id: r.id,
@@ -383,7 +449,7 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
383
449
  },
384
450
  title: r.title,
385
451
  description: describeFirst(r.title, `rule ${r.id} holds`),
386
- gate: true,
452
+ gate: runs.off == null && runs.onCi,
387
453
  build: false,
388
454
  blocking: r.blocking,
389
455
  owner: r.owner,
@@ -392,7 +458,9 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
392
458
  budgetMs: budgetOf(r.id, undefined, { build: false, cmd: false, world: r.ttl != null || r.never != null }, c.budgets),
393
459
  optIn: false,
394
460
  fixable: r.fix != null,
395
- when: whenOfRule(c, r.id),
461
+ runs,
462
+ cache: legacyCacheText(r),
463
+ when: whenSummary(runs, repoOff(c, r.id), false),
396
464
  deps: null,
397
465
  from: 'repo',
398
466
  }
@@ -409,12 +477,14 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
409
477
  // file's own cache declaration wins over the task's
410
478
  const d = task == null ? found : { ...found, gate: found.gate || task.optIn !== true }
411
479
  const cached = d.inputs != null || d.ttl != null || d.never != null
480
+ const li = { inputs: d.inputs ?? (cached ? undefined : task?.inputs), ttl: d.ttl ?? (cached ? undefined : task?.ttl), never: d.never ?? (cached ? undefined : task?.never) }
481
+ const runs = withRepoOff(derivedRuns({ gate: d.gate, build: false, inputs: li.inputs, ttl: li.ttl, kind: 'a legacy check script', repair: false }), repoOff(c, d.name))
412
482
  const row: CheckRow = {
413
483
  decl: {
414
484
  id: d.name,
415
- inputs: d.inputs ?? (cached ? undefined : task?.inputs),
416
- ttl: d.ttl ?? (cached ? undefined : task?.ttl),
417
- never: d.never ?? (cached ? undefined : task?.never),
485
+ inputs: li.inputs,
486
+ ttl: li.ttl,
487
+ never: li.never,
418
488
  code: [rel],
419
489
  argv: ['bun', rel],
420
490
  // no `extra`, so WHERE it runs stays out of a legacy check's
@@ -425,7 +495,7 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
425
495
  },
426
496
  title: d.name,
427
497
  description: describeFirst(`\`bun ${rel}\` exits 0`, `${d.name} exits 0`),
428
- gate: d.gate,
498
+ gate: runs.off == null && runs.onCi,
429
499
  build: false,
430
500
  // the file wins, then the repo default, then nothing: an
431
501
  // unclassified check is an error, never a guess
@@ -437,7 +507,9 @@ export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts
437
507
  optIn: false,
438
508
  // the reader is a source scanner, so it cannot carry a function
439
509
  fixable: false,
440
- when: whenOfLegacyCheck(c, d),
510
+ runs,
511
+ cache: legacyCacheText(li),
512
+ when: whenSummary(runs, repoOff(c, d.name), false),
441
513
  deps: task?.deps ?? null,
442
514
  from: 'repo',
443
515
  }