refactor: flatten monorepo to a single @vzn/run package - #1
Merged
Conversation
The workspace only had one package, so the monorepo scaffolding was pure overhead. Move packages/run/src to ./src, merge both package.json files into a single @vzn/run at the root, collapse tsconfig.base.json + the leaf tsconfig.json into one, drop pnpm-workspace.yaml, and prune workspace-only .npmrc settings. Regenerated lockfile. Build + 114 tests pass.
Exelord
pushed a commit
that referenced
this pull request
May 13, 2026
… workers, bottlenecks, queue, task detail Spec expansion + companion architect design doc. ## Spec (docs/design/tui.md) — extended Original spec covered a single dashboard layout. Extended to five top-level views (`1`-`5` keys): - Overview — the original dashboard. - Graph — full-screen project DAG (indented topological tree with cross-project ▶ arrows, status icons, per-task progress bars). - Workers — N concurrency slots with per-slot 30s heatmap of busy/idle, utilization-over-time sparkline, parallelization % gauge. - Bottlenecks — critical path (longest dependency chain by duration), tasks blocking the most dependents, slow-vs-historical callouts, cache-miss impact ranking. - Queue — ready-but-waiting vs blocked-by-deps, queue-throughput sparkline. Plus a Task Detail overlay (Enter from any view): full task introspection with command, hash, inputs, outputs, deps, dependents, historical stats from cache.db's runs table (last N runs, avg, p50, p99, success rate, cache-hit rate), estimated progress bar with ETA, live log pane. Group tasks (▣ icon) and persistent tasks (⚡ icon) get first-class rendering — groups roll up child status and are Space-expandable; persistent show "ready since X" badges and a header ⚡<n> counter. Header gains a parallel <P>% gauge (`floor(running / capacity * 100)`) with threshold-tinted color (green ≥80, yellow 50-79, red <50). Data-model section grew to include: worker slot map, per-slot 30-sample heatmap ring buffers, historical-stats cache (batched SQL at runStart), critical-path computation (recomputed each taskComplete), group rollup state. 10 open questions for the architect (#1-3 from original, #4-10 new). ## Design doc (docs/design/tui-design.md) — appended §11 Architect pass returned with concrete decisions for the extended scope: - Historical stats: ONE batched SQLite query in prepareRun() returning a HistoryTable threaded into RunStartEvent. Capped at 50 rows per (project, task); client-side slice to 10 for Task Detail. Available to every Observer consumer, not just the TUI. Never-run tasks render `▱▱▱▱ ?`. - Critical path: topo-order DP recomputed on every taskComplete (O(V+E), microseconds at ≤1000 nodes — no throttling needed). Persistent tasks excluded. - Worker slots: runGraph allocates from a lowest-free-index free-list; execute() callback gains slot: number; Observer.taskStart grows the same field. Small scheduler change. - Heatmap sampler: TUI-owned setInterval(1000ms) combined with the existing 1 Hz sparkline tick. Uint8Array(30) per slot. - Multi-view: single store, state.activeView switches between view components under src/tui/views/. Task Detail + Help under src/tui/overlays/. Filter state per-view (filtering "test" in Workers is incoherent). - Graph view: indented topo tree, cross-project deps inline. Critical- path overlay is a left-margin marker. - Bottlenecks: four pure selectors. New Observer event cacheProbe fired from execute-task.ts populates cacheStatus for the cache-miss- impact ranking. - Queue: ready vs blocked via dep-status predicate. Throughput sparkline counts waiting→ready transitions per second. - Groups: transitive children precomputed at runStart; reverse-walk updates rollup counters on taskComplete. Per-group expanded flag survives view switches. Implementation order in §11: scheduler slot allocation first (unblocks the Observer event), then cacheProbe event, then batched history SQL, then state/reducer extensions, then selectors, then view components, then overlays, then critical-path recompute, then the 1 Hz sampler. No source changes in this commit — design only. Phase 1 implementation will land in a separate PR.
Exelord
pushed a commit
that referenced
this pull request
May 13, 2026
User decision: use OpenTUI. Updates both spec and design doc to reflect the renderer change. Ink remains documented as the fallback if the OpenTUI bun-compile gate fails. ## Why OpenTUI over Ink - Bun-first by design — renderer is a small native lib invoked via bun:ffi. No React-reconciler + Yoga (WASM) layout pass per frame; diff-based partial redraws happen in native code. - Sidesteps the known yoga.wasm bun-compile bug (bun#13552 / bun#2034) that Ink hits inside a compiled binary. - Built by the opencode team — its primitives are tuned for the same use case the spec calls "tiny stark level god." - React ergonomics preserved via @opentui/react; component-tree mental model is identical to Ink. ## What changed in the spec (tui.md) - Library-choice subsection rewritten with OpenTUI rationale. - New "Compile-gate experiment" subsection with two paths (sibling- file install vs embed+extract shim) to validate bun build --compile works with the native lib before merging Phase 1. - Module layout renames `App.tsx`'s import path: a new `src/tui/tui-shim.ts` is the only place that imports `@opentui/react`. All other components import from the shim — so swapping to Ink later is one file. - Lifecycle step 2 says "Mount the OpenTUI app rooted at <App /> via @opentui/react"; step 3 explains the renderer does native partial redraws (still throttle React component churn at 30 Hz). - Testing section rewritten: pure-function layer is unchanged (reducer, selectors, sparkline math). Component-layer plan depends on whether @OpenTui ships a frame-capture testing helper; if not, we write a ~50-line stub renderer. - Open questions #1, #13, #14 updated for OpenTUI. ## What changed in the design doc (tui-design.md) - §1 question 7 answers "Bun compile + OpenTUI" with the native-lib bundling decision instead of yoga.wasm. - §2 rewritten as "OpenTUI vs Ink vs hand-roll": - Recommendation: OpenTUI; Ink fallback if compile-gate fails for both bundling paths. - Why-OpenTUI-over-Ink rationale. - Why-Ink-stays-the-fallback (battle-tested, rich ecosystem). - Compile-gate experiment expanded: try sibling-file install first, then embed+extract. 30-60 min prototype. - Startup cost estimate revised: ~30-60ms cold (faster than Ink's ~80-120ms). - Bundle size: ~150-250 KB JS + 200-400 KB native lib per arch = ~400-650 KB compiled-binary contribution. Comparable to Ink. - Sed-swept remaining Ink references throughout the rest of the doc (Observer wiring, store/reducer notes, lifecycle, resize, testing, module layout, extension-point notes) → either reworded as renderer-agnostic or explicitly OpenTUI. ## What's unchanged The Observer surface, store / reducer architecture, fallback decision (`shouldUseTui`), lifecycle (alt-screen, SIGINT, runEnd), multi-view structure, all 5 views, Task Detail overlay, historical-stats SQL pull, critical-path computation, worker-slot allocation in the scheduler, and the implementation order are unchanged — they're all renderer-agnostic by design. The compile-gate experiment is the gate before merging Phase 1. If OpenTUI's native lib can't be bundled cleanly (neither sibling- file nor embed+extract works), Ink is the well-trodden fallback. 414 tests still pass; format + lint clean.
2 tasks
Exelord
pushed a commit
that referenced
this pull request
Jul 5, 2026
Owner (2026-07-05): 'No auto input and no global'. Mark both as REJECTED non-goals so they aren't re-proposed. Global inputs/env: TS configs compose (a shared preset spread into each config is the mechanism; the migrate path already emits vx-preset.ts) — same rationale as the earlier rejected named-inputs machinery. Auto-input tracing: was already out-of-scope (2026-06, a multi-OS native-helper project incompatible with vx's no-build-step distribution); reconfirmed. Explicit cache.inputs stays the contract (Architecture principle #1).
Exelord
pushed a commit
that referenced
this pull request
Jul 10, 2026
The doc half of native-cache-wire-2026-07 Phases B+C. Live docs stop describing the retired Turbo client / VX_REMOTE_CACHE_* env hatch and describe the seams instead: - comparison.md: the 'Remote cache wire' row reads vx-native /v1/cache (plugin-driven; Turbo = third-party plugin story); gaps #1 rewritten around the shipped wire; the HMAC row becomes the always-on structural x-vx-digest integrity. - cli.md: the env-driven remote-cache section becomes 'Remote cache (plugin-driven)' pointing at cloud() + the extensibility recipe; the VX_REMOTE_CACHE_* table is gone; the serve section + route table speak /v1/cache with the digest/duration headers; vx info drops the remote-cache row. - architecture/caching/execution/flows/patterns/README: the cache cluster describes RemoteCacheLayer as the wire seam, the layer resolution (injection > plugin > bare local), and the native wire as the first-party implementation. - modules/: remote-cache.md + remote-cache-setup.md deleted (their sources are gone); layered-cache.md rewritten to the current surface (it still described the v13 tar.gz/stage-dir era); index + README + prepare.md synced. - Site guides: remote-caching.md replaces the Turbo-compatible section with the bring-your-own-wire story + an artifact-integrity section; extensibility.md gains the full third-party Turbo plugin recipe from the design appendix (proof the seam suffices) and its acmeCache example now implements RemoteCacheLayer instead of constructing the deleted RemoteCache; plugins.md's cache recipe likewise; ci/distributed-ci/self-hosting/dashboard/from-turborepo re-point endpoints + env mentions. - design/presigned-artifacts-2026-07.md gets a status note (wire premise superseded; the blob-offload phasing survives via the native client's one-hop 307 follow); design/remote-cache.md is marked RETIRED as the historical record. Design docs otherwise stay frozen historical records; the generated apps/docs copies of docs/ regenerate on the next site build.
Exelord
pushed a commit
that referenced
this pull request
Jul 13, 2026
Audit cycle 3, wave 2 — three findings actioned, one discovery documented. CORE-1 (real bug, experimental --predict path): the graph Map inserts a dependent BEFORE the deps it pulls in (pre-order from the requested roots), and computePredictedPriorities' old traversal never actually pushed anything (every unvisited node was already on the seeded stack), so it folded in reverse-insertion order — every upstream's memo was computed before its dependents existed and its priority collapsed to its own historical duration. LPT ordering silently lost its lookahead on every real graph; the existing tests passed nodes in topo order, which made the broken scan accidentally correct. The fold is now an explicit Kahn pass over the dependents relation (order-independent, O(N+E), iterative). Two regression tests pin real pre-order insertion + a diamond whose long branch hangs off a short shared head — both fail on the old code. DX-2: when a cloud connection resolved, the GitHub job summary gains "▸ Open this run in the vx dashboard → <origin>/#/runs/<runId>" and the check run sets details_url to the same page — a red check is one click from the run's logs and artifacts. Absent a connection (summary-only mode) nothing changes. DX-5a: the UI vite dev server now proxies /v1, /health, /mcp, /events and /stream to the platform (VX_CLOUD_DEV_PROXY overrides the localhost:4321 default), making UI development same-origin so the HttpOnly session cookie and CSRF header work exactly like the hosted build; the UI README was rewritten from the retired "vx serve --ui reads cache.db" story to the compose-platform recipe. Documented in docs/design/audit-cycle-2026-07.md: P4/P5/P7/C3 are MOOT — the queue protocol has zero server-side implementation left (the P4-server fold deleted it), /v1/meta never advertises `queue`, /version 404s, so the spawn bar, queueRun, RunSession and the foreign-jobs poll are unreachable dead code. Optimizing them would tune code that cannot execute; the doc proposes repurposing the live surface onto the platform's org-scoped /stream (restores dashboard lens #1) vs deleting the machinery, as an owner decision for cycle 4.
Exelord
pushed a commit
that referenced
this pull request
Jul 25, 2026
The run-detail triage card (dev-scenarios #1) classifies each failed task as flaky / pre-existing / new-failure, but the place a developer actually stands when a check goes red is the PR page — so carry the verdict there. When the run is connected to a platform AND a GitHub surface is active (job summary or check), a red run fetches GET /v1/triage/:runId once and annotates each failed row's status cell: 🎲 flaky — not this change (same key passed N×) 📌 already broken on the default branch 🆕 new failure — this run changed its inputs Formatter: GithubSummaryOptions.triage (per-taskId verdict map) + triageMarker consulted ONLY by the failed branch of statusCell, so a green row never changes. Plugin: fetchTriage after the ingest POST (the triage query needs this run's rows), bearer-authenticated, clearable 5s timeout, undefined on ANY problem — never-fail and purely additive: no connection / green run / fetch error renders the rows byte-identically to before. Both consumers (appendGithubSummary + postGithubCheck) share the same GithubSummaryOptions, so the check and the job summary always agree. Pinned: 3 formatter cases (marker per verdict + green-row-never- consults, bare new-failure when keyChanged is null, no-map additive) + 2 plugin e2e against a fake serve (verdict lands in the written summary file with the bearer on the triage GET; a triage 500 degrades to the plain failed cell). Docs: guides/ci.md PR-checks section + cloud/api.md route row name the consumer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW7aso5j5CrBo7cjyET23D
Exelord
pushed a commit
that referenced
this pull request
Jul 26, 2026
…anks
Measured first. A probe seeding 1000 projects x 10 tasks (100k task_runs)
through the real ingest wire showed the failure is correctness, not
latency: /v1/projects?limit=500 returns a PAGE, and three dashboard
sources did "fetch a page then find in it", so
- every project past the page rendered a BLANK detail page,
- the ranking card claimed "vs 500 projects" when there were 1000, and
ranked within the page, and
- the Projects filter box could never reach a tail project.
Payloads were fine (103 KB / 143 ms for 500 projects). The data was wrong.
Fixed where it can be right — server-side. listProjects gains ILIKE
search and exact-name fetch (the point lookup); countProjects is the true
denominator; rankProject computes per-axis ranks with window functions
over every project in one query, returning top-N per axis plus the named
project with its TRUE rank. /v1/projects answers {projects, total}; new
/v1/projects/rank. The client-side ranker is replaced by a thin shaper,
and the Projects table now says "showing N of M" instead of implying the
page is the workspace.
The guard caught a bug in the fix itself: = ANY($1) binds a JS array as a
malformed array literal on this driver, which would have 500'd the
point-lookup route. IN ${sql(array)} is the form, per provenanceForHashes.
Pinned by a scale test seeding 620 projects: the tail project resolves by
exact fetch and by search, ranks #1 by avg exec with total === 620, and a
mid-pack project reports a true rank past the top-8 window.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RW7aso5j5CrBo7cjyET23D
Exelord
pushed a commit
that referenced
this pull request
Jul 30, 2026
REVERSES a design decision I recorded earlier today, and the reversal is the point of this commit. The plan was to parse NODE_OPTIONS and fold its code-injecting flags (--require/--import/--loader/--conditions) into the cache key, on the grounds that those genuinely change what a task emits under an unchanged key. That is true, and it is still the wrong fix for vx. Architecture principle #1 is "explicit over magical", and this repo's owner has rejected inference twice on exactly that basis — globalInputs and auto-input tracing. A hidden key input derived by parsing an environment variable is the same shape: vx would be guessing which part of NODE_OPTIONS matters. And singling it out is inconsistent, because LC_ALL changes `sort` collation and CI/TERM/FORCE_COLOR change the stdout vx caches and replays. The whole allowlist has this property. vx cannot hash the allowlist either: PATH, HOME and TERM differ on every machine, so hashing them means a laptop and a CI runner can NEVER share a remote cache entry — which is the entire point of a remote cache. So the honest position, and the one already consistent with how vx refuses to infer inputs anywhere else: the essentials are a deliberate, documented, un-hashed pass-through, and a build whose output depends on one declares it in `cache.inputs.env`. That mechanism already exists and already works. What was missing was saying so — `docs/schema.md` spelled out "NOT folded into the cache key" for passThrough one bullet below while the essentials bullet said nothing at all, which reads as an oversight rather than a decision. Now documented with the three groups that can change a task's OUTPUT named explicitly, the reason they are not hashed, and the one-line escape hatch. Pinned so neither direction can be reversed silently. Four mutations, each verified applied: dropping NODE_OPTIONS or LC_ALL from the allowlist fails 1 each (the forwarding half must not regress — a CI raising the heap depends on it); forwarding everything from the host fails 3 (without the boundary, "not hashed" would be a determinism disaster rather than a trade); and folding NODE_OPTIONS into the key — the change this commit decided against — fails 1. A test of mine was VACUOUS and I caught it by reading, not by mutation: it called `cache.key()` twice with identical arguments and asserted they matched. `key()` takes envValues as a parameter and never reads process.env, so it was equal whatever the essentials did. Rewritten through `resolveInputs`, where the decision actually lives. And one mutation NEVER APPLIED on its first attempt — the pattern missed the real signature, so the "pass" it produced proved nothing. Caught by checking `git diff --stat` before reading the result, which is exactly what that rule is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW7aso5j5CrBo7cjyET23D
This was referenced Jul 30, 2026
Exelord
added a commit
that referenced
this pull request
Aug 24, 2026
The fourth output mode (Turbo parity, comparison gap #7): one line per task — outcome word, task id, cache key — with zero log output. The run's audit trail of which key each task resolved to. discardsOutput covers the new mode so output the mode promises never to print is never buffered, the same reasoning none already had. Pinned byte-exact (the exact-expected-set rule: a mangled leak sails past not.toContain) across success/hit/failure/skip, plus parse pins for both spellings and the typo message naming all four modes. The comparison doc audit that sourced this also surfaced two stale cloud references, corrected in place: gap #1 advertised the removed platform's S3 blob backend (now points at the archived design doc), and gap #12 claimed the deleted dashboard covers last-run replay — its removal raises that gap's value, and the entry now says so.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The workspace only contained one package (
@vzn/run), so the monorepo scaffolding was pure overhead. This flattens to a single-package layout.packages/run/src/*→src/*(git-detected as renames, 100% similarity)packages/run/package.json+ rootvzn-monorepopackage into one@vzn/runat the roottsconfig.base.json+ leaftsconfig.jsoninto one roottsconfig.json(dropcomposite— no project references left)pnpm-workspace.yaml.npmrcsettings (shared-workspace-lockfile,link-workspace-packages,prefer-workspace-packages,save-workspace-protocol)packages/run/src/→src/) indocs/architecture.md,docs/schema.md,docs/modules/README.mdpnpm-lock.yamlTest plan
pnpm installsucceeds with regenerated lockfilepnpm buildproducesdist/pnpm test— all 114 tests passpnpm format:check— cleanhttps://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
Generated by Claude Code