rvlib-shipkit 0.38.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +367 -16
  2. package/README.md +26 -14
  3. package/package.json +2 -2
  4. package/src/assets/gen/comfyts-gen.ts +27 -10
  5. package/src/assets/gen/generate-roll.ts +1 -1
  6. package/src/assets/kit-config.ts +6 -6
  7. package/src/assets/run-assets.ts +14 -10
  8. package/src/assets/server/studio-server.ts +6 -5
  9. package/src/check.ts +343 -0
  10. package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
  11. package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
  12. package/src/checks/SK003-claude-md-shape.check.ts +66 -0
  13. package/src/checks/SK004-gitignore-policy.check.ts +309 -0
  14. package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
  15. package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
  16. package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
  17. package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
  18. package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
  19. package/src/checks/SK010-undeclared-unit.check.ts +111 -0
  20. package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
  21. package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
  22. package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
  23. package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
  24. package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
  25. package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
  26. package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
  27. package/src/checks/SK018-test-files-run.check.ts +28 -0
  28. package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
  29. package/src/checks/SK020-self-spawning-check.check.ts +147 -0
  30. package/src/checks/contracts.check.ts +20 -0
  31. package/src/cli/cmd_Asks.ts +3 -2
  32. package/src/cli/cmd_Assets.ts +2 -2
  33. package/src/cli/cmd_Bump.ts +1 -1
  34. package/src/cli/cmd_Check.ts +6 -6
  35. package/src/cli/cmd_Checks.ts +95 -0
  36. package/src/cli/cmd_Ci.ts +48 -13
  37. package/src/cli/cmd_Commands.ts +2 -6
  38. package/src/cli/cmd_ConfigKeys.ts +69 -0
  39. package/src/cli/cmd_Contracts.ts +1 -1
  40. package/src/cli/cmd_Fleet.ts +6 -2
  41. package/src/cli/cmd_Hooks.ts +1 -1
  42. package/src/cli/cmd_Keys.ts +43 -18
  43. package/src/cli/cmd_Mv.ts +2 -2
  44. package/src/cli/cmd_Prose.ts +10 -1
  45. package/src/cli/cmd_Releases.ts +2 -2
  46. package/src/cli/cmd_Serve.ts +1 -1
  47. package/src/cli/cmd_Start.ts +6 -6
  48. package/src/cli/cmd_Status.ts +25 -0
  49. package/src/cli/cmd_Tui.ts +2 -2
  50. package/src/cli.ts +7 -4
  51. package/src/collections/actions.ts +4 -4
  52. package/src/collections/checks.ts +258 -0
  53. package/src/collections/collection-types.ts +254 -1
  54. package/src/collections/comfy.ts +23 -0
  55. package/src/collections/md-body.ts +14 -0
  56. package/src/collections/pings.ts +38 -12
  57. package/src/collections/posts.ts +19 -3
  58. package/src/collections/providers.ts +198 -91
  59. package/src/collections/reflections.ts +41 -0
  60. package/src/collections/state-rules.ts +73 -0
  61. package/src/collections/test-time.ts +288 -0
  62. package/src/collections/tests.ts +181 -0
  63. package/src/collections/wikis.ts +66 -0
  64. package/src/comfy/comfy-panel.ts +292 -0
  65. package/src/contracts/apply-contracts.ts +17 -12
  66. package/src/contracts/contract-types.ts +16 -4
  67. package/src/contracts/contracts-engine.ts +78 -42
  68. package/src/contracts/manifests/discord.contract.ts +6 -6
  69. package/src/contracts/manifests/identity.contract.ts +3 -3
  70. package/src/contracts/manifests/itch.contract.ts +12 -15
  71. package/src/contracts/manifests/social.contract.ts +5 -5
  72. package/src/contracts/manifests/steam.contract.ts +16 -16
  73. package/src/contracts/run-contracts.ts +4 -3
  74. package/src/core/attach.ts +96 -0
  75. package/src/core/canonical-scripts.ts +25 -0
  76. package/src/core/check-ctx.ts +204 -14
  77. package/src/core/check-file-names.ts +112 -0
  78. package/src/core/check-pass.ts +84 -0
  79. package/src/core/check-runner.ts +796 -0
  80. package/src/core/config-keys.ts +256 -0
  81. package/src/core/config-rewrite.ts +77 -0
  82. package/src/core/discover-checks.ts +166 -90
  83. package/src/core/discover-legacy-checks.ts +26 -0
  84. package/src/core/doc-block.ts +13 -0
  85. package/src/core/floor-resolve.ts +264 -0
  86. package/src/core/floor.ts +20 -137
  87. package/src/core/git-bin.ts +24 -0
  88. package/src/core/git-root.ts +108 -0
  89. package/src/core/instance.ts +195 -0
  90. package/src/core/jobs.ts +118 -0
  91. package/src/core/layout.ts +52 -0
  92. package/src/core/legacy-shapes.ts +207 -0
  93. package/src/core/lib-root.ts +5 -0
  94. package/src/core/locked-write.ts +110 -39
  95. package/src/core/machine-commit.ts +5 -4
  96. package/src/core/migrate-layout.ts +557 -0
  97. package/src/core/motherhome.ts +2 -8
  98. package/src/core/nesting.ts +4 -12
  99. package/src/core/one-way-in-policy.ts +73 -17
  100. package/src/core/outcome-words.ts +45 -0
  101. package/src/core/package-manager.ts +86 -0
  102. package/src/core/parallel.ts +21 -0
  103. package/src/core/process-tree.ts +44 -0
  104. package/src/core/raw-access.ts +93 -0
  105. package/src/core/read-set.ts +152 -0
  106. package/src/core/run-check.ts +155 -213
  107. package/src/core/run-ci.ts +153 -189
  108. package/src/core/run-fix.ts +96 -0
  109. package/src/core/run-fixers.ts +53 -30
  110. package/src/core/self-update.ts +9 -3
  111. package/src/core/slots.ts +83 -0
  112. package/src/core/spawn-timeout.ts +34 -3
  113. package/src/core/when-block.ts +401 -0
  114. package/src/core/when.ts +135 -0
  115. package/src/core/write-status.ts +5 -2
  116. package/src/hooks/leak-guard.ts +10 -8
  117. package/src/hooks/one-way-in-guard.ts +13 -5
  118. package/src/hooks/root-identity.ts +2 -1
  119. package/src/hooks/run-hooks.ts +35 -23
  120. package/src/init-repo.ts +39 -23
  121. package/src/ledger/check-stats.ts +155 -0
  122. package/src/ledger/check-table.ts +179 -0
  123. package/src/ledger/db.ts +255 -0
  124. package/src/ledger/gate-checks.ts +433 -0
  125. package/src/ledger/git-key.ts +116 -77
  126. package/src/ledger/git-scan.ts +96 -35
  127. package/src/ledger/ledger.ts +116 -42
  128. package/src/ledger/live-runs.ts +111 -0
  129. package/src/ledger/live-timeline.ts +97 -0
  130. package/src/ledger/repo-view.ts +73 -0
  131. package/src/ledger/run-log.ts +448 -0
  132. package/src/ledger/run-timeline.ts +173 -0
  133. package/src/ledger/store.ts +23 -12
  134. package/src/ledger/suite-log.ts +161 -0
  135. package/src/ledger/toolchain-identity.ts +7 -1
  136. package/src/ledger/world-cache.ts +111 -0
  137. package/src/mv/mv-md.ts +16 -6
  138. package/src/problems/extract.ts +182 -0
  139. package/src/problems/problem-board.ts +37 -0
  140. package/src/problems/problem-line.ts +77 -0
  141. package/src/prose/prose-view.ts +90 -0
  142. package/src/prose/run-prose.ts +60 -19
  143. package/src/prose/ste.ts +140 -24
  144. package/src/reflections/reflections-queue.ts +81 -0
  145. package/src/release/git-facts.ts +15 -9
  146. package/src/release/run-bump.ts +6 -5
  147. package/src/release/run-release.ts +25 -11
  148. package/src/release/run-releases.ts +2 -2
  149. package/src/release/unit-gate.ts +7 -54
  150. package/src/release/unit-resolve.ts +282 -0
  151. package/src/release/units.ts +33 -217
  152. package/src/repo-config.ts +155 -85
  153. package/src/rules/rule.ts +36 -96
  154. package/src/rules/untrack-generated.ts +5 -4
  155. package/src/scaffold/files/vscode-tasks.json +10 -31
  156. package/src/scaffold/jsonc-spans.ts +113 -0
  157. package/src/scaffold/project-scaffold.ts +16 -43
  158. package/src/scan.ts +98 -0
  159. package/src/serve/gate-trigger.ts +148 -0
  160. package/src/serve/health-schedule.ts +102 -0
  161. package/src/serve/hub-app/chart-view.ts +179 -0
  162. package/src/serve/hub-app/check-view.tsx +345 -0
  163. package/src/serve/hub-app/check-words.ts +164 -0
  164. package/src/serve/hub-app/checks-svc.ts +280 -0
  165. package/src/serve/hub-app/clock-svc.ts +33 -0
  166. package/src/serve/hub-app/col-card.tsx +23 -39
  167. package/src/serve/hub-app/cols-svc.ts +44 -20
  168. package/src/serve/hub-app/comfy-svc.ts +171 -0
  169. package/src/serve/hub-app/comfy-view.tsx +81 -0
  170. package/src/serve/hub-app/dur.ts +41 -0
  171. package/src/serve/hub-app/hash-route.ts +121 -0
  172. package/src/serve/hub-app/header-bar.tsx +117 -0
  173. package/src/serve/hub-app/health-svc.ts +23 -28
  174. package/src/serve/hub-app/help.tsx +29 -0
  175. package/src/serve/hub-app/hub-app.tsx +122 -119
  176. package/src/serve/hub-app/hub-st.ts +115 -42
  177. package/src/serve/hub-app/hub-types.ts +117 -2
  178. package/src/serve/hub-app/hub.css +534 -192
  179. package/src/serve/hub-app/index.html +2 -0
  180. package/src/serve/hub-app/live-svc.ts +174 -0
  181. package/src/serve/hub-app/md.ts +15 -0
  182. package/src/serve/hub-app/media-svc.ts +4 -1
  183. package/src/serve/hub-app/media-view.tsx +15 -0
  184. package/src/serve/hub-app/next-run.ts +68 -0
  185. package/src/serve/hub-app/project-gutter.css +65 -0
  186. package/src/serve/hub-app/project-gutter.tsx +128 -0
  187. package/src/serve/hub-app/projects-svc.ts +226 -0
  188. package/src/serve/hub-app/prose-hits-st.ts +124 -0
  189. package/src/serve/hub-app/prose-hits-view.tsx +176 -0
  190. package/src/serve/hub-app/prose-hits.css +21 -0
  191. package/src/serve/hub-app/run-strip.ts +47 -0
  192. package/src/serve/hub-app/runs-tabs.ts +24 -0
  193. package/src/serve/hub-app/test-view.tsx +107 -0
  194. package/src/serve/hub-app/tests-svc.ts +62 -0
  195. package/src/serve/hub-app/tests-view.tsx +238 -0
  196. package/src/serve/hub-app/time-ago.tsx +34 -0
  197. package/src/serve/hub-app/timeline-chart.tsx +274 -0
  198. package/src/serve/hub-app/timeline-geometry.ts +314 -0
  199. package/src/serve/hub-app/timeline-svc.ts +62 -0
  200. package/src/serve/hub-app/timeline-view.tsx +245 -0
  201. package/src/serve/hub-app/tree-build.ts +410 -0
  202. package/src/serve/hub-app/tree-row.tsx +42 -0
  203. package/src/serve/hub-app/tree-svc.ts +174 -0
  204. package/src/serve/hub-app/wikis-view.tsx +53 -0
  205. package/src/serve/other-repos.ts +79 -0
  206. package/src/serve/project-gutter-route.ts +350 -0
  207. package/src/serve/project-gutter.ts +449 -0
  208. package/src/serve/run-serve.ts +245 -16
  209. package/src/serve/same-origin.ts +14 -0
  210. package/src/serve/serve-hub.ts +213 -18
  211. package/src/shipkit.ts +19 -12
  212. package/src/smoke/run-smoke.ts +3 -2
  213. package/src/start-repo.ts +75 -36
  214. package/src/suite/judge-suites.ts +209 -15
  215. package/src/suite/kept-timings.ts +58 -0
  216. package/src/suite/runner-shapes.ts +88 -0
  217. package/src/suite/suite-cli.ts +147 -24
  218. package/src/suite/suite-cpu-preload.ts +92 -0
  219. package/src/suite/test-units.ts +243 -0
  220. package/src/ts/config.ts +4 -69
  221. package/src/ts/jsonc.ts +69 -0
  222. package/src/ts/lex.ts +143 -0
  223. package/src/ts/rename.ts +73 -10
  224. package/src/ts/source-graph.ts +28 -8
  225. package/src/ts/tsconfig-projects.ts +6 -97
  226. package/src/ts/tsconfig-resolve.ts +108 -0
  227. package/src/tui/build-steps-panel.ts +61 -0
  228. package/src/tui/checks-panel.ts +24 -16
  229. package/src/tui/run-tui.ts +2 -2
  230. package/src/tui/tui-panel.ts +5 -4
  231. package/src/wiki/graph-colors.ts +68 -0
  232. package/src/wiki/graph-mount.ts +7 -9
  233. package/src/wiki/graph-page.ts +42 -25
  234. package/src/wiki/graph-positions.ts +105 -0
  235. package/src/wiki/run-wiki.ts +1 -1
  236. package/src/wiki/serve-graph.ts +33 -3
  237. package/src/cli/cmd_Rules.ts +0 -51
  238. package/src/cli/cmd_Units.ts +0 -82
  239. package/src/collections/graphs.ts +0 -32
  240. package/src/contracts/contracts-once.ts +0 -43
  241. package/src/core/check-suite.ts +0 -302
  242. package/src/core/repair-contract-dirs.ts +0 -136
  243. package/src/core/task-runner.ts +0 -339
  244. package/src/ledger/gate-units.ts +0 -138
  245. package/src/ledger/unit-table.ts +0 -105
  246. package/src/rules/SK002-contracts-fulfillness.ts +0 -24
  247. package/src/rules/SK003-claude-md-shape.ts +0 -70
  248. package/src/rules/SK004-gitignore-policy.ts +0 -238
  249. package/src/rules/SK010-undeclared-release-unit.ts +0 -127
  250. package/src/rules/rule-registry.ts +0 -64
  251. package/src/rules/run-fix.ts +0 -72
  252. package/src/tui/tasks-panel.ts +0 -60
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # rvlib-shipkit
2
2
 
3
- Repo toolkit for shipping: one config file gives a repo a commit gate (task DAG), release-contract fulfillness checks, a human ask queue, markdown-wiki tooling, and the asset atelier (generate, curate, and ship assets — favicon/logo/OG included).
3
+ Repo toolkit for shipping: one config file gives a repo a commit gate (one DAG over every check), release-contract fulfillness checks, a human ask queue, markdown-wiki tooling, and the asset atelier (generate, curate, and ship assets — favicon/logo/OG included).
4
4
 
5
5
  ```sh
6
6
  bun add -d rvlib-shipkit
@@ -14,12 +14,23 @@ import { defineRepo } from 'rvlib-shipkit'
14
14
  export default defineRepo({
15
15
  name: 'my-game',
16
16
  venues: ['itch'], // which release contracts are REQUIRED
17
- social: ['x', 'discord'], // .rv-social/<channel>/ post folders (+ .rv-discord/)
18
- tasks: [{ id: 'e2e', cmd: ['bun', 'run', 'e2e'], deps: ['lint'] }],
17
+ social: ['x', 'discord'], // .shipkit/social/<channel>/ post folders (+ .shipkit/discord/)
19
18
  })
20
19
  ```
21
20
 
22
- Health checks need no config at all: drop a `<name>.check.ts` file anywhere and `shipkit check` discovers and runs it (wire output + exit code).
21
+ Everything the gate runs beyond the floor is a CHECK, and a check needs no config at all: drop a `<name>.check.ts` file anywhere and `shipkit ci`, `shipkit check`, `shipkit checks` and the hub discover it.
22
+
23
+ ```ts
24
+ // e2e.check.ts
25
+ import { check } from 'rvlib-shipkit/src/check.ts'
26
+ export default check({
27
+ description: 'the end to end suite passes against a built app',
28
+ blocking: true,
29
+ build: true,
30
+ cmd: ['bun', 'run', 'e2e'],
31
+ deps: ['lint'],
32
+ })
33
+ ```
23
34
 
24
35
  ## CLI
25
36
 
@@ -27,17 +38,18 @@ Health checks need no config at all: drop a `<name>.check.ts` file anywhere and
27
38
  shipkit ci commit gate: floor (lint/typecheck/test/imports/cycles —
28
39
  scripts win, imports/cycles/typecheck fill from bundled
29
40
  tools with zero config, gaps print visibly) + contracts
30
- + your tasks, parallel DAG; refreshes STATUS.md
41
+ + your checks, parallel DAG; refreshes STATUS.md
31
42
  [--bail --skip g --only id --json]
32
43
  shipkit contracts release-contract fulfillness alone [--venue v --json]
33
- shipkit check periodic health: built-in SK rules + every *.check.ts in
44
+ shipkit check periodic health: the built-in checks + every *.check.ts in
34
45
  the repo, auto-discovered + TTL-cached
35
46
  [--json --fresh --frozen --fix [SK### …]]
36
- --fix applies rule autofixes then re-lints; --frozen =
37
- self-maintaining rules (rvlib self-update) report only
38
- shipkit rules the SK rule catalog: id, what it lints, owning doc,
39
- fixable? [--json]
40
- shipkit tui live dashboard: ci tasks + discovered checks + panels
47
+ --fix applies check autofixes then re-runs them; --frozen =
48
+ self-maintaining checks (rvlib self-update) report only
49
+ shipkit checks the inventory: every check, where it runs, blocks or warns,
50
+ and how it caches. --fixable = what repairs itself,
51
+ with its owning doc [--json --md --out]
52
+ shipkit tui live dashboard: build steps + every other check + panels
41
53
  the repo injects via repo.config tui.panels
42
54
  shipkit asks human action queue: list · ask "<title>" · asks check (auto-close)
43
55
  shipkit needs everything blocked on a human, red/yellow by collection
@@ -45,7 +57,7 @@ shipkit needs everything blocked on a human, red/yellow by collection
45
57
  shipkit wiki serve|lint — live md link-graph + orphan/dangling-ref lint
46
58
  shipkit mv rename an md page + its sibling dir, sweeping every ref
47
59
  shipkit assets serve|fill|add|gen|identity|check — the asset atelier
48
- (declare kinds in .rv-assets/kit.ts, roll, curate, ship)
60
+ (declare kinds in .shipkit/assets/kit.ts, roll, curate, ship)
49
61
  shipkit pb collection <name> — scaffold a PocketBase collection's UI
50
62
  folder (columns/card/grid/form/index) from the app's
51
63
  generated pb types, create-only [--dry-run]
@@ -55,7 +67,7 @@ shipkit smoke browser pass over a DEPLOYED web app, read only: login
55
67
  quota headers, a detail page per `open` link. Exit code =
56
68
  failed routes. Needs playwright in the repo
57
69
  [--base <url> --routes /a,/b]
58
- shipkit serve the repo's ONE server: hub page, graphs, studio, status
70
+ shipkit serve the repo's ONE server: hub page, wikis, studio, status
59
71
  shipkit reflect your prompts across every Claude Code session of a repo:
60
72
  all config roots, queued-mid-run ones included, harness
61
73
  noise out, chronological md corpus for a retrospective
@@ -75,6 +87,6 @@ Ship your own contracts via `contracts: { extra: [defineContract({...})] }` —
75
87
 
76
88
  ## Library use
77
89
 
78
- Everything the CLI does is importable: `runTasks`, `CheckSuite`, `evaluateContracts`, the asks queue, `scanMdGraph`/`lintMd`/`serveMdGraph`, and the assets surface (`defineKit`/`defineKind`, `identityKinds`, `studioRoutes`, `saveEndpoint` via `rvlib-shipkit/vite`). Ships TypeScript source. Bun ≥ 1.0.
90
+ Everything the CLI does is importable: `runChecks`, `evaluateContracts`, the asks queue, `scanMdGraph`/`lintMd`/`serveMdGraph`, and the assets surface (`defineKit`/`defineKind`, `identityKinds`, `studioRoutes`, `saveEndpoint` via `rvlib-shipkit/vite`). Ships TypeScript source. Bun ≥ 1.0.
79
91
 
80
92
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rvlib-shipkit",
3
- "version": "0.38.1",
3
+ "version": "1.0.0",
4
4
  "funding": "https://github.com/sponsors/rvion",
5
5
  "description": "All-in-one repo companion: ci gate (task DAG), auto-discovered health checks, release-contract fulfillness, asks queue, md-wiki tools, asset atelier, live tui",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@napi-rs/canvas": "^1.0.2",
37
37
  "clipanion": "^4.0.0-rc.4",
38
- "comfy-ts": "^2.4.2",
38
+ "comfy-ts": "^2.9.0",
39
39
  "ink": "^7.1.1",
40
40
  "marked": "^18.0.7",
41
41
  "mobx": "^7.0.0",
@@ -96,15 +96,22 @@ export function applyVarValues(wf: DefinedWorkflow, vars: Record<string, unknown
96
96
  }
97
97
  }
98
98
 
99
- // ── per-asset drafts — comfy-ts disk format, path rule taken from the
100
- // ComfyTS instance so it cannot drift: .comfy-ts/drafts/<wfId>/<assetId>.json.
99
+ // ── drafts — comfy-ts disk format, path rule taken from the ComfyTS instance
100
+ // so it cannot drift: .comfy-ts/drafts/<wfId>/<draftId>.json.
101
+ //
102
+ // The draft id is the KIND, never the asset: ONE `creature` draft behind all
103
+ // 107 creatures, so tuning the loras once tunes them for the whole kind. The
104
+ // draft is the BASE and the asset's own prompt is injected ON TOP of it at run
105
+ // time, so a prompt left sitting in a draft can never reach an asset, and
106
+ // playing with a draft cannot break the assets that share it.
107
+ //
101
108
  // Shipkit does its own IO here because comfy-ts exports no drafts API yet;
102
109
  // swap when it lands.
103
110
  const draftDir = (lw: LoadedWorkflow): string => ComfyTS.create().resolveFromDrafts(lw.id)
104
111
  export const draftPath = (lw: LoadedWorkflow, name: string): string => join(draftDir(lw), `${name}.json`)
105
112
 
106
- export function readAssetDraft(lw: LoadedWorkflow, assetId: string): Record<string, unknown> | null {
107
- const p = draftPath(lw, assetId)
113
+ export function readAssetDraft(lw: LoadedWorkflow, draftId: string): Record<string, unknown> | null {
114
+ const p = draftPath(lw, draftId)
108
115
  if (!existsSync(p)) return null
109
116
  try {
110
117
  return JSON.parse(readFileSync(p, 'utf8')) as Record<string, unknown>
@@ -116,8 +123,8 @@ export function readAssetDraft(lw: LoadedWorkflow, assetId: string): Record<stri
116
123
  /** Seed `{ prompt }` ONLY when the draft is absent (hand-tuned state is never
117
124
  * overwritten). The stored prompt is TUI context; generation overrides it.
118
125
  * Returns true when just seeded. */
119
- export function ensureAssetDraft(lw: LoadedWorkflow, assetId: string, prompt: string): boolean {
120
- const p = draftPath(lw, assetId)
126
+ export function ensureAssetDraft(lw: LoadedWorkflow, draftId: string, prompt: string): boolean {
127
+ const p = draftPath(lw, draftId)
121
128
  if (existsSync(p)) return false
122
129
  mkdirSync(draftDir(lw), { recursive: true })
123
130
  writeFileSync(p, JSON.stringify({ prompt }, null, 2) + '\n')
@@ -133,22 +140,32 @@ export function ensureAssetDraft(lw: LoadedWorkflow, assetId: string, prompt: st
133
140
  export async function generateComfyTs(
134
141
  rt: ComfyTsRuntime,
135
142
  lw: LoadedWorkflow,
136
- args: { prompt: string; seed: number; assetId: string; destDir: string; name: string; vars?: Record<string, unknown>; onProgress?: OnProgress },
143
+ args: { prompt: string; seed: number; assetId: string; draftId?: string; destDir: string; name: string; vars?: Record<string, unknown>; onProgress?: OnProgress },
137
144
  ): Promise<string[]> {
138
- ensureAssetDraft(lw, args.assetId, args.prompt)
145
+ // the KIND's shared draft, falling back to the asset for a caller that
146
+ // names no kind. It is what the tune button edits
147
+ const draftId = args.draftId ?? args.assetId
148
+ ensureAssetDraft(lw, draftId, args.prompt)
139
149
  const defs = varsOf(lw.wf)
140
150
  // modules are import-cached: a previous asset's values must not leak
141
151
  for (const [, varDef] of lw.wf.entries()) varDef.reset()
142
152
  if (args.vars != null) applyVarValues(lw.wf, args.vars, `asset '${args.assetId}' kit vars`)
143
- const draft = readAssetDraft(lw, args.assetId)
153
+ const draft = readAssetDraft(lw, draftId)
144
154
  if (draft != null) {
145
155
  for (const [key, value] of Object.entries(draft)) {
146
156
  const varDef = defs[key]
147
- if (varDef == null) { console.log(`WARN draft '${args.assetId}': stale var '${key}' not on workflow '${lw.id}' — ignored`); continue }
157
+ if (varDef == null) { console.log(`WARN draft '${draftId}': stale var '${key}' not on workflow '${lw.id}' — ignored`); continue }
148
158
  varDef.loadJSON(value)
149
159
  }
150
160
  }
161
+ // LAST, and unconditionally, the three the KIT owns go ON TOP of the shared
162
+ // draft: the asset's prompt, the kind's declared size, and the roll's seed.
163
+ // Everything else (loras, steps, cfg, removeBg) is the draft's to own. Size
164
+ // is here because the kind ships its rolls into a fixed box, so a size tried
165
+ // in the panel must not silently resize every asset that shares the draft
151
166
  defs.prompt!.parse(args.prompt)
167
+ const kitSize = (args.vars ?? {})['size']
168
+ if (kitSize != null) defs['size']?.loadJSON(kitSize)
152
169
  // '= N' pins mode AND value, neutralizing any ?/+ mode a draft restored,
153
170
  // so the v<seed> file name stays truthful
154
171
  defs.seed!.parse(`= ${args.seed}`)
@@ -45,7 +45,7 @@ export async function generateRoll(
45
45
  }
46
46
  const rt = await resolveComfy()
47
47
  const lw = workflowFor(rt.workflows, { workflow: gen.workflow, assetId: found.asset.id })
48
- await generateComfyTs(rt, lw, { prompt: args.prompt, seed: args.seed, assetId: found.asset.id, destDir: args.destDir, name: `v${args.seed}`, vars: gen.vars, onProgress: args.onProgress })
48
+ await generateComfyTs(rt, lw, { prompt: args.prompt, seed: args.seed, assetId: found.asset.id, draftId: found.kind.id, destDir: args.destDir, name: `v${args.seed}`, vars: gen.vars, onProgress: args.onProgress })
49
49
  }
50
50
 
51
51
  /** Memoized lazy comfy runtime for a resolved kit — loads the cflow modules on
@@ -1,4 +1,4 @@
1
- // defineKit — the repo-side surface: .rv-assets/kit.ts declares asset kinds,
1
+ // defineKit — the repo-side surface: .shipkit/assets/kit.ts declares asset kinds,
2
2
  // generators, ship targets and metadata panels; the studio/CLI derive
3
3
  // everything else. STRUCTURE lives here (git-reviewed TS); studio-edited
4
4
  // state lives in the workspace overlays (pick.json, prompt.json, *.rect.json).
@@ -64,7 +64,7 @@ export type CustomPanelSpec = {
64
64
  panel: 'custom'
65
65
  /** url-safe kebab-case, unique per kind — becomes the served module name */
66
66
  id: string
67
- /** repo-root-relative ES module path (e.g. .rv-assets/panels/legs-editor.js) */
67
+ /** repo-root-relative ES module path (e.g. .shipkit/assets/panels/legs-editor.js) */
68
68
  script: string
69
69
  /** default corner of the floating card on the canvas (drag overrides) */
70
70
  anchor?: 'tl' | 'bl'
@@ -113,7 +113,7 @@ export type Kind = {
113
113
  media?: Media
114
114
  /** icon kinds get no geometry, thumbnails only */
115
115
  mode?: 'sprite' | 'icon'
116
- /** via = repo hook module path (default .rv-assets/ship.ts) · fn = a
116
+ /** via = repo hook module path (default .shipkit/assets/ship.ts) · fn = a
117
117
  * built-in lib hook (e.g. identityKinds re-rendering the set) — never
118
118
  * both. With NO hook, `dir` alone is the zero-config ship: the picked
119
119
  * roll is copied to <dir>/<assetId>.<ext>; hooks read dir as data. */
@@ -224,14 +224,14 @@ export function missingPanelScripts(kit: KitConfig, root: string): string[] {
224
224
  export function defineKit(k: KitConfig): ResolvedKit {
225
225
  const errs = validateKit(k)
226
226
  if (errs.length > 0) throw new Error(`invalid kit:\n ${errs.join('\n ')}`)
227
- return { ...k, workspace: k.workspace ?? '.rv-assets/out' }
227
+ return { ...k, workspace: k.workspace ?? '.shipkit/assets/out' }
228
228
  }
229
229
 
230
230
  export const defineKind = (k: Kind): Kind => k
231
231
 
232
232
  export async function loadKit(root: string): Promise<ResolvedKit> {
233
- const path = join(root, '.rv-assets/kit.ts')
234
- if (!existsSync(path)) throw new Error(`no .rv-assets/kit.ts in ${root} — create one (defineKit) or run the init snippet from the docs`)
233
+ const path = join(root, '.shipkit/assets/kit.ts')
234
+ if (!existsSync(path)) throw new Error(`no .shipkit/assets/kit.ts in ${root} — create one (defineKit) or run the init snippet from the docs`)
235
235
  const mod = (await import(path)) as { default?: ResolvedKit }
236
236
  if (mod.default == null) throw new Error(`${path}: must default-export defineKit({...})`)
237
237
  const missing = missingPanelScripts(mod.default, root)
@@ -107,19 +107,19 @@ export const runAssetsGen = (root: string, io: AssetsIo, o: { assetId: string; n
107
107
  // ── identity: ONE render path + a brand.ts content hash so drift is
108
108
  // machine-detectable — and machine-CLEARED (auto re-render), never a nag.
109
109
  // Only a missing PICK still asks a human (taste).
110
- const brandHashPath = (root: string): string => join(root, '.rv-identity/brand.hash')
110
+ const brandHashPath = (root: string): string => join(root, '.shipkit/identity/brand.hash')
111
111
  const brandHash = async (root: string): Promise<string> =>
112
- new Bun.CryptoHasher('sha256').update(await Bun.file(join(root, '.rv-identity/brand.ts')).text()).digest('hex').slice(0, 16)
112
+ new Bun.CryptoHasher('sha256').update(await Bun.file(join(root, '.shipkit/identity/brand.ts')).text()).digest('hex').slice(0, 16)
113
113
 
114
114
  export type IdentityRender = { written: string[] } | { blocked: string }
115
115
 
116
116
  export async function renderIdentityNow(root: string): Promise<IdentityRender> {
117
- const brandPath = join(root, '.rv-identity/brand.ts')
118
- if (!existsSync(brandPath)) return { blocked: 'no .rv-identity/brand.ts — create one exporting a Brand (name, palette, mark)' }
117
+ const brandPath = join(root, '.shipkit/identity/brand.ts')
118
+ if (!existsSync(brandPath)) return { blocked: 'no .shipkit/identity/brand.ts — create one exporting a Brand (name, palette, mark)' }
119
119
  // cache-bust: bun caches dynamic imports for the process lifetime — a
120
120
  // long-running hub must re-render the EDITED brand, not the cached one
121
121
  const mod = (await import(`${brandPath}?v=${await brandHash(root)}`)) as { default?: Brand }
122
- if (mod.default == null) return { blocked: '.rv-identity/brand.ts must default-export the Brand' }
122
+ if (mod.default == null) return { blocked: '.shipkit/identity/brand.ts must default-export the Brand' }
123
123
  const brand = mod.default
124
124
  let markFile: string | undefined
125
125
  if (brand.mark.backend === 'comfy') {
@@ -141,7 +141,7 @@ export async function renderIdentityNow(root: string): Promise<IdentityRender> {
141
141
  /** brand.ts drifted since the last render → re-render NOW (autonomous).
142
142
  * Returns what happened; 'blocked' = only a human pick can clear it. */
143
143
  export async function healIdentity(root: string): Promise<{ state: 'fresh' | 'rendered' | 'blocked' | 'absent'; detail?: string }> {
144
- if (!existsSync(join(root, '.rv-identity/brand.ts'))) return { state: 'absent' }
144
+ if (!existsSync(join(root, '.shipkit/identity/brand.ts'))) return { state: 'absent' }
145
145
  const outputsMissing = ['public/favicon.png', 'public/og.png', 'public/logo.png'].some((p) => !existsSync(join(root, p)))
146
146
  const stored = existsSync(brandHashPath(root)) ? (await Bun.file(brandHashPath(root)).text()).trim() : null
147
147
  if (!outputsMissing && stored === (await brandHash(root))) return { state: 'fresh' }
@@ -197,10 +197,11 @@ export const runAssetsCheck = (root: string, io: AssetsIo): Promise<number> =>
197
197
  bad++
198
198
  }
199
199
  }
200
- // one draft per comfy asset — the TUI's tweakable entry.
201
- // ONE aggregate line: per-asset rows would drown the check
200
+ // one draft per comfy KIND, shared by its assets — the tweakable
201
+ // entry. ONE aggregate line: per-asset rows would drown the check
202
202
  let draftCount = 0
203
203
  let draftSeeded = 0
204
+ const draftKinds = new Set<string>()
204
205
  for (const f of allAssets(kit).filter((x) => x.kind.generate.backend === 'comfy')) {
205
206
  let lw: LoadedWorkflow
206
207
  try {
@@ -209,10 +210,13 @@ export const runAssetsCheck = (root: string, io: AssetsIo): Promise<number> =>
209
210
  continue // an unresolvable workflow already FAILed on its kind above
210
211
  }
211
212
  const prompt = (await readPromptOverlay(assetDir(join(root, kit.workspace), f.asset.id))).prompt ?? f.asset.prompt
212
- if (ensureAssetDraft(lw, f.asset.id, prompt)) draftSeeded++
213
+ // the first asset of a kind seeds it and the rest share it, so
214
+ // a lora tuned once holds for every asset of that kind
215
+ if (ensureAssetDraft(lw, f.kind.id, prompt)) draftSeeded++
216
+ draftKinds.add(`${lw.id}/${f.kind.id}`)
213
217
  draftCount++
214
218
  }
215
- if (draftCount > 0) io.print(`ok: drafts ensured for ${draftCount} comfy asset(s) in .comfy-ts/drafts/${draftSeeded > 0 ? ` (${draftSeeded} seeded now)` : ''}`)
219
+ if (draftCount > 0) io.print(`ok: ${draftKinds.size} draft(s) behind ${draftCount} comfy asset(s) in .comfy-ts/drafts/${draftSeeded > 0 ? ` (${draftSeeded} seeded now)` : ''}`)
216
220
  // offline graph validation when the host has a schema cache;
217
221
  // a missing cache is world state → WARN, never a repo FAIL
218
222
  const hostsWarned = new Set<string>()
@@ -83,10 +83,10 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
83
83
  let ship: AssetInfo['ship']
84
84
  if (f.kind.ship?.fn != null) ship = { mode: 'built-in', note: 're-renders the set on pick' }
85
85
  else {
86
- const via = f.kind.ship?.via ?? '.rv-assets/ship.ts'
86
+ const via = f.kind.ship?.via ?? '.shipkit/assets/ship.ts'
87
87
  ship = existsSync(join(root, via)) ? { mode: 'hook', path: via } : { mode: 'record', note: 'pick.json only — no ship hook' }
88
88
  }
89
- return { backend, deterministic: backend === 'canvas', workflow, ship, metaFile: f.kind.meta?.file ?? null, kindId: f.kind.id, kitSource: '.rv-assets/kit.ts' }
89
+ return { backend, deterministic: backend === 'canvas', workflow, ship, metaFile: f.kind.meta?.file ?? null, kindId: f.kind.id, kitSource: '.shipkit/assets/kit.ts' }
90
90
  }
91
91
 
92
92
  // effective prompt: workspace overlay wins over the kit's builder output
@@ -100,7 +100,8 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
100
100
  try {
101
101
  const gen = f.kind.generate as { workflow?: string }
102
102
  const lw = workflowFor(workflows, { workflow: gen.workflow, assetId: f.asset.id })
103
- if (ensureAssetDraft(lw, f.asset.id, await promptOf(f))) console.log(`ok: draft '${f.asset.id}' seeded → .comfy-ts/drafts/${lw.id}/${f.asset.id}.json`)
103
+ // the KIND's draft, shared by every asset of that kind
104
+ if (ensureAssetDraft(lw, f.kind.id, await promptOf(f))) console.log(`ok: draft '${f.kind.id}' seeded → .comfy-ts/drafts/${lw.id}/${f.kind.id}.json`)
104
105
  } catch (e) {
105
106
  console.log(`WARN draft for '${f.asset.id}' not seeded — ${(e as Error).message}`)
106
107
  }
@@ -176,12 +177,12 @@ export async function studioRoutes(deps: StudioDeps): Promise<StudioRoutes> {
176
177
 
177
178
  // ── ship hooks: a kind's BUILT-IN ship.fn (e.g. identityKinds re-rendering
178
179
  // the set from the pick) wins; otherwise the repo module is probed once
179
- // PER PATH (ship.via, default .rv-assets/ship.ts; fixing a hook needs a
180
+ // PER PATH (ship.via, default .shipkit/assets/ship.ts; fixing a hook needs a
180
181
  // studio restart) ───────────────────────────────────────────────────────
181
182
  const shipFns = new Map<string, ShipFn | null>()
182
183
  const resolveShipFn = async (f: FoundAsset): Promise<ShipFn | null> => {
183
184
  if (f.kind.ship?.fn != null) return f.kind.ship.fn
184
- const shipPath = join(root, f.kind.ship?.via ?? '.rv-assets/ship.ts')
185
+ const shipPath = join(root, f.kind.ship?.via ?? '.shipkit/assets/ship.ts')
185
186
  let fn = shipFns.get(shipPath)
186
187
  if (fn === undefined) {
187
188
  if (existsSync(shipPath)) {