rvlib-shipkit 0.30.0 → 0.31.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 +5 -0
- package/package.json +1 -1
- package/src/collections/graphs.ts +17 -19
- package/src/serve/run-serve.ts +53 -16
- package/src/serve/serve-hub.ts +13 -15
- package/src/shipkit.ts +3 -2
- package/src/wiki/graph-mount.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
- **Every repo browses its shared wiki from its own hub.** A repo whose CLAUDE.md include chain points at a wiki living in another repo (`@~/dev/<other-repo>/wiki/index.md`, read from `CLAUDE.md`, `CLAUDE.local.md` or `CLAUDE.private.md`) mounts it at `/graph/base`, named after the repo that owns it. That wiki binds every agent working here, so the hub lets a human read it where the work happens. An index inside the repo is skipped, since it is already its own mount, and the card carries no lint verdict: the owning repo lints its own pages.
|
|
6
|
+
- **The graphs section stops advertising a graph the server does not serve.** A repo without a `wiki/` dir, which every fresh `shipkit start` is, still got a `wiki graph` card, and opening it embedded the hub's own 404 body ("not found — the hub page is at /") inside the card. Routes, `/hub.json` tools and cards now read ONE `GraphMount[]` that `resolveHubDeps` resolves (`HubDeps.wikiGraph` and `HubDeps.agentGraph` are gone, `HubDeps.graphs` replaces both), instead of the collection re-deriving the wiki graph from the repo root. A graph that is not mounted has no card, and the test asserts every href the section advertises answers 200.
|
|
7
|
+
|
|
3
8
|
## 0.30.0
|
|
4
9
|
|
|
5
10
|
- **The studio asset page is tools on one shared canvas, not tabs on a drawer.** Every metadata family is a toggleable TOOL: `oval`, `legs`, `size` (the geometry editor split into its parts), each repo `customPanel`, `asset json`, `roll meta`. Several can be on at once, the URL records the set (`#<assetId>?tools=a,b`, shareable, restored on load), and the last set per kind is remembered. Kit-declared TABS (`meta.tabs: [{ id, tools }]`) are one-click presets, validated at kit load (an undeclared panel OR an unknown tool id refuses, loud). Panel tools float as draggable cards ON the canvas (a drag clamps inside the stage), `customPanel({ anchor: 'bl' })` starts a card bottom-left, so switching a tab never reflows the stage, and the drawer, its tab row and the bottom json body are deleted. Toggling a custom editor on does fold the variants bar, the one deliberate reflow. Custom panels can draw on the art in place: `api.stageOverlay()` returns an element covering the displayed roll (the tint-mask painter paints THERE, controls stay in its floating card), `api.dropOverlays()` cleans up on unmount, and the host calls it. Variants (rolls) move to a thin resizable right bar (drag handle, width remembered, slim-rail collapse, auto-folds when a custom editor turns on). The prompt keeps its fold. The stage remeasures through a ResizeObserver, so overlays track the image through any layout change, not only window resizes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"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",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1,34 +1,32 @@
|
|
|
1
|
-
// graphs collection — the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
import { join } from 'path'
|
|
1
|
+
// graphs collection — the MOUNTED md graphs as CHECKED section entries. The
|
|
2
|
+
// mounts arrive as data from the hub's own route resolution (serve/run-serve),
|
|
3
|
+
// never re-derived here: a card linking at an unmounted route renders the 404
|
|
4
|
+
// body inside its iframe. Each instance carries its lint state (orphans +
|
|
5
|
+
// dangling refs = yellow with counts; clean = quiet). A graph whose lint
|
|
6
|
+
// belongs to another repo is link-only, never double-judged here.
|
|
8
7
|
import { lintMd } from '../wiki/lint-md.ts'
|
|
8
|
+
import type { GraphMount } from '../wiki/graph-mount.ts'
|
|
9
9
|
import type { Collection, Instance } from './collection-types.ts'
|
|
10
10
|
|
|
11
|
-
export function graphsCollection(
|
|
11
|
+
export function graphsCollection(mounts: GraphMount[]): Collection {
|
|
12
12
|
return {
|
|
13
13
|
id: 'graphs',
|
|
14
14
|
title: 'graphs',
|
|
15
15
|
media: 'data',
|
|
16
|
-
list: async () =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
16
|
+
list: async () =>
|
|
17
|
+
mounts.map((m): Instance => {
|
|
18
|
+
const id = `graph:${m.id}`
|
|
19
|
+
const href = `/graph/${m.id}`
|
|
20
|
+
const label = m.note != null ? `${m.label} (${m.note})` : m.label
|
|
21
|
+
if (m.lint == null) return { id, text: label, href }
|
|
22
|
+
const r = lintMd({ root: m.lint.root, index: m.lint.index })
|
|
20
23
|
const issues = r.orphans.length + r.dangling.length
|
|
21
24
|
return {
|
|
22
25
|
id,
|
|
23
|
-
text: issues === 0 ?
|
|
26
|
+
text: issues === 0 ? label : `${label} — ${r.orphans.length} orphan(s), ${r.dangling.length} dangling ref(s)`,
|
|
24
27
|
severity: issues === 0 ? undefined : 'yellow',
|
|
25
28
|
href,
|
|
26
29
|
}
|
|
27
|
-
}
|
|
28
|
-
if (o.customWiki) out.push({ id: 'graph:wiki', text: 'wiki graph (repo-owned lint)', href: '/graph/wiki' })
|
|
29
|
-
else out.push(checked('graph:wiki', 'wiki graph', '/graph/wiki', root))
|
|
30
|
-
if (existsSync(join(root, 'agent'))) out.push(checked('graph:agent', 'agent graph', '/graph/agent', join(root, 'agent'), o.agentIndex))
|
|
31
|
-
return out
|
|
32
|
-
},
|
|
30
|
+
}),
|
|
33
31
|
}
|
|
34
32
|
}
|
package/src/serve/run-serve.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// shipkit serve — resolve the repo's toolbox and run the hub. What mounts
|
|
2
2
|
// comes from convention + repo.config's `serve` block: the wiki graph is
|
|
3
3
|
// always on (custom provider via serve.wikiGraph, else the generic md
|
|
4
|
-
// scan),
|
|
4
|
+
// scan), the base-context wiki when a CLAUDE.md include names one, agent/
|
|
5
|
+
// graph when the dir exists, the studio when .rv-assets/
|
|
5
6
|
// kit.ts loads. Port: --port > serve.port > a deterministic house-range
|
|
6
7
|
// default (4800-4979, hashed from the repo name — stable across launches;
|
|
7
8
|
// pin serve.port when two repos collide).
|
|
8
9
|
import { existsSync, readFileSync } from 'fs'
|
|
9
|
-
import {
|
|
10
|
+
import { homedir } from 'os'
|
|
11
|
+
import { basename, dirname, join, resolve, sep } from 'path'
|
|
10
12
|
import { fileURLToPath } from 'url'
|
|
11
13
|
import { loadKit, type ResolvedKit } from '../assets/kit-config.ts'
|
|
12
14
|
import { studioRoutes, type StudioRoutes } from '../assets/server/studio-server.ts'
|
|
@@ -15,6 +17,7 @@ import { graphsCollection } from '../collections/graphs.ts'
|
|
|
15
17
|
import { pingTick } from '../collections/pings.ts'
|
|
16
18
|
import { buildCollections } from '../collections/providers.ts'
|
|
17
19
|
import { loadRepoConfig, type RepoConfig } from '../repo-config.ts'
|
|
20
|
+
import type { GraphMount } from '../wiki/graph-mount.ts'
|
|
18
21
|
import { scanMdGraph } from '../wiki/scan-md.ts'
|
|
19
22
|
import { serveHub, type HubDeps } from './serve-hub.ts'
|
|
20
23
|
|
|
@@ -26,19 +29,54 @@ export function defaultHubPort(name: string): number {
|
|
|
26
29
|
return 4800 + (h % 180)
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
// the BASE-CONTEXT wiki: the shared wiki this repo's CLAUDE.md include chain
|
|
33
|
+
// points at (`@~/dev/<other-repo>/wiki/index.md`). It lives in ANOTHER repo and
|
|
34
|
+
// binds every agent working here, so the hub makes it browsable. Never linted
|
|
35
|
+
// from here. The local-include forms (`@./agent/index.md`,
|
|
36
|
+
// `@./CLAUDE.local.md`) and any index inside this repo are skipped: those are
|
|
37
|
+
// already their own mounts.
|
|
38
|
+
export function baseWikiRoot(root: string): string | null {
|
|
39
|
+
const abs = resolve(root)
|
|
40
|
+
for (const f of ['CLAUDE.md', 'CLAUDE.local.md', 'CLAUDE.private.md']) {
|
|
41
|
+
const p = join(abs, f)
|
|
42
|
+
if (!existsSync(p)) continue
|
|
43
|
+
for (const line of readFileSync(p, 'utf8').split('\n')) {
|
|
44
|
+
const m = line.trim().match(/^@(\S+\.md)$/)
|
|
45
|
+
if (m == null) continue
|
|
46
|
+
const raw = m[1]!
|
|
47
|
+
const target = resolve(abs, raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : raw)
|
|
48
|
+
const dir = dirname(target)
|
|
49
|
+
if (!existsSync(target)) continue
|
|
50
|
+
if (dir === abs || dir.startsWith(abs + sep)) continue
|
|
51
|
+
return dir
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// `~/dev/<repo>/wiki` reads as "<repo> wiki" — a dir literally named wiki says
|
|
58
|
+
// nothing on its own, so the owning repo names the card
|
|
59
|
+
const wikiLabel = (dir: string): string => (basename(dir) === 'wiki' ? `${basename(dirname(dir))} wiki` : basename(dir))
|
|
60
|
+
|
|
29
61
|
export async function resolveHubDeps(root: string, c: RepoConfig, io: ServeIo, o?: { port?: number }): Promise<HubDeps> {
|
|
30
|
-
// the
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
// ONE list: the routes, the /hub.json tools and the graphs section all
|
|
63
|
+
// read it, so nothing can advertise a graph the server does not serve.
|
|
64
|
+
// The repo's OWN wiki mounts when it has one (a game repo's hub
|
|
65
|
+
// advertising an empty wiki route was startup noise), a config-declared
|
|
66
|
+
// provider always mounts, and the base-context wiki (the shared one named
|
|
67
|
+
// by the CLAUDE.md include chain) mounts wherever it is reachable: an
|
|
68
|
+
// agent working here obeys that wiki, so a reader can open it.
|
|
69
|
+
const graphs: GraphMount[] = []
|
|
70
|
+
if (c.serve?.wikiGraph != null)
|
|
71
|
+
graphs.push({ id: 'wiki', label: 'wiki graph', title: c.serve.wikiGraph.title ?? `${c.name} wiki`, load: c.serve.wikiGraph.load, lint: null, note: 'repo-owned lint' })
|
|
72
|
+
else if (existsSync(join(root, 'wiki')))
|
|
73
|
+
graphs.push({ id: 'wiki', label: 'wiki graph', title: `${c.name} wiki`, load: () => scanMdGraph({ root }), lint: { root } })
|
|
74
|
+
const baseWiki = baseWikiRoot(root)
|
|
75
|
+
if (baseWiki != null)
|
|
76
|
+
graphs.push({ id: 'base', label: wikiLabel(baseWiki), title: wikiLabel(baseWiki), load: () => scanMdGraph({ root: baseWiki }), lint: null })
|
|
38
77
|
const agentRoot = join(root, 'agent')
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
: null
|
|
78
|
+
if (existsSync(agentRoot))
|
|
79
|
+
graphs.push({ id: 'agent', label: 'agent graph', title: `${c.name} agent/`, load: () => scanMdGraph({ root: agentRoot, index: c.serve?.agentIndex }), lint: { root: agentRoot, index: c.serve?.agentIndex } })
|
|
42
80
|
// ONE page for every repo: the studio ALWAYS
|
|
43
81
|
// mounts — kit-less repos get an empty media section, never a second
|
|
44
82
|
// page. A broken kit degrades to the same empty kit, loudly.
|
|
@@ -60,15 +98,14 @@ export async function resolveHubDeps(root: string, c: RepoConfig, io: ServeIo, o
|
|
|
60
98
|
const cli = fileURLToPath(new URL('../cli.ts', import.meta.url))
|
|
61
99
|
const allCollections = () => [
|
|
62
100
|
...buildCollections(root, c, kit, { bootVersion: BOOT_VERSION, libVersion }),
|
|
63
|
-
graphsCollection(
|
|
101
|
+
graphsCollection(graphs),
|
|
64
102
|
]
|
|
65
103
|
const port = o?.port ?? c.serve?.port ?? defaultHubPort(c.name)
|
|
66
104
|
return {
|
|
67
105
|
root,
|
|
68
106
|
name: c.name,
|
|
69
107
|
port,
|
|
70
|
-
|
|
71
|
-
agentGraph,
|
|
108
|
+
graphs,
|
|
72
109
|
studio,
|
|
73
110
|
checkCmd: [process.execPath, cli, 'check'],
|
|
74
111
|
needs: () => projectNeeds(allCollections()),
|
package/src/serve/serve-hub.ts
CHANGED
|
@@ -6,23 +6,21 @@
|
|
|
6
6
|
import { existsSync } from 'fs'
|
|
7
7
|
import { join } from 'path'
|
|
8
8
|
import { graphHtml } from 'rvlib-shipkit/src/wiki/graph-page.ts'
|
|
9
|
-
import type { MdGraph } from 'rvlib-shipkit/src/wiki/md-graph.ts'
|
|
10
9
|
import type { StudioRoutes } from '../assets/server/studio-server.ts'
|
|
11
10
|
import { appendAskRemark, appendAssetNote, appendPostFeedback, askDone, askProof, markPosted, pickPostVariant } from '../collections/actions.ts'
|
|
12
11
|
import type { Collection, Instance, NeedItem } from '../collections/collection-types.ts'
|
|
12
|
+
import type { GraphMount } from '../wiki/graph-mount.ts'
|
|
13
13
|
import hubIndex from './hub-app/index.html'
|
|
14
14
|
|
|
15
|
-
export type HubGraph = { title: string; load: () => MdGraph }
|
|
16
15
|
export type HubTool = { path: string; label: string; desc: string }
|
|
17
16
|
export type HubDeps = {
|
|
18
17
|
root: string
|
|
19
18
|
name: string
|
|
20
19
|
/** 0 = random (tests); real launches pass repo.config/default */
|
|
21
20
|
port: number
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
agentGraph: HubGraph | null
|
|
21
|
+
/** every md graph this hub serves, in card order: /graph/<id>. run-serve
|
|
22
|
+
* resolves the list, the routes AND the graphs section read it */
|
|
23
|
+
graphs: GraphMount[]
|
|
26
24
|
/** the media API (mounted under /media/*); null only if the route
|
|
27
25
|
* table failed to build — the page shows the WARN, never a second page */
|
|
28
26
|
studio: StudioRoutes | null
|
|
@@ -43,11 +41,11 @@ export type Hub = { server: ReturnType<typeof Bun.serve>; tools: HubTool[]; stop
|
|
|
43
41
|
|
|
44
42
|
// the toolbox as data — stdout ok: lines and /hub.json render THIS list,
|
|
45
43
|
// so they can never disagree
|
|
46
|
-
export function hubTools(d: Pick<HubDeps, '
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
export function hubTools(d: Pick<HubDeps, 'graphs'>): HubTool[] {
|
|
45
|
+
return [
|
|
46
|
+
{ path: '/', label: 'hub', desc: 'THE page — media · asks · creds · posts · health · graphs' },
|
|
47
|
+
...d.graphs.map((g) => ({ path: `/graph/${g.id}`, label: g.label, desc: g.title })),
|
|
48
|
+
]
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
// drill-down pages (graph canvases) get one fixed home link back to THE page
|
|
@@ -125,10 +123,10 @@ export function serveHub(d: HubDeps): Hub {
|
|
|
125
123
|
return new Response(proc.stdout, { headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' } })
|
|
126
124
|
}
|
|
127
125
|
const page = (s: string): Response => new Response(withHome(s), { headers: { 'content-type': 'text/html; charset=utf-8' } })
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
for (const g of d.graphs) {
|
|
127
|
+
if (path === `/graph/${g.id}`) return page(graphHtml(g.load(), { title: g.title }))
|
|
128
|
+
if (path === `/graph/${g.id}/graph.json`) return Response.json(g.load())
|
|
129
|
+
}
|
|
132
130
|
// the media API namespace — the studio route table, ONE prefix strip
|
|
133
131
|
if (d.studio != null && path.startsWith('/media/')) return d.studio.handle(req, path.slice('/media'.length))
|
|
134
132
|
// stale bookmarks (/assets/, /status, …) land here — name the way home
|
package/src/shipkit.ts
CHANGED
|
@@ -5,8 +5,9 @@ export type { RepoConfig, ServeConfig, FloorResolution, FloorSource } from './re
|
|
|
5
5
|
export { contractsConfigFrom, activeContracts, applyContractsResult } from './contracts/apply-contracts.ts'
|
|
6
6
|
// the hub (`shipkit serve`): one server, one port, every tool a click away
|
|
7
7
|
export { serveHub, hubTools } from './serve/serve-hub.ts'
|
|
8
|
-
export type { Hub, HubDeps,
|
|
9
|
-
export {
|
|
8
|
+
export type { Hub, HubDeps, HubTool } from './serve/serve-hub.ts'
|
|
9
|
+
export type { GraphMount } from './wiki/graph-mount.ts'
|
|
10
|
+
export { runServe, resolveHubDeps, defaultHubPort, baseWikiRoot } from './serve/run-serve.ts'
|
|
10
11
|
export { projectNeeds, redCount } from './collections/collection-types.ts'
|
|
11
12
|
export type { Collection, Instance, NeedItem, NeedSeverity, CurationVerb } from './collections/collection-types.ts'
|
|
12
13
|
export { buildCollections } from './collections/providers.ts'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// one MOUNTED md graph, as the hub serves it and as the graphs section shows
|
|
2
|
+
// it. ONE list per repo (serve/run-serve builds it): the route, the card and
|
|
3
|
+
// the lint verdict all read the same entries, so a card can never link at a
|
|
4
|
+
// route that is not served.
|
|
5
|
+
import type { MdGraph } from 'rvlib-shipkit/src/wiki/md-graph.ts'
|
|
6
|
+
|
|
7
|
+
export type GraphMount = {
|
|
8
|
+
/** route + card id: /graph/<id>, instance graph:<id> */
|
|
9
|
+
id: string
|
|
10
|
+
/** card text and tool label, e.g. `wiki graph`, `<repo> wiki` */
|
|
11
|
+
label: string
|
|
12
|
+
/** graph page title */
|
|
13
|
+
title: string
|
|
14
|
+
load: () => MdGraph
|
|
15
|
+
/** null = someone else owns this graph's lint (a repo-owned provider, or a
|
|
16
|
+
* wiki living in another repo): the card links, it never judges */
|
|
17
|
+
lint: { root: string; index?: string } | null
|
|
18
|
+
/** parenthesized on the CARD only, never in the terminal banner: why this
|
|
19
|
+
* graph carries no verdict here */
|
|
20
|
+
note?: string
|
|
21
|
+
}
|