rvlib-shipkit 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/package.json +3 -1
  3. package/src/check.ts +24 -0
  4. package/src/checks/SK022-machine-runs-the-pin.check.ts +43 -0
  5. package/src/checks/SK023-stores-under-cap.check.ts +49 -0
  6. package/src/checks/SK024-identity-colors.check.ts +49 -0
  7. package/src/cli/cmd_Check.ts +26 -1
  8. package/src/cli/cmd_Ci.ts +18 -5
  9. package/src/cli/cmd_Home.ts +170 -0
  10. package/src/cli.ts +22 -2
  11. package/src/comfy/comfy-panel.ts +1 -16
  12. package/src/core/attach.ts +14 -9
  13. package/src/core/check-file-names.ts +8 -0
  14. package/src/core/check-runner.ts +40 -18
  15. package/src/core/discover-checks.ts +36 -18
  16. package/src/core/identity-color.ts +203 -0
  17. package/src/core/identity.ts +91 -0
  18. package/src/core/job-follow.ts +102 -0
  19. package/src/core/job-run.ts +27 -0
  20. package/src/core/jobs.ts +87 -27
  21. package/src/core/legacy-shapes.ts +1 -14
  22. package/src/core/machine-db.ts +83 -0
  23. package/src/core/one-lib.ts +85 -0
  24. package/src/core/port-holders.ts +43 -0
  25. package/src/core/retention.ts +213 -0
  26. package/src/core/run-check.ts +6 -2
  27. package/src/core/run-ci.ts +3 -0
  28. package/src/core/slots.ts +5 -0
  29. package/src/core/statement-end.ts +17 -0
  30. package/src/home/attach.ts +95 -0
  31. package/src/home/autostart.ts +108 -0
  32. package/src/home/global-units.ts +86 -0
  33. package/src/home/gui-app/ShipkitGui.swift +159 -0
  34. package/src/home/gui-app.ts +99 -0
  35. package/src/home/handoff.ts +51 -0
  36. package/src/home/home-launchd.ts +104 -0
  37. package/src/home/home-paths.ts +88 -0
  38. package/src/home/home-routes.ts +208 -0
  39. package/src/home/home-start.ts +105 -0
  40. package/src/home/home-state.ts +43 -0
  41. package/src/home/home-words.ts +12 -0
  42. package/src/home/hubs.ts +286 -0
  43. package/src/home/lib-watch.ts +139 -0
  44. package/src/home/machine-stores.ts +115 -0
  45. package/src/home/open-app.ts +16 -0
  46. package/src/home/pm-config.ts +37 -0
  47. package/src/home/pm.ts +141 -0
  48. package/src/home/resources.ts +338 -0
  49. package/src/home/run-daemon.ts +137 -0
  50. package/src/home/run-pm.ts +149 -0
  51. package/src/home/run-unit-action.ts +25 -0
  52. package/src/home/run-web.ts +37 -0
  53. package/src/home/serve-daemon.ts +31 -0
  54. package/src/home/serve-web.ts +172 -0
  55. package/src/home/supervise.ts +346 -0
  56. package/src/home/via-hub.ts +142 -0
  57. package/src/home/widgets.ts +102 -0
  58. package/src/ledger/check-stats.ts +19 -45
  59. package/src/ledger/db.ts +69 -17
  60. package/src/ledger/gate-checks.ts +5 -0
  61. package/src/ledger/git-scan.ts +31 -7
  62. package/src/ledger/ledger.ts +40 -14
  63. package/src/ledger/run-log.ts +58 -23
  64. package/src/ledger/store.ts +228 -103
  65. package/src/ledger/world-cache.ts +15 -13
  66. package/src/plan/cadence.ts +237 -0
  67. package/src/plan/planner.ts +303 -0
  68. package/src/plan/repo-source.ts +100 -0
  69. package/src/problems/problem-line.ts +1 -1
  70. package/src/repo-config.ts +51 -2
  71. package/src/serve/hub-app/check-view.tsx +9 -9
  72. package/src/serve/hub-app/check-words.ts +1 -1
  73. package/src/serve/hub-app/checks-svc.ts +12 -8
  74. package/src/serve/hub-app/col-card.tsx +4 -4
  75. package/src/serve/hub-app/cols-svc.ts +92 -13
  76. package/src/serve/hub-app/comfy-svc.ts +12 -6
  77. package/src/serve/hub-app/comfy-view.tsx +3 -3
  78. package/src/serve/hub-app/floating.tsx +49 -0
  79. package/src/serve/hub-app/geometry-view.tsx +4 -4
  80. package/src/serve/hub-app/hash-route.ts +1 -1
  81. package/src/serve/hub-app/header-bar.tsx +44 -12
  82. package/src/serve/hub-app/health-svc.ts +3 -3
  83. package/src/serve/hub-app/help.tsx +10 -7
  84. package/src/serve/hub-app/home-st.ts +241 -0
  85. package/src/serve/hub-app/home-strip.css +70 -0
  86. package/src/serve/hub-app/home-strip.tsx +245 -0
  87. package/src/serve/hub-app/home.html +17 -0
  88. package/src/serve/hub-app/hub-app.tsx +201 -93
  89. package/src/serve/hub-app/hub-types.ts +0 -3
  90. package/src/serve/hub-app/hub.css +43 -23
  91. package/src/serve/hub-app/icons.tsx +14 -0
  92. package/src/serve/hub-app/index.html +3 -1
  93. package/src/serve/hub-app/keys.ts +25 -0
  94. package/src/serve/hub-app/layout-st.ts +55 -0
  95. package/src/serve/hub-app/live-svc.ts +12 -9
  96. package/src/serve/hub-app/machine-st.ts +270 -0
  97. package/src/serve/hub-app/media-svc.ts +63 -44
  98. package/src/serve/hub-app/media-view.tsx +20 -20
  99. package/src/serve/hub-app/next-run.ts +24 -30
  100. package/src/serve/hub-app/palette-st.ts +88 -0
  101. package/src/serve/hub-app/planned-st.ts +171 -0
  102. package/src/serve/hub-app/planned-view.tsx +109 -0
  103. package/src/serve/hub-app/planned.css +39 -0
  104. package/src/serve/hub-app/processes-svc.ts +63 -0
  105. package/src/serve/hub-app/project-list.css +97 -0
  106. package/src/serve/hub-app/project-list.tsx +167 -0
  107. package/src/serve/hub-app/projects-svc.ts +139 -17
  108. package/src/serve/hub-app/prose-hits-st.ts +3 -3
  109. package/src/serve/hub-app/prose-hits-view.tsx +6 -6
  110. package/src/serve/hub-app/{hub-st.ts → repo-st.ts} +155 -84
  111. package/src/serve/hub-app/runs-tabs.ts +1 -1
  112. package/src/serve/hub-app/screen-cache.ts +74 -0
  113. package/src/serve/hub-app/test-view.tsx +4 -4
  114. package/src/serve/hub-app/tests-svc.ts +3 -3
  115. package/src/serve/hub-app/tests-view.tsx +10 -10
  116. package/src/serve/hub-app/timeline-chart.tsx +24 -12
  117. package/src/serve/hub-app/timeline-svc.ts +5 -4
  118. package/src/serve/hub-app/timeline-view.tsx +13 -13
  119. package/src/serve/hub-app/tree-row.tsx +2 -2
  120. package/src/serve/hub-app/tree-svc.ts +2 -2
  121. package/src/serve/hub-app/wikis-view.tsx +10 -6
  122. package/src/serve/hub-runtime.ts +542 -0
  123. package/src/serve/project-gutter-route.ts +21 -0
  124. package/src/serve/project-gutter.ts +3 -2
  125. package/src/serve/repo-processes.ts +89 -0
  126. package/src/serve/run-serve.ts +110 -402
  127. package/src/serve/serve-hub.ts +142 -44
  128. package/src/serve/stale-restart.ts +5 -0
  129. package/src/serve/{gate-trigger.ts → tracked-watch.ts} +4 -64
  130. package/src/serve/warm-cell.ts +95 -0
  131. package/src/shipkit-files.ts +114 -0
  132. package/src/shipkit.ts +2 -1
  133. package/src/suite/kept-timings.ts +1 -1
  134. package/src/wiki/graph-positions.ts +1 -1
  135. package/src/serve/health-schedule.ts +0 -102
  136. package/src/serve/hub-app/project-gutter.css +0 -65
  137. package/src/serve/hub-app/project-gutter.tsx +0 -128
@@ -1,102 +0,0 @@
1
- // THE HEALTH PASS TIMER. While the hub serves, the whole health pass runs once
2
- // a day, and once at startup when the last whole pass on record is older than
3
- // that. One timer for the repo, no per check schedule: a check's own `ttl` is
4
- // its cadence, and this pass is only the heartbeat that replays an answer
5
- // still fresh and re-measures one that expired.
6
- //
7
- // The run is the hub's own in process runner, the same path as the button, so
8
- // its rows land in the same run record. "When was the last pass" is read off
9
- // that record, which is what makes a `shipkit check` in a terminal count too.
10
-
11
- /** how often the health pass runs while the hub serves */
12
- export const HEALTH_EVERY_MS = 24 * 60 * 60 * 1000
13
-
14
- /** how often the timer looks. The pass itself is daily, this only bounds how
15
- * late after its due time it starts */
16
- export const SCHEDULE_LOOK_MS = 5 * 60 * 1000
17
-
18
- export type ScheduleState = {
19
- everyMs: number
20
- /** when the last whole health pass on record started, null = none */
21
- lastPassAt: number | null
22
- /** when the next one starts: now when it is overdue */
23
- nextAt: number
24
- /** the timer's own pass is running right now */
25
- running: boolean
26
- }
27
-
28
- export type ScheduleDeps = {
29
- now: () => number
30
- /** the last whole health pass on record, whoever ran it */
31
- lastPassAt: () => number | null
32
- /** another run in this process is going: the timer never stacks on it */
33
- busy: () => boolean
34
- /** the pass itself */
35
- run: () => Promise<void>
36
- everyMs?: number
37
- }
38
-
39
- export type TickResult = 'ran' | 'fresh' | 'busy'
40
-
41
- export class HealthSchedule {
42
- readonly everyMs: number
43
- running = false
44
- /** when this timer last STARTED a pass. A pass that dies before it writes
45
- * a record would otherwise be due again at the very next look */
46
- private attemptedAt: number | null = null
47
- private timer: ReturnType<typeof setInterval> | null = null
48
-
49
- constructor(private readonly d: ScheduleDeps) {
50
- this.everyMs = d.everyMs ?? HEALTH_EVERY_MS
51
- }
52
-
53
- /** the newest of the record and this timer's own last attempt */
54
- private lastAt(): number | null {
55
- const rec = this.d.lastPassAt()
56
- if (rec == null) return this.attemptedAt
57
- return this.attemptedAt == null ? rec : Math.max(rec, this.attemptedAt)
58
- }
59
-
60
- due(): boolean {
61
- const last = this.lastAt()
62
- return last == null || this.d.now() - last >= this.everyMs
63
- }
64
-
65
- state(): ScheduleState {
66
- const now = this.d.now()
67
- const last = this.lastAt()
68
- return {
69
- everyMs: this.everyMs,
70
- lastPassAt: this.d.lastPassAt(),
71
- nextAt: last == null ? now : Math.max(now, last + this.everyMs),
72
- running: this.running,
73
- }
74
- }
75
-
76
- /** one look: run the pass when it is due and nothing else is running */
77
- async tick(): Promise<TickResult> {
78
- if (this.running || this.d.busy()) return 'busy'
79
- if (!this.due()) return 'fresh'
80
- this.running = true
81
- this.attemptedAt = this.d.now()
82
- try {
83
- await this.d.run()
84
- } finally {
85
- this.running = false
86
- }
87
- return 'ran'
88
- }
89
-
90
- /** look now, then every SCHEDULE_LOOK_MS. A second start is a no-op */
91
- start(onError: (e: Error) => void, lookMs = SCHEDULE_LOOK_MS): void {
92
- if (this.timer != null) return
93
- const look = (): void => void this.tick().catch((e: Error) => onError(e))
94
- look()
95
- this.timer = setInterval(look, lookMs)
96
- }
97
-
98
- stop(): void {
99
- if (this.timer != null) clearInterval(this.timer)
100
- this.timer = null
101
- }
102
- }
@@ -1,65 +0,0 @@
1
- /* the project gutter. It is FIXED to the left edge, so it takes no cell of
2
- the .app grid, and the page makes room for it by padding, whatever the
3
- grid holds. Colours are hub.css tokens only, spacing is its scale:
4
- tests/hub-css-invariants.test.ts reads this sheet too. */
5
- .app:has(> .pgut){--gut:44px;padding-left:var(--gut)}
6
- .pgut{position:fixed;left:0;top:0;bottom:0;width:var(--gut,44px);z-index:5;display:flex;flex-direction:column;align-items:center;
7
- gap:var(--s1);padding:var(--s2) 0;background:var(--bg);border-right:1px solid var(--line);overflow-y:auto;scrollbar-width:none}
8
- .pgt{position:relative;flex:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;
9
- background:transparent;border:0;border-radius:6px;cursor:pointer}
10
- .pgt:hover{background:var(--panel2)}
11
- .pgt.cur{background:var(--sel)}
12
- .pgt img{width:24px;height:24px;border-radius:5px;object-fit:cover;pointer-events:none}
13
- /* the initial on a hue hashed from the name (projects-svc hueOf) */
14
- .pgi{width:24px;height:24px;border-radius:5px;display:flex;align-items:center;justify-content:center;
15
- font-size:12px;font-weight:600;user-select:none;color:var(--c);background:color-mix(in srgb,var(--c) 22%,var(--panel2))}
16
- .pgi.h1{--c:var(--k1)}
17
- .pgi.h2{--c:var(--k2)}
18
- .pgi.h3{--c:var(--k3)}
19
- .pgi.h4{--c:var(--k4)}
20
- .pgi.h5{--c:var(--k5)}
21
- .pgi.h6{--c:var(--k6)}
22
- /* the face says three things and no more (projects-svc faceOf) */
23
- /* closed in VS Code: the icon dims, the badge does not */
24
- .pgt.dim img,.pgt.dim .pgi{opacity:.45}
25
- /* the one badge: open asks, top right */
26
- .pga{position:absolute;top:0;right:0;min-width:14px;height:14px;padding:0 var(--s0);border-radius:7px;
27
- font-size:9px;font-weight:700;line-height:14px;text-align:center;color:var(--bg);background:var(--warn)}
28
- /* Claude sessions running: a thin edge on the left that breathes */
29
- .pgs{position:absolute;left:0;top:6px;bottom:6px;width:2px;border-radius:1px;background:var(--accent);pointer-events:none;animation:pgpulse 2.4s ease-in-out infinite}
30
- @keyframes pgpulse{0%,100%{opacity:1}50%{opacity:.25}}
31
- @media (prefers-reduced-motion:reduce){.pgs{animation:none}}
32
- /* the hover card: right of the rail, one at a time, never interactive.
33
- Header, then what needs a human, then the label and value rows of the
34
- check page (.wfact .wk .wv from hub.css) */
35
- .pgcard{position:fixed;left:calc(var(--gut,44px) + var(--s2));z-index:20;width:300px;padding:var(--s3);pointer-events:none;
36
- background:var(--panel);border:1px solid var(--line);border-radius:4px;box-shadow:0 6px 20px var(--shadow);font-size:12px;line-height:1.45;color:var(--ink)}
37
- .pgch{display:flex;align-items:center;gap:var(--s2);min-width:0}
38
- .pgcface{flex:none;display:flex}
39
- .pgcface img,.pgcface .pgi{width:20px;height:20px;border-radius:4px;font-size:11px}
40
- .pgcname{flex:1;min-width:0;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
41
- .pgcthis{font-weight:400;font-size:11px;color:var(--dim)}
42
- .pgcver{flex:none;color:var(--dim);font-size:11px;font-variant-numeric:tabular-nums}
43
- .pgcline{margin-top:var(--s1);color:var(--dim);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
44
- .pgcdesc{margin-top:var(--s1);color:var(--dim);font-size:11px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
45
- /* NEEDS YOU: a warn toned bullet, set apart from the facts */
46
- .pgcneeds{margin-top:var(--s2);padding:var(--s1) var(--s2);border-left:2px solid var(--warn);background:var(--panel2);border-radius:0 3px 3px 0}
47
- .pgcneed{display:flex;gap:var(--s1);min-width:0;color:var(--warn)}
48
- .pgcneed.red{color:var(--red);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
49
- .pgcask{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
50
- .pgcmore{flex:none}
51
- .pgcfacts{margin-top:var(--s2)}
52
- .pgcfacts .wk{flex-basis:64px;white-space:nowrap}
53
- .pgcfacts .wv{text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;word-break:normal}
54
- .pgcfacts .wv.warn{color:var(--warn)}
55
- .pgerr{font-size:12px;font-weight:700;color:var(--red);cursor:help}
56
- /* the one time sort by last activity, above the icons */
57
- .pgsort{flex:none;width:32px;height:20px;padding:0;background:transparent;border:1px solid var(--line);border-radius:3px;
58
- color:var(--dim);font-size:12px;line-height:18px;cursor:pointer}
59
- .pgsort:hover{color:var(--ink);border-color:var(--accent)}
60
- /* a drag: the tile being moved fades, and the gap it would land in is a line
61
- drawn on a tile edge, centred in the gap between two tiles */
62
- .pgt.dragging{opacity:.4}
63
- .pgt.dropb::before,.pgt.dropa::after{content:'';position:absolute;left:2px;right:2px;height:2px;border-radius:1px;background:var(--accent);pointer-events:none}
64
- .pgt.dropb::before{top:-3px}
65
- .pgt.dropa::after{bottom:-3px}
@@ -1,128 +0,0 @@
1
- // the project gutter: one icon per repo that shares this hub's house wiki,
2
- // down the far left of the page. The face stays quiet (faceOf): at most one
3
- // badge, the open asks; the icon dims while VS Code does not have the folder
4
- // open; a thin edge pulses while Claude sessions run in it; the selected
5
- // background marks this hub's own repo. Everything else is the hover card,
6
- // shown at once to the right of the icon. A click opens the folder in VS
7
- // Code, a drag reorders the rail, and the button on top sorts it once by
8
- // last activity. Fixed position, so it takes no cell of the page grid
9
- // (project-gutter.css).
10
- import { observer } from 'mobx-react-lite'
11
- import { useEffect, useLayoutEffect, useRef } from 'react'
12
- import type { HubSt } from './hub-st.ts'
13
- import { cardModel, faceOf, hueOf, type Project } from './projects-svc.ts'
14
-
15
- export const Tile = observer(({ app, p, i }: { app: HubSt; p: Project; i: number }) => {
16
- const s = app.projects
17
- const gap = s.shownGap
18
- const face = faceOf(p)
19
- // the drop line rides on a tile edge, so showing it moves nothing
20
- const edge = gap === i ? ' dropb' : gap === i + 1 && i === s.list.length - 1 ? ' dropa' : ''
21
- const cls = `pgt${p.current ? ' cur' : ''}${face.dim ? ' dim' : ''}${s.drag === p.name ? ' dragging' : ''}${edge}`
22
- return (
23
- <button className={cls} aria-label={p.name}
24
- draggable onClick={() => void s.open(p.name)}
25
- onMouseEnter={(e) => {
26
- const r = e.currentTarget.getBoundingClientRect()
27
- s.hoverIn(p.name, r.top + r.height / 2)
28
- }}
29
- onMouseLeave={() => s.hoverOut(p.name)}
30
- onDragStart={(e) => {
31
- e.dataTransfer.effectAllowed = 'move'
32
- e.dataTransfer.setData('text/plain', p.name)
33
- s.dragStart(p.name)
34
- }}
35
- onDragOver={(e) => {
36
- if (s.drag == null) return
37
- e.preventDefault()
38
- const r = e.currentTarget.getBoundingClientRect()
39
- s.dragOver(i, e.clientY - r.top, r.height)
40
- }}
41
- onDrop={(e) => {
42
- e.preventDefault()
43
- s.drop()
44
- }}
45
- onDragEnd={() => s.dragEnd()}>
46
- <Face p={p} />
47
- {face.badge != null ? <b className="pga">{face.badge}</b> : null}
48
- {face.busy ? <i className="pgs" /> : null}
49
- </button>
50
- )
51
- })
52
-
53
- const Face = ({ p }: { p: Project }) => (p.icon != null
54
- ? <img src={p.icon} alt="" draggable={false} />
55
- : <span className={`pgi h${hueOf(p.name)}`}>{(p.name.match(/[a-zA-Z]/)?.[0] ?? p.name.charAt(0)).toUpperCase()}</span>)
56
-
57
- /** the card for one repo: header, what needs a human, then the fact rows,
58
- * in the label and value shape the check page's WHAT widget uses */
59
- export const Card = ({ p, now }: { p: Project; now: number }) => {
60
- const m = cardModel(p, now)
61
- return (
62
- <>
63
- <div className="pgch">
64
- <span className="pgcface"><Face p={p} /></span>
65
- <b className="pgcname">{m.head.name}{m.head.current ? <span className="pgcthis"> this hub</span> : null}</b>
66
- {m.head.version == null ? null : <span className="pgcver">v{m.head.version.replace(/^v/, '')}</span>}
67
- </div>
68
- {m.head.branch == null ? null : <div className="pgcline">{m.head.branch}</div>}
69
- {m.head.description == null ? null : <div className="pgcdesc">{m.head.description}</div>}
70
- {m.needs == null ? null : (
71
- <div className="pgcneeds">
72
- {m.needs.asks == null ? null : (
73
- <div className="pgcneed">
74
- <span className="pgcask">{m.needs.asks.count} ask{m.needs.asks.count === 1 ? '' : 's'}: {m.needs.asks.titles.join(' · ')}</span>
75
- {m.needs.asks.more > 0 ? <span className="pgcmore">+{m.needs.asks.more}</span> : null}
76
- </div>
77
- )}
78
- {m.needs.gate == null ? null : <div className="pgcneed red">{m.needs.gate}</div>}
79
- </div>
80
- )}
81
- {m.facts.length === 0 ? null : (
82
- <div className="pgcfacts">
83
- {m.facts.map((f) => (
84
- <div key={f.k} className="wfact">
85
- <span className="wk">{f.k}</span>
86
- <span className={`wv${f.tone == null ? '' : ` ${f.tone}`}`}>{f.v}</span>
87
- </div>
88
- ))}
89
- </div>
90
- )}
91
- </>
92
- )
93
- }
94
-
95
- /** the one hover card: right of the rail, centred on the icon's row, then
96
- * clamped into the viewport once its own height is known */
97
- export const HoverCard = observer(({ app }: { app: HubSt }) => {
98
- const s = app.projects
99
- const p = s.hovered
100
- const y = s.hover?.y ?? 0
101
- const ref = useRef<HTMLDivElement>(null)
102
- useLayoutEffect(() => {
103
- const el = ref.current
104
- if (el == null) return
105
- const h = el.offsetHeight
106
- el.style.top = `${Math.max(8, Math.min(y - h / 2, window.innerHeight - h - 8))}px`
107
- })
108
- if (p == null) return null
109
- return (
110
- <div ref={ref} className="pgcard" role="tooltip">
111
- <Card p={p} now={Date.now()} />
112
- </div>
113
- )
114
- })
115
-
116
- export const ProjectGutter = observer(({ app }: { app: HubSt }) => {
117
- useEffect(() => app.projects.start(), [app])
118
- const s = app.projects
119
- if (s.list.length === 0 && s.error === '') return null
120
- return (
121
- <nav className="pgut" aria-label="projects">
122
- <button className="pgsort" title="sort by last activity, once: the result is the new manual order" onClick={() => s.sortByActivity()}>⇅</button>
123
- {s.list.map((p, i) => <Tile key={p.name} app={app} p={p} i={i} />)}
124
- {s.error !== '' ? <span className="pgerr" title={s.error}>!</span> : null}
125
- <HoverCard app={app} />
126
- </nav>
127
- )
128
- })