rvlib-shipkit 0.38.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +371 -16
  2. package/README.md +26 -14
  3. package/package.json +2 -2
  4. package/src/assets/gen/comfyts-gen.ts +27 -10
  5. package/src/assets/gen/generate-roll.ts +1 -1
  6. package/src/assets/kit-config.ts +6 -6
  7. package/src/assets/run-assets.ts +14 -10
  8. package/src/assets/server/studio-server.ts +6 -5
  9. package/src/check.ts +343 -0
  10. package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
  11. package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
  12. package/src/checks/SK003-claude-md-shape.check.ts +66 -0
  13. package/src/checks/SK004-gitignore-policy.check.ts +309 -0
  14. package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
  15. package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
  16. package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
  17. package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
  18. package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
  19. package/src/checks/SK010-undeclared-unit.check.ts +111 -0
  20. package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
  21. package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
  22. package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
  23. package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
  24. package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
  25. package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
  26. package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
  27. package/src/checks/SK018-test-files-run.check.ts +28 -0
  28. package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
  29. package/src/checks/SK020-self-spawning-check.check.ts +147 -0
  30. package/src/checks/contracts.check.ts +20 -0
  31. package/src/cli/cmd_Asks.ts +3 -2
  32. package/src/cli/cmd_Assets.ts +2 -2
  33. package/src/cli/cmd_Bump.ts +1 -1
  34. package/src/cli/cmd_Check.ts +6 -6
  35. package/src/cli/cmd_Checks.ts +95 -0
  36. package/src/cli/cmd_Ci.ts +48 -13
  37. package/src/cli/cmd_Commands.ts +2 -6
  38. package/src/cli/cmd_ConfigKeys.ts +69 -0
  39. package/src/cli/cmd_Contracts.ts +1 -1
  40. package/src/cli/cmd_Fleet.ts +6 -2
  41. package/src/cli/cmd_Hooks.ts +1 -1
  42. package/src/cli/cmd_Keys.ts +43 -18
  43. package/src/cli/cmd_Mv.ts +2 -2
  44. package/src/cli/cmd_Prose.ts +10 -1
  45. package/src/cli/cmd_Releases.ts +2 -2
  46. package/src/cli/cmd_Serve.ts +1 -1
  47. package/src/cli/cmd_Start.ts +6 -6
  48. package/src/cli/cmd_Status.ts +25 -0
  49. package/src/cli/cmd_Tui.ts +2 -2
  50. package/src/cli.ts +7 -4
  51. package/src/collections/actions.ts +4 -4
  52. package/src/collections/checks.ts +258 -0
  53. package/src/collections/collection-types.ts +254 -1
  54. package/src/collections/comfy.ts +23 -0
  55. package/src/collections/md-body.ts +14 -0
  56. package/src/collections/pings.ts +38 -12
  57. package/src/collections/posts.ts +19 -3
  58. package/src/collections/providers.ts +198 -91
  59. package/src/collections/reflections.ts +41 -0
  60. package/src/collections/state-rules.ts +73 -0
  61. package/src/collections/test-time.ts +288 -0
  62. package/src/collections/tests.ts +181 -0
  63. package/src/collections/wikis.ts +66 -0
  64. package/src/comfy/comfy-panel.ts +292 -0
  65. package/src/contracts/apply-contracts.ts +17 -12
  66. package/src/contracts/contract-types.ts +16 -4
  67. package/src/contracts/contracts-engine.ts +78 -42
  68. package/src/contracts/manifests/discord.contract.ts +6 -6
  69. package/src/contracts/manifests/identity.contract.ts +3 -3
  70. package/src/contracts/manifests/itch.contract.ts +12 -15
  71. package/src/contracts/manifests/social.contract.ts +5 -5
  72. package/src/contracts/manifests/steam.contract.ts +16 -16
  73. package/src/contracts/run-contracts.ts +4 -3
  74. package/src/core/attach.ts +96 -0
  75. package/src/core/canonical-scripts.ts +25 -0
  76. package/src/core/check-ctx.ts +204 -14
  77. package/src/core/check-file-names.ts +112 -0
  78. package/src/core/check-pass.ts +84 -0
  79. package/src/core/check-runner.ts +796 -0
  80. package/src/core/config-keys.ts +256 -0
  81. package/src/core/config-rewrite.ts +77 -0
  82. package/src/core/discover-checks.ts +166 -90
  83. package/src/core/discover-legacy-checks.ts +26 -0
  84. package/src/core/doc-block.ts +13 -0
  85. package/src/core/floor-resolve.ts +264 -0
  86. package/src/core/floor.ts +20 -137
  87. package/src/core/git-bin.ts +24 -0
  88. package/src/core/git-root.ts +108 -0
  89. package/src/core/instance.ts +195 -0
  90. package/src/core/jobs.ts +118 -0
  91. package/src/core/layout.ts +52 -0
  92. package/src/core/legacy-shapes.ts +207 -0
  93. package/src/core/lib-root.ts +5 -0
  94. package/src/core/locked-write.ts +110 -39
  95. package/src/core/machine-commit.ts +5 -4
  96. package/src/core/migrate-layout.ts +557 -0
  97. package/src/core/motherhome.ts +2 -8
  98. package/src/core/nesting.ts +4 -12
  99. package/src/core/one-way-in-policy.ts +73 -17
  100. package/src/core/outcome-words.ts +45 -0
  101. package/src/core/package-manager.ts +86 -0
  102. package/src/core/parallel.ts +21 -0
  103. package/src/core/process-tree.ts +44 -0
  104. package/src/core/raw-access.ts +93 -0
  105. package/src/core/read-set.ts +152 -0
  106. package/src/core/run-check.ts +155 -213
  107. package/src/core/run-ci.ts +153 -189
  108. package/src/core/run-fix.ts +96 -0
  109. package/src/core/run-fixers.ts +53 -30
  110. package/src/core/self-update.ts +9 -3
  111. package/src/core/slots.ts +83 -0
  112. package/src/core/spawn-timeout.ts +34 -3
  113. package/src/core/when-block.ts +401 -0
  114. package/src/core/when.ts +135 -0
  115. package/src/core/write-status.ts +5 -2
  116. package/src/hooks/leak-guard.ts +10 -8
  117. package/src/hooks/one-way-in-guard.ts +13 -5
  118. package/src/hooks/root-identity.ts +2 -1
  119. package/src/hooks/run-hooks.ts +35 -23
  120. package/src/init-repo.ts +39 -23
  121. package/src/ledger/check-stats.ts +155 -0
  122. package/src/ledger/check-table.ts +179 -0
  123. package/src/ledger/db.ts +255 -0
  124. package/src/ledger/gate-checks.ts +433 -0
  125. package/src/ledger/git-key.ts +116 -77
  126. package/src/ledger/git-scan.ts +96 -35
  127. package/src/ledger/ledger.ts +116 -42
  128. package/src/ledger/live-runs.ts +111 -0
  129. package/src/ledger/live-timeline.ts +97 -0
  130. package/src/ledger/repo-view.ts +73 -0
  131. package/src/ledger/run-log.ts +448 -0
  132. package/src/ledger/run-timeline.ts +173 -0
  133. package/src/ledger/store.ts +23 -12
  134. package/src/ledger/suite-log.ts +161 -0
  135. package/src/ledger/toolchain-identity.ts +7 -1
  136. package/src/ledger/world-cache.ts +111 -0
  137. package/src/mv/mv-md.ts +16 -6
  138. package/src/problems/extract.ts +182 -0
  139. package/src/problems/problem-board.ts +37 -0
  140. package/src/problems/problem-line.ts +77 -0
  141. package/src/prose/prose-view.ts +90 -0
  142. package/src/prose/run-prose.ts +60 -19
  143. package/src/prose/ste.ts +140 -24
  144. package/src/reflections/reflections-queue.ts +81 -0
  145. package/src/release/git-facts.ts +15 -9
  146. package/src/release/run-bump.ts +6 -5
  147. package/src/release/run-release.ts +25 -11
  148. package/src/release/run-releases.ts +2 -2
  149. package/src/release/unit-gate.ts +7 -54
  150. package/src/release/unit-resolve.ts +282 -0
  151. package/src/release/units.ts +33 -217
  152. package/src/repo-config.ts +155 -85
  153. package/src/rules/rule.ts +36 -96
  154. package/src/rules/untrack-generated.ts +5 -4
  155. package/src/scaffold/files/vscode-tasks.json +10 -31
  156. package/src/scaffold/jsonc-spans.ts +113 -0
  157. package/src/scaffold/project-scaffold.ts +16 -43
  158. package/src/scan.ts +98 -0
  159. package/src/serve/gate-trigger.ts +148 -0
  160. package/src/serve/health-schedule.ts +102 -0
  161. package/src/serve/hub-app/chart-view.ts +179 -0
  162. package/src/serve/hub-app/check-view.tsx +345 -0
  163. package/src/serve/hub-app/check-words.ts +164 -0
  164. package/src/serve/hub-app/checks-svc.ts +280 -0
  165. package/src/serve/hub-app/clock-svc.ts +33 -0
  166. package/src/serve/hub-app/col-card.tsx +23 -39
  167. package/src/serve/hub-app/cols-svc.ts +44 -20
  168. package/src/serve/hub-app/comfy-svc.ts +171 -0
  169. package/src/serve/hub-app/comfy-view.tsx +81 -0
  170. package/src/serve/hub-app/dur.ts +41 -0
  171. package/src/serve/hub-app/hash-route.ts +121 -0
  172. package/src/serve/hub-app/header-bar.tsx +117 -0
  173. package/src/serve/hub-app/health-svc.ts +23 -28
  174. package/src/serve/hub-app/help.tsx +29 -0
  175. package/src/serve/hub-app/hub-app.tsx +122 -119
  176. package/src/serve/hub-app/hub-st.ts +115 -42
  177. package/src/serve/hub-app/hub-types.ts +117 -2
  178. package/src/serve/hub-app/hub.css +534 -192
  179. package/src/serve/hub-app/index.html +2 -0
  180. package/src/serve/hub-app/live-svc.ts +174 -0
  181. package/src/serve/hub-app/md.ts +15 -0
  182. package/src/serve/hub-app/media-svc.ts +4 -1
  183. package/src/serve/hub-app/media-view.tsx +15 -0
  184. package/src/serve/hub-app/next-run.ts +68 -0
  185. package/src/serve/hub-app/project-gutter.css +65 -0
  186. package/src/serve/hub-app/project-gutter.tsx +128 -0
  187. package/src/serve/hub-app/projects-svc.ts +226 -0
  188. package/src/serve/hub-app/prose-hits-st.ts +124 -0
  189. package/src/serve/hub-app/prose-hits-view.tsx +176 -0
  190. package/src/serve/hub-app/prose-hits.css +21 -0
  191. package/src/serve/hub-app/run-strip.ts +47 -0
  192. package/src/serve/hub-app/runs-tabs.ts +24 -0
  193. package/src/serve/hub-app/test-view.tsx +107 -0
  194. package/src/serve/hub-app/tests-svc.ts +62 -0
  195. package/src/serve/hub-app/tests-view.tsx +238 -0
  196. package/src/serve/hub-app/time-ago.tsx +34 -0
  197. package/src/serve/hub-app/timeline-chart.tsx +274 -0
  198. package/src/serve/hub-app/timeline-geometry.ts +314 -0
  199. package/src/serve/hub-app/timeline-svc.ts +62 -0
  200. package/src/serve/hub-app/timeline-view.tsx +245 -0
  201. package/src/serve/hub-app/tree-build.ts +410 -0
  202. package/src/serve/hub-app/tree-row.tsx +42 -0
  203. package/src/serve/hub-app/tree-svc.ts +174 -0
  204. package/src/serve/hub-app/wikis-view.tsx +53 -0
  205. package/src/serve/other-repos.ts +79 -0
  206. package/src/serve/project-gutter-route.ts +350 -0
  207. package/src/serve/project-gutter.ts +449 -0
  208. package/src/serve/run-serve.ts +245 -16
  209. package/src/serve/same-origin.ts +14 -0
  210. package/src/serve/serve-hub.ts +213 -18
  211. package/src/shipkit.ts +19 -12
  212. package/src/smoke/run-smoke.ts +3 -2
  213. package/src/start-repo.ts +75 -36
  214. package/src/suite/judge-suites.ts +216 -18
  215. package/src/suite/kept-timings.ts +58 -0
  216. package/src/suite/runner-shapes.ts +88 -0
  217. package/src/suite/suite-cli.ts +147 -24
  218. package/src/suite/suite-cpu-preload.ts +92 -0
  219. package/src/suite/test-units.ts +243 -0
  220. package/src/ts/config.ts +4 -69
  221. package/src/ts/jsonc.ts +69 -0
  222. package/src/ts/lex.ts +143 -0
  223. package/src/ts/rename.ts +73 -10
  224. package/src/ts/source-graph.ts +28 -8
  225. package/src/ts/tsconfig-projects.ts +6 -97
  226. package/src/ts/tsconfig-resolve.ts +108 -0
  227. package/src/tui/build-steps-panel.ts +61 -0
  228. package/src/tui/checks-panel.ts +24 -16
  229. package/src/tui/run-tui.ts +2 -2
  230. package/src/tui/tui-panel.ts +5 -4
  231. package/src/wiki/graph-colors.ts +68 -0
  232. package/src/wiki/graph-mount.ts +7 -9
  233. package/src/wiki/graph-page.ts +42 -25
  234. package/src/wiki/graph-positions.ts +105 -0
  235. package/src/wiki/run-wiki.ts +1 -1
  236. package/src/wiki/serve-graph.ts +33 -3
  237. package/src/cli/cmd_Rules.ts +0 -51
  238. package/src/cli/cmd_Units.ts +0 -82
  239. package/src/collections/graphs.ts +0 -32
  240. package/src/contracts/contracts-once.ts +0 -43
  241. package/src/core/check-suite.ts +0 -302
  242. package/src/core/repair-contract-dirs.ts +0 -136
  243. package/src/core/task-runner.ts +0 -339
  244. package/src/ledger/gate-units.ts +0 -138
  245. package/src/ledger/unit-table.ts +0 -105
  246. package/src/rules/SK002-contracts-fulfillness.ts +0 -24
  247. package/src/rules/SK003-claude-md-shape.ts +0 -70
  248. package/src/rules/SK004-gitignore-policy.ts +0 -238
  249. package/src/rules/SK010-undeclared-release-unit.ts +0 -127
  250. package/src/rules/rule-registry.ts +0 -64
  251. package/src/rules/run-fix.ts +0 -72
  252. package/src/tui/tasks-panel.ts +0 -60
@@ -1,7 +1,7 @@
1
1
  // defineRepo — the ONE import a repo needs: repo.config.ts at the root
2
- // declares venues/social/tasks, and the toolkit derives everything else.
3
- // Health checks are NOT declared here: drop a `<name>.check.ts` file and
4
- // `shipkit check` discovers it (core/discover-checks.ts). The FLOOR
2
+ // declares venues/social, and the toolkit derives everything else.
3
+ // Checks are NOT declared here: drop a `<name>.check.ts` file and every
4
+ // surface discovers it (core/discover-checks.ts). The FLOOR
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);
@@ -11,22 +11,22 @@
11
11
  import { existsSync, readFileSync } from 'fs'
12
12
  import { isAbsolute, join, normalize } from 'path'
13
13
  import { fileURLToPath } from 'url'
14
- import { activeContracts, applyContractsResult, contractsConfigFrom } from './contracts/apply-contracts.ts'
15
14
  import { ROUTED_SCRIPTS } from './core/canonical-scripts.ts'
16
15
  import { FLOOR_IDS, resolveFloor, type FloorResolution } from './core/floor.ts'
17
16
  export { resolveFloor, type FloorResolution, type FloorSource } from './core/floor.ts'
18
- import { evaluateContractsOnce } from './contracts/contracts-once.ts'
19
17
  import type { ContractManifest } from './contracts/contract-types.ts'
20
18
  import type { ProseConfig } from './prose/run-prose.ts'
19
+ import { proseWordsProblems } from './prose/ste.ts'
21
20
  import { validateSmokeConfig, type SmokeConfig } from './smoke/run-smoke.ts'
22
- import { contractsFulfillness } from './rules/SK002-contracts-fulfillness.ts'
23
- import { BUILTIN_RULE_IDS, validateRules } from './rules/rule-registry.ts'
24
- import type { Rule } from './rules/rule.ts'
21
+ import { RULE_ID_RE, validateRules } from './rules/rule.ts'
22
+ import { builtinCheckIds } from './core/discover-checks.ts'
23
+ import { parseWhen } from './core/when.ts'
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 { UNIT_DECL_KEYS, type ReleaseUnitDecl } from './release/units.ts'
29
- import { TASK_DEF_KEYS, type TaskDef } from './core/task-runner.ts'
28
+ import { TEST_ONLY_UNIT_KEYS, UNIT_DECL_KEYS, type UnitDecl } from './release/unit-resolve.ts'
29
+ import { TASK_DEF_KEYS, type TaskDef } from './check.ts'
30
30
  import { testsConfigProblems, type TestsConfig } from './suite/judge-suites.ts'
31
31
  import type { TuiPanelDef } from './tui/tui-panel.ts'
32
32
  import type { MdGraph } from './wiki/md-graph.ts'
@@ -35,23 +35,40 @@ import type { MdGraph } from './wiki/md-graph.ts'
35
35
  // rest by convention: agent/ graph when the dir exists, studio when the
36
36
  // kit loads)
37
37
  export type ServeConfig = {
38
- /** hub port — house range 4800+; default hashes the repo name into 4800-4979 */
38
+ /** hub port, house range 4800+. The default hashes the repo name into 4800-4979 */
39
39
  port?: number
40
- /** custom wiki-graph provider for repos with their own link semantics; default: generic md scan of the repo root */
41
- wikiGraph?: { title?: string; load: () => MdGraph }
40
+ /** the comfy-ts panel the comfy section embeds runs as its own process on
41
+ * its own port — house range 5800+, default = the hub port plus 1000
42
+ * (build/comfyui.md owns that surface) */
43
+ comfyPort?: number
44
+ /** custom wiki-graph provider for repos with their own link semantics.
45
+ * Default: a generic md scan of the repo root. */
46
+ wikiGraph?: {
47
+ /** the graph header title (default: the repo name) */
48
+ title?: string
49
+ /** builds the graph, called on every page load so a wiki edit shows up */
50
+ load: () => MdGraph
51
+ /** the check ids whose last verdicts are this wiki's verdict: red when
52
+ * one failed, green when all passed. Default: the built-in md lint
53
+ * (orphans and dangling refs) over the repo root */
54
+ lint?: string[]
55
+ }
42
56
  /** agent/ graph entrypoint (default: index.md AND README.md) */
43
57
  agentIndex?: string
44
- /** off-machine hostname for the hub (e.g. a tailnet MagicDNS name) —
45
- * printed beside localhost and appended to ping messages (phones
46
- * can't resolve localhost) */
58
+ /** off-machine hostname for the hub, as in a tailnet MagicDNS name. It
59
+ * prints beside localhost and rides on every ping message, because a
60
+ * phone cannot resolve localhost */
47
61
  publicHost?: string
48
62
  }
49
63
 
50
64
  export type RepoConfig = {
65
+ /** the repo name, as every surface prints it: the gate header, the hub
66
+ * title, the motherhome row, the default hub port hash. The pre-commit
67
+ * root-identity gate compares it to the package.json name. */
51
68
  name: string
52
69
  /** venue ids this repo ships on — selects which contracts are REQUIRED */
53
70
  venues?: string[]
54
- /** social channels with a .rv-social/<channel>/ post folder */
71
+ /** social channels with a .shipkit/social/<channel>/ post folder */
55
72
  social?: string[]
56
73
  /** prefix for contract fix: pointers (docs checkout or URL) — written by init, never baked in */
57
74
  docsBase?: string
@@ -62,13 +79,34 @@ export type RepoConfig = {
62
79
  * restating the command: the read set is repo knowledge, the command is
63
80
  * already in package.json, and restating it is a second source of truth. */
64
81
  defaults?: Record<string, Partial<TaskDef> | false>
65
- /** repo-specific commit-gate tasks; deps double as race guards */
82
+ /** DEPRECATED, read through 0.40 and rewritten by `shipkit check --fix
83
+ * SK016`: a build step is a `<name>.check.ts` file with `build: true` */
66
84
  tasks?: TaskDef[]
67
- /** emptyText: the `shipkit asks` all-clear line, in the repo's own voice */
68
- asks?: { dir?: string; emptyText?: string }
69
- /** secrets this repo needs; mint artifact rides as data (needs feed tile) */
70
- creds?: { name: string; service?: string; mintUrl?: string; steps?: string[] }[]
71
- /** store-command template for a missing secret tile, `{name}` substituted (e.g. your secret manager's set command) */
85
+ /** the human action queue: the folder it lives in, and the line the
86
+ * all-clear prints (`shipkit asks`). */
87
+ asks?: {
88
+ /** the queue folder, repo-relative (default '.shipkit/asks') */
89
+ dir?: string
90
+ /** the `shipkit asks` all-clear line, in the repo's own voice */
91
+ emptyText?: string
92
+ }
93
+ /** the secrets this repo needs. The mint artifact rides as data, so a
94
+ * missing one becomes a tile that says where to get it. */
95
+ creds?: {
96
+ /** the secret name the repo asks for, as `credsCommand` spells it */
97
+ name: string
98
+ /** which service issues it, shown as the tile description */
99
+ service?: string
100
+ /** where a human mints it, shown as the tile link */
101
+ mintUrl?: string
102
+ /** the steps to mint it, one line each */
103
+ steps?: string[]
104
+ /** the id of the check that proves it works (the id `--only` takes).
105
+ * Its last recorded run, from a gate or a health pass, is the secret's
106
+ * verdict. Absent: no check probes it, and the row says so */
107
+ probedBy?: string
108
+ }[]
109
+ /** store-command template for a missing secret tile, `{name}` substituted, as in your secret manager's set command */
72
110
  credsCommand?: string
73
111
  /** READ-command template, the twin of `credsCommand`: prints one field of
74
112
  * a secret, `{name}` and `{field}` (get = the value, account = the
@@ -76,9 +114,16 @@ export type RepoConfig = {
76
114
  * way a value reaches a command (`shipkit smoke` login), so it never sits
77
115
  * in config, argv or env. Required when `smoke.login` is declared. */
78
116
  credsReadCommand?: string
79
- /** notify command for new RED needs while the hub serves, `{message}` substituted (e.g. a push-notification CLI) */
117
+ /** notify command for new RED needs while the hub serves, `{message}` substituted, as in a push-notification CLI */
80
118
  notifyCommand?: string
81
- contracts?: { exempt?: Record<string, string>; extra?: ContractManifest[] }
119
+ /** the `.shipkit/<venue>/` release contracts: which ones this repo is excused
120
+ * from, and any manifest the published toolkit does not carry. */
121
+ contracts?: {
122
+ /** venue id to the reason it is waived. The only bypass, and it always prints */
123
+ exempt?: Record<string, string>
124
+ /** manifests declared by this repo, beside the built-in venues */
125
+ extra?: ContractManifest[]
126
+ }
82
127
  /** repo-contributed .gitignore lines, rendered INSIDE rule SK004's managed
83
128
  * block — for ecosystem-specific names the published lib may not carry */
84
129
  gitignore?: string[]
@@ -92,9 +137,18 @@ export type RepoConfig = {
92
137
  * folder only: it never commits, untracks, tags or pushes on the parent's
93
138
  * history. Rule SK015 fails when this and the disk disagree (core/nesting.ts) */
94
139
  nested?: boolean
95
- /** rule id → the reason this repo breaks that invariant on purpose. An
96
- * exempted rule does not lint and prints a skip line carrying the reason.
97
- * `defineRepo` throws on an unknown id or a blank reason */
140
+ /** WHERE EACH CHECK RUNS, keyed by check id. One declaration for a
141
+ * question that used to have three: `'always'` every gate and every
142
+ * health pass, `'release'` only at `--full` and on the release path,
143
+ * `'health'` only in the health pass, `'off:<reason>'` not at all.
144
+ * `defineRepo` throws on a value it cannot parse, on an `off` with no
145
+ * reason, and on an id no check carries. The hub writes this
146
+ * map through the managed block in this file (core/when-block.ts). */
147
+ when?: Record<string, string>
148
+ /** DEPRECATED, read into `when` as `off:<reason>`: rule id → the reason
149
+ * this repo breaks that invariant on purpose. An exempted rule does not
150
+ * lint and prints a skip line carrying the reason. `defineRepo` throws on
151
+ * an unknown id or a blank reason */
98
152
  ruleExempt?: Record<string, string>
99
153
  /** declared github visibility INTENT (absent = 'private') — rule SK007
100
154
  * fails loud when `gh repo view` disagrees in either direction */
@@ -105,33 +159,45 @@ export type RepoConfig = {
105
159
  * read-only allowlist SK008 enforces (default: Read, Grep, Glob, Bash).
106
160
  * Rule SK008 lints it, `check --fix SK008` writes it. Absent = no
107
161
  * declared reviewer. */
108
- reviewer?: { name: string; brief: string; tools?: string[] }
162
+ reviewer?: {
163
+ /** the agent type, kebab-case: `.claude/agents/<name>.md` */
164
+ name: string
165
+ /** path to the standing brief the agent reads first. It can sit outside the repo */
166
+ brief: string
167
+ /** widens the read-only tool allowlist SK008 enforces (default Read, Grep, Glob, Bash) */
168
+ tools?: string[]
169
+ }
109
170
  /** ONE WAY IN escape hatch (build/shipkit.md): governed script names this
110
171
  * repo keeps as a direct tool call. Declared per NAME, never as a list of
111
172
  * banned binaries: the invariant is about which VERBS an agent reaches
112
173
  * for, and a binary blocklist goes stale the day a repo changes runner.
113
174
  * A name outside ROUTED_SCRIPTS throws here, so a typo can never silently
114
175
  * un-govern a script. Rule SK001 reads it. */
115
- oneWayIn?: { exempt?: string[] }
116
- /** repo-local rules, run by `shipkit check` beside the built-in SK ones and
117
- * listed by `shipkit rules`. Same shape, same id-tagging, same fix pointers;
118
- * the repo picks its own id prefix (SK is reserved). This is how a repo's
119
- * own invariants become DISCOVERABLE instead of living as anonymous
120
- * failures inside a hand-rolled lint script. */
176
+ oneWayIn?: {
177
+ /** governed script names this repo calls directly: the package.json
178
+ * route and the shell deny both drop for each one named */
179
+ exempt?: string[]
180
+ }
181
+ /** DEPRECATED, read through 0.40: a repo invariant is a `<name>.check.ts`
182
+ * file whose `run` is the old `lint`. SK016 names each entry, and moving
183
+ * one is a hand pass because it holds a function */
121
184
  rules?: Rule[]
122
185
  /** `shipkit release`, branch, tag shape, changelog, npm + GitHub release.
123
186
  * Absent is fine: the defaults (main, `v{version}`, CHANGELOG.md, npm only
124
187
  * when public, GitHub release on) fit most repos. release/run-release.ts */
125
188
  release?: ReleaseConfig
126
- /** releasable packages INSIDE this repo, beside the mandatory root unit.
127
- * Most repos have none and declare nothing. Nothing is scanned into
128
- * existence: a package nobody declared fails rule SK010 with the entry to
129
- * paste. release/units.ts */
130
- units?: ReleaseUnitDecl[]
189
+ /** the units this repo is cut into, beside the mandatory root unit. Each
190
+ * one scopes its own checks (`test:<unit id>` run in its dir, and
191
+ * `typecheck:<unit id>` when it has a tsconfig) and, unless
192
+ * it says `release: false`, is a releasable package with its own version,
193
+ * tag and changelog. Most repos have none and declare nothing. Nothing is
194
+ * scanned into existence: a package nobody declared fails rule SK010 with
195
+ * the entry to paste. release/units.ts */
196
+ units?: UnitDecl[]
131
197
  /** repo-relative dirs whose `package.json` is deliberately NOT releasable
132
198
  * (an electron shell, a cloudflare worker, a fixture, an examples app).
133
199
  * Rule SK010 needs an achievable green: without this, a package that
134
- * cannot be a release unit leaves the gate permanently red. Declaring it
200
+ * cannot release leaves the gate permanently red. Declaring it
135
201
  * here is a statement, so the package still cannot hide. One entry names
136
202
  * ONE package dir (SK010 fails an entry with no package.json), and the
137
203
  * repo root is refused outright: a single line that excuses everything
@@ -141,12 +207,14 @@ export type RepoConfig = {
141
207
  * repo's own commands. `shipkit hooks list` enumerates them, so what blocks
142
208
  * a commit is answerable without reading shell. hooks/run-hooks.ts */
143
209
  hooks?: HooksConfig
144
- /** what a discovered `*.check.ts` means when the FILE does not say.
145
- * Most repos are uniform (every probe reports the world), and repeating
146
- * the same line in every file is one fact written N times. Declare it
147
- * once here; a file that differs overrides with its own
148
- * `export const blocking = true`. With neither, the check fails loud. */
149
- checkDefaults?: { blocking?: boolean }
210
+ /** DEPRECATED, read through 0.40: what a legacy `*.check.ts` with no
211
+ * `export const blocking` means. `shipkit check --fix SK016` names the
212
+ * value in each script's port, where `blocking` is required, and drops
213
+ * the key once no script leans on it */
214
+ checkDefaults?: {
215
+ /** what a `*.check.ts` with no `export const blocking` means here */
216
+ blocking?: boolean
217
+ }
150
218
  /** house prose in STE (identity/writing-voice.md), gated by rule SK014.
151
219
  * `include` is what makes the rule run at all: a repo that declares no
152
220
  * scope is skipped rather than failed, because STE binds the prose an
@@ -161,14 +229,20 @@ export type RepoConfig = {
161
229
  * slow suites the quick tier leaves out, and the suites that cannot run in
162
230
  * parallel, each with its reason. suite/judge-suites.ts */
163
231
  tests?: TestsConfig
164
- /** extra dashboard panels appended after the built-in Tasks/Checks (`shipkit tui`) */
165
- tui?: { panels?: TuiPanelDef[] }
232
+ /** the live dashboard, `shipkit tui`. */
233
+ tui?: {
234
+ /** extra dashboard panels, appended after the built-in build steps and checks panels (`shipkit tui`) */
235
+ panels?: TuiPanelDef[]
236
+ }
166
237
  /** the `shipkit serve` hub: port + optional graph hooks */
167
238
  serve?: ServeConfig
239
+ /** where a gate run writes its per task logs, repo-relative (default
240
+ * '.shipkit/tmp/shipkit-ci'). Each run owns a subdir of it, so several agents in
241
+ * one checkout never overwrite each other. Keep it gitignored. */
168
242
  logDir?: string
169
243
  }
170
244
 
171
- const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'credsReadCommand', 'notifyCommand', 'contracts', 'gitignore', 'untrackGenerated', 'nested', 'ruleExempt', 'visibility', 'reviewer', 'oneWayIn', 'rules', 'release', 'units', 'notReleasable', 'hooks', 'checkDefaults', 'tui', 'serve', 'logDir', 'prose', 'smoke', 'tests'])
245
+ const KNOWN_KEYS = new Set(['name', 'venues', 'social', 'docsBase', 'defaults', 'tasks', 'asks', 'creds', 'credsCommand', 'credsReadCommand', 'notifyCommand', 'contracts', 'gitignore', 'untrackGenerated', 'nested', 'when', 'ruleExempt', 'visibility', 'reviewer', 'oneWayIn', 'rules', 'release', 'units', 'notReleasable', 'hooks', 'checkDefaults', 'tui', 'serve', 'logDir', 'prose', 'smoke', 'tests'])
172
246
 
173
247
  export function defineRepo(c: RepoConfig): RepoConfig {
174
248
  if (typeof c.name !== 'string' || c.name === '') throw new Error('defineRepo: name is required')
@@ -190,6 +264,13 @@ export function defineRepo(c: RepoConfig): RepoConfig {
190
264
  if (!UNIT_DECL_KEYS.includes(k)) throw new Error(`defineRepo: units['${u.dir}'] unknown key '${k}' (known: ${UNIT_DECL_KEYS.join(', ')})`)
191
265
  const problems = u.tests != null ? testsConfigProblems(u.tests) : []
192
266
  if (problems.length > 0) throw new Error(`defineRepo: units['${u.dir}'] ${problems[0]}`)
267
+ if ('release' in u && u.release !== false) throw new Error(`defineRepo: units['${u.dir}'].release takes only false (a unit that never releases)`)
268
+ if (u.release === false) {
269
+ const idle = Object.keys(u).filter((k) => !TEST_ONLY_UNIT_KEYS.includes(k))
270
+ if (idle.length > 0) throw new Error(`defineRepo: units['${u.dir}'] is release: false, so ${idle.join(', ')} describe(s) a release that never happens (it reads ${TEST_ONLY_UNIT_KEYS.join(', ')})`)
271
+ }
272
+ if (u.deps != null && (!Array.isArray(u.deps) || u.deps.some((d) => typeof d !== 'string' || d === '')))
273
+ throw new Error(`defineRepo: units['${u.dir}'].deps is a list of check ids`)
193
274
  }
194
275
  for (const d of c.notReleasable ?? []) {
195
276
  if (typeof d !== 'string' || d === '') throw new Error(`defineRepo: every notReleasable entry is a repo-relative dir path`)
@@ -226,14 +307,30 @@ export function defineRepo(c: RepoConfig): RepoConfig {
226
307
  throw new Error(`defineRepo: oneWayIn.exempt '${n}' is not a governed script name (governed: ${ROUTED_SCRIPTS.join(', ')})`)
227
308
  // repo rules are validated against the built-ins, so a bad id, a duplicate,
228
309
  // or an SK squatter dies at config load, not silently mid-check
229
- if (c.rules != null) validateRules(c.rules, { label: 'defineRepo: rules', taken: BUILTIN_RULE_IDS })
310
+ if (c.rules != null) validateRules(c.rules, { label: 'defineRepo: rules', taken: builtinCheckIds() })
311
+ // a `when` moves a check off the gate, so a value it cannot act on dies
312
+ // here. The RULE namespace is the one this file can check: a task is
313
+ // resolved from the floor and a check is discovered on disk, so those two
314
+ // are caught by the gate against the real inventory (core/when.ts
315
+ // unknownWhenIds)
316
+ for (const [id, raw] of Object.entries(c.when ?? {})) {
317
+ if (id.trim() === '') throw new Error(`defineRepo: when has an entry with no check id`)
318
+ const w = parseWhen(raw)
319
+ if (typeof w === 'string') throw new Error(`defineRepo: when.${id} ${w}`)
320
+ if (RULE_ID_RE.test(id) && !builtinCheckIds().includes(id) && !(c.rules ?? []).some((r) => r.id === id))
321
+ throw new Error(`defineRepo: when.${id} names no rule, the ids are in \`shipkit checks\``)
322
+ }
230
323
  // an exemption silences a gate, so it names a rule that exists and says why
231
324
  for (const [id, reason] of Object.entries(c.ruleExempt ?? {})) {
232
- if (!BUILTIN_RULE_IDS.includes(id) && !(c.rules ?? []).some((r) => r.id === id))
233
- throw new Error(`defineRepo: ruleExempt.${id} names no rule, the ids are in \`shipkit rules\``)
325
+ if (!builtinCheckIds().includes(id) && !(c.rules ?? []).some((r) => r.id === id))
326
+ throw new Error(`defineRepo: ruleExempt.${id} names no rule, the ids are in \`shipkit checks\``)
234
327
  if (typeof reason !== 'string' || reason.trim() === '')
235
328
  throw new Error(`defineRepo: ruleExempt.${id} needs a reason, an exemption nobody can explain is a silenced gate`)
236
329
  }
330
+ // a word list entry silences or adds gate hits, so a malformed one dies
331
+ // here rather than matching nothing
332
+ const words = proseWordsProblems(c.prose?.words)
333
+ if (words.length > 0) throw new Error(`defineRepo: ${words[0]}`)
237
334
  // a smoke typo would land as a run that visits nothing and exits 0
238
335
  if (c.smoke != null) validateSmokeConfig(c.smoke)
239
336
  // a leak-guard waiver is the one config row that can make a gate quieter,
@@ -253,40 +350,13 @@ export function defineRepo(c: RepoConfig): RepoConfig {
253
350
  return c
254
351
  }
255
352
 
256
- export async function loadRepoConfig(root: string): Promise<RepoConfig> {
353
+ /** `fresh` busts the module cache: a process that WROTE the config (the hub's
354
+ * toggle) must read the bytes it just wrote, and an ES module import is
355
+ * cached by path for the life of the process */
356
+ export async function loadRepoConfig(root: string, o: { fresh?: boolean } = {}): Promise<RepoConfig> {
257
357
  const path = join(root, 'repo.config.ts')
258
358
  if (!existsSync(path)) throw new Error(`no repo.config.ts in ${root} — run: shipkit init`)
259
- const mod = (await import(path)) as { default?: RepoConfig }
359
+ const mod = (await import(o.fresh === true ? `${path}?v=${Date.now()}-${Math.random().toString(36).slice(2)}` : path)) as { default?: RepoConfig }
260
360
  if (mod.default == null) throw new Error(`${path}: must default-export defineRepo({...})`)
261
361
  return mod.default
262
362
  }
263
-
264
- // the contracts commit-gate task — venue deploy tasks should dep on it.
265
- // Same SK002-tagged rendering as `shipkit check` and `shipkit contracts`
266
- // (contracts/apply-contracts.ts is the one bridge).
267
- //
268
- // DELIBERATELY UNDECLARED for the ledger, and this is where that decision
269
- // lives rather than in a commit message. It runs in process, shares one
270
- // evaluation with rule SK002 through `withContractsScope`, and measures 0.0s,
271
- // so a key would be risk against no reward. It is not `never:` either: the
272
- // contract dirs describe it perfectly well, so claiming nothing can is false,
273
- // and `never` has to keep meaning "somebody looked and no key exists".
274
- //
275
- // Declaring `inputs: ['.']` to clear its burn-down row was TRIED and reverted:
276
- // a keyable contracts task writes a ledger entry inside every fixture repo,
277
- // which is six ledger tests asserting an entry count that is really about the
278
- // task under test. The row it leaves behind is the honest cost of the model
279
- // having no third state between "nobody looked" and "no key can exist".
280
- export function contractsTask(c: RepoConfig, root: string): TaskDef {
281
- return {
282
- id: 'contracts',
283
- logic: async (t) => {
284
- applyContractsResult(t, await evaluateContractsOnce(activeContracts(c), contractsConfigFrom(c, root)), contractsFulfillness)
285
- },
286
- }
287
- }
288
-
289
- export function allTasks(c: RepoConfig, root: string): FloorResolution {
290
- const floor = resolveFloor(c, root)
291
- return { ...floor, tasks: [...floor.tasks, contractsTask(c, root), ...(c.tasks ?? [])] }
292
- }
package/src/rules/rule.ts CHANGED
@@ -1,103 +1,43 @@
1
- // Rule — one machine-enforced invariant with a stable id. One module per
2
- // rule (`src/rules/SK###-<slug>.ts`), registered in rule-registry.ts; ids
3
- // are never reused or renumbered. `shipkit check` runs every rule's lint;
4
- // a rule may also carry an idempotent fix() (the `check --fix` runner).
5
- // Every wire line a rule emits is tagged `[SK###]` so output is greppable
6
- // by rule and a fix pointer names the rule that can repair it.
7
- import type { CheckCtx } from '../core/check-ctx.ts'
8
- import type { Line } from '../core/wire.ts'
9
- import type { RepoConfig } from '../repo-config.ts'
1
+ // The LEGACY rule shape, kept for the compatibility window. A repo that
2
+ // still registers invariants through `repo.config.ts rules: []` imports
3
+ // `Rule` from here, and the engine reads each one as a check whose body is
4
+ // `lint` (check.ts owns the shape now). Both die together, at the first
5
+ // release after `shipkit fleet` shows no repo below 0.40.
6
+ //
7
+ // A new invariant is a `<name>.check.ts` file: `check({ blocking, run })`,
8
+ // dropped anywhere in the repo, discovered with no config edit.
9
+ import { whenOfRule } from '../core/when.ts'
10
+ import { tagCheckLines, checkCacheExtra, type Rule, type RuleEnv } from '../check.ts'
10
11
 
11
- export type RuleEnv = { root: string; config: RepoConfig }
12
+ export type { FixResult, Rule, RuleEnv } from '../check.ts'
12
13
 
13
- export type FixResult = { changed: boolean; summary: string }
14
-
15
- export type Rule = {
16
- /** stable PREFIX + 3 digits, never reused or renumbered (the registry throws).
17
- * `SK###` is reserved for shipkit's built-ins; a repo registering its own
18
- * rules through `repo.config.ts rules: []` picks its own prefix (`RV###`). */
19
- id: string
20
- /** check-suite display name — stable: the check name in STATUS.md, the TUI row, and the suite cache */
21
- title: string
22
- /** can this stop you? REQUIRED, no default. true = a failure is about THIS
23
- * repo and blocks. false = it reports the world (a box that is off, a cred
24
- * nobody minted), so a failure prints as a warning and never blocks.
25
- * The only question worth asking about a check, asked once, in one place. */
26
- blocking: boolean
27
- /** wiki page owning the invariant — the run-check framework appends `docsBase + owner`
28
- * as the fix: pointer whenever a failing/warning lint attached none */
29
- owner: string
30
- /** clean-pass cache duration (check-suite TTL, e.g. '24h') — for rules that
31
- * probe the network; skip/WARN/FAIL runs are never cached. Declares the
32
- * WORLD population (ledger/git-key.ts). */
33
- ttl?: string
34
- /** CONTENT population: the repo-relative paths this rule reads. A rule with
35
- * neither `inputs` nor `ttl` is never cached, which is the safe default.
36
- * A rule declared by a REPO rather than by shipkit must list the dir its
37
- * own code lives in too: shipkit's `src` tree id covers the built-ins and
38
- * cannot see a repo's. Declaring both `inputs` and `ttl` is a registration
39
- * failure — a rule reads files or it reads the world, never both. */
40
- inputs?: string[]
41
- /** repo-relative entry file(s) of this rule's OWN code, walked through the
42
- * module graph so a shared helper the rule imports is in its key too. A
43
- * built-in SK rule needs none: the toolchain identity content-hashes
44
- * shipkit's whole source, so editing any of it moves every key. A rule a
45
- * REPO declares is invisible to that, so it names its module here, or
46
- * includes the dir holding it in `inputs`. Without either, fixing a bug
47
- * in the rule does not move its key. */
48
- code?: string[]
49
- /** a DECIDED never-cacheable, with the reason: this rule reads git history,
50
- * a path that may live outside the repo, or the wall clock, so no key can
51
- * describe it. Says somebody looked, which is what takes it off the
52
- * burn-down list. */
53
- never?: string
54
- /** cache-identity contribution for TTL'd rules whose verdict depends on
55
- * config — a changed key busts the cache instantly instead of replaying
56
- * a stale assertion for the TTL */
57
- cacheKey?: (env: RuleEnv) => string
58
- lint: (t: CheckCtx, env: RuleEnv) => void | Promise<void>
59
- /** idempotent auto-repair; `check --fix` applies it then re-lints — never interactive */
60
- fix?: (env: RuleEnv) => FixResult | Promise<FixResult>
61
- }
62
-
63
- /** the config part of a rule's cache identity */
14
+ /** the config part of a rule's cache identity, including where it runs */
64
15
  export function ruleCacheExtra(rule: Rule, env: RuleEnv): string | undefined {
65
- const key = rule.cacheKey?.(env)
66
- const exempt = env.config.ruleExempt?.[rule.id]
67
- return exempt == null ? key : `${key ?? ''}|exempt:${exempt}`
16
+ return checkCacheExtra(rule, env, whenOfRule(env.config, rule.id))
68
17
  }
69
18
 
70
- // THE one way to run a rule's lint (check suite + the --fix runner): a
71
- // throwing lint still fails with its id, a failing/warning run always
72
- // carries at least one fix: pointer (the lint's own precise ones win, else
73
- // docsBase + the rule's owner page), and every line gets tagged.
74
- export async function runRuleLint(rule: Rule, t: CheckCtx, env: RuleEnv): Promise<void> {
75
- const exempt = env.config.ruleExempt?.[rule.id]
76
- if (exempt != null) {
77
- t.lines.push({ kind: 'skip', text: `${rule.id} exempt: ${exempt}` })
78
- t.lines = tagRuleLines(rule, t.lines)
79
- return
80
- }
81
- try {
82
- await rule.lint(t, env)
83
- } catch (e) {
84
- t.fail(`${rule.title} lint errored (${String(e).slice(0, 200)}) — network down, or a bug in this rule`)
85
- }
86
- if ((t.failed || t.warned) && !t.lines.some((l) => l.kind === 'fix-fail' || l.kind === 'fix-warn'))
87
- t.lines.push({ kind: t.failed ? 'fix-fail' : 'fix-warn', text: `${env.config.docsBase ?? ''}${rule.owner}` })
88
- t.lines = tagRuleLines(rule, t.lines)
89
- }
19
+ /** tag every severity line with the rule id (check.ts owns the rendering) */
20
+ export const tagRuleLines = tagCheckLines
21
+
22
+ // ── the legacy id namespace ──────────────────────────────────────────────
23
+ // A rule id is PREFIX plus exactly 3 digits, and `SK` is reserved for the
24
+ // toolkit's own checks so a repo rule can never shadow one. A `*.check.ts`
25
+ // file takes its id from its NAME and may use plain words, so this shape
26
+ // binds the `rules: []` key only.
90
27
 
91
- // tag every severity line with the rule id; fix pointers of a fixable rule
92
- // advertise the autofix. Idempotent — already-tagged lines pass through
93
- // (cached suite replays re-enter here).
94
- export function tagRuleLines(rule: Rule, lines: Line[]): Line[] {
95
- return lines.map((l) => {
96
- if (l.kind === 'fix-warn' || l.kind === 'fix-fail')
97
- return rule.fix == null || l.text.includes('--fix ')
98
- ? l
99
- : { ...l, text: `${l.text} · autofix: shipkit check --fix ${rule.id}` }
100
- if (l.kind === 'note') return l
101
- return l.text.startsWith(`[${rule.id}]`) ? l : { ...l, text: `[${rule.id}] ${l.text}` }
102
- })
28
+ export const RULE_ID_RE = /^[A-Z]{2,4}\d{3}$/
29
+
30
+ /** validate the rules a repo registers. Throws on a malformed id, a
31
+ * duplicate, a missing owner page, or one squatting the reserved SK
32
+ * prefix, so a bad registration dies at config load and never mid gate. */
33
+ export function validateRules(rules: readonly Rule[], o: { label: string; taken?: Iterable<string> } = { label: 'rules' }): void {
34
+ const seen = new Set<string>(o.taken ?? [])
35
+ for (const r of rules) {
36
+ if (!RULE_ID_RE.test(r.id)) throw new Error(`${o.label}: bad id '${r.id}' — rules are PREFIX+3 digits (e.g. SK001, RV001)`)
37
+ if (r.id.startsWith('SK'))
38
+ throw new Error(`${o.label}: id '${r.id}' uses the reserved SK prefix — repo rules pick their own (e.g. RV${r.id.slice(-3)})`)
39
+ if (seen.has(r.id)) throw new Error(`${o.label}: duplicate id ${r.id}`)
40
+ if (r.owner === '') throw new Error(`${o.label}: ${r.id} has no owner page`)
41
+ seen.add(r.id)
42
+ }
103
43
  }
@@ -23,9 +23,10 @@
23
23
  // it could surprise someone.
24
24
  import { existsSync } from 'fs'
25
25
  import { join } from 'path'
26
+ import { gitBin } from '../core/git-bin.ts'
26
27
 
27
- /** rendered into the SK004 managed block. A glob is fine HERE: ignoring is
28
- * not destructive. */
28
+ /** rendered into the SK004 managed block, by basename so they match wherever
29
+ * they sit. A glob is fine HERE: ignoring is not destructive. */
29
30
  export const GENERATED_IGNORE_LINES = [
30
31
  '# shipkit writes these on every run — ignored AND untracked (untrack-generated.ts)',
31
32
  'STATUS.md',
@@ -40,7 +41,7 @@ export const GENERATED_IGNORE_LINES = [
40
41
  * never derived from a pattern: `git rm --cached` driven by a wildcard is how
41
42
  * a fixer eats source it was never pointed at. A future entry is added HERE,
42
43
  * by hand, spelled out. */
43
- export const GENERATED_TRACKED_PATHS = ['STATUS.md', 'STATUS.md.lock', 'STATUS.full.md', 'STATUS.full.md.lock'] as const
44
+ export const GENERATED_TRACKED_PATHS = ['.shipkit/STATUS.md', '.shipkit/STATUS.md.lock', '.shipkit/STATUS.full.md', '.shipkit/STATUS.full.md.lock'] as const
44
45
 
45
46
  export type UntrackOutcome =
46
47
  | { did: 'nothing' }
@@ -48,7 +49,7 @@ export type UntrackOutcome =
48
49
  | { did: 'untracked'; paths: string[]; commit: string }
49
50
 
50
51
  const git = (root: string, args: string[]): { code: number; out: string } => {
51
- const p = Bun.spawnSync(['git', ...args], { cwd: root })
52
+ const p = Bun.spawnSync([gitBin(), ...args], { cwd: root })
52
53
  return { code: p.exitCode, out: `${p.stdout.toString()}${p.stderr.toString()}`.trim() }
53
54
  }
54
55
 
@@ -1,45 +1,24 @@
1
1
  {
2
- // one master task with runOn folderOpen, every dev process as a subtask.
3
- // Opening the folder in vscode starts them all. Add a subtask per
4
- // long-running watcher the stack earns (dev server, css, tests), never
5
- // for a one-off script.
2
+ // the hub is the one process every wired repo starts, so it opens with the
3
+ // folder itself. It runs every check after each save and its problem
4
+ // matcher fills the problems pane, so there is no type checker task. A
5
+ // stack that earns more long-running watchers (dev server, css) adds a
6
+ // master task with runOn folderOpen whose dependsOn lists them all, the hub
7
+ // included, and moves runOn off the hub.
6
8
  "version": "2.0.0",
7
9
  "tasks": [
8
- {
9
- "label": "starts everything",
10
- "dependsOn": ["👁 TSC", "🚀 shipkit"],
11
- "group": { "kind": "build", "isDefault": true },
12
- "runOptions": { "runOn": "folderOpen", "instanceLimit": 1 },
13
- "problemMatcher": []
14
- },
15
- {
16
- "label": "👁 TSC",
17
- "detail": "Get all typescript errors in problems pane",
18
- "type": "process",
19
- "command": "${workspaceFolder}/node_modules/.bin/tsc",
20
- "args": ["--noEmit", "--incremental", "--watch", "--project", "tsconfig.json"],
21
- "options": {
22
- "cwd": "${workspaceFolder}",
23
- "env": { "NODE_OPTIONS": "--max-old-space-size=8192" }
24
- },
25
- "isBackground": true,
26
- "presentation": { "reveal": "never", "clear": true },
27
- "group": "build",
28
- "runOptions": { "instanceLimit": 1 },
29
- "problemMatcher": []
30
- },
31
10
  {
32
11
  "label": "🚀 shipkit",
33
- "detail": "The shipkit hub at http://localhost:{{PORT}} — clickable URLs: the page, /graph/wiki, /graph/agent",
12
+ "detail": "The shipkit hub at http://localhost:{{PORT}}, and every check's findings in the problems pane — clickable URLs: the page, /graph/wiki, /graph/agent",
34
13
  "type": "process",
35
14
  "command": "bun",
36
15
  "args": ["run", "hub"],
37
16
  "options": { "cwd": "${workspaceFolder}" },
38
17
  "isBackground": true,
39
18
  "presentation": { "reveal": "always", "close": false },
40
- "group": "build",
41
- "runOptions": { "instanceLimit": 1 },
42
- "problemMatcher": []
19
+ "group": { "kind": "build", "isDefault": true },
20
+ "runOptions": { "runOn": "folderOpen", "instanceLimit": 1 },
21
+ "problemMatcher": {{HUB_PROBLEM_MATCHER}}
43
22
  }
44
23
  ]
45
24
  }