From 742db17586a3ed33ee0aababe80536b06f31eefa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 22:58:06 +0000 Subject: [PATCH 1/3] The Human Queue keeps humans in control, not under it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Queues section's rendered copy flipped the meaning of its own spec — "the cockpit that keeps humans in control" — into "keeps humans under control". Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework.ai/pages/index/Queues.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/the-framework.ai/pages/index/Queues.tsx b/packages/the-framework.ai/pages/index/Queues.tsx index 65494afca..b3f9e828f 100644 --- a/packages/the-framework.ai/pages/index/Queues.tsx +++ b/packages/the-framework.ai/pages/index/Queues.tsx @@ -54,7 +54,7 @@ export function Queues() { (important) decisions with subtle pros and cons.

- It's your cockpit — it keeps humans under control. + It's your cockpit — it keeps humans in control.

From b93eab14b97ad859860f0e9acdcb8be677ee4d0c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 22:58:06 +0000 Subject: [PATCH 2/3] Remove the watch-relay orphans the #1536 removal left behind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared watch link's serving side went in #1536; the browser side stayed: RelayView, App's dead ?run= branch, and six spec sentences still promising the feature. The component and branch go, the hooks fall back to their defaults (the dashboard is always local now), and the specs stop naming the watch view, the shareable-link menu item, and the relay's own agents. The device relay — a different, live feature — is untouched. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/SPEC.md | 2 +- packages/the-framework/dashboard/App.SPEC.md | 2 +- packages/the-framework/dashboard/App.tsx | 24 +++-------- packages/the-framework/dashboard/SPEC.md | 3 +- .../dashboard/components/AgentFeed.tsx | 9 ++--- .../dashboard/components/RelayView.SPEC.md | 5 --- .../dashboard/components/RelayView.tsx | 40 ------------------- .../dashboard/components/SPEC.md | 2 +- .../src/dashboard-rpc/events.SPEC.md | 2 +- 9 files changed, 15 insertions(+), 74 deletions(-) delete mode 100644 packages/the-framework/dashboard/components/RelayView.SPEC.md delete mode 100644 packages/the-framework/dashboard/components/RelayView.tsx diff --git a/packages/the-framework/SPEC.md b/packages/the-framework/SPEC.md index 929be98cc..f8c1a97af 100644 --- a/packages/the-framework/SPEC.md +++ b/packages/the-framework/SPEC.md @@ -59,7 +59,7 @@ flowchart TD **Spending limits.** The whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. Nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line — but for work the user asked for, the slider only ever *loosens* the gate, and it is re-read live, so raising it unparks a waiting agent without a restart. The two gates fail in opposite directions on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise, because by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part. -**Surfaces.** The daemon serves the dashboard and answers all its reads from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. For a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin; the device's token is saved only in the user's own browser and handed to the local daemon per call. A shared link re-serves one agent's event stream read-only, from the same daemon that owns it. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed) — with a browser extension inside the user's own claude.ai tab bridging cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching human attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in. +**Surfaces.** The daemon serves the dashboard and answers all its reads from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. For a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin; the device's token is saved only in the user's own browser and handed to the local daemon per call. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed) — with a browser extension inside the user's own claude.ai tab bridging cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching human attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in. **What lands in git.** One record of what happened: each agent's own event log, archived under a per-user directory keyed by the git identity, so cleaning the repo cannot erase the past and two people on one repo do not conflict. The daemon commits those archives after an idle window, only those paths, skipping while someone holds the index. Tickets — `tickets/_.md`, the human-facing roadmap, with optional plan and claim siblings, parsed tolerantly. And the queue file plus a human-readable log of what The Framework did to the project. diff --git a/packages/the-framework/dashboard/App.SPEC.md b/packages/the-framework/dashboard/App.SPEC.md index 9104fffdc..f978ffb4c 100644 --- a/packages/the-framework/dashboard/App.SPEC.md +++ b/packages/the-framework/dashboard/App.SPEC.md @@ -6,7 +6,7 @@ The entire dashboard is this one page: it reads the selection from the address, - The page owns what the views share: the agent list, the project list (each project carrying what the daemon currently finds wrong with it), project files, the cross-project needs-you queue, and the one live event stream the main view and right rail both read. - A just-started session shows live before its record exists; with no id known yet, the page follows the output and adopts the running session once it surfaces. - Live and finished agents are the same view — only the "live" flag flips when an agent ends. -- A shared watch link renders that one agent read-only; a daemon that stops answering gets a banner, so a dead backend never looks like a quiet agent. +- A daemon that stops answering gets a banner, so a dead backend never looks like a quiet agent. ## Rationales diff --git a/packages/the-framework/dashboard/App.tsx b/packages/the-framework/dashboard/App.tsx index c89cd30e4..6fcc16ae7 100644 --- a/packages/the-framework/dashboard/App.tsx +++ b/packages/the-framework/dashboard/App.tsx @@ -13,7 +13,6 @@ import { TicketPlanPage } from './components/TicketPlanPage.js' import { AgentView } from './components/AgentView.js' import { agentLabel } from './lib/agent-label.js' import { RightRail } from './components/RightRail.js' -import { RelayView } from './components/RelayView.js' import { NotFound } from './components/NotFound.js' import { useLiveEvents } from './lib/use-live-events.js' import { useAgents } from './lib/use-agents.js' @@ -92,7 +91,7 @@ export function App() { }, [projectId]) // The selected project's files (git ls-files), handed to both the `#` picker and the tree. - // Empty when no project / on the relay (no checkout). Scoped to the selected session's + // Empty when no project (no checkout). Scoped to the selected session's // worktree (#815), the same checkout the action bar's branch, Serve and open-folder act on; // polled so a file the agent creates shows up rather than waiting for a reload. const { value: files } = usePolled( @@ -244,7 +243,7 @@ export function App() { } // The live agent feed is owned here so both the main view and the right rail's views tab read - // one shared event stream. Hooks run before the relay early return below. + // one shared event stream. // The agent whose feed and controls are in play is simply the one in the URL; in the no-id // fallback there is none yet, and a null id resolves to the project root, as before. const { events, lost } = useLiveEvents(projectId, agentId, agentStart.tick) @@ -258,25 +257,14 @@ export function App() { // from AgentView rather than being folded here: a finished agent's events live in its archived log, // which that view is the one to read. - // On the relay (#426), the URL carries `?run=` and there is no local registry or - // files — show that one agent read-only. Guarded on `window` so the module can be loaded - // without a browser at all, where it resolves to the full shell. - const relayAgent = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get('run') - - // Is an agent working (#875)? Drives the mark and the tab icon. Both off on the relay: there is - // no project registry behind it, so the cross-project read cannot answer, and RelayView owns - // both from its one agent's feed instead. - const local = relayAgent === null - const working = useWorking(local) - useFavicon(working, local) + // Is an agent working (#875)? Drives the mark and the tab icon. + const working = useWorking() + useFavicon(working) // Whether the daemon answers at all (#948). Without this, a dead daemon froze every surface // silently: the channels retry their transport without a verdict and the polls keep their // last value, so "the agent went quiet" and "nothing on this page is live" looked identical. - const healthy = useDaemonHealth(local) - - // Hooks above run unconditionally (rules of hooks); this early return is safe after them. - if (relayAgent) return + const healthy = useDaemonHealth() // Route the main pane: the Overview dashboard when no project is selected (#471); else the // project home/launcher, a running agent's live output, or a finished agent's replay. Each live diff --git a/packages/the-framework/dashboard/SPEC.md b/packages/the-framework/dashboard/SPEC.md index 504e72cbe..3c9de4a01 100644 --- a/packages/the-framework/dashboard/SPEC.md +++ b/packages/the-framework/dashboard/SPEC.md @@ -28,13 +28,12 @@ The dashboard UI: a browser app served by the daemon that renders everything the - The URL is the selection: the overview at `/`, a project at `/{projectId}`, one agent at `/{projectId}/{agentId}`, plus cross-project tickets, a per-ticket page and its plan page, and settings. An agent is a link you can paste, reload, and bookmark — there is no selection state to disagree with the address bar. - An agent's events stream live over one channel bound to its own log; everything else polls. A finished agent reads from the archive instead, catching up whenever the live channel outgrew it. - The dashboard is a plain client-side app: one static page the daemon serves for every address, and all the behaviour in the browser — no server rendering, no framework between the page and the app. -- Watch mode: opened against a shared link, the same app renders one agent read-only. **The overview** is ordered by what governs what: the quota bar first (a week-track with pace and projection — the one figure that decides what agents may do next), then everything that needs *you* — the open-questions hub, every agent's unanswered question across all projects, answerable right there in one scrolling view — then the agents working now, the full AI queue of every project (uncollapsed: a plan you cannot read is not a plan), routine work, and the hottest tickets. An onboarding checklist sits on top until dismissed; each step's "done" is derived from a real fact (a registered project, a ticket on disk, a granted permission, stored credentials), so a step cannot be ticked by clicking it and work done outside the dashboard shows up ticked anyway. **The composer** starts and steers agents. Typing a prompt starts an attended build; picking a preset starts an unattended one. In-editor triggers pull in presets and actions, files, projects, and macro tags; option menus write straight to the user's or project's preferences. Pre-flight checks warn before the agent is spent — a missing or logged-out GitHub CLI, a repo that can't auto-merge. On an agent, the composer is its control: a live one takes messages (options are baked at spawn and hidden), a stopped one offers to resume with reduced options, and the submit slot doubles as Stop while it works. -**The agent view** is a transcript with the controls inline: its questions render as answerable cards exactly where they happened (resolved ones collapse to a checkmark), and its live browser screencast renders inline too, degrading to a last still when the agent ends. Around the transcript: changed files with diffs, git status, the handoff panel (push, open PR, merge), agent-authored views, docs, and history rails, and an actions menu (stop, open in editor or on GitHub, remove worktree, delete it, copy a resume command, copy a shareable watch link). +**The agent view** is a transcript with the controls inline: its questions render as answerable cards exactly where they happened (resolved ones collapse to a checkmark), and its live browser screencast renders inline too, degrading to a last still when the agent ends. Around the transcript: changed files with diffs, git status, the handoff panel (push, open PR, merge), agent-authored views, docs, and history rails, and an actions menu (stop, open in editor or on GitHub, remove worktree, delete it, copy a resume command). **Tickets** are the roadmap surface: a cross-project list with client-side faceted filtering (text, priority/effort/uncertainty buckets or ranges, topics, planning stage, project), sorting, and a group-by-project toggle — the whole view mirrored to the URL so it can be shared. Each ticket row leads with a start button that spins up an unattended agent implementing that one ticket, and shows whether a plan exists: a link to a page rendering the plan when it does, a button that starts an agent to write one when it doesn't. Queueing a ticket into the AI queue happens from the ticket's own page. diff --git a/packages/the-framework/dashboard/components/AgentFeed.tsx b/packages/the-framework/dashboard/components/AgentFeed.tsx index 15f38ce8a..a7cb43bc1 100644 --- a/packages/the-framework/dashboard/components/AgentFeed.tsx +++ b/packages/the-framework/dashboard/components/AgentFeed.tsx @@ -5,11 +5,10 @@ import { EventList } from './EventList.js' import { AgentOverview } from './AgentOverview.js' // One agent's feed: the agent overview plus the live/replayed event log, or a waiting placeholder -// before anything has streamed. Shared by the agent's own view (AgentView, which shows the session -// link in its action bar instead — `showSessionLink={false}`) and the read-only relay watch view -// (RelayView, which keeps it since it has no action bar). `lost` is the live channel's health -// (#948): while the stream is down the feed is behind reality, and saying so beats letting -// "the agent went quiet" and "the connection died" look identical. +// before anything has streamed. Rendered by the agent's own view (AgentView, which shows the +// session link in its action bar instead — `showSessionLink={false}`). `lost` is the live +// channel's health (#948): while the stream is down the feed is behind reality, and saying so +// beats letting "the agent went quiet" and "the connection died" look identical. export function AgentFeed({ events, showSessionLink = true, diff --git a/packages/the-framework/dashboard/components/RelayView.SPEC.md b/packages/the-framework/dashboard/components/RelayView.SPEC.md deleted file mode 100644 index 0f13f5e39..000000000 --- a/packages/the-framework/dashboard/components/RelayView.SPEC.md +++ /dev/null @@ -1,5 +0,0 @@ -The shared watch page: a teammate opening a shared link sees that one agent's live feed read-only — no project rails, no steering — and an agent that has ended or a wrong link says so instead of waiting forever. - -## Before modifying/creating SPEC.md files - -You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md diff --git a/packages/the-framework/dashboard/components/RelayView.tsx b/packages/the-framework/dashboard/components/RelayView.tsx deleted file mode 100644 index 070beb794..000000000 --- a/packages/the-framework/dashboard/components/RelayView.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { AgentFeed } from './AgentFeed.js' -import { Badge } from './ui/badge.js' -import { useLiveEvents } from '../lib/use-live-events.js' -import { isAgentActive } from '../lib/live-state.js' -import { useFavicon } from '../lib/favicon.js' -import { Logo } from './Logo.js' - -// The shared-agent watch view (#426/#230): when the dashboard is opened on the relay at -// `/?run=`, it shows one agent read-only, streamed from the relay's in-memory event -// feed over the same `GET /_rpc/events` stream the daemon uses. No Projects/Runs/Docs -// rails and no steering — a teammate with the link watches, they do not drive. -export function RelayView({ agentId: agentId }: { agentId: string }) { - // The agent id rides in the projectId slot: the relay keys `onEvents` by it (no registry). - const { events, lost, done } = useLiveEvents(agentId) - // The mark and the tab icon (#875) follow the one agent being watched, since that is all the - // relay knows about — it has no project registry to ask. - const working = isAgentActive(events) - useFavicon(working) - return ( -
-
- - The Framework - watching - read-only shared session -
-
- {/* An unknown or ended run closes the channel cleanly with nothing streamed; saying so - beats "Waiting for the session to start…" forever (#948). */} - {events.length === 0 && done ? ( -
- This shared session isn’t available — it may have ended, or the link may be wrong. -
- ) : ( - - )} -
-
- ) -} diff --git a/packages/the-framework/dashboard/components/SPEC.md b/packages/the-framework/dashboard/components/SPEC.md index e0670b203..bf5182501 100644 --- a/packages/the-framework/dashboard/components/SPEC.md +++ b/packages/the-framework/dashboard/components/SPEC.md @@ -10,7 +10,7 @@ The dashboard's React component catalog: every page, panel and control the brows ## Flows -- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, a read-only shared watch view, and not-found. +- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, and not-found. - The agent surface is a transcript with its controls inline: an action bar carrying the branch / PR / handoff and the one menu of agent actions, the event feed rendering its questions as answerable cards and its browser screencast in place, the changes and handoff panels, and one composer that starts, steers, stops and resumes — in a stable frame, so an ending never blanks what you are reading. - The Overview's widgets each show one slice of what the daemon knows: quota pace, agents working now, the Human Queue, the AI queue, routine work, hot tickets, activity and outcomes, and an onboarding checklist whose steps tick off real facts rather than clicks. - The launcher's controls — presets, driver/model and option menus, the Context selector, the system-prompt preview — read and write the same preferences and mappings the agent itself uses, so no surface can disagree with the agent it configures. diff --git a/packages/the-framework/src/dashboard-rpc/events.SPEC.md b/packages/the-framework/src/dashboard-rpc/events.SPEC.md index 536515ca3..18ddc14a8 100644 --- a/packages/the-framework/src/dashboard-rpc/events.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/events.SPEC.md @@ -5,7 +5,7 @@ Streams one agent's events live to the browser: everything already logged is rep - The agent id picks whose journal to follow — each agent logs in its own checkout — so the feed is that agent's alone; without it, the project root's journal is followed, which is only right for an agent that has no checkout. - The tail follows the journal when teardown archives it mid-stream, so a watcher never misses the ending — but an agent-scoped feed never falls back to the project-root journal, which is another agent's story: a deleted agent's tab goes quiet instead. - After the replay, a one-time caught-up marker lets a reconnecting viewer swap its feed whole instead of blanking and refilling; the marker travels only on the wire, never into any journal. -- An agent with no file on this host — the relay's own, or one relayed from a device — streams from memory instead. +- An agent with no file on this host — one relayed from a device — streams from memory instead. ## Before modifying/creating SPEC.md files From eba10825acd9dbc84e2bdc8a8b63f9356e980990 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 22:58:06 +0000 Subject: [PATCH 3/3] Bring twelve spec claims back to what the code does The migration's grounding pass flagged claims the code contradicts; this closes them: the Overview rollup carries no recent-activity data (spec and test spec now say what the tests cover, including the shape guard), the handoff tests read the recorded branch and PR rather than re-deriving them, the handoff setting has no legacy spelled-out form, dashboard-rpc separates control-file writes from direct handoff/daemon writes and drops the preview claim, onboarding no longer names the removed public-host mode, last activity comes from agents alone, the theme default lives dashboard-side, the editor dropdown is runtime-detected, and project.ts loses its dead dependency-detection remnants (unused import, stale docblock, stale spec claims). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../the-framework/dashboard/components/SettingsPage.SPEC.md | 2 +- packages/the-framework/dashboard/lib/preferences.SPEC.md | 2 +- packages/the-framework/src/dashboard-rpc/SPEC.md | 3 ++- packages/the-framework/src/dashboard-rpc/control.SPEC.md | 2 +- packages/the-framework/src/dashboard-rpc/projects.SPEC.md | 2 +- .../the-framework/src/dashboard/agent-handoff.test.SPEC.md | 2 +- packages/the-framework/src/dashboard/dashboard.SPEC.md | 2 +- packages/the-framework/src/dashboard/dashboard.test.SPEC.md | 2 +- packages/the-framework/src/dashboard/projects.SPEC.md | 2 +- packages/the-framework/src/handoff-level.SPEC.md | 2 +- packages/the-framework/src/project.SPEC.md | 3 +-- packages/the-framework/src/project.ts | 5 ++--- 12 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md index 154ae7935..807931452 100644 --- a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md +++ b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md @@ -10,7 +10,7 @@ The settings page: every setting in one findable place — appearance, driver an ## Rationales -- Every list here is static today, so the guard against empty dropdowns is for the next dynamic one. +- The editor dropdown is assembled at run time from the editors detected on the machine; the other lists are static, and the guard against empty dropdowns covers both. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/preferences.SPEC.md b/packages/the-framework/dashboard/lib/preferences.SPEC.md index ab6002f5d..23c387ffd 100644 --- a/packages/the-framework/dashboard/lib/preferences.SPEC.md +++ b/packages/the-framework/dashboard/lib/preferences.SPEC.md @@ -7,7 +7,7 @@ The dashboard's settings: your own options with the open project's committed set - A change shows instantly and saves in the background; the write sends only the keys it changed and adopts the daemon's merged answer, so a stale tab can neither revert other people's changes nor keep showing them wrong. - Returning to the tab re-reads both tiers, making edits from another tab or from the repo's settings file on disk visible. - Each control can also see which tier won its value — a repo-inherited value shows as not yours — and a project's shared, repo-committed custom presets ride along. -- The theme choice and the notification toggles read through here, with their defaults defined framework-side so daemon and dashboard cannot drift. +- The theme choice and the notification toggles read through here; the notification defaults are defined framework-side so daemon and dashboard cannot drift, while the theme's default lives here — absent means follow the OS. ## Rationales diff --git a/packages/the-framework/src/dashboard-rpc/SPEC.md b/packages/the-framework/src/dashboard-rpc/SPEC.md index de7f01360..e5cc7e858 100644 --- a/packages/the-framework/src/dashboard-rpc/SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/SPEC.md @@ -3,7 +3,8 @@ The browser's call surface: every dashboard read and write arrives here as a pla ## Flows - Reads are thin projections of the files agents and the daemon already write, called by name over plain HTTP. Live events stream over one subscription per agent, tailing that agent's own journal — and when teardown archives the journal mid-stream, the stream follows it and delivers exactly what it had not yet shown, once. -- Writes are commands — stop, answer a choice, send a message, arm the handoff, push, open a PR, merge, start an agent, queue a ticket — appended to the target agent's control file; there is no direct channel into the running process. (A Claude web session has none: its answer is queued for the browser extension to type in.) +- Writes that steer a live agent — stop, answer a choice, send a message, arm the handoff — are commands appended to the target agent's control file; there is no direct channel into the running process. (A Claude web session has none: its answer is queued for the browser extension to type in.) +- The other writes act directly: push, open a PR, and merge run the git handoff on the agent's branch, while starting an agent and queueing a ticket go through the daemon's own wiring. - Two routing decisions live here and nowhere else: which checkout an agent-scoped call resolves to (the agent's own, else the project root), and whether the call is local or belongs to an agent relayed to a connected device — forwarded there against a deliberate allowlist that swaps in the device's own home project. - One surface, one host. The daemon wires every capability at start-up and the calls read it as simply there; an unwired one is a bug that says which field is missing, not a degraded mode to render around. diff --git a/packages/the-framework/src/dashboard-rpc/control.SPEC.md b/packages/the-framework/src/dashboard-rpc/control.SPEC.md index fb039eb2d..1d4355cba 100644 --- a/packages/the-framework/src/dashboard-rpc/control.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/control.SPEC.md @@ -3,7 +3,7 @@ Every dashboard action that changes something: steering a live agent, starting o ## Flows - Steering (stop, answer a choice, send a message, arm the handoff, merge) appends a command to the agent's own control file, which the agent watches — the same append whoever asks, no direct line into the process. A Claude web session has none to steer: its answer is queued for the browser extension to type in, and only as a label of the question actually parked. -- Starting an agent, previews, and opening a checkout in an editor call straight into the daemon's own wiring: there is one host and it wires everything, so a missing capability is a wiring bug that names itself rather than a state a request can find. +- Starting an agent and opening a checkout in an editor call straight into the daemon's own wiring: there is one host and it wires everything, so a missing capability is a wiring bug that names itself rather than a state a request can find. - Publishing a finished agent — push its branch, open a PR, merge — first commits what it left uncommitted, and holds a lock across the commit *and* the push, since teardown publishes the same branch under the same lock: a click racing it must neither lose the work nor collide creating the ref. Merge steers a live agent to merge at its natural end, and merges a finished one's PR directly. - Removing a kept checkout or deleting an agent refuses while it is live, saves the work as a commit, and stops any preview serving the tree first. - Queueing a ticket writes the project's backlog directly, under the ticket's priority and linking back to it; a stuck ticket claim can be released by hand. diff --git a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md index ec85972ff..55de534f4 100644 --- a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md @@ -3,7 +3,7 @@ The project list and the launcher's pre-flight answers: which projects are regis ## Flows - Each listed project carries what the daemon's background jobs currently find wrong with it — a data branch that cannot reach origin, say — so the one list every project surface already polls is also how an error reaches the sidebar and the project's page. -- Adding a project (one repo, or every repo under a folder) goes through the daemon so it lands in the shared registry; the onboarding hint offers the daemon's own directory as the first project — and a public host neither offers nor accepts, and must not disclose where it runs. +- Adding a project (one repo, or every repo under a folder) goes through the daemon so it lands in the shared registry; the onboarding hint offers the daemon's own directory as the first project. - The pre-flight reads warn before a doomed start rather than after: whether the repo allows auto-merge (an armed merge otherwise lands before CI has run), and whether the chosen driver's CLI is installed and logged in — reporting only problems the user can act on, never account details a visitor on a network-bound host has no business seeing. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/agent-handoff.test.SPEC.md b/packages/the-framework/src/dashboard/agent-handoff.test.SPEC.md index f1b0297c6..9dd324ddc 100644 --- a/packages/the-framework/src/dashboard/agent-handoff.test.SPEC.md +++ b/packages/the-framework/src/dashboard/agent-handoff.test.SPEC.md @@ -1,4 +1,4 @@ -The tests cover the whole handoff story: reading a branch's work (empty, bookkeeping-only, gone, unpushed, and no-remote cases, against fakes and real repos), push and PR-opening with git's own reason on failure, the push-free draft PR for a remote-only branch with gh's refusal reported rather than thrown, the armed push/draft-PR/merge combinations including never opening a second PR, resolving an agent's PR across its candidate branch names and start time, merge authorization, and the human Merge action with its refusals. +The tests cover the whole handoff story: reading a branch's work (empty, bookkeeping-only, gone, unpushed, and no-remote cases, against fakes and real repos), push and PR-opening with git's own reason on failure, the push-free draft PR for a remote-only branch with gh's refusal reported rather than thrown, the armed push/draft-PR/merge combinations including never opening a second PR, the recorded branch and PR winning over re-derivation, merge authorization, and the human Merge action with its refusals. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/dashboard.SPEC.md b/packages/the-framework/src/dashboard/dashboard.SPEC.md index 74b6c0730..139c1aca6 100644 --- a/packages/the-framework/src/dashboard/dashboard.SPEC.md +++ b/packages/the-framework/src/dashboard/dashboard.SPEC.md @@ -1,4 +1,4 @@ -Assembles the Overview page: the cross-project, at-a-glance rollup of what is running, what recently happened, and how much work is waiting. +Assembles the Overview page: the cross-project, at-a-glance rollup of what is running and how much work is waiting. ## Flows diff --git a/packages/the-framework/src/dashboard/dashboard.test.SPEC.md b/packages/the-framework/src/dashboard/dashboard.test.SPEC.md index 3f8310029..45d142105 100644 --- a/packages/the-framework/src/dashboard/dashboard.test.SPEC.md +++ b/packages/the-framework/src/dashboard/dashboard.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the Overview rollup: totals, per-project counts and running flags, the fixed two-week activity window (oldest first, out-of-window agents not counted), and per-project ticket presence. +Covers the Overview rollup: totals and the working-now list, projects ordered most-recently-active first (whose head onboarding acts on), per-project ticket presence, and a shape guard that the payload carries only what a reader asks for. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/projects.SPEC.md b/packages/the-framework/src/dashboard/projects.SPEC.md index 37d426add..82adc8eb3 100644 --- a/packages/the-framework/src/dashboard/projects.SPEC.md +++ b/packages/the-framework/src/dashboard/projects.SPEC.md @@ -2,7 +2,7 @@ Turns the registered-project list into what the dashboard shows per project: its ## Flows -- Last activity is the newest of the project's log entries and its agents, so an agent that stopped before writing the log still counts as activity. +- Last activity is the newest timestamp across the project's agents. - One provider, the real registry: with one dashboard host there is no per-agent scope to substitute and no public host to blank out. - Forgiving: a failed read shows as an inactive project with no activity, never a crash. diff --git a/packages/the-framework/src/handoff-level.SPEC.md b/packages/the-framework/src/handoff-level.SPEC.md index d31aefe82..b0b2d674f 100644 --- a/packages/the-framework/src/handoff-level.SPEC.md +++ b/packages/the-framework/src/handoff-level.SPEC.md @@ -4,7 +4,7 @@ How far a finished agent publishes itself — one ordinal covering keep it local - One ladder, not three switches. The stages are strictly nested — a pull request needs a pushed branch, a merge needs a pull request — so a rung includes every rung below it, and the impossible combinations are not representable. - Unset means open a pull request: that is what makes the handoff zero-config, so work never sits on a local branch nobody is told about. Merging is the rung above, and landing on the default branch has to be asked for. -- A surface that offers the stages as three separate checkboxes converts both ways, and the conversion is where an impossible answer resolves *downward* rather than being quietly repaired upward. A stored setting that spells the stages out is read through that same conversion, since forgetting it would read "publish nothing" as the default — which publishes. +- A surface that offers the stages as three separate checkboxes converts both ways, and the conversion is where an impossible answer resolves *downward* rather than being quietly repaired upward. ## Rationales diff --git a/packages/the-framework/src/project.SPEC.md b/packages/the-framework/src/project.SPEC.md index e91a079de..828ce2c8f 100644 --- a/packages/the-framework/src/project.SPEC.md +++ b/packages/the-framework/src/project.SPEC.md @@ -1,9 +1,8 @@ -Read-only project helpers: whether a repo has The Framework installed, what its dependencies suggest about it, listing its files, and running git with a time budget fitted to each operation. +Read-only project helpers: whether a repo has The Framework installed, listing its files, and running git with a time budget fitted to each operation. ## Flows - A repo counts as activated when the ignore file the install writes exists — the file that keeps the framework's transient state off the repo's branches. Writing it is a separate concern. -- Detection signals are the dependency names from the project's package manifest; a from-scratch project simply has none. - The file crawl lists everything git sees (tracked and untracked, honoring ignores) and yields nothing rather than failing. - Git operations get one of three time budgets: read, local write, or network/whole-checkout. - "Is this a git repo at all" is answered separately, so a project that cannot host agents is told apart from git failing. diff --git a/packages/the-framework/src/project.ts b/packages/the-framework/src/project.ts index 7e3f6168f..41d3eca1d 100644 --- a/packages/the-framework/src/project.ts +++ b/packages/the-framework/src/project.ts @@ -1,11 +1,10 @@ import { cliRunner, type CliRunner } from './cli-exec.js' -import { readFileSync } from 'node:fs' import { nodeFs } from './node-fs.js' import { gitignorePath } from './framework-gitignore.js' /** - * Project-level repo helpers (#380): the activation marker check, a - * `git ls-files` crawl, and the project's detection signals. Read-only + * Project-level repo helpers (#380): the activation marker check and a + * `git ls-files` crawl. Read-only * building blocks for the sidebars (#314); activation/install (writing the * marker, the install commit) is a separate, deferred concern. */