rvlib-shipkit 0.38.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +379 -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/derived.ts +1 -1
  6. package/src/assets/gen/generate-roll.ts +1 -1
  7. package/src/assets/kit-config.ts +6 -6
  8. package/src/assets/run-assets.ts +14 -10
  9. package/src/assets/server/studio-server.ts +6 -5
  10. package/src/check.ts +343 -0
  11. package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
  12. package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
  13. package/src/checks/SK003-claude-md-shape.check.ts +66 -0
  14. package/src/checks/SK004-gitignore-policy.check.ts +309 -0
  15. package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +49 -32
  16. package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
  17. package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
  18. package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
  19. package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
  20. package/src/checks/SK010-undeclared-unit.check.ts +111 -0
  21. package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
  22. package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
  23. package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
  24. package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
  25. package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
  26. package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
  27. package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
  28. package/src/checks/SK018-test-files-run.check.ts +28 -0
  29. package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
  30. package/src/checks/SK020-self-spawning-check.check.ts +147 -0
  31. package/src/checks/contracts.check.ts +20 -0
  32. package/src/cli/cmd_Asks.ts +3 -2
  33. package/src/cli/cmd_Assets.ts +2 -2
  34. package/src/cli/cmd_Bump.ts +1 -1
  35. package/src/cli/cmd_Check.ts +6 -6
  36. package/src/cli/cmd_Checks.ts +95 -0
  37. package/src/cli/cmd_Ci.ts +48 -13
  38. package/src/cli/cmd_Commands.ts +2 -6
  39. package/src/cli/cmd_ConfigKeys.ts +69 -0
  40. package/src/cli/cmd_Contracts.ts +1 -1
  41. package/src/cli/cmd_Fleet.ts +16 -3
  42. package/src/cli/cmd_Hooks.ts +1 -1
  43. package/src/cli/cmd_Keys.ts +43 -18
  44. package/src/cli/cmd_Mv.ts +2 -2
  45. package/src/cli/cmd_Prose.ts +10 -1
  46. package/src/cli/cmd_Releases.ts +2 -2
  47. package/src/cli/cmd_Serve.ts +1 -1
  48. package/src/cli/cmd_Start.ts +6 -6
  49. package/src/cli/cmd_Status.ts +25 -0
  50. package/src/cli/cmd_Tui.ts +2 -2
  51. package/src/cli.ts +7 -4
  52. package/src/collections/actions.ts +4 -4
  53. package/src/collections/checks.ts +257 -0
  54. package/src/collections/collection-types.ts +254 -1
  55. package/src/collections/comfy.ts +23 -0
  56. package/src/collections/md-body.ts +14 -0
  57. package/src/collections/pings.ts +38 -12
  58. package/src/collections/posts.ts +19 -3
  59. package/src/collections/providers.ts +198 -91
  60. package/src/collections/reflections.ts +41 -0
  61. package/src/collections/state-rules.ts +73 -0
  62. package/src/collections/test-time.ts +288 -0
  63. package/src/collections/tests.ts +181 -0
  64. package/src/collections/wikis.ts +66 -0
  65. package/src/comfy/comfy-panel.ts +292 -0
  66. package/src/contracts/apply-contracts.ts +17 -12
  67. package/src/contracts/contract-types.ts +16 -4
  68. package/src/contracts/contracts-engine.ts +78 -42
  69. package/src/contracts/manifests/discord.contract.ts +6 -6
  70. package/src/contracts/manifests/identity.contract.ts +3 -3
  71. package/src/contracts/manifests/itch.contract.ts +12 -15
  72. package/src/contracts/manifests/social.contract.ts +5 -5
  73. package/src/contracts/manifests/steam.contract.ts +16 -16
  74. package/src/contracts/run-contracts.ts +4 -3
  75. package/src/core/attach.ts +96 -0
  76. package/src/core/canonical-scripts.ts +25 -0
  77. package/src/core/check-ctx.ts +204 -14
  78. package/src/core/check-file-names.ts +112 -0
  79. package/src/core/check-pass.ts +84 -0
  80. package/src/core/check-runner.ts +796 -0
  81. package/src/core/config-keys.ts +256 -0
  82. package/src/core/config-rewrite.ts +77 -0
  83. package/src/core/discover-checks.ts +166 -90
  84. package/src/core/discover-legacy-checks.ts +26 -0
  85. package/src/core/doc-block.ts +13 -0
  86. package/src/core/floor-resolve.ts +264 -0
  87. package/src/core/floor.ts +20 -137
  88. package/src/core/git-bin.ts +24 -0
  89. package/src/core/git-root.ts +108 -0
  90. package/src/core/instance.ts +195 -0
  91. package/src/core/jobs.ts +118 -0
  92. package/src/core/layout.ts +52 -0
  93. package/src/core/legacy-shapes.ts +207 -0
  94. package/src/core/lib-root.ts +5 -0
  95. package/src/core/locked-write.ts +110 -39
  96. package/src/core/machine-commit.ts +5 -4
  97. package/src/core/migrate-layout.ts +616 -0
  98. package/src/core/motherhome.ts +2 -8
  99. package/src/core/nesting.ts +4 -12
  100. package/src/core/one-way-in-policy.ts +73 -17
  101. package/src/core/outcome-words.ts +45 -0
  102. package/src/core/package-manager.ts +88 -0
  103. package/src/core/parallel.ts +21 -0
  104. package/src/core/process-tree.ts +44 -0
  105. package/src/core/raw-access.ts +93 -0
  106. package/src/core/read-set.ts +152 -0
  107. package/src/core/run-check.ts +155 -213
  108. package/src/core/run-ci.ts +152 -189
  109. package/src/core/run-fix.ts +99 -0
  110. package/src/core/run-fixers.ts +64 -33
  111. package/src/core/self-update.ts +11 -3
  112. package/src/core/slots.ts +83 -0
  113. package/src/core/spawn-timeout.ts +34 -3
  114. package/src/core/when-block.ts +401 -0
  115. package/src/core/when.ts +135 -0
  116. package/src/core/write-status.ts +5 -2
  117. package/src/fleet/scan-fleet.ts +16 -0
  118. package/src/hooks/leak-guard.ts +20 -9
  119. package/src/hooks/one-way-in-guard.ts +13 -5
  120. package/src/hooks/root-identity.ts +2 -1
  121. package/src/hooks/run-hooks.ts +38 -26
  122. package/src/init-repo.ts +39 -23
  123. package/src/ledger/check-stats.ts +155 -0
  124. package/src/ledger/check-table.ts +179 -0
  125. package/src/ledger/db.ts +255 -0
  126. package/src/ledger/gate-checks.ts +433 -0
  127. package/src/ledger/git-key.ts +116 -77
  128. package/src/ledger/git-scan.ts +96 -35
  129. package/src/ledger/ledger.ts +116 -42
  130. package/src/ledger/live-runs.ts +111 -0
  131. package/src/ledger/live-timeline.ts +97 -0
  132. package/src/ledger/repo-view.ts +73 -0
  133. package/src/ledger/run-log.ts +448 -0
  134. package/src/ledger/run-timeline.ts +173 -0
  135. package/src/ledger/store.ts +23 -12
  136. package/src/ledger/suite-log.ts +161 -0
  137. package/src/ledger/toolchain-identity.ts +7 -1
  138. package/src/ledger/world-cache.ts +111 -0
  139. package/src/mv/mv-md.ts +16 -6
  140. package/src/problems/extract.ts +182 -0
  141. package/src/problems/problem-board.ts +37 -0
  142. package/src/problems/problem-line.ts +77 -0
  143. package/src/prose/prose-view.ts +90 -0
  144. package/src/prose/run-prose.ts +60 -19
  145. package/src/prose/ste.ts +140 -24
  146. package/src/reflections/reflections-queue.ts +81 -0
  147. package/src/release/git-facts.ts +15 -9
  148. package/src/release/run-bump.ts +6 -5
  149. package/src/release/run-release.ts +25 -11
  150. package/src/release/run-releases.ts +2 -2
  151. package/src/release/unit-gate.ts +7 -54
  152. package/src/release/unit-resolve.ts +282 -0
  153. package/src/release/units.ts +33 -217
  154. package/src/repo-config.ts +157 -88
  155. package/src/rules/rule.ts +36 -96
  156. package/src/rules/untrack-generated.ts +5 -4
  157. package/src/scaffold/files/vscode-tasks.json +10 -31
  158. package/src/scaffold/jsonc-spans.ts +113 -0
  159. package/src/scaffold/project-scaffold.ts +16 -43
  160. package/src/scan.ts +98 -0
  161. package/src/serve/gate-trigger.ts +148 -0
  162. package/src/serve/health-schedule.ts +102 -0
  163. package/src/serve/hub-app/chart-view.ts +179 -0
  164. package/src/serve/hub-app/check-view.tsx +345 -0
  165. package/src/serve/hub-app/check-words.ts +164 -0
  166. package/src/serve/hub-app/checks-svc.ts +280 -0
  167. package/src/serve/hub-app/clock-svc.ts +33 -0
  168. package/src/serve/hub-app/col-card.tsx +23 -39
  169. package/src/serve/hub-app/cols-svc.ts +44 -20
  170. package/src/serve/hub-app/comfy-svc.ts +171 -0
  171. package/src/serve/hub-app/comfy-view.tsx +81 -0
  172. package/src/serve/hub-app/dur.ts +41 -0
  173. package/src/serve/hub-app/hash-route.ts +121 -0
  174. package/src/serve/hub-app/header-bar.tsx +117 -0
  175. package/src/serve/hub-app/health-svc.ts +23 -28
  176. package/src/serve/hub-app/help.tsx +29 -0
  177. package/src/serve/hub-app/hub-app.tsx +122 -119
  178. package/src/serve/hub-app/hub-st.ts +115 -42
  179. package/src/serve/hub-app/hub-types.ts +117 -2
  180. package/src/serve/hub-app/hub.css +534 -192
  181. package/src/serve/hub-app/index.html +2 -0
  182. package/src/serve/hub-app/live-svc.ts +174 -0
  183. package/src/serve/hub-app/md.ts +15 -0
  184. package/src/serve/hub-app/media-svc.ts +4 -1
  185. package/src/serve/hub-app/media-view.tsx +15 -0
  186. package/src/serve/hub-app/next-run.ts +68 -0
  187. package/src/serve/hub-app/project-gutter.css +65 -0
  188. package/src/serve/hub-app/project-gutter.tsx +128 -0
  189. package/src/serve/hub-app/projects-svc.ts +226 -0
  190. package/src/serve/hub-app/prose-hits-st.ts +124 -0
  191. package/src/serve/hub-app/prose-hits-view.tsx +176 -0
  192. package/src/serve/hub-app/prose-hits.css +21 -0
  193. package/src/serve/hub-app/run-strip.ts +47 -0
  194. package/src/serve/hub-app/runs-tabs.ts +24 -0
  195. package/src/serve/hub-app/test-view.tsx +107 -0
  196. package/src/serve/hub-app/tests-svc.ts +62 -0
  197. package/src/serve/hub-app/tests-view.tsx +238 -0
  198. package/src/serve/hub-app/time-ago.tsx +34 -0
  199. package/src/serve/hub-app/timeline-chart.tsx +274 -0
  200. package/src/serve/hub-app/timeline-geometry.ts +314 -0
  201. package/src/serve/hub-app/timeline-svc.ts +62 -0
  202. package/src/serve/hub-app/timeline-view.tsx +245 -0
  203. package/src/serve/hub-app/tree-build.ts +410 -0
  204. package/src/serve/hub-app/tree-row.tsx +42 -0
  205. package/src/serve/hub-app/tree-svc.ts +174 -0
  206. package/src/serve/hub-app/wikis-view.tsx +53 -0
  207. package/src/serve/other-repos.ts +79 -0
  208. package/src/serve/project-gutter-route.ts +350 -0
  209. package/src/serve/project-gutter.ts +449 -0
  210. package/src/serve/run-serve.ts +245 -16
  211. package/src/serve/same-origin.ts +14 -0
  212. package/src/serve/serve-hub.ts +213 -18
  213. package/src/shipkit.ts +19 -12
  214. package/src/smoke/run-smoke.ts +9 -4
  215. package/src/start-repo.ts +75 -36
  216. package/src/suite/judge-suites.ts +209 -15
  217. package/src/suite/kept-timings.ts +58 -0
  218. package/src/suite/runner-shapes.ts +88 -0
  219. package/src/suite/suite-cli.ts +147 -24
  220. package/src/suite/suite-cpu-preload.ts +92 -0
  221. package/src/suite/test-units.ts +243 -0
  222. package/src/ts/config.ts +4 -69
  223. package/src/ts/jsonc.ts +69 -0
  224. package/src/ts/lex.ts +143 -0
  225. package/src/ts/rename.ts +73 -10
  226. package/src/ts/source-graph.ts +28 -8
  227. package/src/ts/tsconfig-projects.ts +6 -97
  228. package/src/ts/tsconfig-resolve.ts +108 -0
  229. package/src/tui/build-steps-panel.ts +61 -0
  230. package/src/tui/checks-panel.ts +24 -16
  231. package/src/tui/run-tui.ts +2 -2
  232. package/src/tui/tui-panel.ts +5 -4
  233. package/src/wiki/graph-colors.ts +68 -0
  234. package/src/wiki/graph-mount.ts +7 -9
  235. package/src/wiki/graph-page.ts +42 -25
  236. package/src/wiki/graph-positions.ts +105 -0
  237. package/src/wiki/run-wiki.ts +1 -1
  238. package/src/wiki/scan-md.ts +1 -1
  239. package/src/wiki/serve-graph.ts +33 -3
  240. package/src/cli/cmd_Rules.ts +0 -51
  241. package/src/cli/cmd_Units.ts +0 -82
  242. package/src/collections/graphs.ts +0 -32
  243. package/src/contracts/contracts-once.ts +0 -43
  244. package/src/core/check-suite.ts +0 -302
  245. package/src/core/repair-contract-dirs.ts +0 -136
  246. package/src/core/task-runner.ts +0 -339
  247. package/src/ledger/gate-units.ts +0 -138
  248. package/src/ledger/unit-table.ts +0 -105
  249. package/src/rules/SK002-contracts-fulfillness.ts +0 -24
  250. package/src/rules/SK003-claude-md-shape.ts +0 -70
  251. package/src/rules/SK004-gitignore-policy.ts +0 -238
  252. package/src/rules/SK010-undeclared-release-unit.ts +0 -127
  253. package/src/rules/rule-registry.ts +0 -64
  254. package/src/rules/run-fix.ts +0 -72
  255. package/src/tui/tasks-panel.ts +0 -60
package/src/cli/cmd_Ci.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // shipkit ci — the commit/deploy gate. Thin clipanion surface; logic in
2
2
  // core/run-ci.ts.
3
3
  import { Command, Option } from 'clipanion'
4
+ import { join } from 'path'
5
+ import { recordExit, startDetached } from '../core/jobs.ts'
4
6
  import { runCi } from '../core/run-ci.ts'
5
7
  import { maybeSelfUpdateAndRestart } from '../core/self-update.ts'
6
8
 
@@ -9,35 +11,68 @@ export class CiCommand extends Command {
9
11
 
10
12
  static override usage = Command.Usage({
11
13
  category: 'Gate',
12
- description: 'THE command. Repairs what a machine can (formatter, imports, repo shape), runs every task, then every check. A check that declares blocking:false warns instead of failing. Run this instead of anything else.',
14
+ description: 'THE command. Repairs what a machine can (formatter, imports, repo shape), then runs every check the gate asks for, through one DAG. A check that declares blocking:false warns instead of failing. Run this instead of anything else.',
13
15
  examples: [
14
16
  ['full gate, mechanical fixes applied', 'shipkit ci'],
15
17
  ['gate without the repair pass (checks can still write)', 'shipkit ci --no-fix'],
16
18
  ['leave repo config files alone', 'shipkit ci --no-fix-rules'],
17
19
  ['stop at the first failure', 'shipkit ci --bail'],
18
- ['one task + its dep closure', 'shipkit ci --only typecheck'],
19
- ['re-measure everything, replay nothing', 'shipkit ci --fresh'],
20
- ['include an opt-in task', 'shipkit ci --with up:itch'],
20
+ ['one check + its dep closure', 'shipkit ci --only typecheck'],
21
+ ['every check declaring one group', 'shipkit ci --only wiki'],
22
+ ['run everything, take nothing from the cache', 'shipkit ci --fresh'],
23
+ ['include an opt in check', 'shipkit ci --with up:itch'],
24
+ ['in the background, read it back with shipkit status', 'shipkit ci --detach'],
21
25
  ],
22
26
  })
23
27
 
24
28
  bail = Option.Boolean('--bail', false, { description: 'stop at the first failure' })
25
29
  json = Option.Boolean('--json', false, { description: 'machine-readable result instead of wire lines' })
26
- fix = Option.Boolean('--fix', true, { description: 'repair the mechanical slots before gating. Default on. --no-fix skips the repair pass, checks can still write. Under CI the fixers still run but the gate FAILS on any file they rewrite' })
27
- fixRules = Option.Boolean('--fix-rules', true, { description: 'apply the structural rule autofixes too. Default on. --no-fix-rules leaves repo config alone' })
28
- frozen = Option.Boolean('--frozen', false, { description: 'report-only mode for self-maintaining rules: SK005 never touches package.json, and generated paths are never untracked. STATUS.md is still refreshed on disk, but it is gitignored, so a run leaves the tree clean' })
29
- skip = Option.Array('--skip', [], { description: 'skip a task group (repeatable)' })
30
- withIds = Option.Array('--with', [], { description: 'include an opt-in task (repeatable)' })
31
- only = Option.Array('--only', [], { description: 'run only this task + its dep closure (repeatable)' })
32
- fresh = Option.Boolean('--fresh', false, { description: 'measure everything: no unit replays from the ledger, and what this run measures is stored fresh' })
30
+ fix = Option.Boolean('--fix', true, { description: 'repair the mechanical slots before gating. Default on. --no-fix skips the repair pass, the checks can still write. Under CI the fixers still run but the gate FAILS on any file they rewrite' })
31
+ fixRules = Option.Boolean('--fix-rules', true, { description: 'apply the structural autofixes of the built-in checks too. Default on. --no-fix-rules leaves repo config alone' })
32
+ frozen = Option.Boolean('--frozen', false, { description: 'report-only mode for self-maintaining checks: SK005 never touches package.json, and generated paths are never untracked. STATUS.md is still refreshed on disk, but it is gitignored, so a run leaves the tree clean' })
33
+ skip = Option.Array('--skip', [], { description: 'skip a check group (repeatable)' })
34
+ withIds = Option.Array('--with', [], { description: 'include a check the quick gate leaves out (repeatable)' })
35
+ only = Option.Array('--only', [], { description: 'run only this check + its dep closure (repeatable). A group name runs every check declaring that group' })
36
+ fresh = Option.Boolean('--fresh', false, { description: 'run every check: no answer comes from the cache, and what this run finds is stored fresh' })
33
37
  full = Option.Boolean('--full', false, { description: `run the FULL test tier: every suite the repo declares, the slow ones it skips while you work included. What the release path always runs. Same as SHIPKIT_TEST_FULL=1` })
34
38
 
39
+ detach = Option.Boolean('--detach', false, { description: 'start this gate in the background and return at once with a run id. `shipkit status <id>` prints it. The gate is an ordinary one: same cache, same run record' })
40
+ job = Option.String('--job', { hidden: true })
41
+
35
42
  async execute(): Promise<number> {
43
+ const root = process.cwd()
44
+ if (this.detach) {
45
+ const d = startDetached(root, { cli: join(import.meta.dir, '../cli.ts'), args: this.argsAgain() })
46
+ for (const l of d.lines) this.context.stdout.write(`${l}\n`)
47
+ return 0
48
+ }
49
+ const code = await this.gate(root)
50
+ if (this.job != null) recordExit(root, this.job, code)
51
+ return code
52
+ }
53
+
54
+ /** this invocation's flags, --detach aside, for the gate that runs detached */
55
+ private argsAgain(): string[] {
56
+ return [
57
+ ...(this.bail ? ['--bail'] : []),
58
+ ...(this.json ? ['--json'] : []),
59
+ ...(this.fix ? [] : ['--no-fix']),
60
+ ...(this.fixRules ? [] : ['--no-fix-rules']),
61
+ ...(this.frozen ? ['--frozen'] : []),
62
+ ...this.skip.flatMap((v) => ['--skip', v]),
63
+ ...this.withIds.flatMap((v) => ['--with', v]),
64
+ ...this.only.flatMap((v) => ['--only', v]),
65
+ ...(this.fresh ? ['--fresh'] : []),
66
+ ...(this.full ? ['--full'] : []),
67
+ ]
68
+ }
69
+
70
+ private async gate(root: string): Promise<number> {
36
71
  // a stale toolkit updates itself and restarts this exact invocation
37
72
  // (loop-guarded, skipped under CI/frozen/source consumption)
38
- await maybeSelfUpdateAndRestart(process.cwd())
73
+ await maybeSelfUpdateAndRestart(root)
39
74
  try {
40
- return await runCi(process.cwd(), {
75
+ return await runCi(root, {
41
76
  bail: this.bail,
42
77
  json: this.json,
43
78
  fix: this.fix,
@@ -2,6 +2,7 @@
2
2
  // drifts the moment a flag changes, and then teaches commands that do not
3
3
  // exist. rendering the live registry makes the doc a build artifact.
4
4
  import { Command, Option } from 'clipanion'
5
+ import { spliceMarkedBlock } from '../core/doc-block.ts'
5
6
 
6
7
  export type CommandOption = { definition: string; description: string }
7
8
  export type CommandRow = {
@@ -74,12 +75,7 @@ export const END = '<!-- /shipkit:commands -->'
74
75
  /** replace the marked block in a host file. Returns null when the markers are
75
76
  * missing, so a renamed or hand-mangled host fails loud instead of silently
76
77
  * writing nothing. */
77
- export function spliceBlock(host: string, block: string): string | null {
78
- const a = host.indexOf(BEGIN)
79
- const b = host.indexOf(END)
80
- if (a === -1 || b === -1 || b < a) return null
81
- return `${host.slice(0, a + BEGIN.length)}\n\n${block}\n\n${host.slice(b)}`
82
- }
78
+ export const spliceBlock = (host: string, block: string): string | null => spliceMarkedBlock(host, block, BEGIN, END)
83
79
 
84
80
  export class CommandsCommand extends Command {
85
81
  static override paths = [['commands']]
@@ -0,0 +1,69 @@
1
+ // `shipkit config-keys` — the repo.config.ts surface as markdown, read from
2
+ // the types themselves. Twin of `shipkit commands`: the binary is the source,
3
+ // the page is the artifact, and a lint in the docs repo fails when the two
4
+ // disagree.
5
+ import { fileURLToPath } from 'url'
6
+ import { Command, Option } from 'clipanion'
7
+ import { BEGIN, END, CHECK_BEGIN, CHECK_END, configKeys, renderKeys, undocumented } from '../core/config-keys.ts'
8
+ import { spliceMarkedBlock } from '../core/doc-block.ts'
9
+
10
+ /** the two types the walks start from, and the files that declare them: what
11
+ * a repo declares in its CONFIG, and what one `*.check.ts` file declares */
12
+ export const CONFIG_TYPE = 'RepoConfig'
13
+ export const CHECK_TYPE = 'CheckDef'
14
+ export const configSourceFile = (): string => fileURLToPath(new URL('../repo-config.ts', import.meta.url))
15
+ export const checkSourceFile = (): string => fileURLToPath(new URL('../check.ts', import.meta.url))
16
+
17
+ export class ConfigKeysCommand extends Command {
18
+ static override paths = [['config-keys']]
19
+
20
+ static override usage = Command.Usage({
21
+ category: 'Health',
22
+ description: 'every repo.config.ts key and every check field with its doc, read from the types — the source a config reference page is generated FROM',
23
+ examples: [
24
+ ['markdown reference', 'shipkit config-keys'],
25
+ ['machine-readable', 'shipkit config-keys --json'],
26
+ ['write into a docs page between the markers', 'shipkit config-keys --out wiki/build/shipkit/config-keys.md'],
27
+ ['fail if that page is stale (lint mode)', 'shipkit config-keys --out wiki/build/shipkit/config-keys.md --check'],
28
+ ['name the keys carrying no doc comment', 'shipkit config-keys --undocumented'],
29
+ ],
30
+ })
31
+
32
+ json = Option.Boolean('--json', false, { description: 'machine-readable { config, check } of [{path, name, type, optional, doc, depth}]' })
33
+ out = Option.String('--out', { description: 'host file carrying the shipkit:config-keys and shipkit:check-keys markers. Both blocks are rewritten' })
34
+ check = Option.Boolean('--check', false, { description: 'with --out: exit 1 if the block is stale, write nothing' })
35
+ undocumented = Option.Boolean('--undocumented', false, { description: 'list the keys with no doc comment in the source, and exit 1 if there are any' })
36
+
37
+ async execute(): Promise<number> {
38
+ const keys = configKeys(configSourceFile(), CONFIG_TYPE)
39
+ const checkKeys = configKeys(checkSourceFile(), CHECK_TYPE)
40
+ if (this.undocumented) {
41
+ const missing = [...undocumented(keys), ...undocumented(checkKeys)]
42
+ if (missing.length === 0) { this.context.stdout.write(`ok: every ${CONFIG_TYPE} and ${CHECK_TYPE} key carries a doc comment (${keys.length + checkKeys.length} keys)\n`); return 0 }
43
+ this.context.stdout.write(`FAIL: ${missing.length} key(s) carry no doc comment — a key nobody documented is a key nobody uses\n`)
44
+ for (const p of missing) this.context.stdout.write(` ${p}\n`)
45
+ return 1
46
+ }
47
+ if (this.out == null) {
48
+ this.context.stdout.write(this.json ? `${JSON.stringify({ config: keys, check: checkKeys })}\n` : `${renderKeys(keys)}\n\n${renderKeys(checkKeys)}\n`)
49
+ return 0
50
+ }
51
+ const { existsSync, readFileSync, writeFileSync } = await import('fs')
52
+ if (!existsSync(this.out)) { this.context.stdout.write(`FAIL: ${this.out} does not exist\n`); return 1 }
53
+ const host = readFileSync(this.out, 'utf8')
54
+ const spliced = spliceMarkedBlock(host, renderKeys(keys), BEGIN, END)
55
+ if (spliced == null) { this.context.stdout.write(`FAIL: ${this.out} has no ${BEGIN} / ${END} markers\n`); return 1 }
56
+ const next = spliceMarkedBlock(spliced, renderKeys(checkKeys), CHECK_BEGIN, CHECK_END)
57
+ if (next == null) { this.context.stdout.write(`FAIL: ${this.out} has no ${CHECK_BEGIN} / ${CHECK_END} markers\n`); return 1 }
58
+ const total = keys.length + checkKeys.length
59
+ if (next === host) { this.context.stdout.write(`ok: ${this.out} config reference current (${total} keys)\n`); return 0 }
60
+ if (this.check) {
61
+ this.context.stdout.write(`FAIL: ${this.out} config reference is STALE — the types carry keys the page does not list\n`)
62
+ this.context.stdout.write(` fix: shipkit config-keys --out ${this.out}\n`)
63
+ return 1
64
+ }
65
+ writeFileSync(this.out, next)
66
+ this.context.stdout.write(`ok: ${this.out} config reference rewritten (${total} keys)\n`)
67
+ return 0
68
+ }
69
+ }
@@ -8,7 +8,7 @@ export class ContractsCommand extends Command {
8
8
 
9
9
  static override usage = Command.Usage({
10
10
  category: 'Gate',
11
- description: 'release-contract fulfillness, fast and filesystem-only — deploy tasks dep on it',
11
+ description: 'release-contract fulfillness, fast and filesystem-only — deploy checks dep on it',
12
12
  examples: [
13
13
  ['all applicable contracts', 'shipkit contracts'],
14
14
  ['one venue', 'shipkit contracts --venue itch'],
@@ -5,10 +5,17 @@
5
5
  import { Command, Option } from 'clipanion'
6
6
  import { existsSync } from 'fs'
7
7
  import { join } from 'path'
8
- import { npmLatest, resolveLatest, scanFleet, type FleetRepo } from '../fleet/scan-fleet.ts'
8
+ import { npmLatest, resolveLatest, scanFleet, updateRefusal, type FleetRepo } from '../fleet/scan-fleet.ts'
9
9
  import { spawnWithTimeout } from '../core/spawn-timeout.ts'
10
+ import { detectPackageManager, isRefusal } from '../core/package-manager.ts'
10
11
 
11
12
  const short = (name: string): string => name.replace(/^rvlib-/, '')
13
+ // the hand-run fallback speaks the manager THAT repo installs with: telling
14
+ // an npm consumer to run bun leaves its package-lock.json behind
15
+ const manualUpdate = (r: FleetRepo): string => {
16
+ const pm = detectPackageManager(r.dir)
17
+ return isRefusal(pm) ? `<its package manager> update ${r.behind.map((d) => d.name).join(' ')} (${pm.refused})` : pm.update(r.behind.map((d) => d.name)).join(' ')
18
+ }
12
19
 
13
20
  export class FleetCommand extends Command {
14
21
  static override paths = [['fleet']]
@@ -88,9 +95,15 @@ export class FleetCommand extends Command {
88
95
  failed++
89
96
  continue
90
97
  }
98
+ const refused = updateRefusal(r)
99
+ if (refused != null) {
100
+ w(`WARN ${r.name}: not updating: ${refused}`)
101
+ failed++
102
+ continue
103
+ }
91
104
  const cli = join(r.dir, 'node_modules', 'rvlib-shipkit', 'src', 'cli.ts')
92
105
  if (!existsSync(cli)) {
93
- w(`WARN ${r.name}: no shipkit installed, so it cannot update itself — run: cd ${r.dir} && bun update --latest ${r.behind.map((d) => d.name).join(' ')}`)
106
+ w(`WARN ${r.name}: no shipkit installed, so it cannot update itself — run: cd ${r.dir} && ${manualUpdate(r)}`)
94
107
  failed++
95
108
  continue
96
109
  }
@@ -105,7 +118,7 @@ export class FleetCommand extends Command {
105
118
  else {
106
119
  failed++
107
120
  w(`WARN ${r.name} did not move — ${line ?? `check exited ${res.code} with no rvlib line`}`)
108
- w(` fix: cd ${r.dir} && bun update --latest ${r.behind.map((d) => d.name).join(' ')}`)
121
+ w(` fix: cd ${r.dir} && ${manualUpdate(r)}`)
109
122
  }
110
123
  }
111
124
  // a repo whose gate refused (dirty tree, frozen, a lease held elsewhere)
@@ -13,7 +13,7 @@ export class HooksInstallCommand extends Command {
13
13
  static override paths = [['hooks', 'install']]
14
14
  static override usage = Command.Usage({
15
15
  category: 'Gate',
16
- description: 'write hooks/pre-commit + hooks/commit-msg and point core.hooksPath at them (idempotent)',
16
+ description: 'write .shipkit/hooks/pre-commit + .shipkit/hooks/commit-msg and point core.hooksPath at them (idempotent)',
17
17
  examples: [['wire the hooks', 'shipkit hooks install']],
18
18
  })
19
19
 
@@ -1,24 +1,26 @@
1
- // shipkit keys — what every gate unit is keyed on, and what it is NOT.
1
+ // shipkit keys — what every gate check is keyed on, and what it is NOT.
2
2
  //
3
- // Nothing is cached yet. This command exists so the key layer is auditable by
4
- // eye BEFORE anything replays off it: a cache whose keys nobody can read is a
3
+ // This command exists so the key layer is auditable by eye, whatever the
4
+ // cache serves off it: a cache whose keys nobody can read is a
5
5
  // cache nobody can debug the day it serves a wrong answer. `--why <id>` prints
6
6
  // the exact component list a key was hashed from, which is the only honest
7
7
  // answer to "why did this key move".
8
8
  import { createHash } from 'crypto'
9
9
  import { Command, Option } from 'clipanion'
10
10
  import { loadRepoConfig } from '../repo-config.ts'
11
- import { envTier, FULL_TESTS_ENV, gateUnits, tieredTasks } from '../ledger/gate-units.ts'
11
+ import { envTier, FULL_TESTS_ENV, gateChecks } from '../ledger/gate-checks.ts'
12
12
  import { KeyLayer } from '../ledger/git-key.ts'
13
+ import { Ledger } from '../ledger/ledger.ts'
14
+ import { gateBinary } from '../ledger/toolchain-identity.ts'
13
15
 
14
16
  export class KeysCommand extends Command {
15
17
  static override paths = [['keys']]
16
18
 
17
19
  static override usage = Command.Usage({
18
20
  category: 'Health',
19
- description: 'the cache key of every gate unit (task, rule, check), and the reason the undeclared ones have none. Reports, never gates',
21
+ description: 'the cache key of every gate check, and the reason the undeclared ones have none. Reports, never gates',
20
22
  examples: [
21
- ['every unit and its key', 'shipkit keys'],
23
+ ['every check and its key', 'shipkit keys'],
22
24
  ['only the ones nothing can cache yet', 'shipkit keys --burn-down'],
23
25
  ['why this key is what it is', 'shipkit keys --why typecheck'],
24
26
  ['re-hash after the fact and report drift', 'shipkit keys --rescan'],
@@ -27,9 +29,9 @@ export class KeysCommand extends Command {
27
29
  ],
28
30
  })
29
31
 
30
- json = Option.Boolean('--json', false, { description: 'machine-readable { units: UnitKey[], burnDown, errors, scanMs }' })
31
- burnDown = Option.Boolean('--burn-down', false, { description: 'list only the units nothing can cache yet, and why' })
32
- why = Option.String('--why', { required: false, description: 'print every component that went into ONE unit\'s key' })
32
+ json = Option.Boolean('--json', false, { description: 'machine-readable { checks: CheckKey[], burnDown, errors, scanMs }' })
33
+ burnDown = Option.Boolean('--burn-down', false, { description: 'list only the checks nothing can cache yet, and why' })
34
+ why = Option.String('--why', { required: false, description: 'print every component that went into ONE check\'s key' })
33
35
  full = Option.Boolean('--full', false, { description: `render the keys a FULL-tier run asks for (same as ${FULL_TESTS_ENV}=1). The tier is in every task's key` })
34
36
  rescan = Option.Boolean('--rescan', false, { description: 'run the post-run re-hash and name every input that moved since the scan' })
35
37
 
@@ -40,29 +42,51 @@ export class KeysCommand extends Command {
40
42
  // every task's env, which is in the key, so a surface that renders keys
41
43
  // without it renders a tier nobody asked for
42
44
  const tier = this.full ? 'full' : envTier()
43
- const decls = gateUnits(root, c, tieredTasks(c, root, tier))
44
- const layer = KeyLayer.open(root, decls, { cliEntry: new URL('../cli.ts', import.meta.url).pathname })
45
+ const decls = await gateChecks(root, c, { tier })
46
+ const layer = KeyLayer.open(root, decls, { cliEntry: gateBinary() })
45
47
  const keys = layer.keys()
46
48
  const out = (l: string): void => { this.context.stdout.write(`${l}\n`) }
47
49
 
48
50
  if (this.json) {
49
51
  const drift = this.rescan ? layer.rescan() : null
50
- out(JSON.stringify({ tier, units: keys, burnDown: layer.registration.burnDown, errors: layer.registration.errors, layerMs: Math.round(layer.openMs), scanMs: Math.round(layer.scanMs), failure: layer.failure, drift }))
52
+ out(JSON.stringify({ tier, checks: keys, burnDown: layer.registration.burnDown, errors: layer.registration.errors, layerMs: Math.round(layer.openMs), scanMs: Math.round(layer.scanMs), failure: layer.failure, drift }))
51
53
  return layer.registration.errors.length > 0 || layer.failure != null ? 1 : 0
52
54
  }
53
55
 
54
56
  if (this.why != null) {
55
57
  const k = keys.find((u) => u.id.toLowerCase() === this.why!.toLowerCase())
56
58
  if (k == null) {
57
- out(`FAIL: no gate unit '${this.why}' — run \`shipkit keys\` for the list`)
59
+ out(`FAIL: no gate check '${this.why}' — run \`shipkit keys\` for the list`)
58
60
  return 1
59
61
  }
60
- out(`${k.kind} ${k.id} — ${k.population}`)
62
+ out(`${k.id} — ${k.population}`)
61
63
  if (k.key == null) {
62
64
  out(` no key: ${k.reason ?? 'undeclared'}`)
63
65
  return 0
64
66
  }
65
67
  out(` key ${k.key}`)
68
+ if (k.population === 'reads') {
69
+ for (const comp of k.components) out(` ${comp}`)
70
+ // the rest of its identity is what it READ, stored with the
71
+ // answer and re-checked before any replay
72
+ const set = Ledger.open(root, decls, { cliEntry: gateBinary(), read: true }).storedReads(k.id)
73
+ if (set == null) {
74
+ out(' no stored answer yet: the next run records what it reads')
75
+ return 0
76
+ }
77
+ const files = Object.entries(set.files)
78
+ const exists = Object.entries(set.exists)
79
+ const lists = Object.entries(set.lists)
80
+ const dirs = Object.entries(set.dirs ?? {})
81
+ const links = Object.entries(set.links ?? {})
82
+ out(` read set: ${files.length} file(s), ${exists.length} existence probe(s), ${lists.length} listing(s), ${dirs.length} dir read(s)${links.length > 0 ? `, ${links.length} link probe(s)` : ''}`)
83
+ for (const [p, oid] of files) out(` read ${p}=${oid ?? 'absent'}`)
84
+ for (const [p, kind] of exists) out(` exists ${p}=${kind ?? 'absent'}`)
85
+ for (const [g, h] of lists) out(` list ${g}=${h}`)
86
+ for (const [d, h] of dirs) out(` entries ${d}=${h ?? 'absent'}`)
87
+ for (const [p, is] of links) out(` link ${p}=${is ? 'yes' : 'no'}`)
88
+ return 0
89
+ }
66
90
  // the excluded block is 44 lines here and it buries the component
67
91
  // that actually moved. It stays HASHED line by line, so this is a
68
92
  // rendering choice and not a different key: the digest moves with
@@ -81,22 +105,23 @@ export class KeysCommand extends Command {
81
105
 
82
106
  for (const e of layer.registration.errors) out(`FAIL: ${e}`)
83
107
  if (layer.failure != null) {
84
- out(`FAIL: git scan failed, no unit may be keyed — ${layer.failure}`)
108
+ out(`FAIL: git scan failed, no check may be keyed — ${layer.failure}`)
85
109
  out(' fix: nothing is cached, so the gate is unaffected. A stale scratch index clears with: rm .git/shipkit/scan.idx')
86
110
  }
87
111
 
88
- // the burn-down is what nobody has LOOKED at. A unit that declared
112
+ // the burn-down is what nobody has LOOKED at. A check that declared
89
113
  // `never` with a reason has been looked at, and listing it here would
90
114
  // put four permanent rows on a list whose whole job is to shrink
91
115
  const decided = new Set(layer.decls.filter((d) => d.never != null && d.never !== '').map((d) => d.id))
92
116
  const shown = this.burnDown ? keys.filter((k) => k.population === 'none' && !decided.has(k.id)) : keys
93
- for (const k of shown) out(`${k.population.padEnd(7)} ${k.kind.padEnd(5)} ${k.id.padEnd(28)} ${k.key ?? `— ${k.reason ?? ''}`}`)
117
+ for (const k of shown) out(`${k.population.padEnd(7)} ${k.id.padEnd(28)} ${k.key ?? `— ${k.reason ?? ''}`}`)
94
118
 
95
119
  const content = keys.filter((k) => k.population === 'content').length
120
+ const reads = keys.filter((k) => k.population === 'reads').length
96
121
  const world = keys.filter((k) => k.population === 'world').length
97
122
  const none = keys.filter((k) => k.population === 'none').length
98
123
  const todo = layer.registration.burnDown.length
99
- out(`\n${keys.length} units — ${content} content, ${world} world, ${none} uncacheable (${none - todo} decided, ${todo} still undeclared) · ${tier} test tier · key layer ${Math.round(layer.openMs)}ms, of which ${Math.round(layer.scanMs)}ms of git`)
124
+ out(`\n${keys.length} checks — ${content} content, ${reads} reads, ${world} world, ${none} never cached (${none - todo} on purpose, ${todo} still undeclared) · ${tier} test tier · key layer ${Math.round(layer.openMs)}ms, of which ${Math.round(layer.scanMs)}ms of git`)
100
125
  if (todo > 0 && !this.burnDown) out(` the burn-down: shipkit keys --burn-down`)
101
126
 
102
127
  if (this.rescan) {
package/src/cli/cmd_Mv.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // shipkit mv, ONE rename command for the whole repo. An md page routes to
2
- // mv/mv-md.ts (page + sibling asset dir + check.ts + every tracked ref);
2
+ // mv/mv-md.ts (page + sibling asset dir + its check file + every tracked ref);
3
3
  // anything else runs shipkit's own rename, which rewrites every path
4
4
  // reference in the code. Renaming used to mean knowing WHICH tool applied,
5
5
  // and knowing that is exactly the kind of doubt the toolkit exists to remove.
@@ -12,7 +12,7 @@ export class MvCommand extends Command {
12
12
 
13
13
  static override usage = Command.Usage({
14
14
  category: 'Refactor',
15
- description: 'rename anything and sweep every reference: an md page (plus its sibling dir and check.ts) or a code file or folder. Dry run by default. Renames must be CHEAP',
15
+ description: 'rename anything and sweep every reference: an md page (plus its sibling dir and its check file) or a code file or folder. Dry run by default. Renames must be CHEAP',
16
16
  examples: [
17
17
  ['dry run, the default, shows what changes', 'shipkit mv docs/old-name.md docs/new-name.md'],
18
18
  ['apply it', 'shipkit mv docs/old-name.md docs/new-name.md --write'],
@@ -4,6 +4,7 @@
4
4
  import { Command, Option } from 'clipanion'
5
5
  import { loadRepoConfig } from '../repo-config.ts'
6
6
  import { classSummary, proseLines, runProseCheck } from '../prose/run-prose.ts'
7
+ import { CheckCtx } from '../core/check-ctx.ts'
7
8
 
8
9
  export class ProseCommand extends Command {
9
10
  static override paths = [['prose']]
@@ -39,7 +40,7 @@ export class ProseCommand extends Command {
39
40
  )
40
41
  return 0
41
42
  }
42
- const r = runProseCheck({ root, config: cfg, paths: explicit ? this.targets : undefined, fix: this.fix })
43
+ const r = runProseCheck({ c: CheckCtx.open({ root, config }), config: cfg, paths: explicit ? this.targets : undefined, fix: this.fix })
43
44
  if (this.json) {
44
45
  // every hit carries its file: a flat list without one cannot be
45
46
  // acted on, and the grouping is already in byFile
@@ -48,6 +49,14 @@ export class ProseCommand extends Command {
48
49
  return r.byTier.fix + r.byTier.hard > 0 ? 1 : 0
49
50
  }
50
51
  const w = (l: string) => this.context.stdout.write(`${l}\n`)
52
+ // a declared scope that resolves to nothing reads as `0 file(s)
53
+ // STE-clean`, the same green a clean repo gets, over a check that
54
+ // inspected no file at all (rule SK014 warns on the same fact)
55
+ if (!explicit && r.files === 0) {
56
+ w(`WARN prose scope matches no file: ${(cfg.include ?? []).join(', ')} — declared and inspecting nothing`)
57
+ w(' fix: fix the globs in repo.config.ts prose.include (or its exempt list), or drop the key to opt out')
58
+ return 0
59
+ }
51
60
  if (r.fixed.length > 0) w(`ok: rewrote ${r.fixed.length} file(s)`)
52
61
  for (const l of proseLines(r, { cap: Number(this.cap) || 25 })) w(l)
53
62
  const blocking = r.byTier.fix + r.byTier.hard
@@ -1,4 +1,4 @@
1
- // shipkit releases — every release unit and where it actually stands. Thin
1
+ // shipkit releases — every releasing unit and where it actually stands. Thin
2
2
  // clipanion surface; logic in release/run-releases.ts.
3
3
  import { Command, Option } from 'clipanion'
4
4
  import { loadRepoConfig } from '../repo-config.ts'
@@ -9,7 +9,7 @@ export class ReleasesCommand extends Command {
9
9
 
10
10
  static override usage = Command.Usage({
11
11
  category: 'Gate',
12
- description: 'every release unit and where it stands: version, npm, tag local and remote, GitHub release, drift. Reports, never gates (only an unresolvable config exits non-zero)',
12
+ description: 'every unit that releases and where it stands: version, npm, tag local and remote, GitHub release, drift. Reports, never gates (only an unresolvable config exits non-zero)',
13
13
  examples: [
14
14
  ['what is published and what is not', 'shipkit releases'],
15
15
  ['filesystem + local git only, no network', 'shipkit releases --offline'],
@@ -8,7 +8,7 @@ export class ServeCommand extends Command {
8
8
 
9
9
  static override usage = Command.Usage({
10
10
  category: 'Serve',
11
- description: 'ONE server, ONE page: media studio + asks + creds + posts + health + graphs, with drill-down graph pages',
11
+ description: 'ONE server, ONE page: media studio + asks + creds + posts + health + wikis, each wiki a link graph page',
12
12
  examples: [
13
13
  ['serve the repo toolbox', 'shipkit serve'],
14
14
  ['pin the port for this launch', 'shipkit serve --port 4804'],
@@ -9,10 +9,10 @@ export class StartCommand extends Command {
9
9
 
10
10
  static override usage = Command.Usage({
11
11
  category: 'Setup',
12
- description: 'create (or enter) ~/dev/<name> from ANY cwd, READY to work in. On any dir with no commit history it writes the day-one shape (package.json + src/index.ts + lint/fmt/typecheck, tsconfig, .vscode tasks + settings, the verify skill, an agent/ wiki, .rv-reflections), installs the toolchain, wires shipkit (repo.config.ts via init), and runs the whole bootstrap chain: git init, first commit, PRIVATE github repo, push. Loose files ride in that first commit, and only a repo with real history is left as it is. Writes are create-only for files, additive for package.json scripts and .gitignore. Seeding CLAUDE.md include lines also opts the repo into the .claude/ policy files (a pre-approved shell allowlist + the attribution silencer). Prints the dir on stdout so a shell alias can cd there. cwd-only sibling: `shipkit init`',
12
+ description: 'create (or enter) ~/dev/<name> from ANY cwd, READY to work in. On any dir with no commit history it writes the day-one shape (package.json + src/index.ts + lint/fmt/typecheck, tsconfig, .vscode tasks + settings, the verify skill, an agent/ wiki, .shipkit/reflections), installs the toolchain, wires shipkit (repo.config.ts via init), and runs the whole bootstrap chain: git init, first commit, PRIVATE github repo, push. Loose files ride in that first commit, and only a repo with real history is left as it is. Writes are create-only for files, additive for package.json scripts and .gitignore. CLAUDE.md is the two-line house template on its own (HOUSE_INCLUDE in src/start-repo.ts: the base wiki include, then `@./agent/index.md`, and --include overrides it), applied only on a machine where the house index exists, and the agent route lands last whenever the repo has agent/index.md. The house lines also opt the repo into the .claude/ policy files (a pre-approved shell allowlist + the attribution silencer), so a start with no house widens no agent permissions. Prints the dir on stdout so a shell alias can cd there. cwd-only sibling: `shipkit init`',
13
13
  examples: [
14
- ['create or enter ~/dev/pong: dir, package.json, shipkit wiring, git, first commit, private github repo, push', 'shipkit start pong'],
15
- ['seed CLAUDE.md includes', "shipkit start pong --include '@~/wiki/index.md' --include '@./agent/index.md'"],
14
+ ['create or enter ~/dev/pong: dir, package.json, shipkit wiring, git, first commit, private github repo, push, the two-line CLAUDE.md', 'shipkit start pong'],
15
+ ['another house: replace the include lines', "shipkit start pong --include '@~/wiki/index.md' --include '@./agent/index.md'"],
16
16
  ['adopt a pre-existing repo from anywhere', 'shipkit start my-old-project'],
17
17
  ['force the private github repo for a folder that was already there', 'shipkit start old-notes --github'],
18
18
  ['local git and the first commit, never touch github', 'shipkit start pong --no-github'],
@@ -21,9 +21,9 @@ export class StartCommand extends Command {
21
21
 
22
22
  name = Option.String()
23
23
  root = Option.String('--root', { description: 'parent dir for repos (default ~/dev)' })
24
- include = Option.Array('--include', { description: 'line CLAUDE.md must carry, repeatable, order kept. Omitted means the run only creates the dir' })
25
- alsoOk = Option.Array('--also-ok', { description: 'alternative lines satisfying the first --include — never double-add' })
26
- skip = Option.Array('--skip', { description: 'repo names shipkit never touches: no CLAUDE.md include, no wiring' })
24
+ include = Option.Array('--include', { description: 'line CLAUDE.md must carry, repeatable, order kept. Omitted: the house template (HOUSE_INCLUDE in src/start-repo.ts), the base wiki include then `@./agent/index.md`, and on a machine without that wiki the agent route alone' })
25
+ alsoOk = Option.Array('--also-ok', { description: 'alternative lines satisfying the first --include — never double-add. Omitted: `@./CLAUDE.local.md`, the public-repo indirection' })
26
+ skip = Option.Array('--skip', { description: 'repo names shipkit never touches: no CLAUDE.md include, no wiring. Omitted: the house list (HOUSE_SKIP in src/start-repo.ts)' })
27
27
  github = Option.Boolean('--github', { description: 'ON by default when a repo is being STARTED (a dir this run created, or one with a package.json): it gets a PRIVATE github repo wired as origin, and the first commit is pushed to it. Pass --github to force it for a pre-existing folder, --no-github to keep everything local, the first commit included (SHIPKIT_NO_GITHUB=1 does it for every run)' })
28
28
 
29
29
  async execute(): Promise<number> {
@@ -0,0 +1,25 @@
1
+ // shipkit status — read back a gate started with `shipkit ci --detach`. Thin
2
+ // clipanion surface; logic in core/jobs.ts.
3
+ import { Command, Option } from 'clipanion'
4
+ import { jobStatus, STILL_RUNNING } from '../core/jobs.ts'
5
+
6
+ export class StatusCommand extends Command {
7
+ static override paths = [['status']]
8
+
9
+ static override usage = Command.Usage({
10
+ category: 'Gate',
11
+ description: `a gate started with \`shipkit ci --detach\`: its wire lines so far, then its verdict. Exit code = the gate's own once it finished, ${STILL_RUNNING} while it still runs, 1 when it died without one`,
12
+ examples: [
13
+ ['the latest detached run of this checkout', 'shipkit status'],
14
+ ['one run by id', 'shipkit status ci-mf3k2a-1x2y'],
15
+ ],
16
+ })
17
+
18
+ id = Option.String({ required: false })
19
+
20
+ async execute(): Promise<number> {
21
+ const r = jobStatus(process.cwd(), this.id)
22
+ for (const l of r.lines) this.context.stdout.write(`${l}\n`)
23
+ return r.code
24
+ }
25
+ }
@@ -1,5 +1,5 @@
1
1
  // shipkit tui — the live dashboard: Tasks (the ci gate, streamed through
2
- // the DAG runner) + Checks (contracts + discovered *.check.ts) built in,
2
+ // the DAG runner) + checks (everything the health pass asks for) built in,
3
3
  // repo extras injected via repo.config `tui: { panels: [...] }`.
4
4
  // The body lives in src/tui/run-tui.ts and is loaded ONLY when the command
5
5
  // runs: the react/ink chain must never load for `shipkit ci` in a repo
@@ -11,7 +11,7 @@ export class TuiCommand extends Command {
11
11
 
12
12
  static override usage = Command.Usage({
13
13
  category: 'Health',
14
- description: 'live dashboard: ci tasks + discovered checks, plus panels the repo injects via repo.config tui.panels',
14
+ description: 'live dashboard: the gate build steps + every other check, plus panels the repo injects via repo.config tui.panels',
15
15
  examples: [['open the dashboard', 'shipkit tui']],
16
16
  })
17
17
 
package/src/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { AssetsAddCommand, AssetsCheckCommand, AssetsFillCommand, AssetsGenComma
9
9
  import { BumpCommand } from './cli/cmd_Bump.ts'
10
10
  import { CheckCommand } from './cli/cmd_Check.ts'
11
11
  import { CommandsCommand } from './cli/cmd_Commands.ts'
12
+ import { ConfigKeysCommand } from './cli/cmd_ConfigKeys.ts'
12
13
  import { CiCommand } from './cli/cmd_Ci.ts'
13
14
  import { ContractsCommand } from './cli/cmd_Contracts.ts'
14
15
  import { FleetCommand } from './cli/cmd_Fleet.ts'
@@ -22,10 +23,10 @@ import { ProseCommand } from './cli/cmd_Prose.ts'
22
23
  import { ReflectCommand } from './cli/cmd_Reflect.ts'
23
24
  import { ReleaseCommand } from './cli/cmd_Release.ts'
24
25
  import { ReleasesCommand } from './cli/cmd_Releases.ts'
25
- import { RulesCommand } from './cli/cmd_Rules.ts'
26
- import { UnitsCommand } from './cli/cmd_Units.ts'
26
+ import { ChecksCommand, UnitsAliasCommand } from './cli/cmd_Checks.ts'
27
27
  import { ServeCommand } from './cli/cmd_Serve.ts'
28
28
  import { SmokeCommand } from './cli/cmd_Smoke.ts'
29
+ import { StatusCommand } from './cli/cmd_Status.ts'
29
30
  import { StartCommand } from './cli/cmd_Start.ts'
30
31
  import { TuiCommand } from './cli/cmd_Tui.ts'
31
32
  import { WikiLintCommand, WikiServeCommand } from './cli/cmd_Wiki.ts'
@@ -47,6 +48,7 @@ export function buildCli(): Cli {
47
48
  cli.register(Builtins.HelpCommand)
48
49
  cli.register(Builtins.VersionCommand)
49
50
  cli.register(CiCommand)
51
+ cli.register(StatusCommand)
50
52
  cli.register(BumpCommand)
51
53
  cli.register(ReleaseCommand)
52
54
  cli.register(ReleasesCommand)
@@ -77,10 +79,11 @@ export function buildCli(): Cli {
77
79
  cli.register(ProseCommand)
78
80
  cli.register(SmokeCommand)
79
81
  cli.register(ReflectCommand)
80
- cli.register(RulesCommand)
81
82
  cli.register(KeysCommand)
82
- cli.register(UnitsCommand)
83
+ cli.register(ChecksCommand)
84
+ cli.register(UnitsAliasCommand)
83
85
  cli.register(CommandsCommand)
86
+ cli.register(ConfigKeysCommand)
84
87
 
85
88
  return cli
86
89
  }
@@ -10,7 +10,7 @@ const today = (): string => new Date().toISOString().slice(0, 10)
10
10
 
11
11
  const postPath = (root: string, channel: string, file: string): string => {
12
12
  if (channel.includes('/') || channel.includes('..') || file.includes('/') || file.includes('..')) throw new Error('bad path')
13
- const p = join(root, '.rv-social', channel, file)
13
+ const p = join(root, '.shipkit/social', channel, file)
14
14
  if (!existsSync(p)) throw new Error(`no such post: ${channel}/${file}`)
15
15
  return p
16
16
  }
@@ -25,7 +25,7 @@ const setStatus = (p: string, status: string): void => {
25
25
  * (files KEPT — rejections are training data for the next generation) */
26
26
  export function pickPostVariant(root: string, channel: string, file: string): void {
27
27
  const p = postPath(root, channel, file)
28
- const dir = join(root, '.rv-social', channel)
28
+ const dir = join(root, '.shipkit/social', channel)
29
29
  const slug = parsePostFile(dir, channel, file).slug
30
30
  setStatus(p, 'picked')
31
31
  for (const f of readdirSync(dir).filter((x) => x.endsWith('.md') && x !== file)) {
@@ -55,11 +55,11 @@ export function appendPostFeedback(root: string, channel: string, file: string,
55
55
  writeFileSync(p, /^## Feedback\s*$/m.test(md) ? `${md.trimEnd()}\n${line}\n` : `${md.trimEnd()}\n\n## Feedback\n${line}\n`)
56
56
  }
57
57
 
58
- /** an asset remark → `.rv-assets/notes.md` (git-tracked; the studio
58
+ /** an asset remark → `.shipkit/assets/notes.md` (git-tracked; the studio
59
59
  * workspace is gitignored, so notes live beside kit.ts) */
60
60
  export function appendAssetNote(root: string, assetId: string, text: string): void {
61
61
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(assetId)) throw new Error('bad asset id')
62
- const p = join(root, '.rv-assets', 'notes.md')
62
+ const p = join(root, '.shipkit/assets', 'notes.md')
63
63
  const line = `- ${today()} [${assetId}]: ${text.replace(/\n+/g, ' ').trim()}`
64
64
  writeFileSync(p, existsSync(p) ? `${readFileSync(p, 'utf8').trimEnd()}\n${line}\n` : `# asset notes, read by agents at boot\n\n${line}\n`)
65
65
  }