rvlib-shipkit 1.1.1 → 1.3.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 +130 -0
- package/README.md +1 -1
- package/package.json +3 -1
- package/src/assets/run-assets.ts +3 -8
- package/src/check.ts +40 -2
- package/src/checks/SK001-package-json-shape.check.ts +14 -3
- package/src/checks/SK002-contracts-fulfillness.check.ts +3 -3
- package/src/checks/SK004-gitignore-policy.check.ts +11 -3
- package/src/checks/SK005-rvlib-versions.check.ts +13 -1
- package/src/checks/SK006-npm-publish-safety.check.ts +5 -0
- package/src/checks/SK007-github-visibility.check.ts +9 -9
- package/src/checks/SK008-reviewer-agent.check.ts +14 -8
- package/src/checks/SK009-commit-ai-tells.check.ts +29 -5
- package/src/checks/SK013-one-way-in-policy.check.ts +10 -2
- package/src/checks/SK015-nested-repo.check.ts +5 -1
- package/src/checks/SK017-vscode-hub-task.check.ts +7 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +47 -0
- package/src/checks/SK024-identity-colors.check.ts +54 -0
- package/src/checks/SK025-release-drift.check.ts +58 -0
- package/src/checks/SK026-asks-autoclose.check.ts +44 -0
- package/src/checks/SK027-agent-wiki-links.check.ts +28 -0
- package/src/checks/SK028-readme.check.ts +79 -0
- package/src/cli/cmd_Assets.ts +1 -1
- package/src/cli/cmd_Check.ts +26 -1
- package/src/cli/cmd_Ci.ts +23 -5
- package/src/cli/cmd_CrossRepoActivity.ts +116 -0
- package/src/cli/cmd_GlobalProcesses.ts +155 -0
- package/src/cli/cmd_Home.ts +170 -0
- package/src/cli/cmd_Keys.ts +17 -3
- package/src/cli/cmd_Overview.ts +98 -0
- package/src/cli/cmd_Planned.ts +117 -0
- package/src/cli.ts +30 -2
- package/src/collections/checks.ts +3 -2
- package/src/collections/providers.ts +1 -1
- package/src/collections/test-time.ts +10 -1
- package/src/comfy/comfy-panel.ts +1 -16
- package/src/core/agent-spawn.ts +46 -0
- package/src/core/archived.ts +82 -0
- package/src/core/attach.ts +14 -9
- package/src/core/canonical-scripts.ts +12 -3
- package/src/core/check-file-names.ts +8 -0
- package/src/core/check-runner.ts +80 -30
- package/src/core/discover-checks.ts +38 -18
- package/src/core/file-stamp.ts +20 -0
- package/src/{checks/SK021-github-login.check.ts → core/gh-login.ts} +45 -54
- package/src/core/identity-color.ts +203 -0
- package/src/core/identity.ts +91 -0
- package/src/core/job-follow.ts +102 -0
- package/src/core/job-run.ts +27 -0
- package/src/core/jobs.ts +87 -27
- package/src/core/legacy-shapes.ts +1 -14
- package/src/core/machine-db.ts +83 -0
- package/src/core/one-lib.ts +85 -0
- package/src/core/port-holders.ts +43 -0
- package/src/core/repo-facts.ts +14 -0
- package/src/core/retention.ts +222 -0
- package/src/core/retired-checks.ts +7 -0
- package/src/core/run-check.ts +12 -4
- package/src/core/run-ci.ts +46 -12
- package/src/core/run-fix.ts +7 -2
- package/src/core/run-fixers.ts +10 -6
- package/src/core/slots.ts +5 -0
- package/src/core/statement-end.ts +17 -0
- package/src/core/wire.ts +11 -0
- package/src/fleet/scan-fleet.ts +2 -1
- package/src/home/activity-shape.ts +135 -0
- package/src/home/activity.ts +249 -0
- package/src/home/attach.ts +95 -0
- package/src/home/autostart.ts +108 -0
- package/src/home/global-units.ts +87 -0
- package/src/home/gui-app/ShipkitGui.swift +159 -0
- package/src/home/gui-app.ts +99 -0
- package/src/home/handoff.ts +51 -0
- package/src/home/home-launchd.ts +104 -0
- package/src/home/home-paths.ts +88 -0
- package/src/home/home-routes.ts +235 -0
- package/src/home/home-start.ts +105 -0
- package/src/home/home-state.ts +43 -0
- package/src/home/home-words.ts +12 -0
- package/src/home/hubs.ts +286 -0
- package/src/home/lib-watch.ts +139 -0
- package/src/home/machine-stores.ts +115 -0
- package/src/home/open-app.ts +16 -0
- package/src/home/pm-config.ts +37 -0
- package/src/home/pm.ts +174 -0
- package/src/home/process-rows.ts +205 -0
- package/src/home/resources.ts +365 -0
- package/src/home/run-daemon.ts +137 -0
- package/src/home/run-pm.ts +150 -0
- package/src/home/run-unit-action.ts +25 -0
- package/src/home/run-web.ts +37 -0
- package/src/home/serve-daemon.ts +31 -0
- package/src/home/serve-web.ts +175 -0
- package/src/home/supervise.ts +361 -0
- package/src/home/via-hub.ts +142 -0
- package/src/home/widget-text.ts +119 -0
- package/src/home/widgets.ts +102 -0
- package/src/hooks/run-hooks.ts +20 -3
- package/src/ledger/check-stats.ts +19 -45
- package/src/ledger/db.ts +69 -17
- package/src/ledger/gate-checks.ts +22 -3
- package/src/ledger/git-scan.ts +31 -7
- package/src/ledger/house-runs.ts +43 -0
- package/src/ledger/ledger.ts +40 -14
- package/src/ledger/live-timeline.ts +4 -19
- package/src/ledger/run-log.ts +92 -28
- package/src/ledger/store.ts +245 -103
- package/src/ledger/world-cache.ts +64 -15
- package/src/plan/cadence.ts +253 -0
- package/src/plan/catch-up.ts +37 -0
- package/src/plan/plan-timeline.ts +99 -0
- package/src/plan/planner.ts +428 -0
- package/src/plan/repo-source.ts +212 -0
- package/src/problems/problem-line.ts +1 -1
- package/src/release/run-release.ts +9 -2
- package/src/release/unit-resolve.ts +8 -12
- package/src/repo-config.ts +61 -4
- package/src/serve/house-runs.ts +53 -0
- package/src/serve/hub-app/activity-st.ts +168 -0
- package/src/serve/hub-app/activity-view.tsx +218 -0
- package/src/serve/hub-app/activity.css +78 -0
- package/src/serve/hub-app/admin-svc.ts +41 -0
- package/src/serve/hub-app/admin-view.tsx +42 -0
- package/src/serve/hub-app/chart-view.ts +12 -5
- package/src/serve/hub-app/check-view.tsx +9 -9
- package/src/serve/hub-app/check-words.ts +3 -1
- package/src/serve/hub-app/checks-svc.ts +12 -8
- package/src/serve/hub-app/col-card.tsx +4 -4
- package/src/serve/hub-app/cols-svc.ts +92 -13
- package/src/serve/hub-app/comfy-svc.ts +12 -6
- package/src/serve/hub-app/comfy-view.tsx +3 -3
- package/src/serve/hub-app/controls.css +32 -0
- package/src/serve/hub-app/cycle-button.tsx +36 -0
- package/src/serve/hub-app/dock-anim.ts +25 -0
- package/src/serve/hub-app/dock-st.ts +329 -0
- package/src/serve/hub-app/dock-view.tsx +68 -0
- package/src/serve/hub-app/dock.css +35 -0
- package/src/serve/hub-app/floating.tsx +49 -0
- package/src/serve/hub-app/geometry-view.tsx +4 -4
- package/src/serve/hub-app/hash-route.ts +8 -2
- package/src/serve/hub-app/header-bar.tsx +102 -95
- package/src/serve/hub-app/health-svc.ts +3 -3
- package/src/serve/hub-app/help.tsx +10 -7
- package/src/serve/hub-app/home-st.ts +278 -0
- package/src/serve/hub-app/home-strip.css +113 -0
- package/src/serve/hub-app/home-strip.tsx +376 -0
- package/src/serve/hub-app/home.html +21 -0
- package/src/serve/hub-app/hub-app.tsx +221 -177
- package/src/serve/hub-app/hub-types.ts +0 -3
- package/src/serve/hub-app/hub.css +112 -52
- package/src/serve/hub-app/icons.tsx +34 -0
- package/src/serve/hub-app/index.html +7 -1
- package/src/serve/hub-app/keys.ts +60 -0
- package/src/serve/hub-app/layout-st.ts +55 -0
- package/src/serve/hub-app/live-svc.ts +35 -72
- package/src/serve/hub-app/machine-st.ts +333 -0
- package/src/serve/hub-app/md.ts +38 -5
- package/src/serve/hub-app/media-svc.ts +63 -44
- package/src/serve/hub-app/media-view.tsx +20 -20
- package/src/serve/hub-app/next-run.ts +24 -30
- package/src/serve/hub-app/palette-st.ts +92 -0
- package/src/serve/hub-app/palette-view.tsx +47 -0
- package/src/serve/hub-app/pill-row.tsx +65 -0
- package/src/serve/hub-app/planned-st.ts +107 -0
- package/src/serve/hub-app/planned-view.tsx +114 -0
- package/src/serve/hub-app/planned.css +42 -0
- package/src/serve/hub-app/processes-svc.ts +63 -0
- package/src/serve/hub-app/project-list.css +119 -0
- package/src/serve/hub-app/project-list.tsx +266 -0
- package/src/serve/hub-app/projects-svc.ts +253 -23
- package/src/serve/hub-app/prose-hits-st.ts +3 -3
- package/src/serve/hub-app/prose-hits-view.tsx +6 -6
- package/src/serve/hub-app/readme-svc.ts +38 -0
- package/src/serve/hub-app/readme-view.tsx +19 -0
- package/src/serve/hub-app/readme.css +11 -0
- package/src/serve/hub-app/repo-sections.tsx +61 -0
- package/src/serve/hub-app/repo-st.ts +340 -0
- package/src/serve/hub-app/runs-tabs.ts +1 -1
- package/src/serve/hub-app/screen-cache.ts +74 -0
- package/src/serve/hub-app/sidebar.tsx +84 -0
- package/src/serve/hub-app/test-view.tsx +4 -4
- package/src/serve/hub-app/tests-svc.ts +3 -3
- package/src/serve/hub-app/tests-view.tsx +41 -14
- package/src/serve/hub-app/timeline-chart.tsx +55 -39
- package/src/serve/hub-app/timeline-geometry.ts +40 -5
- package/src/serve/hub-app/timeline-svc.ts +7 -6
- package/src/serve/hub-app/timeline-view.tsx +33 -16
- package/src/serve/hub-app/tree-row.tsx +2 -2
- package/src/serve/hub-app/tree-svc.ts +2 -2
- package/src/serve/hub-app/wikis-view.tsx +10 -6
- package/src/serve/hub-runtime.ts +550 -0
- package/src/serve/other-repos.ts +8 -45
- package/src/serve/project-gutter-route.ts +121 -20
- package/src/serve/project-gutter.ts +33 -11
- package/src/serve/readme-route.ts +57 -0
- package/src/serve/repo-processes.ts +89 -0
- package/src/serve/run-serve.ts +110 -402
- package/src/serve/serve-hub.ts +155 -52
- package/src/serve/stale-restart.ts +5 -0
- package/src/serve/{gate-trigger.ts → tracked-watch.ts} +37 -65
- package/src/serve/warm-cell.ts +95 -0
- package/src/shipkit-files.ts +130 -0
- package/src/shipkit.ts +2 -1
- package/src/suite/kept-timings.ts +1 -1
- package/src/wiki/graph-positions.ts +1 -1
- package/src/checks/contracts.check.ts +0 -20
- package/src/serve/health-schedule.ts +0 -102
- package/src/serve/hub-app/hub-st.ts +0 -250
- package/src/serve/hub-app/project-gutter.css +0 -65
- package/src/serve/hub-app/project-gutter.tsx +0 -128
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
+
## 1.3.0
|
|
2
|
+
|
|
3
|
+
- **`shipkit ci` prints what the reader acts on, and nothing else.** A green run is one line per check that ran and one verdict line: `ok: test 920 tests (1.4s)` then `ok: 1 check green`, where it used to print nine. A test check carries its count on its own line. The whole gate folds its tier and cache counts into the verdict (`ok: 31 checks green; quick tier; 25 cached, 5 ran, 1 never cached`). A fix pass prints only when it rewrote files, and names them. A green check prints one line, a warned one only its warnings and their `fix:` pointers, a failure prints whole. `--only` says nothing about checks outside its selection (floor gaps, `off` checks, parked checks). `--verbose` restores every line the default leaves out: the clean fix pass, the machine pool, the tier, the cache declarations, the parked checks, the old summary and the cache counts. STATUS.md keeps them all, a release or bump gate still prints them all, and `--json` is unchanged.
|
|
4
|
+
- **Compact monitor cards.** A widget's leading `<header>` joins its card's title line, dim (`splitWidgetHead`), and `shipkit overview` prints it on the title line the same way. A card is as wide as its content, the owner repo moves to the hover, the `.wbar` meter is 56px, and `<table class="wmeters">` lays meters out two to a row, label, bar and value.
|
|
5
|
+
- **Fix: the planned page lists only what is actually planned.** A check waiting for its repo to be active is no longer listed as late: the page folds all of them into one line at the end (`waiting for their repo to be active: 24 checks in 9 repos`) that opens on a click, and `shipkit planned` prints the same line, listing them with `--all`. A check can declare `planOff: (env) => reason | null`, and the plan leaves it out of a repo where it has nothing to do: SK026 without an ask that carries a probe, SK025 without a unit that releases, SK005 without an rvlib dependency. A decided fact (`ttl: 'forever'`) with no answer under its key adopts, once, the newest clean answer stored under its older timed key for the same declared value, so SK007 stays known where it already answered.
|
|
6
|
+
- **Every section of a repo page has a key: ⌥ and its first letter.** ⌥C checks, ⌥R runs, ⌥A asks, ⌥T tests, ⌥M media, ⌥W wikis, ⌥D admin, read from the physical key so macOS's option characters and AZERTY keep them, printed as a badge at the end of each section line and on each section's entry in the ⌘K picker.
|
|
7
|
+
- **The open in VS Code button wears the VS Code mark, and it and open in Fork close the top bar at its far right end.**
|
|
8
|
+
- **The dock header: a cycling size button and small status pills.** The size is one button showing the current size, a dot per size and `⌘B`; a click or the key moves to the next (`CycleButton`, reusable). `runs, every repo`, the `(?)`, the in flight count and the size switch are gone. The status is a row of dim pills that never wraps: idle or running, queued, a warn pill when the plan holds, the last check with its repo icon, verdict, short id and age, the settling repos as one pill listing them on hover, and the window at slim and open; what does not fit folds into `+N`.
|
|
9
|
+
- **A repo page's sections are the top of its left pane, its runs are in the top bar, and the bar under the strip is gone.** Checks, runs, asks, tests, media, wikis and admin are one line each over the tree, in the project list's line style (icon, label, a count where it says something: red checks, open asks, red test files), the current one lit; the `resources` title is gone. The home strip carries the repo's run state (`idle`, or a spinner and what runs, cut, whole on hover) and two compact buttons, `health pass` and `gate`, each with a tooltip naming what it runs; at its right the repo's processes, its hub's trouble, `modernize` while behind, and two icons, open in VS Code and open in Fork. The bar never wraps: below a width the labels shrink to their icons, then the run state to its spinner. A hub of its own draws the same pieces in its own header, with its name.
|
|
10
|
+
- **An admin page per repo (`#page:admin`).** Reload the hub (the old `restart`: stops it in the daemon and loads it again, its config read fresh), keep active (the old `active` checkbox: the plan's hand set active flag) and archive, which now asks for a confirmation first, each with the sentence that says what it does. The three left the bar.
|
|
11
|
+
- **The tree's filter is quieter.** Its placeholder is `filter`, its key `/` sits in a small badge at its right the way the project list prints its keys, and the severity filter sits beside it.
|
|
12
|
+
- **The tests page leads with every test file, slowest first.** One list of every file of every test check: its check, its last duration against its budget, its status and its age, the never ran ones last, then the test checks and the failing files. The `tests` section line opens it. `/api/tests.json` carries it as `files`, merged from each check's own list.
|
|
13
|
+
- **The dock slides between its sizes.** `⌘B` and the size switch ease the boundary over 180ms instead of jumping; a drag, a window resize and the first paint stay instant, and `prefers-reduced-motion` turns the slide off.
|
|
14
|
+
- **The list's foot: a sort menu that names each sort, and an archived button beside it.** The sort button no longer sorts on a click: it names the order the list is in and opens a small menu of labeled sorts, `last activity`, `last commit`, `name` and `open asks`, the current one checked, and a pick sorts once and closes it. `manual order` is the state after a drag, never a pick. The `▸ archived (N)` fold became a small button with the archived count beside the sort button: a click shows the archived repos just above the foot, which never moves, and a second click hides them, kept per browser. `/api/projects.json` rows carry `commit`, the last commit time, for the new sort.
|
|
15
|
+
- **Fix: a daemon restart in place no longer loses a save burst, resets the catch up window, or retries a decided fact.** When a repo joins the plan, a file the owner wrote after its last run on record counts as a save, so the burst settles and runs. The catch up window's start lives in the machine db, and a boot within 10 minutes of the plan's last beat keeps it. A decided fact whose probe skipped retries 1 hour after that skip, doubled per skip in a row up to 24 hours, from the run record. What holds the plan (`held: load 9.2 on 8 cores`, `settling: my-app, 42s`) shows on the planned page, first in `shipkit planned`, in its `--json` as `held`, and at the end of the dock's thin line.
|
|
16
|
+
- **The dock never shows nothing, and the regions read apart.** `⌘B` cycles a one row line (`2 running · 5 queued · last: my-app typecheck ✓ 12s ago`, or `idle`), the slim chart and the open panel; a browser that kept the old hidden size gets the line. The project list, the top bars and the dock each wear a surface token a small step from the page (`--bg-side`, `--bg-top`, `--bg-dock`).
|
|
17
|
+
- **The dock: what shipkit runs in every repo, under the whole page, on every page.** One run chart docked at the bottom of the window, below the rail and the page, one group of lanes per house repo that ran something in the window, the repo's icon and name on the left. `⌘B` (Ctrl+B) cycles its sizes: slim, the last two minutes in thin lanes; open, the last ten with the run bands, the axis and the concurrency band against the cap, its height dragged and kept. The size is read from the browser before the first paint, so a reload moves nothing. It reads ONE route for every repo, `GET /api/house-runs.json`, read only from each repo's record and only when that record moved, asked every 2s while the dock shows and the tab is in front.
|
|
18
|
+
- **A repo's whole run record moved to the chart over its runs page.** The live strip under the header is gone, and so are its fold key `l` and the other repos' collapsed lanes in `/api/live.json`, which the dock replaces.
|
|
19
|
+
- **The overview's processes table reads tighter.** The version has its own column (empty when unknown), the process label column is narrow and cut with the whole label on hover, and each kind of button has its own column (declared actions, take over, restart, start or stop, open), so every start sits under every start and a line without a button keeps the space. A pid copies on a click and says `copied` for a moment (a refused clipboard is said in the strip). A url is a link, opened the way the pick says. The section titles are gone: the open pick and `new window` sit at the right of the table's header row. The widget cards are as wide as what they hold, from 260px, with tighter padding.
|
|
20
|
+
- **The overview is the monitor, then one processes table, shipkit's own group first.** pm, the daemon and the web are lines of the table in the same line UI as the global processes, under a `shipkit` group: dot, label with a `core` tag and the shipkit version, port, pid, uptime from the pid's start (a pm reload no longer resets the daemon's or the web's uptime), and the log on hover. Their stop is disabled (always on), the daemon and the web restart through pm, and pm has none: it is the LaunchAgent, its tooltip says so. The separate shipkit card is gone, the monitor widgets come first, and each group's name sits at the top of its group. pm's status carries `ports` and its own log `tail`, and every child's log tail. `shipkit gp` prints the same core group first, with versions (it asks the daemon's `/hub.json` too), and `shipkit overview` prints the monitor then the processes, as the page.
|
|
21
|
+
- **The daemon or pm down is said on a line at the bottom of the page, never over it.** The line is reserved whether or not it says anything, so an alert moves nothing, and it stays empty until a probe has actually failed.
|
|
22
|
+
- **Fix: the plan no longer keeps the machine busy.** A save burst is asked about once the repo has been quiet for 60 s (every 10 minutes at most for a repo never quiet), where it was every 250 ms pause. A check a save moved waits 20 times its last measured run after that run, so a heavy suite runs at most a few times an hour. Above a load average of 1 per core the plan starts nothing on its own (`Planner.held()` says why), and a click still runs at once. A check nothing can key that has a cadence (its `every`, its `cron`, or the default range) runs on that cadence only, never on every save. Measured on the house repo before the fix: 342 background gates in 2 hours, `test:rvlib-shipkit` 188 times, `pb-playground` 716 runs in a row under a rule that read every 20h..28h.
|
|
23
|
+
- **The home's console commands read as one family.** `shipkit activity` is the activity page's command, `shipkit cross-repo-activity` stays as its long name. `shipkit overview`, `shipkit activity`, `shipkit planned` and `shipkit global-processes` (`gp`) sit together under a `Home` group in `shipkit --help` and the command page. The activity page names its command in a dim footer line, as the overview and the plan do.
|
|
24
|
+
- **`shipkit overview`: the overview page in the console.** Its three sections from the answers the page reads: pm, the daemon and the web with pid, uptime and version (pm's status, the daemon's `/hub.json`), the global processes as `shipkit gp` prints them (one `globalProcessesAnswer`), and every monitor widget as text from the html the daemon last rendered (`/api/home/widgets.json`): tags stripped, a table's cells aligned, a `.wbar` meter drawn as `██████░░░░ 62%` (`src/home/widget-text.ts`). When pm or the daemon does not answer it says which and prints what is still known (the declared processes, their state unknown), and exits 0. `--json` gives `{ now, pm, daemon, processes, widgets }`, widgets the route's exact payload. pm's status carries `since`, so its own uptime shows. The overview names the command in a dim footer line.
|
|
25
|
+
- **The global pages are four lines on top of the project list.** Overview, jump to…, activity and planned each get a line in the repo rows' style, an icon, the label and the shortcut at the line's end, then a thin divider before the repos. The page shown is lit, and the jump to line while the picker is open. At the list's narrowest the labels truncate and the shortcuts stay.
|
|
26
|
+
- **`shipkit planned`: the planned page in the console.** It asks the daemon's `/api/home/planned.json`, the route the page polls, and draws the page's one list (now, late, next) with the page's own functions, now in `src/plan/plan-timeline.ts`: per row when (`in 4m`, `late 2h`), repo, check, its mark, its last result, then its title and the rule that planned it (`every 7d`, `on change (keyed on its files)`). The checks only a save runs are counted, `--all` lists them, `--repo <name>` keeps one repo, `--json` prints the route's exact payload. With the daemon down it says so and exits 0: the plan (its marks, the running batch) lives only there, so nothing is rebuilt from disk. A plan row carries its check's `title`. The planned page names the command in a dim footer line.
|
|
27
|
+
- **A global process's uptime survives a pm reload.** While a process is up with a pid, its uptime counts from when that pid started, as the OS says (`ps -o lstart=`), not from pm's last observed state change. The start is kept per pid: a pid pm spawned needs no `ps` at all, an adopted pid costs one `ps` at adoption, and a new pid (a restart) is read again. A process without a pid keeps `since` as before.
|
|
28
|
+
- **Fix: opening the overview, the picker, the activity or the plan no longer resets the selected repo to the house repo.** The repo list keeps marking the repo you were in, dimmer while a global page is up, and on the home no row is marked before a repo is picked. On a global page the ⌘K picker lists that repo too, since it is not on screen.
|
|
29
|
+
- **The open in Fork button leads each repo's commit list on the activity page.** It left the repo name cell for a narrow column of its own right before the commits, on the same line as the first commit (or the red uncommitted line), still dim until its row is hovered.
|
|
30
|
+
- **The ⌘K picker shows icons.** Every repo entry wears its repo icon (its letter tile when it has none), as the project list shows it, and home, planned, activity and archived wear the icons of their buttons. The order is unchanged. `bun run activity:check` checks it in headless Chrome, with the activity status dot.
|
|
31
|
+
- **The activity page names what is wrong behind each status dot.** A red or orange dot is a button: hovering it floats the failing and warning checks at once, failing first, each with its id, its title, how old that outcome is and the first line it printed about the problem. A click lists the same checks under the repo's row, so they read without hover (the macOS app shows no native tooltip), and a second click hides them. Each id links to that repo's check page. The daemon's activity answer carries them as `failing` on each row, titles from the plan (`Planner.titles`).
|
|
32
|
+
- **Fix: `bun run home:check` reported screenplay's tree as taking 20s on its first visit.** It loads in about 40ms from what was read ahead. The walk waited for a title starting with the home's key (`screenplay ·`), but the title carries the name the repo's own config declares (`screenplay-shed · hub`), so the wait ran to its timeout and the timeout was then reported as a load time. The walk now matches the shown repo by its home key, and reports a load time only for a tree that showed.
|
|
33
|
+
- **Fix: `bun run home:check` read the four buttons on top of the project list as missing.** Its probe quoted `⌘'` inside a single quoted string, the page could not parse it, and the walk's wait read the parse error as "not yet" for 20s. The expected labels are now quoted by `JSON.stringify`, and a probe that does not parse now throws instead of timing out.
|
|
34
|
+
- **A private repo commits its comfy drafts through a `.comfy-ts/` block.** The `blocked-paths` pre-commit gate lets `.comfy-ts/drafts/` through when the repo declares `visibility: 'private'`; `hosts/`, `cache/`, `outputs/` and every other `.comfy-ts/` path stay blocked. A public repo and a repo with no declared visibility keep the full block, and a prefix naming the drafts folder itself still blocks it. Before, a `.comfy-ts/` block contradicted the SK004 `!.comfy-ts/drafts/` re-include, and drafts stayed uncommitted forever. `blockedStaged` is exported from `hooks/run-hooks.ts`, and `shipkit hooks list` names the exemption.
|
|
35
|
+
- **A public repo and an npm unit keep their own files.** Two declared facts, each on its owner. The root unit's `release: { npm: true }` says a package strangers install and work on: SK001 asks only that it can publish (no `"private": true`, a `license`), never routes or renames its scripts, and its fixer writes nothing; SK005 names an rvlib bump and its command and never runs it; SK006 reads it as publishing by design; SK013 and SK017 report green and never write its `.claude/settings.json` or `.vscode/tasks.json`. The repo's `visibility: 'public'` says strangers read its tree: SK004 keeps `.comfy-ts/drafts/` ignored, and SK007 and SK009 read the declared visibility only (absent is private). A public repo with no npm target, like a site, keeps SK013 and SK017. A `shipkit ci` over a public fixture whose root unit declares npm, with its own cli, leaves package.json, `.claude/settings.json` and `.vscode/tasks.json` byte for byte (`tests/public-npm-repo.test.ts`, with a private app control that is rewritten).
|
|
36
|
+
- **The root unit's npm target is declared, never inferred.** A root whose package.json is not private and whose config says neither `release: { npm: true }` nor `npm: false` no longer publishes by default: resolution refuses and names both edits, the same rule sub units follow. So the release path and the checks read one fact.
|
|
37
|
+
- **`shipkit global-processes` (short: `shipkit gp`).** The overview's global processes table in the console, from pm's own answer: per repo, one line per process with its state (`up 2h`, `crashed 3m ago`, `stopped`), label, url, pid, note and last log line. `--json` gives the rows the overview draws. When pm is down it says so, lists the declared processes with their state unknown, and exits 0.
|
|
38
|
+
- **The global processes are one table, a line per process.** The overview's cards are gone: a row group per declaring repo (its icon and name in the first column, as on the activity page), then one line per process with its state dot, icon, label (its log on hover), url, `up 2h` or `crashed 3m ago`, and start or stop and open at the right. Restart, take over and declared actions stay small, shown only when they exist. A running line is tinted green, a crashed one red, and nothing scrolls sideways at phone width. pm's resource view carries `pid` and `since` (the last status change). The rows come from `src/home/process-rows.ts`, one model for the page and the console.
|
|
39
|
+
- **Fix: a link with no class of its own was the browser's dark blue on the dark page**, like `5 archived repos` on the overview. Every such link now takes the accent colour.
|
|
40
|
+
- **Fix: a repo's own cli is never taken for shipkit.** SK001 read any `bun <path>cli.ts` script as the in repo shipkit, so in a repo with its own cli (`bun src/cli/comfy-ts-cli.ts tui`) its fixer rerouted `test`, `lint` and `typecheck` to `bun src/cli/comfy-ts-cli.ts ci --only …`, a command that cli does not have, and SK013 pointed its guard beside it. Only a file named `cli.ts` under a directory whose name says shipkit counts now (`isShipkitCli`), and the `serve` alias WARN reads the same test.
|
|
41
|
+
- **Open a repo in Fork from the activity page.** A small button beside each repo name opens it in the Fork git client (macOS). The daemon takes a house repo name only, refuses an archived one, and says so when Fork is not installed.
|
|
42
|
+
- **`shipkit cross-repo-activity`.** The activity page in the console, from the same computation: every house repo's checks, branch, upstream, uncommitted work and commits in a window (`--max-age 30m`, `--max-commit 3`), or the route's exact payload with `--json`. Local git only, no daemon needed.
|
|
43
|
+
- **Fix: the activity page and the status dot read what is true now.** An activity answer older than 30s is never served (a page opened hours later showed hours old rows), a tab coming back no longer forces a git pass, and a repo's status dot counts only the checks it declares today, so a renamed or deleted check no longer holds it red. The hover says how long each check has been red.
|
|
44
|
+
- **The activity page reads faster.** Six repos at a time, and the git asks inside a repo start together: 0.5s to 0.22s for 19 repos on an idle machine, with a larger gain while the daemon is busy.
|
|
45
|
+
- **The activity page has a status column before the branch.** One dot per repo from its own run record: red with the number of failing checks, orange with the number of warnings, green when every check passed, grey when shipkit never ran there. The check ids show on hover. The project rows carry it as `checks`.
|
|
46
|
+
- **The activity page shows only `main` green.** A repo on `master` is orange like any other branch, so a repo still to rename stands out.
|
|
47
|
+
- **New built-in SK028 readme, non-blocking.** A repo's `README.md` exists and shows at least one screenshot: a markdown or html image whose source is a file in the repo or an https url (a status badge does not count). Each requirement reports its own line, so a new mandatory README item is one more entry. Keyed on the README and the images it names, so it runs on change only.
|
|
48
|
+
- **No built-in rides the daily default range any more.** SK008, SK009, SK015, SK022 and SK024 each declare what their answer depends on and run only when it moves: SK008 on repo.config.ts, the agent definition and the brief (by content, inside the repo or not), SK009 on HEAD, every ref and the settings file (a commit, a fetch or a tag re-asks it, since the save watch now hears a ref move inside `.git`), SK015 on the config and where the work tree root sits, SK022 on package.json, the installed copy and the running version, SK024 on the config and the installed config source. The 20h..28h range is now for a REPO check that declares nothing, and `shipkit keys --burn-down` WARNs on each one (a built-in there is a FAIL line). A guard test fails the toolkit on any built-in without a key, a `ttl` or an `every`.
|
|
49
|
+
- **SK008 is titled "reviewer definition".** It lints the agent definition file and never starts an agent, so the planned page no longer reads as a scheduled review.
|
|
50
|
+
- **A sweep guard: nothing starts an agent on its own.** `agentSpawnIn` (`src/core/agent-spawn.ts`) finds a spawn or exec of `claude`, `claude -p`, or an import of the agent sdk, comments skipped. A test runs it over every source file, beside the samples it must catch and the lookalikes it must let through (a url, `~/.claude/projects`, a help line naming the product).
|
|
51
|
+
- **A repo page with no row picked shows its README, images included.** The first of `README.md`, `readme.md`, `Readme.md`, `README.markdown` or `README` at the repo root, rendered as markdown. A relative image loads through the new `GET /api/readme-file/<path>`, which serves only an image file inside the repo. An http(s) image such as a badge loads as written. A repo without a README keeps the hint.
|
|
52
|
+
- **An archived repo is processed by nothing but its own page.** `houseRepos()` now splits `archived: true` repos out, so the rail, the daemon (hub, watch, plan rows, gates, processes, widgets, identity fill, store sweep), the activity page, pm's global processes and `shipkit fleet` never see one. The project list's folded archived group is gone. `/archived` (the picker's `archived`, and a link at the foot of the overview while one exists) lists them, each with `unarchive`. `/api/projects.json` answers `archived: [{ name, icon }]` beside `projects`, and a row no longer carries `archived`.
|
|
53
|
+
- **Archive a repo: `archived: true` in repo.config.ts.** It stays known and listed (dimmed, in a folded group at the bottom of the project list, out of the ⌘ numbering), and nothing runs there: the daemon never loads its hub, so no file watch, no plan rows, no automatic gate, no repo process, no widget or global process, no identity fill, no store sweep. The `archive` button in the repo header toggles it and moves only that key (`POST /api/projects/archived`, `src/core/archived.ts`). Archiving a loaded repo stops it on the next tick; unarchiving loads it again.
|
|
54
|
+
- **Global process cards: start and open, grouped by repo, tinted by state.** A card has two buttons, `start` (`stop` while it runs) and `open`; restart, take over and declared actions stay as small controls where they apply. Cards group under their declaring repo (its icon, its identity colour) in the project list's order, are smaller, and an up card is tinted green, a crashed one red. Where `open` opens is one icon pick in the section's title row, the default browser or a shipkit app window, kept per browser. New `globalProcess({ icon })`: an emoji or a repo-relative image path, served by pm at `/api/pm/icon/<id>`; without one the card wears its repo's icon. Breaking: `app: true` is removed, since the app window opens any url; pm's `act: 'app'` works for every process with a url.
|
|
55
|
+
- **New built-ins SK026 asks auto close and SK027 agent wiki links.** Every repo with a `.shipkit/asks/` queue now archives the asks whose own `check:` probe passes (an open queue is no warning; a frozen run archives nothing). Every repo with `agent/index.md` fails an orphan page or a dangling ref there. A repo without the queue or the wiki skips.
|
|
56
|
+
- **Retired check ids are listed** (`src/core/retired-checks.ts`), and discovery refuses a built-in that takes one again.
|
|
57
|
+
- **Breaking: the `contracts` check is gone, SK002 is the one row for the release contracts.** Both ran the same scan, so every repo showed one invariant twice. A venue deploy step declares `deps: ['SK002']` instead of `deps: ['contracts']`.
|
|
58
|
+
- **New built-in SK025 release drift.** Every unit a repo releases matches npm and its push remote, the survey `shipkit releases` prints, as warnings. A repo that releases nothing (a private root with no release config and no units) skips it. A clean answer keeps 20h, keyed on every unit's version. It replaces the drift check a repo kept per published lib.
|
|
59
|
+
- **Breaking: SK021 (github login) and SK023 (stores under cap) are no longer built-ins.** Both ask about the machine, never the repo, so every repo re-asked one fact. The gh probe and its `core` credential row stay in the lib as `src/core/gh-login.ts` (`probeGhLogin`, `GH_LOGIN_CHECK`): a house repo declares the one check that runs it. The store caps keep their measure and sweep in `src/core/retention.ts` (`capWords` joins them), for a house check that measures every repo in one pass.
|
|
60
|
+
- **`shipkit assets check` no longer probes the comfy backend.** Whether a box answers is a machine fact, never a question about a repo's kit.
|
|
61
|
+
- **An activity page (`/activity`, ⌘L).** One table of every house repo: its branch (green on main, orange elsewhere) and a red line of what it holds uncommitted (the paths on hover). Beside them, its commits in a window you pick (10m to 7d, 30m by default), newest first, each with a green ✓ once pushed or an orange ↑ until then. A number sets how many commits a repo shows (1 to 15, 3 by default). It reads again every minute while shown, and the countdown is a button that reads at once. The counts beside it are pills. A repo with nothing in the window still shows its last commit, in grey, and quiet repos sit below the busy ones. The daemon reads it with local git only while the page is open and visible, never with a fetch.
|
|
62
|
+
- **New keys, and a picker button.** ⌘J the overview, ⌘K the picker, ⌘L the activity, ⌘' the plan, four buttons on top of the project list in that order. The sort by last activity moves to the foot of the list, and above it a fold lists the archived repos by name, each a link to the archived page.
|
|
63
|
+
- **The house repo is active whenever any house repo is.** Its checks serve every repo, so a `wait` check there no longer turns late while you work elsewhere.
|
|
64
|
+
- **`ttl: 'forever'` for a decided fact.** A fact that moves only when someone decides to move it (a repo's visibility, an account that exists) keeps its clean answer for good, keyed on its declared value alone (`cacheKey`), so a new shipkit asks nothing again, and the store sweep never drops it. The plan reads it as the new `every: 'once'`: it runs only while no answer exists for its current key (first time, a declaration edit, a lost db), never on a clock.
|
|
65
|
+
- **SK007 is a decided fact and no longer blocks.** Its clean answer is kept until `visibility` changes, and a mismatch warns: a gate block does not un-leak a repo.
|
|
66
|
+
- **A check keyed on its files runs on change only.** With no `every` declared, a check with `inputs`, or one keyed on what it read through `c`, no longer runs on the daily default range: its answer replays until a file moves. Only a check nothing can key keeps that range, and a `ttl` still reads as `every`.
|
|
67
|
+
|
|
68
|
+
## 1.2.0
|
|
69
|
+
|
|
70
|
+
- **Fix: SK024 read back only an `identity` key that starts a line.** On a one line config its own fix wrote the key inline, the check kept warning, and every fix pass added the key again, so a release gate always rewrote `repo.config.ts`. The key is now found at the top level of `defineRepo({…})` wherever it sits, bare or quoted, and a nested `identity` (an SK002 exemption) or one in a string or comment is not it.
|
|
71
|
+
|
|
72
|
+
- **A filled bar for widgets.** `<span class="wbar" style="--p:51%"><i></i></span>` draws a bar in the page's state colours (`mid`, `hi`); widget rows stay on one line; the overview's shipkit section is a card.
|
|
73
|
+
|
|
74
|
+
- **The planned page is one list ordered by time.** Three small labels inside it (now, late, next) and three columns per row: when (`now`, `late 2h`, `in 4m`), project · check (the project in its identity colour opens its repo, the check opens its own page), and one mark (a spinner while it runs, an orange dot and `waits: repo inactive` when late, a hollow circle when upcoming, a red ✕ `last failed`). A check that only runs on change is not listed. The rule, the reason, the last result, the inactive policy, an error and `run now` sit in a card that floats over the row on hover. One text filter matches the project or the check, and `/planned?q=` carries it. `Tip` takes `fallbacks`, the sides it tries when its placement does not fit. `bun tests/browser/planned-check.ts` measures it against a running home.
|
|
75
|
+
- **A project list replaces the icon rail.** One row per repo, an 18px icon and the name beside it, the shown repo marked in its identity colour, in a panel whose width is dragged with a separator and kept per browser (`react-resizable-panels`). A repo's resource sidebar is resizable the same way. The rail's three sizes and their button are gone.
|
|
76
|
+
- **Everything that floats is placed by `@floating-ui/react`.** The hover card, the help `(?)`, a process log on hover and the chart tooltip open after a short delay beside their trigger, flipped and shifted into the window, in a portal (`src/serve/hub-app/floating.tsx`).
|
|
77
|
+
- **Command keys.** ⌘1 to ⌘9 select the nth repo of the list (by physical key, any layout), ⌘J the overview, ⌘P the plan, ⌘K the picker. The overview and the plan are two buttons on top of the list, each with its shortcut in its corner.
|
|
78
|
+
- **Fix: a hub serving on its own showed an empty page beside its list.**
|
|
79
|
+
- **Fix: a dark band between the list and the page in the macOS app.** A panel's own scrollbar, which WebKit draws; no panel scrolls itself now.
|
|
80
|
+
|
|
81
|
+
- **The whole home page paints at once on a refresh.** Each repo's first screen is kept per browser in IndexedDB and read before the first paint, the machine is kept for the overview, and every region holds its size from the first paint, so a refresh measures a cumulative layout shift of 0.
|
|
82
|
+
- **One quiet top strip, the places in the rail and the picker.** The strip holds the shown repo (or the page's name) and machine trouble only, no link and no button. A house at the top of the rail opens the overview, and `home` and `planned` lead the `⌘K`/`⌘J` picker.
|
|
83
|
+
- **The overview: shipkit, then one card per global process, then the monitor.** `shipkit` (pm, daemon, web; was `machine`) and the processes under headers with a line icon; each process a card with its switch, state, declaring file, url and buttons. No repo list: the project list is it.
|
|
84
|
+
- **Take over a global process started elsewhere.** One click: pm stops the process group listening on the process's local port (SIGTERM, then SIGKILL after 5 s) and starts its own supervised copy. Not offered without a local url or probe port, and the row says why. `POST /api/pm/resource` takes `act: 'takeover'`.
|
|
85
|
+
- **`open` opens a global process's url in the default browser.** `open as app` is its own button, only for `app: true` (`act: 'app'`).
|
|
86
|
+
|
|
87
|
+
- **A refresh of the home moves nothing.** The rail frame renders from the first paint at the size the browser keeps, and the last rail list (`localStorage` `hub:rail-list`) paints at once while the daemon is asked again, so the page no longer jumps right when the list arrives. `bun run home:shift` measures the cumulative layout shift of a first load and a refresh in headless Chrome.
|
|
88
|
+
|
|
89
|
+
- **`<name>.shipkit.ts` unit files: checks, global processes and widgets.** A repo drops one anywhere and default-exports `check({...})`, `globalProcess({...})` or `widget({...})` (`rvlib-shipkit/src/shipkit-files.ts`). Discovery reads a `check` there beside the `.check.ts` files. A `globalProcess` is a long running process for the whole machine, supervised by pm whichever house repo declares it: pm reads the files at boot and every 15 s, and the overview shows a switch each, its log on hover, restart, one button per declared action (argv, or a function of the file run in its own bun process), and for `app: true` with a `url` an `open` that opens a shipkit app window, or `start and open` in one go on a process that is down. A `widget` returns html the daemon renders on its own clock (`everyMs`, default 5 s, bounded at 5 s) and serves at `/api/home/widgets.json`, and the overview mounts each in its monitor area. BREAKING: the `home.resources` key is removed; `defineRepo` refuses `home` and names the unit file that replaces it. pm's `/api/pm/resource` takes `act`: start, stop, restart, open or action.
|
|
90
|
+
|
|
91
|
+
- **Switching repos on the home is instant.** Each hub keeps its rows (`/api/resources.json`), its runs page (`/api/timeline.json`) and its wiki graphs warm in memory (`WarmCell`, `src/serve/warm-cell.ts`): computed when the hub loads, one hub after another, recomputed in the background after a save burst settles, when the config or the run record stamp moves, or past `WARM_MAX_AGE_MS` (2 minutes), and served from memory, so a request never walks the repo. `?fresh=1` waits on a walk that includes a change the page just made, and the page asks it after an act, a run that ended and a config write. The page reads ahead the first screen of every other repo whose hub is up, one repo and one step at a time, once the shown repo has its own, and a repo read ahead mounts nothing until it shows. The tree, the page links, the runs page, the wiki frame and the wait line show a spinner where they will be. `HubHandler` gains `invalidate()` and `warm()`, `HubDeps` gains `stamp`. Measured on the live home: a first visit went from 80 to 380ms to 38 to 80ms, the runs page from 1.6 to 3.0s to under 110ms.
|
|
92
|
+
|
|
93
|
+
- **Every repo has identity colours, and the home strip holds only what it should.** `repo.config.ts` declares `identity: { colorLight, colorDark }`, one `#rrggbb` each. New rule SK024 (`identity colors`, non-blocking) warns when they are missing or malformed; `shipkit check --fix SK024` derives them from the dominant hue of the repo icon, else from a hue hashed from the name, and writes that one key as one line, every other byte kept. The daemon fills each repo it loads the same way and never commits. A repo whose installed copy does not know the key is never written into. The home strip now holds `home` and `planned` (always there, the current one marked), the shown repo's icon and its name in its colour with two swatches that write the colours back (`POST /api/projects/identity`), and on its right only machine trouble: pm or the daemon down, a child or a resource crashed. Restart, the active flag, VS Code and modernize moved to the repo's own header, the resource switches and `new window` to the overview. The rail frames the shown repo's tile in its colour. `⌘K` opens the same picker as `⌘J`, which now lists the shown repo's targets, then `home`, `planned` and every other house repo.
|
|
94
|
+
|
|
95
|
+
- **The home page is one app over one state tree.** A rail click shows another repo in place: no page load, the rail and the strip stay mounted, and a repo you come back to shows the page, filter, search and scroll it had. `HomeSt` is the root (`window.app`) for `/`, `/planned` and `/r/<repo>/`, with one `RepoSt` per repo visited, and only the shown repo polls. Every request of a repo goes through `RepoSt.url()`. The web serves one page at all three routes (`serveWeb({ page })`), and a hub of its own renders the same app with one repo and no machine.
|
|
96
|
+
|
|
97
|
+
- **`shipkit gui` is its own macOS app.** Built once with swiftc into `~/Applications/Shipkit.app`, with the house repo's mark as its icon, so the dock and cmd-tab show shipkit instead of a second Chrome. Chrome's app mode stays the fallback.
|
|
98
|
+
|
|
99
|
+
- **A boot never fires everything at once.** Work overdue when the plan starts is spread over the next 2 hours, and a check with no cadence runs at a stable point of 20h to 28h.
|
|
100
|
+
|
|
101
|
+
- **The json answer import proves every file against its own hash.** Two check ids that slug alike can no longer share one row, and the import never overwrites a fresher row.
|
|
102
|
+
|
|
103
|
+
- **Machine state lives in one machine database.** The repos flagged active and the pid records pm, the resources and a repo's dev servers are adopted from now live in `~/.config/shipkit/shipkit.sqlite` (beside `SHIPKIT_HOME_DIR` when it is set, or at `SHIPKIT_MACHINE_DB`). The json and pid files an older version wrote are read once and deleted, so a running pm adopts its children across the update. `ProcSpec.pidFile` is renamed `pidKey`.
|
|
104
|
+
- **Nothing shipkit writes grows unbound.** Every store has a declared cap: the answers and the run record in the repo db, the attach slots, the run logs (40 runs, 6 hours), the job folders (100, 7 days), the scratch git indexes, the dev server logs, and on a machine with a home its logs and the gui window's Chrome profile. The daemon sweeps them all on boot and hourly, and a gate still sweeps what it writes. New rule SK023 (`stores under cap`, health) fails when a store is past its cap plus one sweep's drift; `shipkit check --fix SK023` runs the sweep. The old json answers are imported once (the last day's) and deleted.
|
|
105
|
+
- **Cached answers live in the repo's database.** The one json file per cached answer under `.git/shipkit/store` is replaced by an `answers` table in the same `shipkit.sqlite` as the run record. Anything that makes the table unreadable (a corrupt or locked file, a missing row) means the check runs, never a replay and never a red. Answers nobody used for 14 days are dropped, and a repo keeps at most 5,000. The run record keeps 30 days and at most 200,000 rows per table.
|
|
106
|
+
- **A repo's installed copy hands the command to the house source.** `node_modules/.bin/shipkit` and the `.shipkit/hooks` run a repo's own `rvlib-shipkit` cli directly, past the machine launcher. That cli now replaces itself (same pid, same argv) with the house cli the launcher runs, so every repo runs one version. A house checkout runs its own source, and CI, `SHIPKIT_NO_HOME` or a machine with no house run the installed copy. The page no longer says "daily pass": a health check runs each time the plan runs it.
|
|
107
|
+
- **One shipkit version per machine, and any command starts pm.** The cli routes every `rvlib-shipkit` import a repo config makes to the copy that runs, so a repo's own install is never loaded beside it, and new rule SK022 warns when a repo pins or installed a version newer than the running one: the pin is a minimum. Before any command the cli probes pm on its port and, when it is down, kicks its LaunchAgent, or installs it from the house repo the first time. Skipped under CI, `SHIPKIT_NO_HOME=1`, inside a process pm started, off macOS, and after `shipkit pm uninstall`, which stays in force until `shipkit pm install`. `SHIPKIT_LAUNCH_AGENTS_DIR` moves where the agent is written.
|
|
108
|
+
- **The plan replaces the daily pass and the save trigger.** A check declares when it runs beside what it reads: `every: '24h'`, a range `every: ['10h', '12h']` (each repo and check lands on a stable point inside it, so checks spread out), `cron: '0 9 * * 1'`, null for on change only, or a pure function of its last result (`status`, `at`, `streak`) and whether the repo is active, returning any of those. `whenInactive: 'run' | 'wait'` (default wait, also a lambda) says what a due check does while its repo is inactive: wait turns it late and runs it once an agent works in the repo, VS Code has it open, or it is flagged active. With nothing declared, a `ttl` reads as its `every`, a build step runs on change only, and any other check runs every 24h. A bad value dies when the check file loads; a lambda that throws is an error on its row, and the check runs on change only. The daemon keeps ONE plan for the machine: every repo and check pair with its next due time and why (`changed`, `scheduled`, `late`, `asked`), and runs what is due as one background batch per repo through the gate jobs. A save makes due only the checks whose cached answer would not replay. The page `/planned` (the `planned` link in the header strip) shows the whole plan, filterable by repo: running, due, late, next with its time, each row with its reason and the rule that planned it (for a lambda, the value it returned and the state that chose it); a click runs it now. The check page's `next run` reads the same row. `HealthSchedule`, `GateTrigger` and `lastHealthPassAt` are deleted; the save watch lives in `serve/tracked-watch.ts`.
|
|
109
|
+
- **Every gate in the daemon is a detached job, so a daemon reload never kills one.** A terminal `shipkit ci` or `shipkit check`, a save trigger, the daily pass and a button on the page each start `shipkit ci --job <id>` (or `check --job <id>`) in the background, the same mechanism as `--detach`, with its options in the job folder. The daemon runs one job at a time per repo, a background one takes the machine's one background turn inside the job, and it follows the job's `out.log` and `checks.ndjson` into the terminal, the page and the problems pane. A new daemon image follows the jobs still running when it boots, and queues behind them. A terminal whose stream the daemon cut (a reload, `kill -9`) reads the rest from the job folder and exits with the gate's own code; a cut before the job started sends the run again once the daemon is back. The daemon now reloads on its own code at once, gates or not. `SHIPKIT_DAEMON_ONLY=a,b` loads only those house repos.
|
|
110
|
+
- **A hub that crashed on a broken `repo.config.ts` loads again once the file is edited**, as a working hub reloads on an edit. It stayed crashed until a restart by hand.
|
|
111
|
+
- **`shipkit pm`, the machine's process manager.** It runs `shipkit daemon` and `shipkit web` as detached children with pid files, starts one again when it dies (backoff, left down after 5 crashes in 5 minutes), and owns the machine resources (`home.resources`), which moved out of the daemon. Its api answers on port 4797 (`SHIPKIT_PM_PORT`): the status of each child and resource, start, stop and restart of a child, the resource switches. The web proxies `/api/pm/*` to it, and the overview shows pm, the daemon and the web with a restart button each. pm killed or restarted leaves its children running, and the next pm adopts them; a stop asked of pm (SIGTERM, `shipkit pm uninstall`) stops them. `shipkit pm install` writes the LaunchAgent `com.shipkit.pm`, which runs `bun <cli> pm` and replaces the `com.shipkit.home` agent. `shipkit home`, `home install` and `home uninstall` stay as names of the pm commands. `shipkit gui` starts pm when the web does not answer and waits for the web.
|
|
112
|
+
- **pm, the daemon and the web each reload alone.** None runs under a bun watch, which follows every command through `cli.ts`. Each watches the lib files its own runner imports and restarts in place, same pid, on an edit of one of them: a page edit restarts the web, a runner edit the daemon (a gate in flight keeps running, see gate jobs), a supervisor edit pm, which adopts its children.
|
|
113
|
+
- **pm stays small.** It reads the house repo's `home.resources` in a child process and never loads the repo config, and `shipkit pm` replaces its own image with pm's entry file, so pm runs in about 35 MB and reloads only on its own few files. Its boot lines say `adopted` for a child it took from a pid file. A stop or restart of the web asked from the page answers before the web goes down, so the page no longer shows a fetch error for a restart that worked.
|
|
114
|
+
- **A supervised process is started through a shell that exits at once**, so it belongs to init from its start, in its own process group. A process started before its supervisor restarted in place stayed a zombie of the new image and read alive forever, so a crash was never restarted. A pid file whose pid a process started after the record now holds is no longer adopted.
|
|
115
|
+
- **A daemon that hangs reads as down on the page: the web waits 5s for the home api and 30s for a repo request, runs excepted, and the page's poll gives up at 8s. The web learns the repo pages once the daemon first answers, and follows the list after that. A failed poll with the daemon up shows its error and no longer reloads the page.
|
|
116
|
+
- **One app for every repo on the machine: `shipkit daemon` and `shipkit web`.** Run from the repo that hosts the house wiki. `shipkit daemon` loads every repo's hub and answers every api on an internal port (4798, `SHIPKIT_DAEMON_PORT`); `shipkit web` serves the page on port 4799 (`SHIPKIT_HOME_PORT`) and proxies every `/api/*` and `/r/<repo>/…` request to the daemon, run streams included, so a browser only ever sees one origin. `shipkit pm` runs both. A daemon that is down reads as a clear error on the page, which reloads itself once the daemon answers again. A terminal gate talks to the daemon directly, so it never depends on the web. The daemon loads every house repo's hub at boot, one after the other, and serves each under `/r/<repo>/` of its one server: the page, `/api/*`, `/graph/*`, `/media/*` and `/hub.json`. A boot runs no gate: a repo's background gate starts on a save in it, and the daily pass and the RED pings run for ACTIVE repos only (an agent works in it, or it is flagged active). A repo whose config fails to load reads crashed with its error while the others serve. The page has no frames: `/` is the overview of every resource and repo with its state, and `/r/<repo>/` is that repo's own hub page carrying the rail (read from the machine wide `/api/projects.json`) and a home strip over its header, with the resource switches and, for the shown repo, restart, the active flag, VS Code and modernize. A rail click goes to that repo's page, a double click opens VS Code. A repo whose hub cannot load shows why and the fix in place of its page. The home repo declares the machine resources in `home.resources`. `shipkit pm install` adds a macOS LaunchAgent that keeps it alive. Resources and repo processes run detached with a pid file, so the next pm or daemon adopts them.
|
|
117
|
+
- **`shipkit serve` attaches to a running home.** It hands the hub to the home and streams its log, the last problem frame first, so a VS Code folder-open task keeps its problems pane with no change to `tasks.json`. A worktree, `--port` and `SHIPKIT_NO_HOME=1` serve in process as before.
|
|
118
|
+
- **`serve.processes`: a repo's dev servers, supervised by its hub.** One switch each in the hub header, crash restart with backoff, adopted across a hub restart. **`serve.uses`** names the machine resources a repo needs, and the home rail marks it while one is off.
|
|
119
|
+
- **`shipkit gui [repo|.]`: the home in its own window.** A Chrome app window on its own profile (the default browser when Chrome is absent), one more per call and per `new window` in the header. It starts the home when none runs, installing its LaunchAgent the first time from the house repo.
|
|
120
|
+
- **On a machine with the home, no hub runs outside it.** `shipkit serve` hands off, starting an installed home that is down, and never serves in process there. A process the home started, linked worktrees, machines without a home and `SHIPKIT_NO_HOME=1` serve as before. `SHIPKIT_USE_HOME=1` sends hubs and gates to the home on `SHIPKIT_HOME_PORT` on a machine with no LaunchAgent.
|
|
121
|
+
- **The daemon reloads one repo at a time.** An edit of a repo's `repo.config.ts` reloads that hub alone, once its gate is done, its processes kept. The daemon warns while it runs under a bun watch.
|
|
122
|
+
- **A hub stopped by hand stays down** until a restart: its page no longer loads it again, and a stop during a load wins over the load. `/r/<name>/` answers 404 for a name that is not a house repo, never a page whose every call fails. The home's view of a hub drops its dead `external` and `restarts` fields.
|
|
123
|
+
- **One copy of the lib in the home.** Before any repo config loads, a Bun resolver hook routes every `rvlib-shipkit` import (the bare name, an export alias, a subpath) to the home's own copy, so a repo's own install is never loaded and each lib module has one instance.
|
|
124
|
+
- **The hub page works under a base path.** Every request it makes goes through one helper that reads `/r/<repo>` from its own address, so the same page serves standalone at `/` and in the home.
|
|
125
|
+
- **On a machine with the home, `shipkit ci` and `shipkit check` run in the repo's hub, in the home.** They POST to `/r/<repo>/api/run`, and the terminal streams the hub's lines and takes its exit code, so every gate of a repo runs in one process and feeds one problems pane. What runs in process: a process the home started (a check's child, a script calling shipkit), CI, a linked worktree, `SHIPKIT_NO_HOME=1`, a machine without the home, a repo the home cannot load, and `check --fix`, `bump`, `release`. An unreachable home is a loud failure, never a quiet local run.
|
|
126
|
+
- **Background gates take turns on the whole machine.** The save trigger and the daily pass of every hub wait for one shared turn, so several hubs never gate at once. A gate someone asks for never waits for it. A hub start feeds the same quiet window as a save, so a burst of restarts is one run.
|
|
127
|
+
- **The rail: a single click selects, a double click opens VS Code.** On a hub serving on its own, a click opens that repo's page on the home when one serves.
|
|
128
|
+
|
|
129
|
+
- **The project rail has three sizes.** A button pinned to the foot of the rail shows three dots, the current size lit, and a click cycles small (44px rail, 24px icons), medium (76px, 56px) and large (132px, 112px). The browser keeps the choice in `localStorage` `hub:rail`. Every size is a literal of the rail's class in `project-gutter.css`, never an inline value, and each icon carries its size as `width` and `height` attributes with `object-fit: contain`, so a 512px icon file never draws at its natural size, even when the sheet does not apply. `tests/browser/hub-layout-check.ts` measures all of it in headless Chrome.
|
|
130
|
+
|
|
1
131
|
## 1.1.1
|
|
2
132
|
|
|
3
133
|
- **The project rail reads `.shipkit/identity/icon.png` first.** A repo with no public surface has no favicon, so the rail showed its initial. `ICON_FILES` now starts with `.shipkit/identity/icon.png`, ahead of `public/favicon.png`, so a repo can carry a rail icon without shipping a public folder.
|
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ Output is a stable line protocol — `ok:` / `WARN` / `FAIL:` / `skip:` with ind
|
|
|
81
81
|
|
|
82
82
|
## Release contracts
|
|
83
83
|
|
|
84
|
-
A venue contract is data: required files, exact PNG dimensions (header-parsed, no image dependency), glob minimums, required markdown sections. Enforcement is both ways — a declared venue with an incomplete folder fails, and an undeclared venue whose signals fire (deploy scripts, folders) fails too. `exempt: { steam: "reason" }` skips visibly. Wire venue deploys as `deps: ['
|
|
84
|
+
A venue contract is data: required files, exact PNG dimensions (header-parsed, no image dependency), glob minimums, required markdown sections. Enforcement is both ways — a declared venue with an incomplete folder fails, and an undeclared venue whose signals fire (deploy scripts, folders) fails too. `exempt: { steam: "reason" }` skips visibly. Wire venue deploys as `deps: ['SK002']` and an incomplete folder physically blocks the upload.
|
|
85
85
|
|
|
86
86
|
Ship your own contracts via `contracts: { extra: [defineContract({...})] }` — see `rvlib-shipkit/contracts` for the rule vocabulary.
|
|
87
87
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"test:raw": "bun test"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@floating-ui/react": "^0.27.20",
|
|
36
37
|
"@napi-rs/canvas": "^1.0.2",
|
|
37
38
|
"clipanion": "^4.0.0-rc.4",
|
|
38
39
|
"comfy-ts": "^2.9.0",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"mobx-react-lite": "^5.0.0",
|
|
43
44
|
"react": "^19.2.7",
|
|
44
45
|
"react-dom": "^19.2.7",
|
|
46
|
+
"react-resizable-panels": "^4.13.3",
|
|
45
47
|
"sanitize-html": "^2.17.6",
|
|
46
48
|
"sharp": "^0.35.3"
|
|
47
49
|
}
|
package/src/assets/run-assets.ts
CHANGED
|
@@ -236,14 +236,9 @@ export const runAssetsCheck = (root: string, io: AssetsIo): Promise<number> =>
|
|
|
236
236
|
io.print(`WARN no schema cache for host '${lw.wf.host.data.id}' — bunx comfy-ts gen --id ${lw.wf.host.data.id} --host ${lw.wf.host.getServerHostHTTP()}`)
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
io.print(up ? `ok: comfy backend reachable (${url})` : `WARN comfy backend unreachable (${url})${kit.comfy.heal != null ? ` — ${kit.comfy.heal}` : ''}`)
|
|
243
|
-
}
|
|
244
|
-
} finally {
|
|
245
|
-
disconnectAll({ workflows: wfs })
|
|
246
|
-
}
|
|
239
|
+
// whether the backend answers is a machine fact the house repo's
|
|
240
|
+
// box check owns, never a question about this repo's kit
|
|
241
|
+
disconnectAll({ workflows: wfs })
|
|
247
242
|
}
|
|
248
243
|
}
|
|
249
244
|
// machine-clearable red clears itself: drifted/missing identity
|
package/src/check.ts
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
// its cache key automatically, so nothing declares its own code.
|
|
21
21
|
import type { CheckCtx } from './core/check-ctx.ts'
|
|
22
22
|
import { parseWhen } from './core/when.ts'
|
|
23
|
-
import type
|
|
23
|
+
import { planDeclProblem, type Every, type WhenInactiveDecl } from './plan/cadence.ts'
|
|
24
|
+
import { FOREVER, parseDuration, type Line } from './core/wire.ts'
|
|
24
25
|
import type { RepoConfig } from './repo-config.ts'
|
|
25
26
|
|
|
26
27
|
export { defineScan, mdPages, packageJson, type MdPage, type PackageJson, type Scan } from './scan.ts'
|
|
@@ -97,7 +98,10 @@ export type CheckDef = {
|
|
|
97
98
|
* correctness. At most one of the three. */
|
|
98
99
|
inputs?: string[]
|
|
99
100
|
/** WORLD population: nothing on disk describes the answer, so a clean
|
|
100
|
-
* pass keeps for this long, as in '24h'.
|
|
101
|
+
* pass keeps for this long, as in '24h'. `'forever'` for a DECIDED fact
|
|
102
|
+
* (a repo's visibility, an account that exists): a clean answer never
|
|
103
|
+
* expires, it is keyed on the declared value alone (`cacheKey`), and the
|
|
104
|
+
* plan runs it only while no answer exists for that key. */
|
|
101
105
|
ttl?: string
|
|
102
106
|
/** a DECIDED never-cacheable, with the reason. Says somebody looked and
|
|
103
107
|
* no key can describe this, which takes it off the burn-down list. */
|
|
@@ -108,6 +112,36 @@ export type CheckDef = {
|
|
|
108
112
|
* key, and every other check shares one answer between a frozen run and
|
|
109
113
|
* a normal one. Declared, never guessed from the body. */
|
|
110
114
|
writes?: boolean
|
|
115
|
+
/** WHEN the planner runs it on its own, beside the save that moved what
|
|
116
|
+
* it reads: a span since its last run (`'24h'`), a range (`['10h',
|
|
117
|
+
* '12h']`, each repo and check on a stable point inside it, so checks
|
|
118
|
+
* spread out), a cron expression (`'0 9 * * 1'`), `'once'` (only while
|
|
119
|
+
* no answer exists: what `ttl: 'forever'` reads as), or null for on
|
|
120
|
+
* change only. Or a PURE function of its last result (`status`, `at`, `streak`
|
|
121
|
+
* of that same status) and whether the repo is active, returning any of
|
|
122
|
+
* those: `(last) => (last?.status === 'fail' ? '1h' : '7d')`. A lambda
|
|
123
|
+
* that throws is an error row on the planned page, and the check runs on
|
|
124
|
+
* change only. Absent: its `ttl`, else a build step or a check keyed on
|
|
125
|
+
* its files waits for a save. A repo check nothing can key runs about
|
|
126
|
+
* once a day until it declares one, and a built-in never may: `shipkit
|
|
127
|
+
* keys --burn-down` names both. */
|
|
128
|
+
every?: Every
|
|
129
|
+
/** a time of day or week for the rare check that needs one, as five cron
|
|
130
|
+
* fields in local time (`'0 9 * * 1'`, Mondays at 9). Never with `every`. */
|
|
131
|
+
cron?: string
|
|
132
|
+
/** what a due check does while its repo is inactive (no agent session in
|
|
133
|
+
* it, VS Code does not have it open, it is not flagged active): `'wait'`
|
|
134
|
+
* turns it late and runs it once the repo is active again, `'run'` runs
|
|
135
|
+
* it on time anyway (a credential probe, an api key that must keep
|
|
136
|
+
* working). A pure function of the last result may choose. Default
|
|
137
|
+
* `'wait'`. */
|
|
138
|
+
whenInactive?: WhenInactiveDecl
|
|
139
|
+
/** why this repo gives the check nothing to do on a clock, or null: the
|
|
140
|
+
* plan leaves it out there (no row, no timer), and a gate or a health
|
|
141
|
+
* pass still runs it. Read at every reload of the repo's checks, so a
|
|
142
|
+
* file that gives it work (an ask with a probe, a release unit) plans it
|
|
143
|
+
* again. Cheap and pure over the repo's files and config */
|
|
144
|
+
planOff?: (env: CheckEnv) => string | null
|
|
111
145
|
/** cache-identity contribution for a `ttl` check whose verdict depends on
|
|
112
146
|
* config: a changed key busts the answer instantly instead of serving
|
|
113
147
|
* a stale cached assertion for the rest of the TTL. */
|
|
@@ -179,6 +213,10 @@ export function check(d: CheckDef): CheckDef {
|
|
|
179
213
|
const populations = (['inputs', 'ttl', 'never'] as const).filter((k) => has(k))
|
|
180
214
|
if (populations.length > 1)
|
|
181
215
|
throw new Error(`check${d.id == null ? '' : ` ${d.id}`}: ${populations.join(' and ')} contradict each other — a check reads files, or the world, or nothing can describe it`)
|
|
216
|
+
if (d.ttl != null && d.ttl !== FOREVER)
|
|
217
|
+
try { parseDuration(d.ttl) } catch { throw new Error(`check${name}: ttl '${d.ttl}' is neither a span ('7d') nor 'forever'`) }
|
|
218
|
+
const planBad = planDeclProblem({ every: d.every, cron: d.cron, whenInactive: d.whenInactive })
|
|
219
|
+
if (planBad != null) throw new Error(`check${name}: ${planBad}`)
|
|
182
220
|
if (d.when != null) {
|
|
183
221
|
const w = parseWhen(d.when)
|
|
184
222
|
if (typeof w === 'string') throw new Error(`check${d.id == null ? '' : ` ${d.id}`}: when ${w}`)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { canonicalBodies, conditionalBodies, inferBin, ROUTED_SCRIPTS, rawName, routedBody, routesToShipkit } from '../core/canonical-scripts.ts'
|
|
14
14
|
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
15
15
|
import { checkFloorReader, resolveFloor } from '../core/floor-resolve.ts'
|
|
16
|
+
import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
16
17
|
import type { RepoConfig } from '../repo-config.ts'
|
|
17
18
|
import { check, type FixResult } from '../check.ts'
|
|
18
19
|
|
|
@@ -24,7 +25,7 @@ const OWNER = 'meta/rv-conventions/package-json.md'
|
|
|
24
25
|
// alias really is the canonical capability (a `serve` that serves a dist/
|
|
25
26
|
// folder must not be told to rename)
|
|
26
27
|
const ALIASES: Record<string, { canonical: string; bodyMatch: RegExp }> = {
|
|
27
|
-
'serve': { canonical: 'hub', bodyMatch: /(shipkit
|
|
28
|
+
'serve': { canonical: 'hub', bodyMatch: /(?:^|\s)(?:shipkit|\S*shipkit\S*\/cli\.ts) serve\b/ },
|
|
28
29
|
'pretty': { canonical: 'fmt', bodyMatch: /./ },
|
|
29
30
|
'check-types': { canonical: 'typecheck', bodyMatch: /./ },
|
|
30
31
|
}
|
|
@@ -46,6 +47,15 @@ export function evaluatePackageJson(t: CheckCtx): void {
|
|
|
46
47
|
return
|
|
47
48
|
}
|
|
48
49
|
const scripts = pkg.scripts ?? {}
|
|
50
|
+
// a package whose root unit declares npm is a public contract: it
|
|
51
|
+
// publishes, so the app field rules invert, and its script names are its
|
|
52
|
+
// own vocabulary, which no house name or route may rewrite
|
|
53
|
+
if (rootPublishesNpm(c)) {
|
|
54
|
+
if (pkg.private === true) t.fail(`package.json: the root unit declares release.npm, and "private": true makes npm refuse the publish`)
|
|
55
|
+
if (pkg.license == null) t.fail(`package.json: a package published to npm declares its license`)
|
|
56
|
+
if (!t.failed) t.ok(`package.json shape ok (published to npm: publishable, license ${pkg.license})`)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
49
59
|
// rvlib-* as a probe ROOT is theoretical (libs live as sub-packages and
|
|
50
60
|
// the probe only runs where repo.config.ts loads) — field inversion is
|
|
51
61
|
// still handled so a future lib-root repo fails correctly, not absurdly
|
|
@@ -177,14 +187,15 @@ export const packageJsonShape = check({
|
|
|
177
187
|
fix: (c): FixResult => {
|
|
178
188
|
const raw = c.read('package.json')
|
|
179
189
|
if (raw == null) return { changed: false, summary: 'no package.json — nothing to fix' }
|
|
190
|
+
if (rootPublishesNpm(c.config)) return { changed: false, summary: 'published to npm: its package.json is its public contract, never rewritten' }
|
|
180
191
|
let pkg: Pkg & { scripts?: Record<string, string> }
|
|
181
192
|
try { pkg = JSON.parse(raw) as Pkg } catch { return { changed: false, summary: 'package.json unparseable — repair it by hand first' } }
|
|
182
193
|
if (pkg.name?.startsWith('rvlib-') === true) return { changed: false, summary: 'rvlib-* package — license/private are publish decisions, not autofixable' }
|
|
183
194
|
const changes: string[] = []
|
|
184
195
|
const notes: string[] = []
|
|
185
196
|
// any "I am published" marker → the field flips are a publish
|
|
186
|
-
// decision, not a mechanical repair (
|
|
187
|
-
//
|
|
197
|
+
// decision, not a mechanical repair (a package that publishes says so
|
|
198
|
+
// with release.npm, which skips this fixer outright)
|
|
188
199
|
const publishMarkers: string[] = ['files', 'exports', 'publishConfig'].filter((k) => (pkg as Record<string, unknown>)[k] != null)
|
|
189
200
|
if (pkg.scripts?.['prepublishOnly'] != null) publishMarkers.push('prepublishOnly')
|
|
190
201
|
if (publishMarkers.length > 0) {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// registration point; the evaluation machinery stays in
|
|
3
3
|
// contracts/contracts-engine.ts (venue manifests are DATA rows, not rules —
|
|
4
4
|
// one SK id covers the invariant "declared contracts are fulfilled, folders
|
|
5
|
-
// can't rot undeclared"). Every surface reporting fulfillness (`check`,
|
|
6
|
-
// `shipkit contracts
|
|
7
|
-
//
|
|
5
|
+
// can't rot undeclared"). Every surface reporting fulfillness (`check`, `ci`,
|
|
6
|
+
// `shipkit contracts`) tags with this id via contracts/apply-contracts.ts. A
|
|
7
|
+
// venue deploy step deps on it BY ID (`deps: ['SK002']`).
|
|
8
8
|
import { applyContractsResult, contractsResult } from '../contracts/apply-contracts.ts'
|
|
9
9
|
import { check } from '../check.ts'
|
|
10
10
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// `c.exec` is never stored on its reads.
|
|
17
17
|
import { readdirSync } from 'fs'
|
|
18
18
|
import type { RepoConfig } from '../repo-config.ts'
|
|
19
|
+
import { declaredPublic } from '../core/repo-facts.ts'
|
|
19
20
|
import { GENERATED_IGNORE_LINES, GENERATED_TRACKED_PATHS, untrackGenerated } from '../rules/untrack-generated.ts'
|
|
20
21
|
import { check, type CheckCtx, type FixResult } from '../check.ts'
|
|
21
22
|
import { gitBin } from '../core/git-bin.ts'
|
|
@@ -29,6 +30,8 @@ const BEGIN = '# >>> shipkit gitignore policy (SK004) — managed block, edit ou
|
|
|
29
30
|
const END = '# <<< shipkit gitignore policy'
|
|
30
31
|
const CONTRIB = '# repo-contributed lines (repo.config.ts gitignore:)'
|
|
31
32
|
|
|
33
|
+
const DRAFTS_BACK = '!.comfy-ts/drafts/'
|
|
34
|
+
|
|
32
35
|
export const POLICY_LINES = [
|
|
33
36
|
// everything shipkit keeps lives under .shipkit/ (core/layout.ts). Local
|
|
34
37
|
// by default: the private notes, drafts, venue listings and caches, and the
|
|
@@ -62,7 +65,7 @@ export const POLICY_LINES = [
|
|
|
62
65
|
// steps and cfg, so it is authored state and belongs in the repo, unlike the
|
|
63
66
|
// schema dump, the lora previews and the raw run outputs beside it
|
|
64
67
|
'.comfy-ts/*',
|
|
65
|
-
|
|
68
|
+
DRAFTS_BACK,
|
|
66
69
|
'*.loras.local.txt',
|
|
67
70
|
// shipkit's own per-run output. Ignoring is only half of it: the fixer
|
|
68
71
|
// also removes these from the index, because .gitignore does nothing to a
|
|
@@ -70,6 +73,11 @@ export const POLICY_LINES = [
|
|
|
70
73
|
...GENERATED_IGNORE_LINES,
|
|
71
74
|
]
|
|
72
75
|
|
|
76
|
+
/** the policy one repo carries. A declared public tree ships to strangers,
|
|
77
|
+
* so the drafts stay ignored there: a draft holds the prompts and values
|
|
78
|
+
* behind generated assets, authored state in a private repo */
|
|
79
|
+
const policyLines = (config: RepoConfig): string[] => declaredPublic(config) ? POLICY_LINES.filter((l) => l !== DRAFTS_BACK) : POLICY_LINES
|
|
80
|
+
|
|
73
81
|
const LEGACY = '# the old root layout, ignored until the fix pass moves it under .shipkit/'
|
|
74
82
|
|
|
75
83
|
/** `legacy`: the repo still carries contract folders at its root from before `.shipkit/`. Their names
|
|
@@ -77,11 +85,11 @@ const LEGACY = '# the old root layout, ignored until the fix pass moves it under
|
|
|
77
85
|
* that is newer than the tree it guards */
|
|
78
86
|
export function expectedBlock(config: RepoConfig, legacy = false): string {
|
|
79
87
|
const extra = config.gitignore ?? []
|
|
80
|
-
return [BEGIN, ...
|
|
88
|
+
return [BEGIN, ...policyLines(config), ...(legacy ? [LEGACY, ...LEGACY_IGNORED] : []), ...(extra.length > 0 ? [CONTRIB, ...extra] : []), END].join('\n')
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
export function policyPatterns(config: RepoConfig, legacy = false): string[] {
|
|
84
|
-
return [...
|
|
92
|
+
return [...policyLines(config).filter((l) => !l.startsWith('#')), ...(legacy ? LEGACY_IGNORED : []), ...(config.gitignore ?? [])]
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
/** does the root still hold a contract folder from before `.shipkit/` */
|
|
@@ -48,6 +48,7 @@ import { basename, join } from 'path'
|
|
|
48
48
|
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
49
49
|
import { allowMajor, breakingAdvice, isBreaking } from '../core/update-policy.ts'
|
|
50
50
|
import { isRunningCopy, rememberLatest } from '../core/self-update.ts'
|
|
51
|
+
import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
51
52
|
import { mutateLease, withMutateLease } from '../core/locked-write.ts'
|
|
52
53
|
import { detectPackageManager, isRefusal, LOCK_FILES } from '../core/package-manager.ts'
|
|
53
54
|
import { check, type CheckEnv } from '../check.ts'
|
|
@@ -77,6 +78,11 @@ export const rvlibVersions = check({
|
|
|
77
78
|
ttl: '24h',
|
|
78
79
|
// it updates the rvlib deps unless the run is frozen
|
|
79
80
|
writes: true,
|
|
81
|
+
planOff: (env) => {
|
|
82
|
+
let pkg: Pkg
|
|
83
|
+
try { pkg = JSON.parse(readFileSync(join(env.root, 'package.json'), 'utf8')) as Pkg } catch { return 'no readable package.json' }
|
|
84
|
+
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).some((n) => n.startsWith('rvlib-')) ? null : 'no rvlib-* dependency'
|
|
85
|
+
},
|
|
80
86
|
cacheKey: (env) => {
|
|
81
87
|
let pkg: Pkg
|
|
82
88
|
try { pkg = JSON.parse(readFileSync(join(env.root, 'package.json'), 'utf8')) as Pkg } catch { return 'unreadable' }
|
|
@@ -84,7 +90,7 @@ export const rvlibVersions = check({
|
|
|
84
90
|
.filter((n) => n.startsWith('rvlib-'))
|
|
85
91
|
.sort()
|
|
86
92
|
.map((n) => `${n}@${installedVersion(env.root, n) ?? 'absent'}`)
|
|
87
|
-
.join(',')
|
|
93
|
+
.join(',') + (rootPublishesNpm(env.config) ? ' npm' : '')
|
|
88
94
|
},
|
|
89
95
|
run: async (c) => {
|
|
90
96
|
let pkg: Pkg
|
|
@@ -137,6 +143,12 @@ export const rvlibVersions = check({
|
|
|
137
143
|
c.warn(`newer rvlib live: ${drift} (frozen — apply with: ${cmd.join(' ')})`)
|
|
138
144
|
return
|
|
139
145
|
}
|
|
146
|
+
// a package published to npm: its package.json is its public
|
|
147
|
+
// contract, so the bump is a release decision, named and never made
|
|
148
|
+
if (rootPublishesNpm(c.config)) {
|
|
149
|
+
c.warn(`newer rvlib live: ${drift} (published to npm, never updated by a check — apply with: ${cmd.join(' ')})`)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
140
152
|
const dirty = await c.exec(['git', 'status', '--porcelain', '--', ...manifest], { cwd: c.root })
|
|
141
153
|
if (dirty.code !== 0 || dirty.out.trim() !== '') {
|
|
142
154
|
c.warn(`newer rvlib live: ${drift} — self-update deferred (${dirty.code !== 0 ? `git says: ${dirty.out.trim().split('\n')[0] ?? `exit ${dirty.code}`}` : 'package.json/lockfile dirty'}); commit first, then re-run check`)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { join, relative } from 'path'
|
|
16
16
|
import { checkUnitReader, resolveUnits } from '../release/unit-resolve.ts'
|
|
17
17
|
import { check } from '../check.ts'
|
|
18
|
+
import { rootPublishesNpm } from '../core/repo-facts.ts'
|
|
18
19
|
|
|
19
20
|
type Pkg = {
|
|
20
21
|
name?: string
|
|
@@ -65,6 +66,10 @@ export const npmPublishSafety = check({
|
|
|
65
66
|
if (localHits.length === 0) c.ok('rvlib-* package — publishes by design (distribute/npm owns it)')
|
|
66
67
|
return
|
|
67
68
|
}
|
|
69
|
+
if (rootPublishesNpm(c.config)) {
|
|
70
|
+
if (localHits.length === 0) c.ok('the root unit declares release.npm: publishes by design, no local dependency protocol in any releasing unit')
|
|
71
|
+
return
|
|
72
|
+
}
|
|
68
73
|
if (pkg.private !== true) return c.note('private:true missing — SK001 reports it; publish machinery not judged until the lock is on')
|
|
69
74
|
const machinery: string[] = []
|
|
70
75
|
if (pkg.publishConfig != null) machinery.push('publishConfig')
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
// own base-repo resolution prefers `upstream`, which in a fork-shaped
|
|
9
9
|
// checkout would confidently validate the WRONG repository. Flipping a
|
|
10
10
|
// repo public stays a human act behind the pre-public audit; this rule is
|
|
11
|
-
// the standing tripwire if it ever happens out of band.
|
|
12
|
-
//
|
|
11
|
+
// the standing tripwire if it ever happens out of band. A decided fact: a
|
|
12
|
+
// clean pass is kept forever, keyed on the declared value (a flip re-probes
|
|
13
|
+
// immediately, and the plan asks only while no answer exists); every
|
|
13
14
|
// unverifiable path (no git, no remote, non-github remote, gh missing or
|
|
14
15
|
// erroring, unreadable answer) is a loud skip — an UNKNOWN, never red,
|
|
15
16
|
// never cached.
|
|
@@ -17,20 +18,19 @@ import { carveGhJson, firstGhLine } from '../git-bootstrap.ts'
|
|
|
17
18
|
import { probeGitRoot } from '../core/git-root.ts'
|
|
18
19
|
import { nestedIn } from '../core/nesting.ts'
|
|
19
20
|
import { check } from '../check.ts'
|
|
21
|
+
import { declaredVisibility } from '../core/repo-facts.ts'
|
|
20
22
|
|
|
21
23
|
export const githubVisibility = check({
|
|
22
24
|
id: 'SK007',
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
// unverifiable path above is a skip, so an offline machine never goes red
|
|
26
|
-
blocking: true,
|
|
25
|
+
// a gate block does not un-leak a repo: the red shows on the needs surface
|
|
26
|
+
blocking: false,
|
|
27
27
|
title: 'github visibility',
|
|
28
28
|
description: 'the github visibility of this repo matches the one it declares',
|
|
29
29
|
owner: 'meta/contracts.md',
|
|
30
|
-
ttl: '
|
|
31
|
-
cacheKey: (env) => env.config
|
|
30
|
+
ttl: 'forever',
|
|
31
|
+
cacheKey: (env) => declaredVisibility(env.config),
|
|
32
32
|
run: async (c) => {
|
|
33
|
-
const declared = c.config
|
|
33
|
+
const declared = declaredVisibility(c.config)
|
|
34
34
|
const skip = (why: string): void => void c.lines.push({ kind: 'skip', text: `github visibility unverified — ${why} (declared: ${declared})` })
|
|
35
35
|
const firstLine = firstGhLine
|
|
36
36
|
const repo = probeGitRoot(c.root)
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
// harness then inherits every tool. The brief itself must exist. No
|
|
9
9
|
// declaration = not a reviewer repo, and the rule passes silently.
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync } from 'fs'
|
|
11
|
-
import {
|
|
12
|
-
import { dirname, isAbsolute, join } from 'path'
|
|
11
|
+
import { dirname, join } from 'path'
|
|
13
12
|
import type { RepoConfig } from '../repo-config.ts'
|
|
14
13
|
import { check, type FixResult, type CheckEnv } from '../check.ts'
|
|
15
14
|
import { LAYOUT } from '../core/layout.ts'
|
|
16
15
|
import { atomicWrite } from '../core/locked-write.ts'
|
|
16
|
+
import { configPath, fileStamp } from '../core/file-stamp.ts'
|
|
17
|
+
import { CONFIG_FILE } from '../core/check-pass.ts'
|
|
17
18
|
|
|
18
19
|
/** where a review lands when its caller named no output file:
|
|
19
20
|
* `YYYYMMDD-HHMMSS-<review-name>.md`, local time, gitignored */
|
|
@@ -48,8 +49,7 @@ export function reviewerAgentIssues(text: string, reviewer: NonNullable<RepoConf
|
|
|
48
49
|
/** the brief must EXIST: a reviewer whose mandated first action is reading a
|
|
49
50
|
* missing file flounders silently, which is the one thing this rule prevents */
|
|
50
51
|
export function briefMissing(root: string, brief: string): boolean {
|
|
51
|
-
|
|
52
|
-
return !existsSync(p)
|
|
52
|
+
return !existsSync(configPath(root, brief))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function canonicalAgent(reviewer: NonNullable<RepoConfig['reviewer']>): string {
|
|
@@ -83,12 +83,18 @@ That brief overrides any habit of reviewing a diff for correctness alone.
|
|
|
83
83
|
export const reviewerAgent = check({
|
|
84
84
|
id: 'SK008',
|
|
85
85
|
blocking: true,
|
|
86
|
-
title: 'reviewer
|
|
86
|
+
title: 'reviewer definition',
|
|
87
87
|
description: 'the declared reviewer agent has its brief, its definition and read only tools',
|
|
88
88
|
owner: 'identity/how-we-work.md',
|
|
89
|
-
// the
|
|
90
|
-
//
|
|
91
|
-
|
|
89
|
+
// the answer is the config, the definition file and the brief. The brief
|
|
90
|
+
// is a CONFIG path that may sit outside the repo, so both files enter the
|
|
91
|
+
// key by content, wherever they live
|
|
92
|
+
inputs: [CONFIG_FILE],
|
|
93
|
+
cacheKey: (env) => {
|
|
94
|
+
const r = env.config.reviewer
|
|
95
|
+
if (r == null) return 'no reviewer'
|
|
96
|
+
return `${r.name}|${(r.tools ?? REVIEWER_TOOLS).join(',')}|def=${fileStamp(join(env.root, reviewerAgentPath(r.name)))}|${r.brief}=${fileStamp(configPath(env.root, r.brief))}`
|
|
97
|
+
},
|
|
92
98
|
run: (c) => {
|
|
93
99
|
const r = c.config.reviewer
|
|
94
100
|
if (r == null) return c.ok('no reviewer agent declared (repo.config reviewer:) — skipped')
|