rvlib-shipkit 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +17 -17
  3. package/package.json +3 -2
  4. package/src/assets/gen/comfyts-gen.ts +12 -8
  5. package/src/assets/server/studio-server.ts +14 -2
  6. package/src/check.ts +73 -103
  7. package/src/checks/SK001-package-json-shape.check.ts +11 -6
  8. package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
  9. package/src/checks/SK003-claude-md-shape.check.ts +7 -3
  10. package/src/checks/SK004-gitignore-policy.check.ts +7 -4
  11. package/src/checks/SK005-rvlib-versions.check.ts +53 -21
  12. package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
  13. package/src/checks/SK007-github-visibility.check.ts +4 -2
  14. package/src/checks/SK008-reviewer-agent.check.ts +6 -4
  15. package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
  16. package/src/checks/SK010-undeclared-unit.check.ts +4 -0
  17. package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
  18. package/src/checks/SK012-config-barrel-import.check.ts +5 -1
  19. package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
  20. package/src/checks/SK014-prose-ste.check.ts +4 -0
  21. package/src/checks/SK015-nested-repo.check.ts +4 -2
  22. package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
  23. package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
  24. package/src/checks/SK018-test-files-run.check.ts +6 -2
  25. package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
  26. package/src/checks/SK020-self-spawning-check.check.ts +4 -0
  27. package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
  28. package/src/checks/SK024-identity-colors.check.ts +5 -3
  29. package/src/checks/SK025-release-drift.check.ts +4 -5
  30. package/src/checks/SK026-asks-autoclose.check.ts +4 -4
  31. package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
  32. package/src/checks/SK028-readme.check.ts +5 -1
  33. package/src/checks/SK029-explicit-check-files.check.ts +95 -0
  34. package/src/checks/SK030-cache-audit.check.ts +34 -0
  35. package/src/checks/SK031-trace-audit.check.ts +39 -0
  36. package/src/checks/SK032-ignored-inputs.check.ts +79 -0
  37. package/src/cli/cmd_Asks.ts +5 -5
  38. package/src/cli/cmd_Assets.ts +17 -13
  39. package/src/cli/cmd_Bump.ts +12 -11
  40. package/src/cli/cmd_CacheAudit.ts +43 -0
  41. package/src/cli/cmd_Check.ts +8 -7
  42. package/src/cli/cmd_Checks.ts +7 -6
  43. package/src/cli/cmd_Ci.ts +12 -11
  44. package/src/cli/cmd_Commands.ts +16 -13
  45. package/src/cli/cmd_ConfigKeys.ts +7 -6
  46. package/src/cli/cmd_Contracts.ts +3 -3
  47. package/src/cli/cmd_CrossRepoActivity.ts +8 -6
  48. package/src/cli/cmd_Fleet.ts +9 -7
  49. package/src/cli/cmd_GlobalProcesses.ts +120 -7
  50. package/src/cli/cmd_Home.ts +26 -19
  51. package/src/cli/cmd_Hooks.ts +6 -5
  52. package/src/cli/cmd_Init.ts +5 -4
  53. package/src/cli/cmd_Keys.ts +20 -8
  54. package/src/cli/cmd_Mv.ts +7 -6
  55. package/src/cli/cmd_Needs.ts +4 -3
  56. package/src/cli/cmd_Overview.ts +5 -4
  57. package/src/cli/cmd_Pb.ts +2 -2
  58. package/src/cli/cmd_Planned.ts +6 -5
  59. package/src/cli/cmd_Prose.ts +5 -5
  60. package/src/cli/cmd_Reflect.ts +7 -7
  61. package/src/cli/cmd_Release.ts +6 -5
  62. package/src/cli/cmd_Releases.ts +5 -4
  63. package/src/cli/cmd_Serve.ts +6 -4
  64. package/src/cli/cmd_Smoke.ts +5 -4
  65. package/src/cli/cmd_Start.ts +7 -6
  66. package/src/cli/cmd_Status.ts +4 -3
  67. package/src/cli/cmd_TraceAudit.ts +62 -0
  68. package/src/cli/cmd_Tui.ts +3 -2
  69. package/src/cli/cmd_When.ts +71 -0
  70. package/src/cli/cmd_Wiki.ts +4 -4
  71. package/src/cli.ts +11 -2
  72. package/src/collections/checks.ts +2 -0
  73. package/src/collections/collection-types.ts +12 -4
  74. package/src/collections/providers.ts +5 -3
  75. package/src/core/app-icon.ts +154 -0
  76. package/src/core/audit-report.ts +21 -0
  77. package/src/core/cache-audit.ts +72 -0
  78. package/src/core/canonical-scripts.ts +2 -2
  79. package/src/core/check-ctx.ts +14 -5
  80. package/src/core/check-runner.ts +43 -23
  81. package/src/core/config-rewrite.ts +37 -24
  82. package/src/core/discover-checks.ts +23 -2
  83. package/src/core/floor-resolve.ts +5 -7
  84. package/src/core/git-root.ts +13 -0
  85. package/src/core/ignored-block.ts +94 -0
  86. package/src/core/ignored-inputs.ts +47 -0
  87. package/src/core/jobs.ts +9 -2
  88. package/src/core/legacy-shapes.ts +39 -10
  89. package/src/core/one-lib.ts +59 -4
  90. package/src/core/one-way-in-policy.ts +2 -2
  91. package/src/core/outcome-words.ts +18 -2
  92. package/src/core/port-check-cli.ts +52 -0
  93. package/src/core/port-check-file.ts +295 -0
  94. package/src/core/port-check-files.ts +31 -0
  95. package/src/core/read-set.ts +97 -9
  96. package/src/core/retention.ts +0 -14
  97. package/src/core/run-check.ts +12 -9
  98. package/src/core/run-ci.ts +14 -5
  99. package/src/core/run-fix.ts +30 -23
  100. package/src/core/run-fixers.ts +48 -5
  101. package/src/core/runs-matrix.ts +179 -0
  102. package/src/core/runs.ts +242 -0
  103. package/src/core/trace-audit.ts +120 -0
  104. package/src/core/update-policy.ts +20 -0
  105. package/src/core/when-block.ts +29 -30
  106. package/src/core/when.ts +41 -103
  107. package/src/core/wire.ts +10 -14
  108. package/src/fleet/scan-fleet.ts +20 -4
  109. package/src/fleet/update-fleet.ts +6 -6
  110. package/src/home/gui-app.ts +24 -27
  111. package/src/home/home-launchd.ts +20 -4
  112. package/src/home/home-routes.ts +12 -1
  113. package/src/home/home-start.ts +11 -5
  114. package/src/home/hubs.ts +5 -5
  115. package/src/home/open-app.ts +2 -2
  116. package/src/home/pm.ts +8 -0
  117. package/src/home/run-pm.ts +1 -0
  118. package/src/home/serve-web.ts +2 -2
  119. package/src/home/supervise.ts +24 -1
  120. package/src/home/via-hub.ts +14 -0
  121. package/src/hooks/run-hooks.ts +48 -4
  122. package/src/init-repo.ts +6 -6
  123. package/src/ledger/db.ts +2 -13
  124. package/src/ledger/gate-checks.ts +133 -61
  125. package/src/ledger/git-key.ts +153 -31
  126. package/src/ledger/git-scan.ts +5 -2
  127. package/src/ledger/ledger.ts +136 -21
  128. package/src/ledger/store.ts +14 -0
  129. package/src/ledger/toolchain-identity.ts +248 -45
  130. package/src/plan/repo-source.ts +18 -39
  131. package/src/problems/extract.ts +3 -0
  132. package/src/release/run-release.ts +5 -0
  133. package/src/repo-config.ts +46 -54
  134. package/src/rules/rule.ts +2 -2
  135. package/src/scaffold/files/agent-coding.md +1 -1
  136. package/src/scaffold/files/agent-index.md +1 -1
  137. package/src/scaffold/files/verify-skill.md +2 -2
  138. package/src/serve/hub-app/activity-st.ts +5 -4
  139. package/src/serve/hub-app/activity-view.tsx +1 -1
  140. package/src/serve/hub-app/admin-svc.ts +36 -3
  141. package/src/serve/hub-app/admin-view.tsx +46 -12
  142. package/src/serve/hub-app/check-view.tsx +66 -46
  143. package/src/serve/hub-app/check-words.ts +1 -11
  144. package/src/serve/hub-app/checks-svc.ts +14 -35
  145. package/src/serve/hub-app/col-card.tsx +1 -44
  146. package/src/serve/hub-app/col-tables-view.tsx +84 -11
  147. package/src/serve/hub-app/col-tables.ts +27 -0
  148. package/src/serve/hub-app/cols-svc.ts +5 -4
  149. package/src/serve/hub-app/dock-st.ts +3 -2
  150. package/src/serve/hub-app/dock-view.tsx +36 -0
  151. package/src/serve/hub-app/dock.css +6 -0
  152. package/src/serve/hub-app/float-cards.tsx +30 -0
  153. package/src/serve/hub-app/floating.tsx +41 -4
  154. package/src/serve/hub-app/git-st.ts +42 -0
  155. package/src/serve/hub-app/git-view.tsx +61 -0
  156. package/src/serve/hub-app/hash-route.ts +45 -1
  157. package/src/serve/hub-app/header-bar.tsx +62 -72
  158. package/src/serve/hub-app/health-svc.ts +3 -2
  159. package/src/serve/hub-app/home-st.ts +14 -2
  160. package/src/serve/hub-app/home-strip.css +43 -12
  161. package/src/serve/hub-app/home-strip.tsx +115 -64
  162. package/src/serve/hub-app/home.html +1 -0
  163. package/src/serve/hub-app/hub-app.tsx +32 -8
  164. package/src/serve/hub-app/hub-types.ts +5 -2
  165. package/src/serve/hub-app/hub.css +89 -63
  166. package/src/serve/hub-app/icons.tsx +11 -0
  167. package/src/serve/hub-app/index.html +1 -0
  168. package/src/serve/hub-app/keys.ts +15 -4
  169. package/src/serve/hub-app/live-svc.ts +3 -2
  170. package/src/serve/hub-app/machine-st.ts +18 -22
  171. package/src/serve/hub-app/plan-view.tsx +51 -0
  172. package/src/serve/hub-app/planned-st.ts +9 -4
  173. package/src/serve/hub-app/processes-view.tsx +45 -0
  174. package/src/serve/hub-app/project-list.css +4 -1
  175. package/src/serve/hub-app/project-list.tsx +6 -3
  176. package/src/serve/hub-app/projects-svc.ts +25 -10
  177. package/src/serve/hub-app/prose-hits-st.ts +3 -2
  178. package/src/serve/hub-app/reach.ts +36 -0
  179. package/src/serve/hub-app/readme-svc.ts +3 -2
  180. package/src/serve/hub-app/repo-sections.ts +131 -0
  181. package/src/serve/hub-app/repo-st.ts +70 -14
  182. package/src/serve/hub-app/sidebar.tsx +17 -42
  183. package/src/serve/hub-app/timeline-svc.ts +3 -2
  184. package/src/serve/hub-app/tree-build.ts +151 -155
  185. package/src/serve/hub-app/tree-row.tsx +55 -12
  186. package/src/serve/hub-app/tree-svc.ts +97 -56
  187. package/src/serve/hub-app/when-view.tsx +89 -0
  188. package/src/serve/hub-app/when.css +17 -0
  189. package/src/serve/hub-runtime.ts +7 -18
  190. package/src/serve/project-gutter-route.ts +22 -1
  191. package/src/serve/run-serve.ts +1 -1
  192. package/src/serve/serve-hub.ts +4 -14
  193. package/src/suite/judge-suites.ts +6 -0
  194. package/src/trace/interpose.c +655 -0
  195. package/src/trace/read-trace.ts +485 -0
  196. package/src/trace/tracer.ts +282 -0
  197. package/src/serve/hub-app/processes-svc.ts +0 -63
  198. package/src/serve/hub-app/repo-sections.tsx +0 -61
  199. package/src/serve/repo-processes.ts +0 -89
@@ -14,14 +14,15 @@ export class CheckCommand extends Command {
14
14
 
15
15
  static override usage = Command.Usage({
16
16
  category: 'Health',
17
- description: 'every check except the build steps. `shipkit ci` runs these too and is what you normally want. --fix applies check autofixes',
17
+ description: 'every check except the build steps: the slower health surface',
18
+ details: 'every check except the build steps. `shipkit ci` runs these too and is what you normally want. --fix applies check autofixes',
18
19
  examples: [
19
- ['run (cached where valid)', 'shipkit check'],
20
- ['bust every cache layer', 'shipkit check --fresh'],
21
- ['apply every rule autofix, re-lint', 'shipkit check --fix'],
22
- ['fix one rule', 'shipkit check --fix SK004'],
23
- ['report-only: self-maintaining rules never write', 'shipkit check --frozen'],
24
- ['machine-readable', 'shipkit check --json'],
20
+ ['run (cached where valid)', 'sk check'],
21
+ ['bust every cache layer', 'sk check --fresh'],
22
+ ['apply every rule autofix, re-lint', 'sk check --fix'],
23
+ ['fix one rule', 'sk check --fix SK004'],
24
+ ['report-only: self-maintaining rules never write', 'sk check --frozen'],
25
+ ['machine-readable', 'sk check --json'],
25
26
  ],
26
27
  })
27
28
 
@@ -21,13 +21,14 @@ export class ChecksCommand extends Command {
21
21
  // widened so the hidden alias below can drop it: no usage, no help entry
22
22
  static override usage: Usage | undefined = Command.Usage({
23
23
  category: 'Health',
24
- description: 'every gate check on one table: where it comes from, whether it blocks or only warns, whether the gate runs it or only `shipkit check`, and how it caches. Reports, never gates',
24
+ description: 'every gate check on one table: source, blocking or warning, cache',
25
+ details: 'every gate check on one table: where it comes from, whether it blocks or only warns, whether the gate runs it or only `shipkit check`, and how it caches. Reports, never gates',
25
26
  examples: [
26
- ['the whole inventory', 'shipkit checks'],
27
- ['the big markdown table, written to a file', 'shipkit checks --out STATUS.full.md'],
28
- ['only the ones the gate never runs', 'shipkit checks --proactive'],
29
- ['everything that repairs itself, with its owner', 'shipkit checks --fixable'],
30
- ['machine-readable', 'shipkit checks --json'],
27
+ ['the whole inventory', 'sk checks'],
28
+ ['the big markdown table, written to a file', 'sk checks --out STATUS.full.md'],
29
+ ['only the ones the gate never runs', 'sk checks --proactive'],
30
+ ['everything that repairs itself, with its owner', 'sk checks --fixable'],
31
+ ['machine-readable', 'sk checks --json'],
31
32
  ],
32
33
  })
33
34
 
package/src/cli/cmd_Ci.ts CHANGED
@@ -13,18 +13,19 @@ export class CiCommand extends Command {
13
13
 
14
14
  static override usage = Command.Usage({
15
15
  category: 'Gate',
16
- 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.',
16
+ description: 'THE gate: repairs what a machine can, then runs every check. Run this, not the parts',
17
+ details: '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.',
17
18
  examples: [
18
- ['full gate, mechanical fixes applied', 'shipkit ci'],
19
- ['gate without the repair pass (checks can still write)', 'shipkit ci --no-fix'],
20
- ['leave repo config files alone', 'shipkit ci --no-fix-rules'],
21
- ['stop at the first failure', 'shipkit ci --bail'],
22
- ['one check + its dep closure', 'shipkit ci --only typecheck'],
23
- ['every check declaring one group', 'shipkit ci --only wiki'],
24
- ['run everything, take nothing from the cache', 'shipkit ci --fresh'],
25
- ['include an opt in check', 'shipkit ci --with up:itch'],
26
- ['in the background, read it back with shipkit status', 'shipkit ci --detach'],
27
- ['every line, the ones nobody acts on included', 'shipkit ci --verbose'],
19
+ ['full gate, mechanical fixes applied', 'sk ci'],
20
+ ['gate without the repair pass (checks can still write)', 'sk ci --no-fix'],
21
+ ['leave repo config files alone', 'sk ci --no-fix-rules'],
22
+ ['stop at the first failure', 'sk ci --bail'],
23
+ ['one check + its dep closure', 'sk ci --only typecheck'],
24
+ ['every check declaring one group', 'sk ci --only wiki'],
25
+ ['run everything, take nothing from the cache', 'sk ci --fresh'],
26
+ ['include an opt in check', 'sk ci --with up:itch'],
27
+ ['in the background, read it back with shipkit status', 'sk ci --detach'],
28
+ ['every line, the ones nobody acts on included', 'sk ci --verbose'],
28
29
  ],
29
30
  })
30
31
 
@@ -23,6 +23,10 @@ export type Definition = {
23
23
  options?: { definition?: string; description?: string | null }[] | null
24
24
  }
25
25
 
26
+ /** the longest description the index shows: one short line. The rest goes in
27
+ * the command's `details`, which --help prints */
28
+ export const DESCRIPTION_MAX = 100
29
+
26
30
  // clipanion renders a value placeholder as `#0`, which nobody can type
27
31
  const clean = (s: string | null | undefined): string => (s ?? '').trim().replace(/\s+/g, ' ').replace(/ #\d+\b/g, ' <value>')
28
32
 
@@ -34,9 +38,9 @@ const clean = (s: string | null | undefined): string => (s ?? '').trim().replace
34
38
  export function commandRows(plain: Definition[], detailed: Definition[] = []): CommandRow[] {
35
39
  const flagsByPath = new Map(detailed.map((d) => [d.path, d.options ?? []]))
36
40
  return plain
37
- .filter((d) => !/^shipkit (--help|-h|--version|-v)$/.test(d.path))
41
+ .filter((d) => !/^(shipkit|sk) (--help|-h|--version|-v)$/.test(d.path))
38
42
  .map((d) => ({
39
- path: d.path.replace(/^shipkit /, ''),
43
+ path: d.path.replace(/^(shipkit|sk) /, ''),
40
44
  category: clean(d.category) || 'Other',
41
45
  // verbatim from the CLI so the doc cannot say something it does not
42
46
  description: clean(d.description),
@@ -49,21 +53,19 @@ export function commandRows(plain: Definition[], detailed: Definition[] = []): C
49
53
  .sort((a, b) => a.category.localeCompare(b.category) || a.path.localeCompare(b.path))
50
54
  }
51
55
 
52
- /** the markdown block: per command, the real invocation with its arguments and
53
- * flags, then a worked example. A name and a sentence are not enough to act
54
- * on: an agent that cannot see the arguments either guesses them or runs
55
- * `--help` first, and both are the waste this page exists to remove. */
56
+ /** the markdown block, loaded into every agent's context at start: per
57
+ * command its invocation with every flag, one line of description, then its
58
+ * examples. What a flag does and the long text are `sk <command> --help`. */
56
59
  export function renderMarkdown(rows: CommandRow[]): string {
57
60
  const out: string[] = []
58
61
  let cat = ''
59
62
  for (const r of rows) {
60
63
  if (r.category !== cat) { cat = r.category; out.push(`\n### ${cat}\n`) }
61
- const sig = r.usage.replace(/^\$0/, 'shipkit').trim() || `shipkit ${r.path}`
64
+ const sig = r.usage.replace(/^\$0/, 'sk').trim() || `sk ${r.path}`
62
65
  const flags = r.options.map((o) => `[${o.definition}]`).join(' ')
63
66
  // one line per bullet: a description on its own indented line reads as
64
67
  // hard-wrapped prose to the house lint
65
68
  out.push(`- \`${sig}${flags === '' ? '' : ` ${flags}`}\` — ${r.description}`)
66
- for (const o of r.options) if (o.description !== '') out.push(` - \`${o.definition}\` ${o.description}`)
67
69
  for (const [label, cmd] of r.examples) out.push(` - ${label}: \`${cmd}\``)
68
70
  }
69
71
  return out.join('\n').trim()
@@ -82,12 +84,13 @@ export class CommandsCommand extends Command {
82
84
 
83
85
  static override usage = Command.Usage({
84
86
  category: 'Health',
85
- description: 'every command this binary registers, as markdown or json — the source a docs page is generated FROM, never hand-copied',
87
+ description: 'every command this binary registers, as markdown or json',
88
+ details: 'every command this binary registers, as markdown or json — the source a docs page is generated FROM, never hand-copied',
86
89
  examples: [
87
- ['markdown index', 'shipkit commands'],
88
- ['machine-readable', 'shipkit commands --json'],
89
- ['write into a docs page between the markers', 'shipkit commands --out wiki/build/shipkit-cli.md'],
90
- ['fail if that page is stale (lint mode)', 'shipkit commands --out wiki/build/shipkit-cli.md --check'],
90
+ ['markdown index', 'sk commands'],
91
+ ['machine-readable', 'sk commands --json'],
92
+ ['write into a docs page between the markers', 'sk commands --out wiki/build/shipkit-cli.md'],
93
+ ['fail if that page is stale (lint mode)', 'sk commands --out wiki/build/shipkit-cli.md --check'],
91
94
  ],
92
95
  })
93
96
 
@@ -19,13 +19,14 @@ export class ConfigKeysCommand extends Command {
19
19
 
20
20
  static override usage = Command.Usage({
21
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',
22
+ description: 'every repo.config.ts key and check field with its doc, from the types',
23
+ details: '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
24
  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'],
25
+ ['markdown reference', 'sk config-keys'],
26
+ ['machine-readable', 'sk config-keys --json'],
27
+ ['write into a docs page between the markers', 'sk config-keys --out wiki/build/shipkit/config-keys.md'],
28
+ ['fail if that page is stale (lint mode)', 'sk config-keys --out wiki/build/shipkit/config-keys.md --check'],
29
+ ['name the keys carrying no doc comment', 'sk config-keys --undocumented'],
29
30
  ],
30
31
  })
31
32
 
@@ -10,9 +10,9 @@ export class ContractsCommand extends Command {
10
10
  category: 'Gate',
11
11
  description: 'release-contract fulfillness, fast and filesystem-only — deploy checks dep on it',
12
12
  examples: [
13
- ['all applicable contracts', 'shipkit contracts'],
14
- ['one venue', 'shipkit contracts --venue itch'],
15
- ['machine-readable', 'shipkit contracts --json'],
13
+ ['all applicable contracts', 'sk contracts'],
14
+ ['one venue', 'sk contracts --venue itch'],
15
+ ['machine-readable', 'sk contracts --json'],
16
16
  ],
17
17
  })
18
18
 
@@ -8,7 +8,6 @@ import { homedir } from 'os'
8
8
  import { Command, Option } from 'clipanion'
9
9
  import { activityOf, type Declared } from '../home/activity.ts'
10
10
  import { ageShort, clampCap, DEFAULT_CAP, DEFAULT_MINUTES, dirtWords, idleRow, upstreamWords, type ActivityAnswer, type ActivityCommit, type ActivityRow } from '../home/activity-shape.ts'
11
- import { railRepos } from '../serve/project-gutter-route.ts'
12
11
 
13
12
  /** a span as typed: `10m`, `1h`, `3d`, `90` (minutes); null when it is none */
14
13
  export function spanMinutes(v: string): number | null {
@@ -85,12 +84,13 @@ export class CrossRepoActivityCommand extends Command {
85
84
 
86
85
  static override usage = Command.Usage({
87
86
  category: 'Home',
88
- description: 'the activity page in the console: every house repo (archived ones left out), its checks, branch, upstream, uncommitted work and commits in a window. The same computation the page reads, local git only, no daemon needed',
87
+ description: 'the activity page as text: every house repo, its git state and recent commits',
88
+ details: 'the activity page in the console: every house repo (archived ones left out), its checks, branch, upstream, uncommitted work and commits in a window. The same computation the page reads, local git only, no daemon needed',
89
89
  examples: [
90
- ['the last 30 minutes, 3 commits per repo, as the page opens', 'shipkit cross-repo-activity'],
91
- ['the same, by the page name', 'shipkit activity'],
92
- ['the last 3 hours, up to 10 commits per repo', 'shipkit cross-repo-activity --max-age 3h --max-commit 10'],
93
- ['the route\'s exact payload', 'shipkit cross-repo-activity --max-age 30m --json'],
90
+ ['the last 30 minutes, 3 commits per repo, as the page opens', 'sk cross-repo-activity'],
91
+ ['the same, by the page name', 'sk activity'],
92
+ ['the last 3 hours, up to 10 commits per repo', 'sk cross-repo-activity --max-age 3h --max-commit 10'],
93
+ ['the route\'s exact payload', 'sk cross-repo-activity --max-age 30m --json'],
94
94
  ],
95
95
  })
96
96
 
@@ -107,6 +107,8 @@ export class CrossRepoActivityCommand extends Command {
107
107
  const cap = clampCap(this.maxCommit ?? DEFAULT_CAP)
108
108
  const declared = await daemonDeclared()
109
109
  // HOME as the shell has it now: the house include names `~`
110
+ // the hub route module draws app icons, so it loads only when this runs
111
+ const { railRepos } = await import('../serve/project-gutter-route.ts')
110
112
  const a = await activityOf(railRepos(process.cwd(), process.env.HOME || homedir()), minutes, Date.now(), declared)
111
113
  if (this.json) {
112
114
  this.context.stdout.write(`${JSON.stringify(a, null, 2)}\n`)
@@ -14,11 +14,11 @@ export class FleetCommand extends Command {
14
14
  category: 'Health',
15
15
  description: 'every repo on this disk that installs the toolkit, and how far behind each one is',
16
16
  examples: [
17
- ['what is stale out there', 'shipkit fleet'],
18
- ['filesystem only, no registry', 'shipkit fleet --offline'],
19
- ['bring every behind repo up, through its own gate', 'shipkit fleet --update'],
20
- ['one repo only', 'shipkit fleet --update --only my-app'],
21
- ['machine-readable', 'shipkit fleet --json'],
17
+ ['what is stale out there', 'sk fleet'],
18
+ ['filesystem only, no registry', 'sk fleet --offline'],
19
+ ['bring every behind repo up, through its own gate', 'sk fleet --update'],
20
+ ['one repo only', 'sk fleet --update --only my-app'],
21
+ ['machine-readable', 'sk fleet --json'],
22
22
  ],
23
23
  })
24
24
 
@@ -66,10 +66,12 @@ export class FleetCommand extends Command {
66
66
  private row(r: FleetRepo): string {
67
67
  const cells = r.deps.map((d) => {
68
68
  if (d.pin.startsWith('link:') || d.pin.startsWith('file:')) return `${short(d.name)}=source`
69
- const at = d.installed ?? 'absent'
69
+ // a linked dep reads as its pin: the source it links is always newest
70
+ const at = d.linkedInstall ? `pin ${d.at ?? d.pin}` : (d.installed ?? 'absent')
71
+ const tag = d.linkedInstall ? ' (linked)' : ''
70
72
  const stale = r.behind.some((b) => b.name === d.name)
71
73
  const breaks = r.breaking.some((b) => b.name === d.name)
72
- return stale ? `${short(d.name)} ${at} → ${d.latest}${breaks ? ' BREAKING' : ''}` : `${short(d.name)} ${at}`
74
+ return stale ? `${short(d.name)} ${at} → ${d.latest}${breaks ? ' BREAKING' : ''}${d.linkedInstall ? ' (linked, stale pin)' : ''}` : `${short(d.name)} ${at}${tag}`
73
75
  })
74
76
  const flag = r.linked ? 'source' : r.breaking.length > 0 ? `${r.breaking.length} BREAKING` : r.behind.length > 0 ? `${r.behind.length} behind` : 'current'
75
77
  return ` ${r.name.padEnd(24)} ${flag.padEnd(10)} ${cells.join(', ')}`
@@ -9,10 +9,10 @@ import { Command, Option } from 'clipanion'
9
9
  import { daemonPort, daemonUrl, homePort, pmPort, pmUrl } from '../home/home-paths.ts'
10
10
  import { houseRootOf } from '../home/home-start.ts'
11
11
  import type { PmAnswer } from '../home/pm.ts'
12
+ import type { ResourceView } from '../home/resources.ts'
12
13
  import { coreGroup, declaredRow, groupRows, processGroups, stateWords, type ProcessGroup } from '../home/process-rows.ts'
13
14
  import type { PmConfig } from '../home/pm-config.ts'
14
15
  import { readPmConfig } from '../home/run-pm.ts'
15
- import { railRepos } from '../serve/project-gutter-route.ts'
16
16
 
17
17
  export type GlobalProcessesAnswer = {
18
18
  /** pm as it answered, null when it did not */
@@ -74,11 +74,13 @@ export async function askPm(url = pmUrl()): Promise<PmAnswer | null> {
74
74
 
75
75
  /** the project list's order and the declared units, read from disk the way
76
76
  * pm reads them: what the table falls back on while pm does not answer */
77
- export function localProcessDeps(cwd: string): Pick<GlobalProcessesDeps, 'declared' | 'order'> {
77
+ export async function localProcessDeps(cwd: string): Promise<Pick<GlobalProcessesDeps, 'declared' | 'order'>> {
78
78
  // HOME as the shell has it now: the house include names `~`
79
79
  const home = process.env.HOME || homedir()
80
80
  let order: string[] = []
81
81
  try {
82
+ // the hub route module draws app icons: loaded only when this runs
83
+ const { railRepos } = await import('../serve/project-gutter-route.ts')
82
84
  order = railRepos(cwd, home).map((r) => r.name)
83
85
  } catch {
84
86
  // no rail from here: the groups sort by name
@@ -133,18 +135,19 @@ export class GlobalProcessesCommand extends Command {
133
135
 
134
136
  static override usage = Command.Usage({
135
137
  category: 'Home',
136
- description: 'every process on the machine and its state, as the overview shows them: shipkit\'s own (pm, the daemon, the web, with their version) first, then per repo, one line per global process with its state, url, pid and last log line. When pm is down, the declared ones with their state unknown. Always exits 0',
138
+ description: 'every process on the machine and its state, as the overview shows it',
139
+ details: 'every process on the machine and its state, as the overview shows them: shipkit\'s own (pm, the daemon, the web, with their version) first, then per repo, one line per global process with its state, url, pid and last log line. When pm is down, the declared ones with their state unknown. Always exits 0',
137
140
  examples: [
138
- ['the table', 'shipkit global-processes'],
139
- ['the same, short', 'shipkit gp'],
140
- ['the rows the overview draws, as json', 'shipkit global-processes --json'],
141
+ ['the table', 'sk global-processes'],
142
+ ['the same, short', 'sk gp'],
143
+ ['the rows the overview draws, as json', 'sk global-processes --json'],
141
144
  ],
142
145
  })
143
146
 
144
147
  json = Option.Boolean('--json', false, { description: 'the rows as json: { pm, now, groups, errors, house }, groups exactly as the overview draws them' })
145
148
 
146
149
  async execute(): Promise<number> {
147
- const a = await globalProcessesAnswer({ ...localProcessDeps(process.cwd()), pm: () => askPm(), daemon: () => askDaemon(), now: Date.now() })
150
+ const a = await globalProcessesAnswer({ ...(await localProcessDeps(process.cwd())), pm: () => askPm(), daemon: () => askDaemon(), now: Date.now() })
148
151
  if (this.json) {
149
152
  this.context.stdout.write(`${JSON.stringify(a, null, 2)}\n`)
150
153
  return 0
@@ -153,3 +156,113 @@ export class GlobalProcessesCommand extends Command {
153
156
  return 0
154
157
  }
155
158
  }
159
+
160
+ export type SwitchAct = 'start' | 'stop' | 'restart'
161
+
162
+ export type SwitchDeps = {
163
+ /** pm's answer to the switch: null when it took it, else why not */
164
+ post: (id: string, act: SwitchAct) => Promise<string | null>
165
+ /** the process as pm sees it now, null when pm does not answer */
166
+ view: (id: string) => Promise<ResourceView | null>
167
+ sleep: (ms: number) => Promise<void>
168
+ now: () => number
169
+ log: (line: string) => void
170
+ }
171
+
172
+ /** the longest a switch is followed: a bring-up polls its health for minutes */
173
+ export const SWITCH_WAIT_MS = 10 * 60_000
174
+ const SWITCH_POLL_MS = 1_000
175
+
176
+ /** flip a global process through pm, then follow pm's state until its probe
177
+ * settles it: 0 once it is up (or down for stop), 1 otherwise. The launcher's
178
+ * exit code proves nothing, pm's probe does */
179
+ export async function switchProcess(id: string, act: SwitchAct, d: SwitchDeps, waitMs = SWITCH_WAIT_MS): Promise<number> {
180
+ const refused = await d.post(id, act)
181
+ if (refused != null) {
182
+ d.log(`FAIL ${act} ${id}: ${refused}`)
183
+ return 1
184
+ }
185
+ const want = act === 'stop' ? 'down' : 'up'
186
+ const end = d.now() + waitMs
187
+ let said = ''
188
+ for (;;) {
189
+ const v = await d.view(id)
190
+ if (v == null) {
191
+ d.log(`FAIL pm stopped answering while ${id} was switched ${want}: shipkit gp`)
192
+ return 1
193
+ }
194
+ if (v.status !== said) {
195
+ said = v.status
196
+ d.log(`${id}: ${v.status}`)
197
+ }
198
+ const moving = v.status === 'starting' || v.status === 'stopping' || v.want !== want
199
+ if (!moving) {
200
+ if (v.status === want) return 0
201
+ d.log(`FAIL ${id} is ${v.status}, not ${want}${v.note == null ? '' : `: ${v.note}`}`)
202
+ for (const l of v.tail.slice(-10)) d.log(` log: ${l}`)
203
+ return 1
204
+ }
205
+ if (d.now() >= end) {
206
+ d.log(`FAIL ${id} still ${v.status} after ${Math.round(waitMs / 1000)}s: shipkit gp shows where it stands`)
207
+ return 1
208
+ }
209
+ await d.sleep(SWITCH_POLL_MS)
210
+ }
211
+ }
212
+
213
+ /** the switch against the running pm */
214
+ export const pmSwitchDeps = (log: (line: string) => void, url = pmUrl()): SwitchDeps => ({
215
+ post: async (id, act) => {
216
+ try {
217
+ const r = await fetch(`${url}/api/pm/resource`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id, act }), signal: AbortSignal.timeout(5_000) })
218
+ return r.ok ? null : await r.text()
219
+ } catch (e) {
220
+ return `pm does not answer on ${url} (${(e as Error).message}): shipkit pm install`
221
+ }
222
+ },
223
+ view: async (id) => (await askPm(url))?.resources.find((r) => r.id === id) ?? null,
224
+ sleep: (ms) => Bun.sleep(ms),
225
+ now: () => Date.now(),
226
+ log,
227
+ })
228
+
229
+ abstract class SwitchCommand extends Command {
230
+ abstract readonly act: SwitchAct
231
+ id = Option.String({ required: true, name: 'id' })
232
+
233
+ async execute(): Promise<number> {
234
+ return switchProcess(this.id, this.act, pmSwitchDeps((l) => this.context.stdout.write(`${l}\n`)))
235
+ }
236
+ }
237
+
238
+ export class GlobalProcessStartCommand extends SwitchCommand {
239
+ readonly act = 'start'
240
+ static override paths = [['global-processes', 'start'], ['gp', 'start']]
241
+ static override usage = Command.Usage({
242
+ category: 'Home',
243
+ description: 'start a global process through pm, wait until its probe answers',
244
+ details: 'start a global process through pm, the same switch as the overview\'s play, and wait until its probe answers. Exit 0 once it is up, 1 when it crashed, stayed down or pm refused',
245
+ examples: [['start ComfyUI on the GPU box', 'sk gp start comfyui']],
246
+ })
247
+ }
248
+
249
+ export class GlobalProcessStopCommand extends SwitchCommand {
250
+ readonly act = 'stop'
251
+ static override paths = [['global-processes', 'stop'], ['gp', 'stop']]
252
+ static override usage = Command.Usage({
253
+ category: 'Home',
254
+ description: 'stop a global process through pm, wait until it is down',
255
+ details: 'stop a global process through pm, the same switch as the overview\'s stop, and wait until its probe no longer answers. Exit 0 once it is down',
256
+ examples: [['stop ComfyUI', 'sk gp stop comfyui']],
257
+ })
258
+ }
259
+
260
+ export class GlobalProcessRestartCommand extends SwitchCommand {
261
+ readonly act = 'restart'
262
+ static override paths = [['global-processes', 'restart'], ['gp', 'restart']]
263
+ static override usage = Command.Usage({
264
+ category: 'Home',
265
+ description: 'restart a global process through pm and wait until its probe answers again. Exit 0 once it is up',
266
+ examples: [['restart ComfyUI, so it rescans its models', 'sk gp restart comfyui']],
267
+ })
268
+ }
@@ -10,7 +10,6 @@ import { homeUp, homeUrl, hubBase } from '../home/home-paths.ts'
10
10
  import { homeRepo, houseIndex } from '../serve/project-gutter.ts'
11
11
  import { homedir } from 'os'
12
12
  import { execPm, runPm } from '../home/run-pm.ts'
13
- import { runDaemon } from '../home/run-daemon.ts'
14
13
  import { runWeb } from '../home/run-web.ts'
15
14
 
16
15
  const OWN_CLI = fileURLToPath(new URL('../cli.ts', import.meta.url))
@@ -35,10 +34,11 @@ export class PmCommand extends Command {
35
34
 
36
35
  static override usage = Command.Usage({
37
36
  category: 'Serve',
38
- description: 'the machine\'s process manager: runs `shipkit daemon` and `shipkit web` as detached children, starts one again when it dies, owns the global processes every house repo declares in its .shipkit.ts files, and serves its api on port 4797 (SHIPKIT_PM_PORT). A pm restarted or killed leaves its children running and the next one adopts them. Run from the repo that hosts the house wiki',
37
+ description: 'the machine\'s process manager: runs the daemon, the web and every global process',
38
+ details: 'the machine\'s process manager: runs `shipkit daemon` and `shipkit web` as detached children, starts one again when it dies, owns the global processes every house repo declares in its .shipkit.ts files, and serves its api on port 4797 (SHIPKIT_PM_PORT). A pm restarted or killed leaves its children running and the next one adopts them. Run from the repo that hosts the house wiki',
39
39
  examples: [
40
- ['run pm in this terminal', 'shipkit pm'],
41
- ['on test ports', 'SHIPKIT_PM_PORT=4887 SHIPKIT_HOME_PORT=4889 SHIPKIT_DAEMON_PORT=4888 SHIPKIT_HOME_DIR=/tmp/home shipkit pm'],
40
+ ['run pm in this terminal', 'sk pm'],
41
+ ['on test ports', 'SHIPKIT_PM_PORT=4887 SHIPKIT_HOME_PORT=4889 SHIPKIT_DAEMON_PORT=4888 SHIPKIT_HOME_DIR=/tmp/home sk pm'],
42
42
  ],
43
43
  })
44
44
 
@@ -57,10 +57,11 @@ export class DaemonCommand extends Command {
57
57
 
58
58
  static override usage = Command.Usage({
59
59
  category: 'Serve',
60
- description: 'the home\'s daemon: loads every house repo hub in this one process and serves their api, their run streams and the home api under /r/<repo>/ on an internal port. No page: `shipkit web` serves it. Run from the repo that hosts the house wiki',
60
+ description: 'the home daemon: every house repo\'s hub in one process',
61
+ details: 'the home\'s daemon: loads every house repo hub in this one process and serves their api, their run streams and the home api under /r/<repo>/ on an internal port. No page: `shipkit web` serves it. Run from the repo that hosts the house wiki',
61
62
  examples: [
62
- ['the daemon alone', 'shipkit daemon'],
63
- ['on another port', 'shipkit daemon --port 4888'],
63
+ ['the daemon alone', 'sk daemon'],
64
+ ['on another port', 'sk daemon --port 4888'],
64
65
  ],
65
66
  })
66
67
 
@@ -69,6 +70,8 @@ export class DaemonCommand extends Command {
69
70
  async execute(): Promise<number> {
70
71
  const port = portOf(this, this.port)
71
72
  if (port === null) return 1
73
+ // the daemon hosts every hub, image tools included: loaded when it runs
74
+ const { runDaemon } = await import('../home/run-daemon.ts')
72
75
  return runDaemon(process.cwd(), io(this), { port })
73
76
  }
74
77
  }
@@ -78,10 +81,11 @@ export class WebCommand extends Command {
78
81
 
79
82
  static override usage = Command.Usage({
80
83
  category: 'Serve',
81
- description: 'the home\'s page: one server on the home port that serves the page and proxies every api request to the daemon, so a browser only sees this origin. It loads no repo',
84
+ description: 'the home page server: serves the page, proxies the api to the daemon',
85
+ details: 'the home\'s page: one server on the home port that serves the page and proxies every api request to the daemon, so a browser only sees this origin. It loads no repo',
82
86
  examples: [
83
- ['the page alone', 'shipkit web'],
84
- ['on another port', 'shipkit web --port 4889'],
87
+ ['the page alone', 'sk web'],
88
+ ['on another port', 'sk web --port 4889'],
85
89
  ],
86
90
  })
87
91
 
@@ -99,8 +103,9 @@ export class PmInstallCommand extends Command {
99
103
 
100
104
  static override usage = Command.Usage({
101
105
  category: 'Serve',
102
- description: 'keep pm running, and through it the daemon and the web: a macOS LaunchAgent starts it now and at every login, and restarts it when it dies. Idempotent',
103
- examples: [['install from the house repo', 'shipkit pm install']],
106
+ description: 'keep pm running at every login through a macOS LaunchAgent',
107
+ details: 'keep pm running, and through it the daemon and the web: a macOS LaunchAgent starts it now and at every login, and restarts it when it dies. Idempotent',
108
+ examples: [['install from the house repo', 'sk pm install']],
104
109
  })
105
110
 
106
111
  async execute(): Promise<number> {
@@ -114,8 +119,9 @@ export class PmUninstallCommand extends Command {
114
119
 
115
120
  static override usage = Command.Usage({
116
121
  category: 'Serve',
117
- description: 'stop pm, the daemon and the web, and remove the LaunchAgent. The resources and repo processes keep running. No later command starts pm on its own until `shipkit pm install`',
118
- examples: [['remove the agent', 'shipkit pm uninstall']],
122
+ description: 'stop pm, the daemon and the web, and remove the LaunchAgent',
123
+ details: 'stop pm, the daemon and the web, and remove the LaunchAgent. The resources and repo processes keep running. No later command starts pm on its own until `shipkit pm install`',
124
+ examples: [['remove the agent', 'sk pm uninstall']],
119
125
  })
120
126
 
121
127
  async execute(): Promise<number> {
@@ -130,11 +136,12 @@ export class GuiCommand extends Command {
130
136
 
131
137
  static override usage = Command.Usage({
132
138
  category: 'Serve',
133
- description: 'open the home in its own window, outside VS Code and the browser, starting pm first when the web does not answer (and installing its LaunchAgent the first time). Every call opens one more window',
139
+ description: 'open the home in its own window',
140
+ details: 'open the home in its own window, outside VS Code and the browser, starting pm first when the web does not answer (and installing its LaunchAgent the first time). Every call opens one more window',
134
141
  examples: [
135
- ['a window on the home', 'shipkit gui'],
136
- ['a window on one repo', 'shipkit gui my-app'],
137
- ['a window on the repo you stand in', 'shipkit gui .'],
142
+ ['a window on the home', 'sk gui'],
143
+ ['a window on one repo', 'sk gui my-app'],
144
+ ['a window on the repo you stand in', 'sk gui .'],
138
145
  ],
139
146
  })
140
147
 
@@ -163,7 +170,7 @@ export class GuiCommand extends Command {
163
170
  // `.` = the repo you stand in, named the way the rail names it
164
171
  const name = this.repo === '.' && houseIndex(homeRepo(process.cwd()), homedir()) != null ? basename(homeRepo(process.cwd())) : this.repo
165
172
  const url = `${homeUrl()}${name != null && name !== '.' ? `${hubBase(name)}/` : '/'}`
166
- const cmd = openWindow(url, { version: LIB_VERSION, warn: out.printErr })
173
+ const cmd = await openWindow(url, { version: LIB_VERSION, warn: out.printErr })
167
174
  out.print(`ok: a home window on ${url}${cmd[0] === 'open' ? ' (the shipkit app)' : ''}`)
168
175
  return 0
169
176
  }
@@ -1,7 +1,7 @@
1
1
  // shipkit hooks, install the git hooks, run their gates, or list what
2
2
  // blocks a commit. Thin clipanion surface; logic in hooks/run-hooks.ts.
3
3
  import { Command, Option } from 'clipanion'
4
- import { hookBinInvocation, installHooks, listGates, runCommitMsg, runPreCommit, type GateOutcome } from '../hooks/run-hooks.ts'
4
+ import { hookBinInvocation, installHooks, listGates, preCommitChecks, runCommitMsg, runPreCommit, type GateOutcome } from '../hooks/run-hooks.ts'
5
5
  import { loadRepoConfig } from '../repo-config.ts'
6
6
 
7
7
  const report = (out: GateOutcome[], write: (s: string) => void): number => {
@@ -13,8 +13,9 @@ 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 .shipkit/hooks/pre-commit + .shipkit/hooks/commit-msg and point core.hooksPath at them (idempotent)',
17
- examples: [['wire the hooks', 'shipkit hooks install']],
16
+ description: 'write the pre-commit and commit-msg hooks and point core.hooksPath at them',
17
+ details: 'write .shipkit/hooks/pre-commit + .shipkit/hooks/commit-msg and point core.hooksPath at them (idempotent)',
18
+ examples: [['wire the hooks', 'sk hooks install']],
18
19
  })
19
20
 
20
21
  bin = Option.String('--bin', { description: 'how the hook body invokes shipkit (default: the repo\'s own node_modules copy, else the running cli — in-repo consumers pass `bun <path to cli.ts>`)' })
@@ -56,12 +57,12 @@ export class HooksListCommand extends Command {
56
57
  static override usage = Command.Usage({
57
58
  category: 'Gate',
58
59
  description: 'what blocks a commit in this repo, built-ins and repo-declared',
59
- examples: [['list the gates', 'shipkit hooks list']],
60
+ examples: [['list the gates', 'sk hooks list']],
60
61
  })
61
62
 
62
63
  async execute(): Promise<number> {
63
64
  const config = await loadRepoConfig(process.cwd())
64
- for (const l of listGates(config, process.cwd())) this.context.stdout.write(`${l}\n`)
65
+ for (const l of listGates(config, process.cwd(), await preCommitChecks(process.cwd(), config))) this.context.stdout.write(`${l}\n`)
65
66
  return 0
66
67
  }
67
68
  }
@@ -8,11 +8,12 @@ export class InitCommand extends Command {
8
8
 
9
9
  static override usage = Command.Usage({
10
10
  category: 'Setup',
11
- description: 'wire THE CURRENT DIRECTORY as a shipkit repo, like git init: cwd only, never creates a project dir. scaffolds repo.config.ts + ci/check/contracts package.json scripts + the create-only .vscode/ that starts the watchers and the hub on folder open, prints how the floor resolved, and runs the bootstrap chain around it: git init, then first commit + PRIVATE github repo + push once the scaffold is in. Idempotent on an already-wired repo. To create or adopt ~/dev/<name> from anywhere: `shipkit start <name>`',
11
+ description: 'wire the current directory as a shipkit repo, like git init',
12
+ details: 'wire THE CURRENT DIRECTORY as a shipkit repo, like git init: cwd only, never creates a project dir. scaffolds repo.config.ts + ci/check/contracts package.json scripts + the create-only .vscode/ that starts the watchers and the hub on folder open, prints how the floor resolved, and runs the bootstrap chain around it: git init, then first commit + PRIVATE github repo + push once the scaffold is in. Idempotent on an already-wired repo. To create or adopt ~/dev/<name> from anywhere: `shipkit start <name>`',
12
13
  examples: [
13
- ['wire the repo you are standing in', 'shipkit init'],
14
- ['local git and the first commit, never touch github', 'shipkit init --no-github'],
15
- ['in-repo-source consumer: scripts run the CLI by path', 'shipkit init --bin wiki/build/shipkit/rvlib-shipkit/src/cli.ts'],
14
+ ['wire the repo you are standing in', 'sk init'],
15
+ ['local git and the first commit, never touch github', 'sk init --no-github'],
16
+ ['in-repo-source consumer: scripts run the CLI by path', 'sk init --bin wiki/build/shipkit/rvlib-shipkit/src/cli.ts'],
16
17
  ],
17
18
  })
18
19