From 2b8f83d2dd04bb35fd44e6efbe6da5760ab81e67 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Mon, 28 Sep 2026 17:44:27 +0700 Subject: [PATCH] refactor(lanes): drop the workflow skills and Superpowers link; pipelines go dormant The plugin stops shipping its workflow layer: - skills/ship-feature and skills/ship-feature-lane, the 18 stage/ship slash commands (plugin.json keeps agenthub-doctor/update/open), and all four agents/ (qc-local, senior-gate-reviewer, backend-reviewer, frontend-reviewer). - CLI `agenthub skills install`, `agenthub lanes agents install`, `agenthub ship-full|ship-quick`; routes POST /api/skills/install and POST /api/lanes/:id/agents/install; the Settings button, the lane Tools menu item and the Add-lane setup step that called them. - The Superpowers auto-install: server/lib/skill-plugins.js, the bootstrap's autoInstallSkillPlugins / skillPluginsSettled gate and the doctor row. Pipelines are kept but dormant: server/data/pipelines/ is deleted, so no template ships. `default` resolves to EMPTY_PIPELINE (no nodes, progress 0), createLane still accepts it, POST /api/lanes/:id/stage answers 400 ENOPIPELINE, and the Workspace hides the stage timer, pickers, PipelineMap and strip stage chip for a lane with no nodes. A template dropped into DASHBOARD_PIPELINES_DIR turns it all back on. The server suite loads its templates from server/__tests__/fixtures/pipelines/. Docs: CLAUDE.md (marker, retired rules), README(.vi), INSTALL, ARCHITECTURE, docs/LANES|PLUGINS|CLI|API, agenthub-introduction.html. --- .claude-plugin/plugin.json | 20 +- ARCHITECTURE.md | 17 +- CLAUDE.md | 12 +- INSTALL.md | 4 +- README.md | 53 +-- README.vi.md | 41 +- agenthub-introduction.html | 87 +--- agents/backend-reviewer.md | 27 -- agents/frontend-reviewer.md | 27 -- agents/qc-local.md | 74 ---- agents/senior-gate-reviewer.md | 52 --- bin/agenthub.js | 157 +------ client/src/components/lanes/AddLaneModal.tsx | 9 +- client/src/components/lanes/LaneCard.tsx | 33 +- client/src/components/lanes/LaneStripCard.tsx | 8 +- .../lanes/__tests__/AddLaneModal.test.tsx | 7 +- .../lanes/__tests__/LaneCard.test.tsx | 12 - client/src/i18n/locales/en/lanes.json | 3 - client/src/i18n/locales/en/settings.json | 4 +- client/src/i18n/locales/vi/lanes.json | 3 - client/src/i18n/locales/vi/settings.json | 4 +- client/src/lib/api.ts | 7 - client/src/pages/Settings.tsx | 23 -- client/src/pages/Workspace.tsx | 13 +- .../screens.snapshot.test.tsx.snap | 35 +- docs/API.md | 16 +- docs/CLI.md | 9 +- docs/LANES.md | 389 +++--------------- docs/PLUGINS.md | 84 +--- plugins/agenthub/commands/agenthub-doctor.md | 6 - plugins/agenthub/commands/done.md | 23 -- plugins/agenthub/commands/e2e-passed.md | 23 -- plugins/agenthub/commands/e2e.md | 23 -- plugins/agenthub/commands/gate.md | 23 -- plugins/agenthub/commands/implement.md | 23 -- plugins/agenthub/commands/intake.md | 23 -- plugins/agenthub/commands/merged.md | 23 -- plugins/agenthub/commands/plan.md | 23 -- plugins/agenthub/commands/pr-open.md | 23 -- plugins/agenthub/commands/publishing.md | 23 -- plugins/agenthub/commands/qc-plan.md | 23 -- plugins/agenthub/commands/qc.md | 23 -- plugins/agenthub/commands/reported.md | 23 -- plugins/agenthub/commands/review.md | 23 -- plugins/agenthub/commands/ship-full.md | 23 -- plugins/agenthub/commands/ship-quick.md | 23 -- plugins/agenthub/commands/tests.md | 23 -- plugins/agenthub/commands/watching-pr.md | 23 -- scripts/plugin-bootstrap.js | 54 --- scripts/plugin-doctor.js | 42 +- scripts/test-server.js | 9 +- .../fixtures}/pipelines/default.json | 52 +-- .../fixtures}/pipelines/ship-feature.json | 70 +--- server/__tests__/lane-agents.test.js | 88 +--- server/__tests__/lanes-cli.test.js | 36 +- server/__tests__/pipelines-dormant.test.js | 82 ++++ server/__tests__/plugin-bootstrap.test.js | 30 -- .../plugin-doctor-skill-plugins.test.js | 123 ------ server/__tests__/plugins-marketplace.test.js | 16 - server/__tests__/skill-plugins.test.js | 289 ------------- server/__tests__/skills-install.test.js | 83 ---- server/index.js | 2 - server/lib/lane-agents.js | 69 +--- server/lib/lanes.js | 5 +- server/lib/pipelines.js | 26 +- server/lib/skill-plugins.js | 239 ----------- server/lib/skills-install.js | 36 -- server/routes/lanes.js | 34 +- server/routes/skills.js | 28 -- skills/ship-feature-lane/SKILL.md | 225 ---------- skills/ship-feature/SKILL.md | 321 --------------- .../references/feature-checklist.md | 21 - 72 files changed, 325 insertions(+), 3305 deletions(-) delete mode 100644 agents/backend-reviewer.md delete mode 100644 agents/frontend-reviewer.md delete mode 100644 agents/qc-local.md delete mode 100644 agents/senior-gate-reviewer.md delete mode 100644 plugins/agenthub/commands/done.md delete mode 100644 plugins/agenthub/commands/e2e-passed.md delete mode 100644 plugins/agenthub/commands/e2e.md delete mode 100644 plugins/agenthub/commands/gate.md delete mode 100644 plugins/agenthub/commands/implement.md delete mode 100644 plugins/agenthub/commands/intake.md delete mode 100644 plugins/agenthub/commands/merged.md delete mode 100644 plugins/agenthub/commands/plan.md delete mode 100644 plugins/agenthub/commands/pr-open.md delete mode 100644 plugins/agenthub/commands/publishing.md delete mode 100644 plugins/agenthub/commands/qc-plan.md delete mode 100644 plugins/agenthub/commands/qc.md delete mode 100644 plugins/agenthub/commands/reported.md delete mode 100644 plugins/agenthub/commands/review.md delete mode 100644 plugins/agenthub/commands/ship-full.md delete mode 100644 plugins/agenthub/commands/ship-quick.md delete mode 100644 plugins/agenthub/commands/tests.md delete mode 100644 plugins/agenthub/commands/watching-pr.md rename server/{data => __tests__/fixtures}/pipelines/default.json (82%) rename server/{data => __tests__/fixtures}/pipelines/ship-feature.json (84%) create mode 100644 server/__tests__/pipelines-dormant.test.js delete mode 100644 server/__tests__/plugin-doctor-skill-plugins.test.js delete mode 100644 server/__tests__/skill-plugins.test.js delete mode 100644 server/__tests__/skills-install.test.js delete mode 100644 server/lib/skill-plugins.js delete mode 100644 server/lib/skills-install.js delete mode 100644 server/routes/skills.js delete mode 100644 skills/ship-feature-lane/SKILL.md delete mode 100644 skills/ship-feature/SKILL.md delete mode 100644 skills/ship-feature/references/feature-checklist.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 09246b3c..354e9c9c 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -12,25 +12,7 @@ "commands": [ "./plugins/agenthub/commands/agenthub-doctor.md", "./plugins/agenthub/commands/agenthub-update.md", - "./plugins/agenthub/commands/agenthub-open.md", - "./plugins/agenthub/commands/intake.md", - "./plugins/agenthub/commands/plan.md", - "./plugins/agenthub/commands/implement.md", - "./plugins/agenthub/commands/tests.md", - "./plugins/agenthub/commands/e2e.md", - "./plugins/agenthub/commands/e2e-passed.md", - "./plugins/agenthub/commands/review.md", - "./plugins/agenthub/commands/qc-plan.md", - "./plugins/agenthub/commands/qc.md", - "./plugins/agenthub/commands/gate.md", - "./plugins/agenthub/commands/publishing.md", - "./plugins/agenthub/commands/pr-open.md", - "./plugins/agenthub/commands/reported.md", - "./plugins/agenthub/commands/watching-pr.md", - "./plugins/agenthub/commands/merged.md", - "./plugins/agenthub/commands/done.md", - "./plugins/agenthub/commands/ship-full.md", - "./plugins/agenthub/commands/ship-quick.md" + "./plugins/agenthub/commands/agenthub-open.md" ], "hooks": { "PreToolUse": [ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 840c764c..1a6c764c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -606,7 +606,7 @@ graph LR | `/activity` | ActivityFeed | `GET /api/events?limit=100` — click row to expand inline payload; "Session →" button navigates to `/sessions/:id` | | `/workflows` | Workflows | `GET /api/workflows?status=active\|completed`, `GET /api/workflows/session/:id` + WebSocket auto-refresh (3s debounce) | | `/cc-config` | *(redirect)* | Redirects to `/settings?tool=claude-code` — the config explorer moved under the Settings hub as one of its per-tool panels (see `/settings` below) | -| `/run` | Workspace | Merged workspace page combining lanes and runs. Attaches to a tmux-backed pseudoterminal tied to a lane: the UI selects a lane, calls `POST /api/run` with that lane's `id`, and receives a `runId` + tmux session name. The Workspace displays a horizontal lane strip at the top, the selected lane's pipeline map, and a real interactive terminal (xterm.js) fed by `/ws-pty/:runId` binary frames below. `TerminalView` takes the keyboard on mount (`term.focus()`), so typing in a lane reaches tmux without clicking the pane first, and it reports the server's `exit` frame to its owner through `onExit` — `LaneConsolePane` runs `newRun()` there, so a pane that ends (a double Ctrl+C out of `claude`, `exit`, a crash) swaps straight back to the run setup form instead of leaving a dead terminal behind a "New run" button. It re-attaches 1.5 s after any unexpected socket close (server restart included) and stops only on that `exit` frame — a dead socket used to leave the pane frozen on its last frame, looking live while swallowing every keystroke and mouse report. It also calls `preventDefault()` on Shift+Tab (keeping xterm's own handling): xterm emits `ESC[Z` for that combo but, unlike plain Tab, never marks the event cancelled, so the browser's default moved focus out of the terminal and the pane saw only the first press — `shift+tab to cycle` (claude's permission mode) looked dead in the browser while working in a real terminal. It also installs a custom wheel handler that returns `false`: without it, xterm falls back on an alt-screen buffer to translating each wheel notch into a cursor-key press (`ESC[A`/`ESC[B`), which a `claude` pane reads as arrow up/down and uses to walk the prompt history instead of scrolling. That handler suppresses only the emulation branch — real mouse reports are emitted by a separate listener xterm registers when the pane's program enables mouse tracking. Its xterm viewport scrollbar is hidden by design (`.xterm-viewport` rule in `client/src/index.css`): tmux repaints the whole pane, so xterm's scrollback is always empty and scrolling happens via mouse reports forwarded to the pane's program, not the DOM. Pre-flight: `GET /api/run/{tmux,binary,cwds,files}` for tmux availability + `claude` binary check + `@`-file autocomplete. Start/resume: `POST /api/run` (requires `laneId`; optionally accepts `prompt` to send immediately); `GET /api/run/:id` (returns handle); `DELETE /api/run/:id` (stops). History: `GET /api/run/history?laneId=` lists only that lane's runs. PTY streaming: `/ws-pty/:runId` delivers raw PTY frames as binary WebSocket frames — no JSON envelope overhead, direct to xterm.js for live rendering; the same tmux session can have multiple simultaneous clients (browser Workspace, `agenthub lanes shell` CLI, other tools), all synced live. Lane self-heal: `GET /api/lanes/:id` auto-corrects `run_id`/`status` if the tmux session has been killed externally. Tier 1 TUI parity: tmux session is a real shell, not headless — supports editors, pagers, interactive subcommands. **The console never writes a lane's stage** — stage moves only through `agenthub stage` commands. Live / Offline indicator next to the title | +| `/run` | Workspace | Merged workspace page combining lanes and runs. Attaches to a tmux-backed pseudoterminal tied to a lane: the UI selects a lane, calls `POST /api/run` with that lane's `id`, and receives a `runId` + tmux session name. The Workspace displays a horizontal lane strip at the top, the selected lane's pipeline map (only while a pipeline template is installed — none ships), and a real interactive terminal (xterm.js) fed by `/ws-pty/:runId` binary frames below. `TerminalView` takes the keyboard on mount (`term.focus()`), so typing in a lane reaches tmux without clicking the pane first, and it reports the server's `exit` frame to its owner through `onExit` — `LaneConsolePane` runs `newRun()` there, so a pane that ends (a double Ctrl+C out of `claude`, `exit`, a crash) swaps straight back to the run setup form instead of leaving a dead terminal behind a "New run" button. It re-attaches 1.5 s after any unexpected socket close (server restart included) and stops only on that `exit` frame — a dead socket used to leave the pane frozen on its last frame, looking live while swallowing every keystroke and mouse report. It also calls `preventDefault()` on Shift+Tab (keeping xterm's own handling): xterm emits `ESC[Z` for that combo but, unlike plain Tab, never marks the event cancelled, so the browser's default moved focus out of the terminal and the pane saw only the first press — `shift+tab to cycle` (claude's permission mode) looked dead in the browser while working in a real terminal. It also installs a custom wheel handler that returns `false`: without it, xterm falls back on an alt-screen buffer to translating each wheel notch into a cursor-key press (`ESC[A`/`ESC[B`), which a `claude` pane reads as arrow up/down and uses to walk the prompt history instead of scrolling. That handler suppresses only the emulation branch — real mouse reports are emitted by a separate listener xterm registers when the pane's program enables mouse tracking. Its xterm viewport scrollbar is hidden by design (`.xterm-viewport` rule in `client/src/index.css`): tmux repaints the whole pane, so xterm's scrollback is always empty and scrolling happens via mouse reports forwarded to the pane's program, not the DOM. Pre-flight: `GET /api/run/{tmux,binary,cwds,files}` for tmux availability + `claude` binary check + `@`-file autocomplete. Start/resume: `POST /api/run` (requires `laneId`; optionally accepts `prompt` to send immediately); `GET /api/run/:id` (returns handle); `DELETE /api/run/:id` (stops). History: `GET /api/run/history?laneId=` lists only that lane's runs. PTY streaming: `/ws-pty/:runId` delivers raw PTY frames as binary WebSocket frames — no JSON envelope overhead, direct to xterm.js for live rendering; the same tmux session can have multiple simultaneous clients (browser Workspace, `agenthub lanes shell` CLI, other tools), all synced live. Lane self-heal: `GET /api/lanes/:id` auto-corrects `run_id`/`status` if the tmux session has been killed externally. Tier 1 TUI parity: tmux session is a real shell, not headless — supports editors, pagers, interactive subcommands. **The console never writes a lane's stage** — stage moves only through `agenthub stage` commands. Live / Offline indicator next to the title | | `/settings` | SettingsHub | Tabbed shell (`?tool=` query param picks the active tab, `PageTabs`) hosting the dashboard's own settings plus one configuration panel per AI tool. **dashboard** tab (default, no `?tool=`) → `Settings`: an **Appearance** section first (mode + named theme, `localStorage` only), `GET /api/settings/info`, `GET /api/pricing`, `GET /api/pricing/cost` + `localStorage` for notification prefs; hosts the **Remote Data Sources** panel (`components/RemoteSources.tsx`) — CRUD + test + sync over `/api/remote-sources`, live status from `remote_source.status` WS messages. **claude-code** tab (`?tool=claude-code`) → `CcConfig`: 12-tab Claude Code configuration explorer. Reads via `GET /api/cc-config/{overview,skills,agents,commands,output-styles,plugins,marketplaces,mcp,hooks,hook-scripts,keybindings,statusline,settings,memory}`. Mutations for skills/agents/commands/output-styles/memory — including the per-project file-based auto-memory store (`*.md` under `~/.claude/projects//memory/`, grouped by project and searchable in the Memory tab, with clickable `MEMORY.md` index links that scroll to + highlight the matching fact file) — via `PUT /api/cc-config/file` + `DELETE /api/cc-config/file` (timestamped backups, atomic writes). The Keybindings tab additionally offers a structured inline editor that persists via `PUT /api/cc-config/keybindings` (same backup-first, atomic-write guarantees). `GET /api/cc-config/file?path=…` for single-file viewer. `GET /api/cc-config/backups` for the recovery modal. A **Switch profile** button left of Backups opens the auth-profile modal (`/api/cc-config/profiles*`): save the logged-in Anthropic account, rename it by clicking its name, then switch between accounts — only the login is swapped, so skills / subagents / memory / plugins / marketplaces / settings stay put. There is no name field (the server derives the identifier) and the save button shows only while the live account is unsaved, since one account is one profile. It cannot log in for you (`claude auth login` does that) and a switch applies to new sessions only. Subscribes to `cc_config_changed` WS messages for live refresh on both dashboard mutations and external file edits picked up by `cc-watcher`. The Settings tab (within CcConfig) leads with a client-side **Current configuration** summary that resolves the `/config` options (model, verbose, theme, output style, effort, auto-compact, notifications, …) across user / project / project-local scopes, showing defaults when unset. **codex** tab (`?tool=codex`) → `CodexConfig`: the Codex CLI counterpart — 8 tabs (overview, skills, agents, rules, memory, config, mcp, plugins; marketplaces are folded into the plugins tab rather than given their own, since the CLI treats them as one small add/remove surface). Codex's scope union is `"user"` \| `"project"` only — no `"project-local"`, no per-project file-based memory. Reads/mutates via `GET/PUT/DELETE /api/codex-config/*` (skills/agents/rules/memory/config are file-backed with the same backup-first write path as `cc-config`; mcp/plugins/marketplaces are CLI-owned add/remove only, no artifact editor). Subscribes to `codex_config_changed` WS messages. Both config panels share a tool-agnostic component set under `client/src/components/config/` (`EditorModal`, `ConfirmDeleteModal`, `FileViewer`, `ToastNotice`, `MdItemList`, `ScopeBadge`, `configPrimitives`) generic over an artifact-type parameter, so the two tools' editors stay visually and behaviorally consistent without duplicating the modal code. Every tab shows the Live / Offline indicator next to the page title | | `/*` | NotFound | None (static 404 page) | @@ -1107,14 +1107,6 @@ launcher, and the detached server spawn with `NODE_PATH` and the plugin cache, which Claude Code garbage-collects and replaces on every update. -Last in that worker — deliberately, because it clones from the network and -nothing above it may wait — it installs the plugins the pipeline templates drive -(`superpowers`) via `server/lib/skill-plugins.js`, the same module -`/agenthub-doctor`'s `Skill plugins` row reports from. The report is derived from -the templates; the install consults a fixed allowlist in that file instead, -because a template is user-writable. Full behavior: -[`docs/PLUGINS.md`](docs/PLUGINS.md). - --- ## Import Pipeline @@ -1587,12 +1579,9 @@ graph TD - `.claude/rules/` adds path-scoped guidance by file area. - `skills/` provides reusable workflows, shipped as the `agenthub` plugin's own skills (`/agenthub:`, bare `/` alias): - onboarding - - feature shipping - live issue debugging -- `agents/` provides specialized review workers (shipped as the `agenthub` plugin's agents): - - backend reviewer - - frontend reviewer - - qc-local, senior-gate-reviewer (the ship-feature-lane pipeline's QC and publish gate) + - project-doc updates + - file headers - `plugins/` provides distributable plugin marketplace (see [Plugin Marketplace](#plugin-marketplace)): - agenthub-analytics (session reports, cost analysis, usage trends, productivity scoring) - agenthub-productivity (standups, weekly reports, sprint summaries, workflow optimization) diff --git a/CLAUDE.md b/CLAUDE.md index c8aec173..4b7dae89 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Claude Code Working Guide -_Last synced: v1.8.10 (2026-09-25) + `antigravity` lanes (a third `lanes.tool`, running Antigravity's `agy` CLI; no pipeline, no AI Router, no model/effort picker; the lane card no longer renders a bare tool chip, and the prompt box names the lane's CLI via `LANE_TOOL_LABEL` in `client/src/lib/types.ts`) + a lane whose pane EXITED being released on a server beat (`healAllRunIds` in `server/routes/lanes.js`, every `DASHBOARD_RUN_HEAL_MS`=10 s from `server/index.js`) — a codex/antigravity pane fires no hook, so nothing else re-read it and the lane stayed `running` + a codex Start no longer sending `resumeLast` (only **Resume last session** does) + per-CHANGE-BLOCK review bars (issue #50: `stage-hunk`/`discard-hunk` take an optional `block` — the index of a run of consecutive +/- lines inside the `@@` hunk — and `onlyBlock` in `server/lib/git-diff.js` cuts git's fresh hunk down to it, folding every other block into the side being checked; `changeBlocks`/`LaneDiffChange` in `LaneDiffView.tsx` hang one bar per block, and the arrows land on `data-change-index`) + `codex` lanes losing their pipeline (`codex-ingest.js` mirrors only idle↔running — no stage detection or auto-advance, no `keepDetection`; `codex-nudge.js` deleted; every server start runs `clearCodexPipelines` (`server/lib/lanes.js`: a codex lane's stage columns, `lane_stage_events` and `lane_features` wiped) and `removeStageDoc` (`server/lib/lane-agents.js`: the old `` block taken out of each codex lane's `AGENTS.md`, the file deleted when nothing else is left); `agenthub lanes agents install` installs nothing on a codex lane; the Workspace hides stage, time-on-stage, pipeline/feature pickers and PipelineMap for one) + `info/exclude` entries AgentHub writes into a lane going to the COMMON git dir with the lane's path prefix (`server/lib/git-exclude.js`, used by `router-apply.js` and `lane-mcp.js` — a linked worktree's own `info/exclude` is never read by git, and a subdir lane has no `.git`) + macOS start-at-login (`scripts/launchd-service.js`: the LaunchAgent `~/Library/LaunchAgents/io.smartgift.agenthub.plist`, same API as `systemd-service.js`, chosen by `scripts/service-manager.js` — which the bootstrap, `agenthub service` and `/agenthub-doctor` now require instead of `systemd-service` directly) + v1.8.6 (2026-09-24) + four fixes found by the #37 evaluation, shipped without #37 itself: tmux targets are EXACT (`=name` for has-/kill-/attach-session, `=name:` for pane and option targets, in `server/lib/tmux.js`, `pty-attach.js` and `scripts/cc-update-all-helper.js`) because a bare `-t agenthub-lane-1` resolves by prefix to `agenthub-lane-12` once lane 1 is gone; `getRun`/`killRun` in `server/lib/pty-run.js` refuse any id that is not `agenthub-lane-`, so `DELETE /api/run/main` can no longer kill a user's own tmux session; `POST /api/lanes/:id/router/apply` reports `restart_required` only when the pane's status is `running` (it was always true, since `getRun` returns a `gone` handle); and a lane's `needs_action` is cleared only by the session that raised it (new nullable `lanes.needs_action_session`, `server/routes/hooks.js`) + macOS support, code-audited and untested on a Mac (#22): `server/index.js` deletes a DENYLIST of parent-session variables at startup (`PARENT_SESSION_VARS`: CLAUDECODE, CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_MESSAGING_*, CLAUDE_PLUGIN_ROOT, AI_AGENT, …, plus GIT_EDITOR only when it is Claude Code's `true`; user config such as CLAUDE_CONFIG_DIR/CLAUDE_CODE_USE_BEDROCK/ANTHROPIC_* passes through), because a server started from a Claude Code session handed them through tmux to every lane pane; the `~/.local/bin/agenthub` launcher falling back to `node` on PATH when its pinned node is gone (`linkCli` in `scripts/plugin-bootstrap.js`); `liveServers` ignoring a discovery entry whose `startedAt` predates this boot, so a recycled pid is never a live dashboard; lane cwds stored by real path (`canonicalCwd` in `server/lib/lanes.js`, used by `createLane`, `POST /api/lanes/ensure` and the liveness reaper — `/tmp` becomes `/private/tmp`, a not-yet-existing path through its deepest existing ancestor, existing rows untouched); an `__setExecImpl` seam in `server/lib/session-liveness.js`; and the `INSTALL.md` § macOS section with the Mac checklist + issue #34's empty-repo half — `publish` as a git write (`server/lib/git-ops.js`: `remote add origin -- ` + `push -u origin HEAD`, only in a repo with a commit and NO remote, URL allowlisted by `validateRemoteUrl` to https-without-userinfo/ssh:///scp-like, `origin` removed again on a failed push; `ENOCOMMITS`/`EREMOTEEXISTS`/`EBADURL`), an unborn repo being readable (`gitFacts`/`statusCounts` in `server/lib/worktree.js`: `head: null`, new `unborn`, branch via `symbolic-ref`), `POST /api/lanes/worktree` answering `400 EEMPTYREPO` instead of leaving a `failed` row, and `LaneGitBar.tsx` gaining **Publish…** (no remote), **Publish branch** (remote but no upstream) and a commit-first hint (unborn) while `AddLaneModal.tsx` steers an empty repo to Repo mode + a dashboard fetch/pull/push that needs a git LOGIN asking for one in the git bar (issue #45: `409 EGITAUTH` with `auth`, resent with `credentials`; AgentHub's own shared store `server/lib/git-credentials.js` + helper `server/lib/git-credential-helper.js`, `/api/git-credentials`, Settings → **Git credentials** = `client/src/pages/GitCredentialsConfig.tsx`) + MERGE and conflict resolution from the dashboard (issue #45 part B: `merge`/`resolve`/`merge-commit`/`merge-abort` in `server/lib/git-ops.js`, `gitFacts.merging`/`conflicts`, the git bar's **Merge into current** and diverged-upstream **Merge**, the Changes panel's merge banner and **Merge conflicts** group, and the per-block conflict view `client/src/components/lanes/LaneConflictView.tsx` over the pure parser `client/src/lib/conflicts.ts` — keep mine/theirs/both per block, saved through the whole-file editor's `PUT /git/file` + `resolve done`, no new API) + drag-and-drop of an image onto the lane terminal (`TerminalView.tsx` uploads to `POST /api/run/drop-image` in `server/routes/run.js` — raw body, png/jpeg/gif/webp only, random name under `/agenthub-drops-/`, never a name from the request — then `term.paste()`s the returned path, which is how `claude` attaches it) + Claude Opus 5.5 getting its own pricing row (`claude-opus-5-5%` at 4/20/0.2/5/8, fast 8/40 — `claude-opus-5%` had been pricing it as Opus 5) + AUTOMATIC pricing (`server/lib/pricing-sync.js`, started in `startBackgroundServices`: a model family used in `token_usage` with no `%` row of its own is INSERTED from LiteLLM's public price list, never updated, off with `AGENTHUB_PRICING_SYNC=0`) + the Changes panel at **Full screen** moving the whole Files view (finder + expand-all + tree, one `browsePane`) into a left SIDEBAR and dropping the Files tab and in-place editing (`full` prop on `LaneChangesPanel` from `Workspace.tsx`; a whole file opens for READING and enters the editor only on a click on its text — `onEditAt` on `LaneFileView`, caret at the clicked line, a drag-selection is not a click; Cancel returns to reading, and only a diff keeps an Edit button) and height-locking the page (`viewportLocked` also true at full, else the panel shrank to content with no live run) + the overview splitting into **Staged Changes** / **Changes** groups in the `work` range + a changed tree row tinted whole (`treeMark` tone carries a `bg-status-*/10`) + the Files tree listing untracked-not-ignored files and carrying each entry's git status (`treeStatus`/`treeEntries`/`TREE_LS` in `server/lib/git-diff.js`: file `status` = porcelain code, folder `changed: true`, from ONE `laneStatus` per tree read; `treeMark` in `LaneChangesPanel.tsx` paints M/U/A/D/R) + split panes on ONE lane (every Workspace pane shows the strip's selected lane, each in its own tmux session: `slot` 0–3 on `POST /api/lanes/:id/start`, `agenthub-lane-` for slot 0 = `run_id`, `agenthub-lane--` for 1–3 — `runIdForLane(id, slot)`/`MAX_PANE_SLOT` in `server/lib/pty-run.js`, the `-[1-3]` suffix accepted by `pty-attach.js` and `websocket.js`; `stop`/`reset`/`remove` kill every slot via `laneRunIds`; a pane's Active runs hides what the other panes show; no per-pane lane picker) + the server suite running on a PRIVATE tmux server (`npm run test:server` = `scripts/test-server.js`: drops `TMUX`, sets `TMUX_TMPDIR` to a temp dir, kills that server after — suites spawn and kill real `agenthub-lane-` sessions with ids from their own DB, and on the default server that killed the user's real lane panes, including the one running the tests) + the whole-file EDITOR (`PUT /api/lanes/:id/git/file`, `server/lib/lane-file-write.js`: the read path's guards plus existing-text-file-only and a `baseSha` stale check → `409 ESTALE`; `readFileAt` returns that `sha`) + the per-hunk controls moving into an editor-style review bar under each hunk's last changed line (`HunkBar` in `LaneDiffView.tsx`: ^/v, `n of N`, Undo, Keep) + **Unstage all** on the ONE commit/Stage-all row + Changes sizes Narrow (30%) / Medium (50/50) / Full screen, no 70/30 + the lane card's needs-you banner gone (the strip tile keeps it) + per-HUNK Stage/Undo in the Changes panel's Review and single-file views (`stage-hunk`/`discard-hunk` in `server/lib/git-ops.js`: the hunk is named by its `@@` line and re-matched against a FRESH `git diff -U HEAD` by `hunkPatch` in `git-diff.js`, then `git apply --cached` / `git apply -R` from a temp file — `409 EHUNKGONE` when it moved, `400 EUNTRACKED` on an untracked file; `discard-hunk` is the SECOND irreversible git action and carries the same `confirm: true` gate and two-click UI; the controls reach every `HunkHeader` through a context in `LaneDiffView.tsx`), each Review file header sticky with its own stage/unstage/discard (`FileActions`, shared with the overview row) + the Changes panel gaining a FULL-width step (`CHANGES_SIZES` in `Workspace.tsx`: narrow → 50/50 → 70/30 → full → closed; at full the `lane-detail` section gets `hidden` — CSS, so the one console render site stays mounted), **Stage all** plus the staged-only commit box ABOVE the overview list and sticky (as is the Files finder), and **Expand all folders** being ONE `GET /api/lanes/:id/git/tree-all` (`listTreeAll` in `server/lib/git-diff.js`: one `ls-files`, every level shaped like `listTree`'s) instead of a request — and a `git ls-files` — per folder + the lane's git section MOVING from the lane card to the top of the Changes panel (`client/src/components/lanes/LaneGitHeader.tsx`: `useLaneGitFacts`, the working-copy line, `LaneGitFilesPanel` and `LaneGitBar`, sharing ONE facts read with `LaneChangesPanel`, which refreshes it after every stage/unstage/discard/commit while a git bar action re-reads the diff; `onNewLane` comes from `Workspace.tsx`, and `LaneCard` has no git row and no `onNewLaneFromRepo` any more) and the git bar losing its commit UI — the overview's staged-only box is the ONE commit path, the server's `commit` without `only` is unchanged; the git line and its icon-only actions are ONE row in one card, the panel toolbar ONE wrapping row, and `LaneCard` hides itself (`empty:hidden`) when it has nothing to show + the run form's `/` and `@` autocomplete being REAL again (`PromptEditor` in `client/src/components/run/RunSetup.tsx` — the `2f39f4e` rewrite had left a bare textarea: `/` offers the run cwd's project + user commands and skills, via `api.ccConfig.commands|skills(scope, cwd)`, plus the CLI built-ins, claude lanes only; `@` offers `GET /api/run/files`; Enter picks while the list is open and sends otherwise) + the console's **Active runs** listing only the CURRENT lane's runs and external sessions (`LaneConsolePane.tsx`: runs by `laneId`, sessions by the longest path-boundary lane cwd, `owningLaneId`) + v1.8.3 (2026-09-23) + `GET /api/settings/info` reporting `server.memory.heapLimit` (V8's `heap_size_limit`), which the Dashboard's V8 Heap bar and Health Score now divide by instead of `heapTotal` + every `claude plugin install|update -y` (`cc-plugin-cli.js`, `skill-plugins.js`, both detached update helpers, `/agenthub-update`) retrying WITHOUT `-y` when an older Claude Code answers `unknown option '-y'` + `LaneDiffView.tsx` WINDOWING a diff above `WINDOW_THRESHOLD` (500) items at a fixed `ROW_PX` row height (`visibleRange`, spacers, a `ch` min-width off the longest line; at or below the threshold the DOM is unchanged) and clamping its mirrored split scroll (`clampScroll`/`follow`) + a git action that git itself refuses (a numeric exec exit status) answering 409 `EGIT` instead of 500 with a number for a code (`gitErrorCode` in `server/routes/lanes.js`), and one `parseNumstat` (`server/lib/git-diff.js`) shared by the Changes panel and `gitFacts.files` + the Dashboard heatmap ramp mixing `--accent` into `--surface-0` so it reads on every theme + the lane git surface being HARDENED (`server/lib/git-diff.js`, `server/lib/git-ops.js`, `server/lib/worktree.js`: every lane git call runs with `GIT_LITERAL_PATHSPECS=1` and `-z` porcelain, is scoped to the LANE's cwd — a subdir lane sees and acts on only its own paths, `gitFacts.files` included — and reads through `lanePath` at every rev; any file whose basename is `settings.local.json`, the lane router key file, is refused as `ESECRET`/403 on disk and at every rev; `commit` refuses with `ESTAGEDOUTSIDE` while anything outside the lane is staged, checked with `--no-renames`; `push` without a resolvable remote is `ENOREMOTE`; git writes serialize on the repo's absolute git dir, not the lane id) + the xterm terminal following the selected DARK theme (`client/src/lib/terminalTheme.ts`, live via `term.options.theme`) and chart colours using structural tokens + the Add-lane modal asking `POST /api/lanes/ensure` with `exact: true` (#42) — without it a directory NESTED inside an existing lane resolves to that parent lane with `created: false`, so the modal closed on the parent and no new card ever appeared; `exact` counts only a lane on that very directory, and a `created: false` answer now names the lane that owns the folder instead of closing. The CLI, the run console and Sagi keep the parent lookup + Settings → **Appearance** (`client/src/components/settings/AppearanceSettings.tsx`: mode Dark/Light/**System** — `prefers-color-scheme`, followed live — plus one NAMED theme remembered per mode: dark Default/Darcula/Apple Dark, light Default/Apple Light/IntelliJ Light/Canvas). A theme is a CSS-variable override block in `client/src/index.css` keyed by `data-theme` on `` (`.dark[data-theme=…]` / `[data-mode="light"][data-theme=…]`, `data-mode` is set on `` too), and `--tint-base`/light `--syn-plain` are now `var(--surface-3)`/`var(--fg-primary)` so no theme restates them; `hooks/useTheme.ts` is a `useSyncExternalStore` reader over `localStorage` (`theme` = `dark|light|system`, `agenthub.theme.dark|light`) that caches NOTHING in the module, so the Sidebar and Settings can never disagree — do not reintroduce per-instance `useState` there; an inline `