rvlib-shipkit 0.10.0 → 0.13.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.
- package/CHANGELOG.md +204 -0
- package/README.md +17 -3
- package/package.json +5 -2
- package/src/asks/asks-queue.ts +42 -0
- package/src/assets/kit-config.ts +2 -3
- package/src/assets/roll-workspace.ts +2 -1
- package/src/assets/run-assets.ts +0 -11
- package/src/assets/server/studio-server.ts +7 -24
- package/src/cli/cmd_Assets.ts +3 -14
- package/src/cli/cmd_Check.ts +22 -4
- package/src/cli/cmd_Needs.ts +47 -0
- package/src/cli/cmd_Reflect.ts +116 -0
- package/src/cli/cmd_Rules.ts +32 -0
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli.ts +7 -2
- package/src/collections/actions.ts +85 -0
- package/src/collections/collection-types.ts +65 -0
- package/src/collections/graphs.ts +34 -0
- package/src/collections/pings.ts +72 -0
- package/src/collections/posts.ts +125 -0
- package/src/collections/providers.ts +275 -0
- package/src/contracts/apply-contracts.ts +47 -0
- package/src/contracts/run-contracts.ts +8 -3
- package/src/core/canonical-scripts.ts +46 -0
- package/src/core/check-suite.ts +6 -3
- package/src/core/locked-write.ts +39 -0
- package/src/core/motherhome.ts +87 -0
- package/src/core/run-check.ts +28 -15
- package/src/core/run-ci.ts +4 -0
- package/src/core/write-status.ts +7 -26
- package/src/init-repo.ts +24 -11
- package/src/reflect/prompt-corpus.ts +305 -0
- package/src/repo-config.ts +26 -38
- package/src/rules/SK001-package-json-shape.ts +156 -0
- package/src/rules/SK002-contracts-fulfillness.ts +19 -0
- package/src/rules/SK003-claude-md-shape.ts +64 -0
- package/src/rules/SK004-gitignore-policy.ts +105 -0
- package/src/rules/SK005-rvlib-versions.ts +99 -0
- package/src/rules/SK006-npm-publish-safety.ts +42 -0
- package/src/rules/SK007-github-visibility.ts +63 -0
- package/src/rules/rule-registry.ts +32 -0
- package/src/rules/rule.ts +62 -0
- package/src/rules/run-fix.ts +57 -0
- package/src/serve/hub-app/col-card.tsx +192 -0
- package/src/serve/hub-app/cols-svc.ts +136 -0
- package/src/serve/hub-app/geometry-view.tsx +155 -0
- package/src/serve/hub-app/health-svc.ts +49 -0
- package/src/serve/hub-app/hub-app.tsx +240 -127
- package/src/serve/hub-app/hub-st.ts +169 -0
- package/src/serve/hub-app/hub-types.ts +72 -0
- package/src/serve/hub-app/hub.css +286 -35
- package/src/serve/hub-app/index.html +1 -1
- package/src/serve/hub-app/md.ts +24 -0
- package/src/serve/hub-app/media-svc.ts +429 -0
- package/src/serve/hub-app/media-view.tsx +299 -0
- package/src/serve/run-serve.ts +46 -91
- package/src/serve/serve-hub.ts +99 -49
- package/src/shipkit.ts +13 -6
- package/src/assets/server/studio-page.ts +0 -687
- package/src/core/package-json-check.ts +0 -88
- package/src/serve/hub-pages.ts +0 -45
package/src/serve/serve-hub.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
// shipkit serve — ONE
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// run-serve.ts's job.
|
|
1
|
+
// shipkit serve — the repo's ONE Bun.serve, serving the ONE page (his
|
|
2
|
+
// rules 2026-07-23: one server per repo, one surface, mobx+react). `/` is
|
|
3
|
+
// the hub app (Bun HTML import — media studio + asks + creds + posts +
|
|
4
|
+
// health + graphs as sections); the studio route table mounts as the media
|
|
5
|
+
// API under /media/*; graph canvases stay as drill-down pages. Route logic
|
|
6
|
+
// only — resolving WHAT to mount from repo.config is run-serve.ts's job.
|
|
7
7
|
import { existsSync } from 'fs'
|
|
8
8
|
import { join } from 'path'
|
|
9
9
|
import { graphHtml } from 'rvlib-shipkit/src/wiki/graph-page.ts'
|
|
10
10
|
import type { MdGraph } from 'rvlib-shipkit/src/wiki/md-graph.ts'
|
|
11
11
|
import type { StudioRoutes } from '../assets/server/studio-server.ts'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
// build/webapp doctrine; legacy pages (graphs, studio SPA) keep the
|
|
15
|
-
// server-injected rail until they migrate
|
|
12
|
+
import { appendAskRemark, appendAssetNote, appendPostFeedback, askDone, askProof, markPosted, pickPostVariant } from '../collections/actions.ts'
|
|
13
|
+
import type { Collection, Instance, NeedItem } from '../collections/collection-types.ts'
|
|
16
14
|
import hubIndex from './hub-app/index.html'
|
|
17
15
|
|
|
18
16
|
export type HubGraph = { title: string; load: () => MdGraph }
|
|
@@ -25,74 +23,126 @@ export type HubDeps = {
|
|
|
25
23
|
wikiGraph: HubGraph
|
|
26
24
|
/** null = no agent/ dir */
|
|
27
25
|
agentGraph: HubGraph | null
|
|
28
|
-
/**
|
|
26
|
+
/** the media API (mounted under /media/*); null only if the route
|
|
27
|
+
* table failed to build — the page shows the WARN, never a second page */
|
|
29
28
|
studio: StudioRoutes | null
|
|
30
|
-
/** argv shelling the check run streamed by /
|
|
29
|
+
/** argv shelling the check run streamed by /api/check/run (test seam) */
|
|
31
30
|
checkCmd: string[]
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
|
|
31
|
+
/** every human-action item, red/yellow, gathered per request from the
|
|
32
|
+
* collection providers (run-serve builds it) */
|
|
33
|
+
needs: () => Promise<NeedItem[]>
|
|
34
|
+
/** BROWSE-ALL: the collections with every instance incl. healthy —
|
|
35
|
+
* the page shows everything that exists (his trust rule) */
|
|
36
|
+
collections: () => Collection[]
|
|
37
|
+
/** asks dir (repo-root-relative) for the act-in-place ask verbs */
|
|
38
|
+
asksDir: string
|
|
39
|
+
/** RED-only ping tick, called on an interval while serving (null = off) */
|
|
40
|
+
pingTick?: ((needs: NeedItem[]) => void) | null
|
|
35
41
|
}
|
|
36
42
|
export type Hub = { server: ReturnType<typeof Bun.serve>; tools: HubTool[]; stop: () => void }
|
|
37
43
|
|
|
38
|
-
// the toolbox as data —
|
|
39
|
-
//
|
|
40
|
-
export function hubTools(d: Pick<HubDeps, 'wikiGraph' | 'agentGraph'
|
|
41
|
-
const tools: HubTool[] = [{ path: '/
|
|
44
|
+
// the toolbox as data — stdout ok: lines and /hub.json render THIS list,
|
|
45
|
+
// so they can never disagree
|
|
46
|
+
export function hubTools(d: Pick<HubDeps, 'wikiGraph' | 'agentGraph'>): HubTool[] {
|
|
47
|
+
const tools: HubTool[] = [{ path: '/', label: 'hub', desc: 'THE page — media · asks · creds · posts · health · graphs' }]
|
|
48
|
+
tools.push({ path: '/graph/wiki', label: 'wiki graph', desc: d.wikiGraph.title })
|
|
42
49
|
if (d.agentGraph != null) tools.push({ path: '/graph/agent', label: 'agent graph', desc: d.agentGraph.title })
|
|
43
|
-
if (d.studio != null) tools.push({ path: '/assets/', label: 'assets studio', desc: 'roll, curate, pick, ship' })
|
|
44
|
-
tools.push({ path: '/status', label: 'status', desc: 'STATUS.md + run check' })
|
|
45
50
|
return tools
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
// drill-down pages (graph canvases) get one fixed home link back to THE page
|
|
54
|
+
const HOME_LINK = `<style>.skhome{position:fixed;left:10px;top:10px;z-index:9999;font-size:17px;text-decoration:none;
|
|
55
|
+
background:#17140f;border:1px solid #332b22;border-radius:9px;padding:5px 9px}</style><a class=skhome href="/" title="hub — THE page">⌂</a>`
|
|
56
|
+
const withHome = (html: string): string => html.replace(/<body([^>]*)>/, `<body$1>${HOME_LINK}`)
|
|
57
|
+
|
|
48
58
|
export function serveHub(d: HubDeps): Hub {
|
|
49
59
|
const tools = hubTools(d)
|
|
50
|
-
// every HTML page carries the rail — one keystroke between surfaces;
|
|
51
|
-
// the red dot self-fetches client-side (no collector on passive navs)
|
|
52
|
-
const shell = (s: string, active: string): Response =>
|
|
53
|
-
new Response(withRail(s, { tools, active }), { headers: { 'content-type': 'text/html; charset=utf-8' } })
|
|
54
60
|
const server: ReturnType<typeof Bun.serve> = Bun.serve({
|
|
55
61
|
hostname: '127.0.0.1',
|
|
56
62
|
port: d.port,
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
routes: { '/': hubIndex, '/status': hubIndex },
|
|
63
|
+
// `/` IS the page — the react app; there is no other surface
|
|
64
|
+
routes: { '/': hubIndex },
|
|
60
65
|
async fetch(req): Promise<Response> {
|
|
61
66
|
const path = new URL(req.url).pathname
|
|
62
67
|
// identity probe: lets a second launch tell "same repo, another
|
|
63
68
|
// window" from "a DIFFERENT repo hashed onto my port"
|
|
64
69
|
if (path === '/hub.json') return Response.json({ hub: 'shipkit', name: d.name, tools })
|
|
65
|
-
if (path === '/api/
|
|
66
|
-
const
|
|
67
|
-
return Response.json({
|
|
70
|
+
if (path === '/api/needs.json') {
|
|
71
|
+
const needs = await d.needs().catch((e: Error) => [{ collection: 'hub', id: 'hub:broken', text: `needs projection broke: ${e.message}`, severity: 'red' as const }])
|
|
72
|
+
return Response.json({ needs })
|
|
73
|
+
}
|
|
74
|
+
if (path === '/api/collections.json') {
|
|
75
|
+
// browse-all: every instance of every collection, healthy
|
|
76
|
+
// included — failure-isolated per collection
|
|
77
|
+
const out: { id: string; title: string; media: string; instances: Instance[] }[] = []
|
|
78
|
+
for (const col of d.collections()) {
|
|
79
|
+
try {
|
|
80
|
+
out.push({ id: col.id, title: col.title, media: col.media, instances: await col.list() })
|
|
81
|
+
} catch (e) {
|
|
82
|
+
out.push({ id: col.id, title: col.title, media: col.media, instances: [{ id: `${col.id}:broken`, text: `collector broke: ${(e as Error).message}`, severity: 'red' }] })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Response.json({ collections: out })
|
|
86
|
+
}
|
|
87
|
+
if (path.startsWith('/api/act/') && req.method === 'POST') {
|
|
88
|
+
// act-in-place verbs — every mutation hits a git-tracked truth
|
|
89
|
+
// file; errors surface as 400 text the card shows verbatim
|
|
90
|
+
try {
|
|
91
|
+
const b = (await req.json()) as Record<string, string>
|
|
92
|
+
if (path === '/api/act/ask-done') askDone(d.root, d.asksDir, b.file ?? '')
|
|
93
|
+
else if (path === '/api/act/ask-proof') askProof(d.root, d.asksDir, b.file ?? '', b.url ?? '')
|
|
94
|
+
else if (path === '/api/act/post-pick') pickPostVariant(d.root, b.channel ?? '', b.file ?? '')
|
|
95
|
+
else if (path === '/api/act/post-posted') {
|
|
96
|
+
const rating = b.rating != null ? Number(b.rating) : undefined
|
|
97
|
+
if (rating != null && (!Number.isFinite(rating) || rating < 1 || rating > 5)) return new Response('rating must be 1-5', { status: 400 })
|
|
98
|
+
markPosted(d.root, b.channel ?? '', b.file ?? '', b.url ?? '', rating)
|
|
99
|
+
}
|
|
100
|
+
else if (path === '/api/act/post-feedback') appendPostFeedback(d.root, b.channel ?? '', b.file ?? '', b.text ?? '')
|
|
101
|
+
else if (path === '/api/act/remark') {
|
|
102
|
+
// ONE remark verb, routed by collection — his words
|
|
103
|
+
// land in the item's own truth file
|
|
104
|
+
const text = (b.text ?? '').trim()
|
|
105
|
+
if (text === '') return new Response('empty remark', { status: 400 })
|
|
106
|
+
if (b.kind === 'ask') appendAskRemark(d.root, d.asksDir, b.file ?? '', text)
|
|
107
|
+
else if (b.kind === 'media') appendAssetNote(d.root, b.id ?? '', text)
|
|
108
|
+
else if (b.kind === 'post') appendPostFeedback(d.root, b.channel ?? '', b.file ?? '', text)
|
|
109
|
+
else return new Response('unknown remark kind', { status: 400 })
|
|
110
|
+
}
|
|
111
|
+
else return new Response('unknown act', { status: 404 })
|
|
112
|
+
return Response.json({ ok: true })
|
|
113
|
+
} catch (e) {
|
|
114
|
+
return new Response((e as Error).message, { status: 400 })
|
|
115
|
+
}
|
|
68
116
|
}
|
|
69
117
|
if (path === '/api/status.json') {
|
|
70
118
|
const p = join(d.root, 'STATUS.md')
|
|
71
119
|
return Response.json({ statusMd: existsSync(p) ? await Bun.file(p).text() : null })
|
|
72
120
|
}
|
|
73
|
-
if (path === '/
|
|
74
|
-
if (path === '/graph/wiki/graph.json') return Response.json(d.wikiGraph.load())
|
|
75
|
-
if (d.agentGraph != null && path === '/graph/agent') return shell(graphHtml(d.agentGraph.load(), { title: d.agentGraph.title }), path)
|
|
76
|
-
if (d.agentGraph != null && path === '/graph/agent/graph.json') return Response.json(d.agentGraph.load())
|
|
77
|
-
if (path === '/status/run' && req.method === 'POST') {
|
|
121
|
+
if (path === '/api/check/run' && req.method === 'POST') {
|
|
78
122
|
// stream wire lines as the check emits them; sh merges stderr
|
|
79
123
|
// so FAIL lines reach the page, not just the terminal
|
|
80
124
|
const proc = Bun.spawn(['sh', '-c', 'exec "$0" "$@" 2>&1', ...d.checkCmd], { cwd: d.root, stdout: 'pipe', stderr: 'ignore' })
|
|
81
125
|
return new Response(proc.stdout, { headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' } })
|
|
82
126
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (path.startsWith('/assets/')) return d.studio.handle(req, path.slice('/assets'.length))
|
|
93
|
-
}
|
|
94
|
-
return new Response('not found', { status: 404 })
|
|
127
|
+
const page = (s: string): Response => new Response(withHome(s), { headers: { 'content-type': 'text/html; charset=utf-8' } })
|
|
128
|
+
if (path === '/graph/wiki') return page(graphHtml(d.wikiGraph.load(), { title: d.wikiGraph.title }))
|
|
129
|
+
if (path === '/graph/wiki/graph.json') return Response.json(d.wikiGraph.load())
|
|
130
|
+
if (d.agentGraph != null && path === '/graph/agent') return page(graphHtml(d.agentGraph.load(), { title: d.agentGraph.title }))
|
|
131
|
+
if (d.agentGraph != null && path === '/graph/agent/graph.json') return Response.json(d.agentGraph.load())
|
|
132
|
+
// the media API namespace — the studio route table, ONE prefix strip
|
|
133
|
+
if (d.studio != null && path.startsWith('/media/')) return d.studio.handle(req, path.slice('/media'.length))
|
|
134
|
+
// stale bookmarks (/assets/, /status, …) land here — name the way home
|
|
135
|
+
return new Response('not found — the hub page is at /', { status: 404 })
|
|
95
136
|
},
|
|
96
137
|
})
|
|
97
|
-
|
|
138
|
+
// RED-only pings ride the serving process: one tick now (state file
|
|
139
|
+
// guarantees a restart never re-fires), then every minute
|
|
140
|
+
let pingTimer: ReturnType<typeof setInterval> | null = null
|
|
141
|
+
if (d.pingTick != null) {
|
|
142
|
+
// tick failures are LOUD (console = the serve terminal), never swallowed
|
|
143
|
+
const tick = (): void => void d.needs().then((n) => d.pingTick?.(n)).catch((e: Error) => console.error(`WARN ping tick broke: ${e.message}`))
|
|
144
|
+
tick()
|
|
145
|
+
pingTimer = setInterval(tick, 60_000)
|
|
146
|
+
}
|
|
147
|
+
return { server, tools, stop: () => { if (pingTimer != null) clearInterval(pingTimer); server.stop(true) } }
|
|
98
148
|
}
|
package/src/shipkit.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// rvlib-shipkit — barrel. One import per repo: defineRepo in repo.config.ts;
|
|
2
2
|
// everything else is the machinery behind the shipkit CLI.
|
|
3
|
-
export { defineRepo, loadRepoConfig, resolveFloor, allTasks, contractsTask
|
|
3
|
+
export { defineRepo, loadRepoConfig, resolveFloor, allTasks, contractsTask } from './repo-config.ts'
|
|
4
4
|
export type { RepoConfig, ServeConfig, FloorResolution, FloorSource } from './repo-config.ts'
|
|
5
|
+
export { contractsConfigFrom, activeContracts, applyContractsResult } from './contracts/apply-contracts.ts'
|
|
5
6
|
// the hub (`shipkit serve`): one server, one port, every tool a click away
|
|
6
7
|
export { serveHub, hubTools } from './serve/serve-hub.ts'
|
|
7
8
|
export type { Hub, HubDeps, HubGraph, HubTool } from './serve/serve-hub.ts'
|
|
8
9
|
export { runServe, resolveHubDeps, defaultHubPort } from './serve/run-serve.ts'
|
|
9
|
-
export {
|
|
10
|
-
export type {
|
|
10
|
+
export { projectNeeds, redCount } from './collections/collection-types.ts'
|
|
11
|
+
export type { Collection, Instance, NeedItem, NeedSeverity, CurationVerb } from './collections/collection-types.ts'
|
|
12
|
+
export { buildCollections } from './collections/providers.ts'
|
|
13
|
+
export { socialPostsCollection, listPosts } from './collections/posts.ts'
|
|
11
14
|
export { discoverChecks } from './core/discover-checks.ts'
|
|
12
15
|
export type { DiscoveredCheck } from './core/discover-checks.ts'
|
|
13
16
|
export { runTasks, selectTasks } from './core/task-runner.ts'
|
|
@@ -24,6 +27,11 @@ export { writeStatus } from './core/write-status.ts'
|
|
|
24
27
|
export type { StatusInput, StatusOutcome } from './core/write-status.ts'
|
|
25
28
|
export { formatLine, parseDuration, fmtAge, parseWireOutput } from './core/wire.ts'
|
|
26
29
|
export type { Line } from './core/wire.ts'
|
|
30
|
+
// the SK rule registry — stable-id invariants `shipkit check` enforces;
|
|
31
|
+
// consumers (e.g. a wiki lint verifying mentioned ids exist) read RULES
|
|
32
|
+
export { RULES, ruleById } from './rules/rule-registry.ts'
|
|
33
|
+
export { tagRuleLines } from './rules/rule.ts'
|
|
34
|
+
export type { Rule, RuleEnv, FixResult } from './rules/rule.ts'
|
|
27
35
|
export { evaluateContracts, pngDims } from './contracts/contracts-engine.ts'
|
|
28
36
|
export type { ContractsConfig, ContractsResult, ContractOutcome } from './contracts/contracts-engine.ts'
|
|
29
37
|
export { defineContract, validateManifest, file, png, glob, md, dir, custom, script, fileContains, dirExists } from './contracts/contract-types.ts'
|
|
@@ -38,9 +46,8 @@ export { defineKit, defineKind, comfy, canvas, manual, gemini, mediaOf, MEDIA_EX
|
|
|
38
46
|
export type { KitConfig, ResolvedKit, Kind, KindMeta, Media, AssetSpec, GeneratorSpec, ComfyGen, CanvasGen, ManualGen, GeminiGen, PanelSpec, GeometryPanelSpec, GridPanelSpec, CustomPanelSpec, FoundAsset, ShipContext, ShipFn } from './assets/kit-config.ts'
|
|
39
47
|
export { identityKinds, renderIdentity, renderIdentityFromMark, NO_PICK_FIX, FAVICON_SIZE, OG_W, OG_H, LOGO_SIZE } from './assets/kits/identity-kit.ts'
|
|
40
48
|
export type { Brand } from './assets/kits/identity-kit.ts'
|
|
41
|
-
export {
|
|
42
|
-
export type {
|
|
43
|
-
export { page as studioPage } from './assets/server/studio-page.ts'
|
|
49
|
+
export { studioRoutes } from './assets/server/studio-server.ts'
|
|
50
|
+
export type { StudioDeps, StudioRoutes } from './assets/server/studio-server.ts'
|
|
44
51
|
export { StudioQueue } from './assets/server/studio-queue.ts'
|
|
45
52
|
export type { Job, QueueState } from './assets/server/studio-queue.ts'
|
|
46
53
|
export { cleanRect, liveRect, defaultLegs, migrateCircle } from './assets/panels/geometry-rects.ts'
|