From 32b84f443da52dc027e3e0145a0ceb08f411c1d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:37:48 +0000 Subject: [PATCH 01/15] Migrate SPEC.md files to the new sdd.md template: root, package, .github, dashboard-rpc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream sdd.md redefined the SPEC.md template: `## TLDR` is gone, flows live under `## Flows` (with an optional `## Flows — TL;DR` summary), why/history clauses move to `## Rationales`, and `## User Stories` / `## Glossary` are new optional sections. This is the first slice of the tree-wide migration: - Root SPEC.md and packages/the-framework/SPEC.md restructured: user stories (traceable to FEATURES-SPEC.md), flows, rationales extracted from history clauses, and a glossary for the product vocabulary (driver, location, gate, ticket, queue entry, empty agent, preset). - .github specs: stale footer variant replaced with the canonical footer; workflow specs restructured. - dashboard-rpc specs: TLDR renamed/merged into Flows, rationales extracted. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .github/SPEC.md | 4 +- .github/workflows/SPEC.md | 12 +++-- .github/workflows/framework-agent.SPEC.md | 18 +++++-- SPEC.md | 17 +++++-- packages/the-framework/SPEC.md | 49 ++++++++++++++++--- .../the-framework/src/dashboard-rpc/SPEC.md | 4 +- .../src/dashboard-rpc/context.SPEC.md | 8 ++- .../src/dashboard-rpc/control.SPEC.md | 2 +- .../src/dashboard-rpc/events-tail.SPEC.md | 2 +- .../src/dashboard-rpc/events.SPEC.md | 2 +- .../src/dashboard-rpc/index.SPEC.md | 2 +- .../src/dashboard-rpc/preferences.SPEC.md | 2 +- .../src/dashboard-rpc/projects.SPEC.md | 2 +- .../src/dashboard-rpc/quota.SPEC.md | 2 +- .../src/dashboard-rpc/reads.SPEC.md | 2 +- .../src/dashboard-rpc/relay-dispatch.SPEC.md | 2 +- .../src/dashboard-rpc/stream-forward.SPEC.md | 6 ++- 17 files changed, 99 insertions(+), 37 deletions(-) diff --git a/.github/SPEC.md b/.github/SPEC.md index 35fcf2b43..f0a1b42d6 100644 --- a/.github/SPEC.md +++ b/.github/SPEC.md @@ -1,5 +1,5 @@ GitHub configuration for the repo; its only content is `workflows/` — the three Actions workflows. -## Before writing SPEC.md files +## Before modifying/creating SPEC.md files -Read https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md +You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md diff --git a/.github/workflows/SPEC.md b/.github/workflows/SPEC.md index 18248de34..a3a40f1da 100644 --- a/.github/workflows/SPEC.md +++ b/.github/workflows/SPEC.md @@ -1,10 +1,14 @@ The repo's GitHub Actions workflows: continuous integration, the dispatchable agent runner, and website deployment. -## TLDR +## Flows - `framework-agent.yml` is the only workflow that is part of the product runtime — the daemon dispatches it to run agent turns on cloud runners; the rest is repo plumbing. -- `ci.yml` builds, typechecks, and tests every push and PR; `website-deploy.yml` publishes the-framework.ai. There is no release workflow: the package is unversioned and unpublished, so there is nothing to cut. +- `ci.yml` builds, typechecks, and tests every push and PR; `website-deploy.yml` publishes the-framework.ai. -## Before writing SPEC.md files +## Rationales -Read https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md +- There is no release workflow: the package is unversioned and unpublished, so there is nothing to cut. + +## 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/.github/workflows/framework-agent.SPEC.md b/.github/workflows/framework-agent.SPEC.md index 4fced4ae6..df6c1974b 100644 --- a/.github/workflows/framework-agent.SPEC.md +++ b/.github/workflows/framework-agent.SPEC.md @@ -1,11 +1,21 @@ The workflow the product dispatches to run one coding-agent turn on a disposable GitHub-hosted runner — how an agent runs "elsewhere" on GitHub Actions. -## TLDR +## User Stories -- One dispatch is one agent turn. The daemon's driver generates a correlation id and finds its own run by it — GitHub's dispatch call never reveals which run it started. -- The run spends the user's own driver subscription, not an API key, and the agent runs unrestricted because the runner is disposable. +- The user runs an agent on a fresh GitHub Actions runner instead of their own machine, and follows it from the same dashboard. + +## Flows + +- One dispatch is one agent turn. The daemon tags the run with a correlation id of its own making and finds the run by that tag. +- The run spends the user's own driver subscription, not an API key, and the agent runs unrestricted. - The turn's work — including anything left uncommitted — is pushed to the branch the driver chose, so the next turn continues exactly where this one stopped. -- The transcript comes back as an uploaded artifact, the only channel out of a run; a failed turn still uploads it, which is exactly when it's most wanted. +- The transcript comes back as an uploaded artifact, the only channel out of a run; a failed turn still uploads it. + +## Rationales + +- The daemon invents its own correlation id because GitHub's dispatch call never reveals which run it started. +- The agent runs unrestricted because the runner is disposable: nothing on it outlives the turn. +- A failed turn still uploads its transcript because a failure is exactly when the transcript is most wanted. ## Before modifying/creating SPEC.md files diff --git a/SPEC.md b/SPEC.md index 0ca3239fc..cad671706 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,8 +1,18 @@ Autonomous AI programming: humans make the important decisions while The Framework runs coding agents unattended — planning its own work, spending idle subscription quota on the roadmap, and handing everything off as pull requests for review. -## TLDR +## User Stories -- The product is a local daemon plus a dashboard. You register your repos, and from then on coding agents (Claude Code today, others behind the same driver seam) work on them: each agent gets a throwaway copy of the repo, does its work, and hands the result off as a pull request. Your own checkout is never touched. +- The user activates a repo from the dashboard; from then on coding agents work on it in throwaway copies, and the user's own checkout is never touched. +- The user types a prompt or picks a preset, then watches the agent live — answering the questions it parks on, chatting with it, stopping it — or is not there at all. +- The user reviews finished work as pull requests: an agent that produced real work pushes it and opens a PR by itself. +- The user walks away and the product keeps working: it drains the confirmed queue, refills it by triaging and planning tickets, fixes red CI on its own PRs, and merges them on green. +- The user never budgets: unattended work spends only the share of the subscription week that has already elapsed, and work the user asks for is never starved. +- The user runs agents on another machine, a GitHub Actions runner, or a Claude cloud session, and steers them from the same dashboard — a browser extension bridges claude.ai cloud sessions back to it. +- The user is notified — browser or Discord — whenever an agent needs a human. + +## Flows + +- The product is a local daemon plus a dashboard. The user registers repos, and from then on coding agents (Claude Code today, other CLIs pluggable behind the same driver interface) work on them: each agent gets a throwaway copy of the repo, does its work, and hands the result off as a pull request. - The human's job shrinks to decisions: answer the questions an agent parks on, accept or reject proposed tickets, review PRs. Everything else — picking the next task, triaging, planning, fixing red CI, merging on green — the daemon does by itself when nobody is at the keyboard, as long as the account's quota allows it. - The driver is a black box: The Framework prompts it, lets it run a full turn, then reads the code and the turn's final message. It never micro-manages individual tool calls, and the CLI keeps its own subscription login — The Framework adds orchestration, not another AI bill. - Two satellites complete the family: a browser extension that bridges claude.ai cloud sessions back to the daemon, and the product's website. @@ -17,8 +27,7 @@ graph TD end ``` -The arrow points one way — the dashboard renders the product — and nothing depends "up". It used to -be a package boundary; the dependency survived the merge, the boundary did not. +The arrow points one way — the dashboard renders the product — and nothing depends "up". ## Rationales diff --git a/packages/the-framework/SPEC.md b/packages/the-framework/SPEC.md index 9393d79e9..929be98cc 100644 --- a/packages/the-framework/SPEC.md +++ b/packages/the-framework/SPEC.md @@ -1,10 +1,23 @@ The product: turnkey AI orchestration that wraps a coding-agent CLI (Claude Code today) as a black box and takes an idea, a ticket, or a queue entry to a reviewed pull request — a CLI, a per-machine daemon, and a localhost dashboard over agents that run unattended. -## TLDR +## User Stories + +- The user installs globally or runs via `npx`; one command serves the dashboard and the daemon, and Ctrl-C takes everything down with it. +- The user activates a repo from the dashboard and it becomes a registered project, with an onboarding checklist derived from real facts rather than clicks. +- The user starts an agent by typing a prompt (attended) or picking a preset (unattended) — from the composer, a ticket row, a queue entry, a routine, or the CLI. +- The user watches a live transcript, answers the agent's questions inline where they happened, chats with it, stops and resumes it, and reopens a finished agent as the same conversation. +- The user receives finished work as a pull request, and can arm auto-merge so it lands once the agent signals ready and checks are green. +- The user browses a cross-project ticket list, filters it, shares the filtered view as a URL, and queues or starts work from a ticket. +- The user walks away and the daemon keeps working the roadmap: draining the confirmed queue, refilling it by triaging and planning tickets, fixing red CI on its own PRs, and merging on green. +- The user sets no budget: unattended work stands down past the elapsed share of the quota week, and work the user asked for is never starved. +- The user runs agents on another machine's daemon, a GitHub Actions runner, or a Claude cloud session, and answers their questions from the local dashboard. +- The user is notified — browser or Discord — when an agent needs a human. + +## Flows — TL;DR - One daemon per machine. Running the CLI in any registered repo finds it; it serves the dashboard, spawns agents, and runs the background services (autonomy sweeps, notifications, chat, CI watch). - An agent is one task worked in its own git worktree on its own branch. It streams what it does as events; the human can watch, answer its questions, and chat with it live — or not be there at all. -- When an agent ends with real work, the work is pushed and opened as a PR. Empty agents publish nothing. The PR number is recorded on the agent the moment one is opened, so every surface reads the same integer instead of re-deriving it from branch names and timestamps. +- When an agent ends with real work, the work is pushed and opened as a PR. Empty agents publish nothing. - When nobody is around, the daemon plays product manager: it drains the confirmed-task queue, refills it by triaging and planning tickets, keeps CI green on the PRs it opened, and merges them once checks pass — all bounded by the account's own quota week. - Which CLI drives the work is a swappable axis (the *driver*), and where it executes is another (the *location*); the CLI keeps its own subscription auth, and The Framework never runs its own model calls for the coding work. @@ -30,17 +43,17 @@ flowchart TD **Workspace.** Every agent gets its own git worktree on its own branch, so concurrent agents never fight and the user's checkout — uncommitted work included — is never touched. Dependency directories are shared from the parent checkout instead of reinstalled. A non-git project falls back to the main checkout, one agent at a time; a git project whose worktree creation failed does **not** fall back — the start fails, because a failed agent is recoverable and a checkout with stray edits is not. -**Driving the work.** The driver runs each turn as a black box to completion. Everything The Framework learns from a turn, it learns by parsing the turn's final message: the session name the agent invented (the branch is renamed to match), agent-authored views for the dashboard, the ready-for-merge signal, and blocking gates. A build and a verbatim prompt are the same path — one opening prompt, honoring gates — since the review loop and the `Bootstrap` spine went; what differs is which prompt opens the agent and whether its own backlog is worked afterwards. A hands-off location (a cloud session) drops every phase after the work is dispatched, because there is nothing to read back. +**Driving the work.** The driver runs each turn as a black box to completion. Everything The Framework learns from a turn, it learns by parsing the turn's final message: the session name the agent invented (the branch is renamed to match), agent-authored views for the dashboard, the ready-for-merge signal, and blocking gates. A build and a verbatim prompt are the same path — one opening prompt, honoring gates; what differs is which prompt opens the agent and whether its own backlog is worked afterwards. A hands-off location (a cloud session) drops every phase after the work is dispatched, because there is nothing to read back. **Gates and chat.** When the agent stops to ask, it parks and the question becomes a card: choices in the dashboard, a message on Discord, a notification. The answer travels back over the agent's control file and it continues from there — unless the answer is one the agent marked as ending it, which is how declining a plan stops the work rather than building on a rejected one. The human can also speak unprompted; each message continues the same conversation, and an idle attended agent stays open waiting for the next one. Unattended agents take the recommended answer instead of parking on a question nobody is there to answer. **The agent's own backlog.** Once the main work settles, the agent drains its queue file one entry per turn — read, complete exactly one entry, check it off, repeat — with a per-entry gate that an unattended agent answers itself. -**Settle and handoff.** Settling is strictly ordered: a final quality turn (which queues the quality presets as backlog entries and folds new learnings into the project docs) → the git handoff → close and archive the agent's history. The handoff runs only on the success path and first decides whether the agent is *empty* — no commits, or only bookkeeping files changed. Empty agents are never published; otherwise pending work is committed, the branch pushed, and a PR opened. Publishing is one ordinal rather than a set of switches — keep it local, push, open a PR, merge — and each rung includes the ones below it, so nothing has to remember that a PR implies a push. Unset means open a pull request: that is the zero-config promise, an agent left alone publishes itself. +**Settle and handoff.** Settling is strictly ordered: a final quality turn (which queues the quality presets as backlog entries and folds new learnings into the project docs) → the git handoff → close and archive the agent's history. The handoff runs only on the success path and first decides whether the agent is *empty* — no commits, or only bookkeeping files changed. Empty agents are never published; otherwise pending work is committed, the branch pushed, and a PR opened. Publishing is one ladder — keep it local, push, open a PR, merge — and each rung includes the ones below it. Unset means open a pull request: that is the zero-config promise, an agent left alone publishes itself. -**Teardown and retention.** One rule decides every removal: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it — so nothing local is ever the last copy, and every deletion is recoverable. A push that cannot land keeps the checkout, and the background sweep retries it later; a repo with nowhere to push keeps everything, which is the honest answer rather than a special case. That replaced three interacting rules that each asked *how did this end* rather than *is the work safe yet*. The branch and the archived history always survive the worktree. An agent that died on a transient error is retried in the same worktree before being declared failed, and a finished one can be reopened later — its history restored so it continues as the same conversation. Acting on an agent the instant it finishes is safe: everything that touches its checkout takes its turn rather than racing, so a click that lands mid-teardown waits a beat instead of failing. +**Teardown and retention.** One rule decides every removal: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it — so nothing local is ever the last copy, and every deletion is recoverable. A push that cannot land keeps the checkout, and the background sweep retries it later; a repo with nowhere to push keeps everything, which is the honest answer rather than a special case. The branch and the archived history always survive the worktree. An agent that died on a transient error is retried in the same worktree before being declared failed, and a finished one can be reopened later — its history restored so it continues as the same conversation. Acting on an agent the instant it finishes is safe: everything that touches its checkout takes its turn rather than racing, so a click that lands mid-teardown waits a beat instead of failing. -**Autonomy.** The repo-root queue file (`TODO_AGENTS.md`) is the durable, priority-ordered list of confirmed work — written directly by agents, unlike a *ticket*, which is a proposal for a human to accept. Because agents run in worktrees, the daemon promotes that one file back into the project checkout, committing only that file, and skipping with a stated reason whenever anything looks unexpected. An entry stays claimed while its agent is live or its PR is open, so parallel drains never double-assign it — the queue file itself is the record of what is left, rather than a claim reassembled at read time from records, PRs and other machines' diffs. On a timer, per project, the daemon asks one policy question — "is now a good time to spend quota on our own roadmap?" — checking the cheapest facts first. A non-empty queue is drained one entry per agent; an empty queue is refilled by rotating through quick triage → consensual triage → ticket planning. Ticket planning fans out several agents, each pinned to exactly one ticket and claimed via a lock file beside the ticket on the default branch, so agents on other machines see the claim too. A calendar-paced maintenance sweep sits outside the rotation and takes precedence when due. Every refusal is phrased as a reason, so a setting never reads as a bug. +**Autonomy.** The repo-root queue file (`TODO_AGENTS.md`) is the durable, priority-ordered list of confirmed work — written directly by agents, unlike a *ticket*, which is a proposal for a human to accept. Because agents run in worktrees, the daemon promotes that one file back into the project checkout, committing only that file, and skipping with a stated reason whenever anything looks unexpected. An entry stays claimed while its agent is live or its PR is open, so parallel drains never double-assign it — the queue file itself is the record of what is left. On a timer, per project, the daemon asks one policy question — "is now a good time to spend quota on our own roadmap?" — checking the cheapest facts first. A non-empty queue is drained one entry per agent; an empty queue is refilled by rotating through quick triage → consensual triage → ticket planning. Ticket planning fans out several agents, each pinned to exactly one ticket and claimed via a lock file beside the ticket on the default branch, so agents on other machines see the claim too. A calendar-paced maintenance sweep sits outside the rotation and takes precedence when due. Every refusal is phrased as a reason, so a setting never reads as a bug. **Merging and CI watch.** Configuration only *arms* auto-merge; what *authorizes* it is the agent's own ready-for-merge signal plus an empty backlog of its own, and an armed-but-unauthorized merge is recorded as withheld, with the reason. The daemon polls the PRs the framework is waiting to land. Green checks on an armed PR: merge it — merge-on-green works even where GitHub's native auto-merge is off. Red checks: one unattended fix agent per failing head commit, told to land the fix on the PR's own branch; after two failed attempts the failure is evidently not one an agent can fix and a human keeps it. Housekeeping retires what has landed: worktrees whose branch merged are removed, and a pinned routine branch left behind by a closed PR is released so the routine can fire again. @@ -48,9 +61,29 @@ flowchart TD **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. -**What lands in git.** One record of what happened, not four: 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. +**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. + +**Prompts and presets.** One assembly path composes the system prompt for every agent — the built-in protocol, the extra protocol each capability brings, the user's own system file, and the picked context — and the exact composed text is recorded, so the dashboard can show precisely what the agent ran under. Two switches dial the wrapping down: *vanilla* drops the enhanced prompt while keeping the framework integration, and *transparent* is the master off-switch — no framework channel at all, the CLI raw. Presets (triage, research, security audit, drain-the-queue, …) are one catalog with prompt text authored as prose; custom presets save to either the user tier (follows the person, private) or the project tier (travels with the repo, shared). A per-repo config file records which preset and switches a project works under, resolved layer over layer. + +## Rationales + +- **One recorded PR number.** The PR number is recorded on the agent the moment one is opened, so every surface reads the same number instead of re-deriving it and disagreeing. +- **Publishing rungs nest.** Because each publishing rung includes the ones below it, a PR always implies a push — no combination of switches can contradict itself. +- **One opening path for builds and verbatim prompts.** Two orchestrators would drift apart; the only real differences are which prompt opens the agent and whether its backlog is worked afterwards. +- **Removal asks one question: is the work safe yet?** How an agent ended has several answers, and none of them bears on whether its work is recoverable. +- **The queue file is the record.** A claim reassembled at read time from agent records, PRs, and other machines' diffs can disagree with itself; the file cannot. +- **Two prompt switches, not modes.** Vanilla and transparent compose independently; a catalog of named modes multiplies instead. + +## Glossary -**Prompts and presets.** One assembly path composes the system prompt for every agent — the built-in protocol, the extra protocol each capability brings, the user's own system file, and the picked context — and the exact composed text is recorded, so the dashboard can show precisely what the agent ran under. Two switches dial the wrapping down, where there were five modes: *vanilla* drops the enhanced prompt while keeping the framework integration, and *transparent* is the master off-switch — no framework channel at all, the CLI raw. Presets (triage, research, security audit, drain-the-queue, …) are one catalog with prompt text authored as prose; custom presets save to either the user tier (follows the person, private) or the project tier (travels with the repo, shared). A per-repo config file records which preset and switches a project works under, resolved layer over layer. +- **driver** — the coding-agent CLI that does the work (Claude Code today, Codex too); swappable per agent. +- **location** — where an agent executes: the local machine, a saved remote device, a GitHub Actions runner, or a Claude cloud session. +- **attended / unattended** — whether a human is expected at the keyboard: an attended agent parks on its questions; an unattended one takes the recommended answer and carries on. +- **gate** — a question an agent parks on, shaped as options; an option can be marked to stop the agent rather than resume it. +- **ticket** — a proposal for a human to accept, kept as a file under `tickets/`. +- **queue entry** — one item of confirmed work in the repo-root queue file `TODO_AGENTS.md`. +- **empty agent** — an agent whose run left no commits (or only bookkeeping changes); it publishes nothing. +- **preset** — a cataloged prompt that starts an unattended agent (triage, research, security audit, …). ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/SPEC.md b/packages/the-framework/src/dashboard-rpc/SPEC.md index a1ab8b281..de7f01360 100644 --- a/packages/the-framework/src/dashboard-rpc/SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/SPEC.md @@ -1,14 +1,12 @@ The browser's call surface: every dashboard read and write arrives here as a plain HTTP call, and steering an agent means appending a command to that agent's own control file — the same append no matter who asked (a dashboard click, a remote device). -## TLDR +## 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.) - 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. -## Flows - ```mermaid flowchart LR B[browser] -->|steer: append command| C["the agent's control file"] diff --git a/packages/the-framework/src/dashboard-rpc/context.SPEC.md b/packages/the-framework/src/dashboard-rpc/context.SPEC.md index 66f767645..c614988ad 100644 --- a/packages/the-framework/src/dashboard-rpc/context.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/context.SPEC.md @@ -1,11 +1,15 @@ How each dashboard call reaches the one host's capabilities, and which checkout an agent-scoped call should act on. -## TLDR +## Flows -- One host wires everything, so a capability is simply there. This used to be a probe: three hosts served this same surface — the dashboard process, a per-session foreground dashboard, and a public relay — each wiring a different subset, so every call read what it needed and every RPC carried a branch for the absent case. A missing capability is a wiring bug now, and says so. +- One host wires everything at start-up, so a capability is simply there; a missing one is a wiring bug that says which capability is missing. - The one exception is "is this agent relayed onward?", which defaults to no: a call arriving over the device relay runs outside a request, and the agent it names is local to that device — forwarding it again would be a loop. - A call that names an agent resolves to that agent's own checkout — where it actually reads, writes, and listens — falling back to the project root only for one that has none. +## Rationales + +- Treating an unwired capability as a bug is what keeps the calls simple: exactly one host serves this surface and wires all of it, so an absent-capability branch in every call would guard a state that cannot legitimately occur — throwing with the missing capability's name surfaces the wiring mistake instead. + ## 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/src/dashboard-rpc/control.SPEC.md b/packages/the-framework/src/dashboard-rpc/control.SPEC.md index 6645541c3..fb039eb2d 100644 --- a/packages/the-framework/src/dashboard-rpc/control.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/control.SPEC.md @@ -1,6 +1,6 @@ Every dashboard action that changes something: steering a live agent, starting one, and publishing or cleaning up what a finished one left. -## TLDR +## 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. diff --git a/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md b/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md index e5460346b..c672179c5 100644 --- a/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md @@ -1,6 +1,6 @@ Follows an agent's on-disk event journal: replay what is already logged, then deliver each appended event — even across the journal's moves. -## TLDR +## Flows - Each logged event is handed over once, with a one-time signal after the backlog — the boundary a reconnecting viewer needs to swap its feed atomically instead of blanking while history re-streams. - A journal does not sit still: teardown copies it into the archive and removes the checkout. "The file existed and is now gone" is treated as that move — the tail asks where the journal lives now and picks up there at the same position, so the ending's final lines arrive exactly once instead of being swallowed. diff --git a/packages/the-framework/src/dashboard-rpc/events.SPEC.md b/packages/the-framework/src/dashboard-rpc/events.SPEC.md index 5892a2fab..536515ca3 100644 --- a/packages/the-framework/src/dashboard-rpc/events.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/events.SPEC.md @@ -1,6 +1,6 @@ Streams one agent's events live to the browser: everything already logged is replayed, then each new event as it happens. -## TLDR +## Flows - 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. diff --git a/packages/the-framework/src/dashboard-rpc/index.SPEC.md b/packages/the-framework/src/dashboard-rpc/index.SPEC.md index 8c94af6c9..c412b3c13 100644 --- a/packages/the-framework/src/dashboard-rpc/index.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/index.SPEC.md @@ -1,6 +1,6 @@ The single doorway to the dashboard's call surface: everything the browser can ask of the server is exported here. -## TLDR +## Flows - The table of callable names is built from the modules' own exports, so a call that exists but was never registered — a 400 with nothing to go on — is not a state this can be in: the name *is* the export name. - It inherits nothing. The name is a path segment off an unauthenticated request, and a table with the usual object behind it answers to `constructor` and its siblings, which are not RPCs. diff --git a/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md b/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md index d3b16ad4c..95dee23b0 100644 --- a/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md @@ -1,6 +1,6 @@ Reading and saving the user's settings: the dashboard preferences, a project's shared presets, the editor picker, and the Discord credentials. -## TLDR +## Flows - Settings live with the dashboard, not the browser, so they survive restarts and follow the user across tabs. A write that fails answers with the reason rather than rejecting, so the client renders it instead of losing the save. - A save can merge only the keys a tab changed and hand back what is now stored, so a stale tab converges instead of reverting settings it never touched. diff --git a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md index c84157f0b..248f94fa1 100644 --- a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md @@ -1,6 +1,6 @@ The project list and the launcher's pre-flight answers: which projects are registered, adding new ones, and whether an agent started now would get anywhere. -## TLDR +## Flows - 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. - 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. diff --git a/packages/the-framework/src/dashboard-rpc/quota.SPEC.md b/packages/the-framework/src/dashboard-rpc/quota.SPEC.md index cd82eae8e..33fb4101e 100644 --- a/packages/the-framework/src/dashboard-rpc/quota.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/quota.SPEC.md @@ -1,6 +1,6 @@ The usage panel's answers: where the account's quota stands, what the background PM last decided, and a way to make it sweep right now. -## TLDR +## Flows - No reading is reported as no reading, never as an empty bar — an empty bar reads as "nothing used", the one thing this panel must never imply. - A hand-fired sweep runs even with the automatic PM switched off: that preference is consent to spend quota unasked, and this click is asking. The call waits for the sweep and returns what it decided, project by project, so the card can say it without racing a poll — and it can be narrowed to only working the queue. diff --git a/packages/the-framework/src/dashboard-rpc/reads.SPEC.md b/packages/the-framework/src/dashboard-rpc/reads.SPEC.md index 70b8da47e..fe40bed56 100644 --- a/packages/the-framework/src/dashboard-rpc/reads.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/reads.SPEC.md @@ -1,6 +1,6 @@ Every read the dashboard makes: agents and their history, docs, tickets and queues, cross-project rollups, files and diffs, and where a finished agent's work stands. -## TLDR +## Flows - Everything is a projection of files the agents and daemon already write — the dashboard holds no state of its own — and an unknown project or a host with no checkout gets empty results, never errors. - The agent list merges archived, live, and device-relayed agents; a live copy wins a tie, so a continued agent reads as running rather than as its finished first leg, and a relayed one survives a reload despite existing only in memory. diff --git a/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md b/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md index e3a80cdad..a1b22309f 100644 --- a/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md @@ -1,6 +1,6 @@ The device side of the remote-agent relay: executes a forwarded call against this device's own home project, and only calls on a fixed allowlist. -## TLDR +## Flows - The caller's project id is discarded and replaced with the device's own home project, so a forwarded call can only ever address the device's home checkout — never another project registered on it. - The allowlist is exactly the agent-scoped read/steer/handoff surface; starting agents, previews, and anything that destroys history or checkouts stays off it. diff --git a/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md b/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md index 4f215a454..d66b51307 100644 --- a/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md @@ -1,4 +1,8 @@ -Plumbing that pumps an in-memory event source — an agent relayed from a device, replaying its buffered history then following live — into a plain callback, stopping when the viewer leaves and saying so when the source runs out on its own. Transport-agnostic on purpose: what a value becomes on the wire is the mount's business, which is what lets this be driven and tested by itself. +Plumbing that pumps an in-memory event source — an agent relayed from a device, replaying its buffered history then following live — into a plain callback, stopping when the viewer leaves and saying so when the source runs out on its own. + +## Rationales + +- Transport-agnostic on purpose: what a value becomes on the wire is the mount's business, which is what lets this be driven and tested by itself. ## Before modifying/creating SPEC.md files From 065b814ea4c1efc216af252f7ca2a425cb91219d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:39:21 +0000 Subject: [PATCH 02/15] Migrate SPEC.md files to the new sdd.md template: src core and driver TLDR sections become Flows (byte-identical where already compliant), history and why clauses move to Rationales reworded in present tense, User Stories added on src/SPEC.md and auto-pm.SPEC.md (traced to FEATURES-SPEC.md), and driver/SPEC.md gets a Glossary owning the coined term "driver". One grounded correction: the Claude Code usage question is answered by the agent, not the driver. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/SPEC.md | 19 ++++++++++------- .../the-framework/src/agent-archive.SPEC.md | 7 +++++-- .../the-framework/src/agent-driver.SPEC.md | 7 +++++-- .../the-framework/src/agent-location.SPEC.md | 9 +++++--- .../the-framework/src/agent-locks.SPEC.md | 9 +++++--- .../the-framework/src/agent-messages.SPEC.md | 4 ++-- .../the-framework/src/agent-options.SPEC.md | 7 +++++-- packages/the-framework/src/agent-spec.SPEC.md | 7 +++++-- .../the-framework/src/agent-telemetry.SPEC.md | 10 ++++++--- packages/the-framework/src/agent-view.SPEC.md | 2 +- packages/the-framework/src/agent.SPEC.md | 11 +++++----- packages/the-framework/src/auto-pm.SPEC.md | 14 ++++++++++--- packages/the-framework/src/await-gate.SPEC.md | 10 ++++++--- .../the-framework/src/branch-links.SPEC.md | 2 +- .../the-framework/src/browser-stream.SPEC.md | 2 +- packages/the-framework/src/browser.SPEC.md | 2 +- packages/the-framework/src/driver/SPEC.md | 21 ++++++++++++------- .../src/driver/actions-zip.SPEC.md | 7 ++++++- .../the-framework/src/driver/actions.SPEC.md | 2 +- .../src/driver/child-registry.SPEC.md | 7 +++++-- .../src/driver/claude-code-quota.SPEC.md | 10 ++++++--- .../src/driver/claude-code.SPEC.md | 8 +++++-- .../src/driver/cli-session.SPEC.md | 8 +++++-- .../the-framework/src/driver/cloud.SPEC.md | 20 ++++++++++++------ .../the-framework/src/driver/codex.SPEC.md | 2 +- .../the-framework/src/driver/fake.SPEC.md | 2 +- .../the-framework/src/driver/index.SPEC.md | 6 +++++- .../the-framework/src/driver/types.SPEC.md | 4 ++-- 28 files changed, 148 insertions(+), 71 deletions(-) diff --git a/packages/the-framework/src/SPEC.md b/packages/the-framework/src/SPEC.md index 593b0e844..543407235 100644 --- a/packages/the-framework/src/SPEC.md +++ b/packages/the-framework/src/SPEC.md @@ -1,14 +1,10 @@ The engine of The Framework: everything that turns an idea, a ticket, or a queue entry into a reviewed pull request — the CLI, the per-machine daemon, the agent runtime that drives the wrapped coding-agent CLI, and the surfaces that watch and steer it. -## TLDR +## User Stories -- One daemon per machine spawns and tracks agents, serves the localhost dashboard, and runs the background services (autonomy sweeps, CI watch, notifications, chat) on one shared clock rather than a timer apiece. Files are the seam: agents narrate onto an on-disk event stream, steering comes back over an append-only control file, and the dashboard is a projection of what they wrote — never a live wire into them. -- An agent is one task worked in its own git worktree on its own branch. The CLI behind it (Claude Code today) stays a swappable black box behind the driver seam; everything the framework learns from a turn — ask-gates, views, the session name, ready-for-merge — is parsed as tagged blocks out of the turn's final message, and there is one gate block rather than four. -- One composition path assembles every agent's system channel (project context and knowledge docs, built-in prompt, the user's own instructions, the emit protocols), so the dashboard can show exactly what the agent ran under; two switches dial the wrapping down — vanilla drops the built-in prompt, transparent empties the channel entirely. -- An agent that ends with real work publishes itself — commit, push, open a PR; empty ones publish nothing, and merging is authorized by the agent's own ready signal plus an empty backlog of its own, never by configuration alone. How far it publishes is one ordinal, not a set of switches, so an impossible combination cannot be represented. -- When nobody is around, the daemon plays product manager bounded by the account's own quota week: drain the confirmed queue, refill it by triaging and planning tickets (claims committed as lock files beside the tickets, so other machines and cloud agents see them), keep CI green on the PRs it opened, and merge on green. -- What must outlive a process lands in git, not memory: each agent's own event log, archived per user so a repo clean cannot erase it and teammates never conflict, plus tickets and their claims, the queue, and the project log. -- The subdirectories hold the seams: the CLI adapters (driver), the on-disk agent state (store), the dashboard and its RPC contract, and the end-to-end proofs. +- The user starts an agent from a prompt, a preset, a ticket, or a queue entry, and gets the finished work back as a pull request — their own checkout untouched. +- The user watches a live agent, answers the question it parked on, and chats with it mid-run. +- The user leaves the keyboard and comes back to a drained queue, freshly triaged and planned tickets, and green pull requests merged — paid for with the account's leftover quota. ## Flows @@ -23,9 +19,16 @@ flowchart LR Handoff --> Watch["CI watch: merge green,
fix red, retire merged"] ``` +- One daemon per machine spawns and tracks agents, serves the localhost dashboard, and runs the background services (autonomy sweeps, CI watch, notifications, chat) on one shared clock rather than a timer apiece. Files are the seam: agents narrate onto an on-disk event stream, steering comes back over an append-only control file, and the dashboard is a projection of what they wrote — never a live wire into them. +- An agent is one task worked in its own git worktree on its own branch. The CLI behind it (Claude Code today) stays a swappable black box behind the driver seam; everything the framework learns from a turn — ask-gates, views, the session name, ready-for-merge — is parsed as tagged blocks out of the turn's final message, and a single gate block carries every kind of question. - A build and a verbatim prompt are one path: an opening prompt that honors gates, differing only in which prompt opens it and whether the agent's own backlog is worked afterwards. Nothing reviews the work — the agent is a black box and its turn is the whole of it. - When the agent stops to ask, the question becomes a card on every surface and the picked answer re-prompts the same conversation — unless the agent marked that answer as one that ends it, which is how a declined plan stops the work rather than building on a rejection. An unattended agent takes the recommended option, and a hands-off one is told up front the gates are unavailable so it never parks on a question nobody can answer. +- One composition path assembles every agent's system channel (project context and knowledge docs, built-in prompt, the user's own instructions, the emit protocols), so the dashboard can show exactly what the agent ran under; two switches dial the wrapping down — vanilla drops the built-in prompt, transparent empties the channel entirely. +- An agent that ends with real work publishes itself — commit, push, open a PR; empty ones publish nothing, and merging is authorized by the agent's own ready signal plus an empty backlog of its own, never by configuration alone. How far it publishes is one ordinal, not a set of switches, so an impossible combination cannot be represented. +- When nobody is around, the daemon plays product manager bounded by the account's own quota week: drain the confirmed queue, refill it by triaging and planning tickets (claims committed as lock files beside the tickets, so other machines and cloud agents see them), keep CI green on the PRs it opened, and merge on green. - Unattended spending stands down past the pro-rated share of the account's week that has elapsed; work the user asked for carries on. The gate is on starting and only on starting — an agent already going is never interrupted to economise. +- What must outlive a process lands in git, not memory: each agent's own event log, archived per user so a repo clean cannot erase it and teammates never conflict, plus tickets and their claims, the queue, and the project log. +- The subdirectories hold the seams: the CLI adapters (driver), the on-disk agent state (store), the dashboard and its RPC contract, and the end-to-end proofs. ## Rationales diff --git a/packages/the-framework/src/agent-archive.SPEC.md b/packages/the-framework/src/agent-archive.SPEC.md index 7323a7dfd..d41f1f486 100644 --- a/packages/the-framework/src/agent-archive.SPEC.md +++ b/packages/the-framework/src/agent-archive.SPEC.md @@ -1,10 +1,13 @@ Names the identity a finished agent's history is filed under, so the archive on the data branch is per user and two people on one repo never conflict. -## TLDR +## Flows -- That history used to live only in untracked state, so an ordinary `git clean` erased every agent a project had ever run; keeping the archive in committed files — on the data branch, beside the queue and the tickets — is the fix. - Each person's history files under a directory named after the git email they already commit with — nothing new to set up. A hostile or unusable value can never escape the archive path; it falls back to an "anonymous" directory rather than dropping history. +## Rationales + +- The archive lives in committed files — on the data branch, beside the queue and the tickets — because untracked state does not survive an ordinary `git clean`, which erases every agent a project has ever run. + ## 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/src/agent-driver.SPEC.md b/packages/the-framework/src/agent-driver.SPEC.md index 4c7bb52ae..755d4bcb8 100644 --- a/packages/the-framework/src/agent-driver.SPEC.md +++ b/packages/the-framework/src/agent-driver.SPEC.md @@ -1,9 +1,12 @@ Turns an agent's chosen target into the driver that executes it: this device by default, a fresh GitHub Actions runner, or a Claude cloud session. -## TLDR +## Flows - The Actions target needs the repo's owner, name, and a token, and fails fast with a clear message without them; the cloud target needs nothing extra, because the agent's own signed-in account carries it. -- Kept apart from which CLI the agent uses, so GitHub configuration is never pushed onto ordinary local agents. + +## Rationales + +- The target is kept apart from which CLI the agent uses, so GitHub configuration is never pushed onto ordinary local agents. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/agent-location.SPEC.md b/packages/the-framework/src/agent-location.SPEC.md index 6edc7cfbb..6580ca918 100644 --- a/packages/the-framework/src/agent-location.SPEC.md +++ b/packages/the-framework/src/agent-location.SPEC.md @@ -1,10 +1,13 @@ Where an agent's turns execute — this device, a fresh CI runner, or a cloud session — as its own axis, separate from which coding-agent CLI drives them. -## TLDR +## Flows -- Two orthogonal questions, two seams: *which driver* is that axis's, *where it runs* is this one's. They used to be one dimension, with the same driver appearing as three implementations depending on where it happened to run. - Whether an agent hands the task somewhere this machine cannot follow is a fact about the location, not about the agent. Only a cloud session does: it opens its own pull request and never reports back, so the first prompt is the whole agent and every later phase would misread the hand-off note as the agent's own reply. A CI runner streams its agent's replies and is followed like a local agent. -- Node-free, because the dashboard, the registry and the store all name this axis and none of them should have to reach the driver layer to do it. + +## Rationales + +- *Which driver* and *where it runs* are two orthogonal questions with two seams: folded into one dimension, the same driver appears as three implementations depending on where it happens to run. +- This axis carries no server-only code, because the dashboard, the registry and the store all name it and none of them should have to reach the driver layer to do it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/agent-locks.SPEC.md b/packages/the-framework/src/agent-locks.SPEC.md index e4a0cd479..060fe4df7 100644 --- a/packages/the-framework/src/agent-locks.SPEC.md +++ b/packages/the-framework/src/agent-locks.SPEC.md @@ -1,11 +1,14 @@ Serializes everything that touches one agent's checkout, so a finishing agent's cleanup and a user action on the same agent (push, open a pull request, remove, resume) never run git against the same checkout at once. -## TLDR +## Flows -- Without it, whichever actor lost the race reported a bogus failure or left a checkout behind that should have been removed. -- Both actors live in the daemon by design, so an in-process lock is the whole fix — there is no second process to coordinate with. - A failed holder surfaces its own error without blocking whoever waits behind it. +## Rationales + +- Without the lock, whichever actor loses the race reports a bogus failure or leaves a checkout behind that should have been removed. +- Both actors live in the daemon by design, so an in-process lock is the whole fix — there is no second process to coordinate with. + ## 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/src/agent-messages.SPEC.md b/packages/the-framework/src/agent-messages.SPEC.md index 22dfee88f..e43b111d0 100644 --- a/packages/the-framework/src/agent-messages.SPEC.md +++ b/packages/the-framework/src/agent-messages.SPEC.md @@ -1,11 +1,11 @@ The live-chat channel into a running agent: the user's own messages, spoken unprompted, each continuing the same agent conversation with its full context. -## TLDR +## Flows - The reverse of the agent asking the user: here the user speaks first, and the agent drains the queue once its current work settles. - A daemon-managed agent ends itself when the queue is idle — a later message reopens the conversation — while an agent whose own terminal is the only surface stays parked, since it has no daemon to resume through. - Stopping or closing wakes every waiter empty so the agent ends cleanly, and a stale message never starts a turn on an aborted agent. -- A headless agent gets no channel at all and ends when it stops asking, exactly as before live chat existed. +- A headless agent gets no channel at all and ends when it stops asking. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/agent-options.SPEC.md b/packages/the-framework/src/agent-options.SPEC.md index 77e8e51f1..d005bc2c8 100644 --- a/packages/the-framework/src/agent-options.SPEC.md +++ b/packages/the-framework/src/agent-options.SPEC.md @@ -1,13 +1,16 @@ Turns the user's saved preferences into the options an agent starts with, so an unattended agent honours exactly the same settings as one started from the dashboard. -## TLDR +## Flows -- One shared mapping because two things start agents — the dashboard and the automatic project manager — and the second used to pass nothing, ignoring every setting. - The repo's committed config file becomes a preference layer of its own, sitting under the project's overrides and over the user's global choices. - Settled answers travel explicitly, "off" included, so a repo file or an agent-side default can never turn back on what the launcher just showed as off. - Publishing is one ordinal, not a set of switches: each rung includes the ones below it, so nothing has to remember that a pull request implies a push. Unset means open a pull request — the zero-config handoff — and merging is the rung above, because landing on the main branch has to be asked for. - Options that would mean nothing are dropped: the browser only goes to the driver that can use it, and the model, driver, and target travel only when they differ from the default. +## Rationales + +- One shared mapping, because two things start agents — the dashboard and the automatic project manager — and a mapping apiece is how one of them ends up ignoring every setting. + ## 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/src/agent-spec.SPEC.md b/packages/the-framework/src/agent-spec.SPEC.md index 5d4783072..93fdabf59 100644 --- a/packages/the-framework/src/agent-spec.SPEC.md +++ b/packages/the-framework/src/agent-spec.SPEC.md @@ -1,12 +1,15 @@ The dashboard's process API to an agent it spawns: a JSON blob on a temp file, not a command line. -## TLDR +## Flows - An agent's whole configuration — prompt, kind, checkout, and every option the launcher and Settings decide — travels as one JSON file, handed to the child as `--agent `. -- JSON has a real `false`, so an option that defaults on can simply be off. The paired `--x` / `--no-x` spellings that argv forced, and the tri-state resolution behind them, do not exist here. - The spec is consumed, not merely read: the child removes the file — and the private directory made for it — once it has it, so a device token carried in the options never outlives the agent that used it and nothing accumulates per session. The spawner also removes the spec when its child ends, which is a no-op after a consumed one and the cleanup for a child that failed to spawn or died before reading it. Only a directory the framework verifiably made — the right name, directly in the configured spec home — is ever removed whole, so neither a hand-written spec nor a user's own directory that happens to share the name goes with the file. - Because the dashboard is the only writer, an invalid combination is never constructed, and the child validates only that the file is a spec at all. +## Rationales + +- JSON has a real `false`, so an option that defaults on can simply be off — a command line can only say present-or-absent, which forces paired on/off flag spellings and a three-way resolution behind them. + ## 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/src/agent-telemetry.SPEC.md b/packages/the-framework/src/agent-telemetry.SPEC.md index 6e5376588..829168bab 100644 --- a/packages/the-framework/src/agent-telemetry.SPEC.md +++ b/packages/the-framework/src/agent-telemetry.SPEC.md @@ -1,13 +1,17 @@ The accounting every agent shares, whatever kind it is: naming the session, following the agent's progress, and totalling what it spends. -## TLDR +## Flows -- Lives once because a build and a direct prompt do different work but identical accounting — keeping two copies is how one path once silently lost a feature the other had. - The agent's real session id is surfaced the moment a turn starts, not only when it ends, so the handle for resuming the conversation survives a stop or crash mid-turn. -- One self-stop, where there were three: an answer that says to stop. The caller's signal and that one are composed, so everything downstream ends the same way whichever fired. The two that went were spending — a per-agent cost cap and a mid-flight quota gate, each firing after the turn that crossed it, when the money was already gone; spending is decided once, before an agent starts, because interrupting mid-flight saves the cheap part and loses the expensive part. The one that stayed is the one a person asked for. +- One self-stop: an answer that says to stop. The caller's signal and that one are composed, so everything downstream ends the same way whichever fired. - An unreadable quota never stops the work: a failing quota check means carry on. - One shared classification of how an agent ended — a user stop, a quota pause (which leaves a note to resume from), or a real failure — so every surface agrees on what "stopped" means. +## Rationales + +- The accounting lives once because a build and a direct prompt do different work but identical accounting — keeping two copies is how one path silently loses a feature the other has. +- Spending never stops an agent mid-flight — no per-agent cost cap, no quota gate between turns: each would fire only after the turn that crossed it, when the money is already gone. Spending is decided once, before an agent starts, because interrupting mid-flight saves the cheap part and loses the expensive part. The one self-stop is the one a person asks for. + ## 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/src/agent-view.SPEC.md b/packages/the-framework/src/agent-view.SPEC.md index 5759c1bd5..e699a0aa3 100644 --- a/packages/the-framework/src/agent-view.SPEC.md +++ b/packages/the-framework/src/agent-view.SPEC.md @@ -1,6 +1,6 @@ Distills an agent's event stream into the dashboard's summary cards: the agent's lifecycle progress, what it will do with its work when it ends, and the agent session behind it. -## TLDR +## Flows - Pure folds over the same events the log renders, so a live dashboard and a replay of a past agent always show the identical summary. - Latest wins throughout: the agent may rename its session or re-arm its handoff at any point. diff --git a/packages/the-framework/src/agent.SPEC.md b/packages/the-framework/src/agent.SPEC.md index 35328790f..2482e47b9 100644 --- a/packages/the-framework/src/agent.SPEC.md +++ b/packages/the-framework/src/agent.SPEC.md @@ -1,8 +1,9 @@ One agent: frame it, send it one prompt, honor the gates it answers with, work the backlog, and stay open for the user's own messages — every step streamed as events. -## TLDR +## Flows -- One path, not two. A build and a verbatim prompt used to be separate orchestrators that each inlined the system composition and drifted apart; once the review loop and the `Bootstrap` spine went, a build *was* one prompt honoring gates. What is left of the difference is two options: which prompt opens the agent, and whether its own backlog is worked afterwards. +- Frame the agent → opening prompt (pausing on its questions) → scaffold retry, if a build produced nothing → backlog loop → live chat → end. +- A build and a verbatim prompt are one path: an opening prompt honoring gates. The whole difference is two options: which prompt opens the agent, and whether its own backlog is worked afterwards. - Nothing about the project reaches the agent's prompt: the system framing shown on the dashboard is exactly and entirely what the agent runs under. - Nothing reviews the work: the agent is a black box, and its turn is the whole of it. - A turn that stops to ask becomes a live question, and the answer continues the same conversation — bounded, so an agent that keeps asking cannot loop forever. With nobody to ask, the recommended option is taken and the agent carries on, which is what an unattended one is for. @@ -11,11 +12,11 @@ One agent: frame it, send it one prompt, honor the gates it answers with, work t - A build whose opening turn leaves the workspace empty means the agent stalled, so it is re-prompted once with a hard "create it from scratch" directive. - An agent whose *location* is a cloud session ends at the hand-off, because every later phase would misread the hand-off note as the agent's own reply. Where an agent runs is its own axis, separate from which coding-agent CLI drives it. - Resuming a stopped agent continues the same conversation with the message sent verbatim — the old transcript already carries the framing — while the surrounding flow still runs. +- On any stop or failure: classify why, emit a clean end event, and release the driver session. -## Flows +## Rationales -- Frame the agent → opening prompt (pausing on its questions) → scaffold retry, if a build produced nothing → backlog loop → live chat → end. -- On any stop or failure: classify why, emit a clean end event, and release the driver session. +- One path serves a build and a verbatim prompt alike: two orchestrators for the same lifecycle each carry their own copy of the system composition and drift apart. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/auto-pm.SPEC.md b/packages/the-framework/src/auto-pm.SPEC.md index 2b463380c..e876af472 100644 --- a/packages/the-framework/src/auto-pm.SPEC.md +++ b/packages/the-framework/src/auto-pm.SPEC.md @@ -1,6 +1,13 @@ Auto PM spends leftover subscription quota on the product's own roadmap: while the account is under its weekly boundary and nobody is at the keyboard, the daemon works the agent queue down and, once it is empty, refills it — importing, triaging, then planning tickets. -## TLDR +## User Stories + +- The user leaves the keyboard and comes back to a worked-down queue and freshly imported, triaged, and planned tickets. +- The user caps how many unattended agents a project runs at once and switches individual routines off. +- The user clicks "Run now" on a routine and it runs, even while the master switch is off. +- The user reads the reason for every tick that started nothing, so a setting never looks like a bug. + +## Flows - One pure policy question per project — enabled, under the concurrency cap, past the cooldown, queue readable, quota under the boundary; the sweep loop only supplies the readings. - A standing queue is drained before new work is invented; a calendar-paced codebase maintenance sweep outranks the rotation when due, and only ever while the queue is genuinely empty. @@ -9,13 +16,14 @@ Auto PM spends leftover subscription quota on the product's own roadmap: while t - A claim whose agent settled with nothing to hand off is released by the sweep: the pull request that normally lifts the lock is never coming, and without the release the queue would jam forever on a dead claim. The freed work is not respawned by this daemon — one commitless run is evidence for a human, not an invitation to repeat it every cooldown — and a claim whose agent never even started (a refused spawn, a stop mid-batch) is freed the same way. - The queue coordinates a ticketless entry only once its check-off is in the checkout, and that leaves a window: an agent handed off to a cloud session settles locally before its pull request lands, so until the merge reaches the checkout the entry still reads open, and past the cooldown it can be fanned out to a second agent. The same window opens when the daemon restarts, since only its in-memory pin covered the wait. - Each routine can be switched off individually, and every stand-down is reported with its reason: a wedged sweep must not look like a healthy idle one. -- Switching the draining routine off means "do not *work* the queue", not "do nothing": the tick falls through to the rotation, which puts entries *on* the queue rather than taking them off. Standing down instead made every inventing routine unreachable for as long as the queue held anything — and the queue is auto-populated, so that was most of the time. The one exception is a click that asked for the queue by name: a drain-only sweep says why it cannot, rather than borrowing the click. +- Switching the draining routine off means "do not *work* the queue", not "do nothing": the tick falls through to the rotation, which puts entries *on* the queue rather than taking them off. The one exception is a click that asked for the queue by name: a drain-only sweep says why it cannot, rather than borrowing the click. ## Rationales - An unreadable quota fails closed — the opposite of the per-agent guard: quietly burning quota on work nobody asked for is worse than skipping a tick. - "Run now" skips only the master switch: the click is the consent the preference exists to record; every other stand-down holds. -- The ticketless hand-off window is accepted rather than closed: the durable per-entry claim it would take was deleted as a read-time guess, tickets — the queue's normal case — are covered by the pushed lock, and the queue's planned move onto an eagerly-pushed data branch closes the window structurally. A second claim shape built now would be deleted then. +- A switched-off draining routine falls through to the rotation rather than standing the tick down, because a stand-down would make every inventing routine unreachable whenever the queue holds anything — and the queue is auto-populated, so it usually does. +- The ticketless hand-off window is accepted rather than closed: closing it would take a durable per-entry claim — a second claim shape beside the pushed ticket lock that already covers the queue's normal case — and the queue's planned move onto an eagerly-pushed data branch closes the window structurally, so a claim shape built now would be deleted then. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/await-gate.SPEC.md b/packages/the-framework/src/await-gate.SPEC.md index 0f3bf9523..8cf6050da 100644 --- a/packages/the-framework/src/await-gate.SPEC.md +++ b/packages/the-framework/src/await-gate.SPEC.md @@ -1,12 +1,16 @@ The shared pause-and-ask machinery: an agent can park mid-conversation to ask the human a question, and carries on with the answer. -## TLDR +## Flows -- One resolution path, not four. A gate shows its options, waits for the pick, and re-prompts the agent with the answer, repeating until the agent stops asking — capped so it cannot ask forever. A gate that takes several picks answers with the labels it got; every other gate answers with the one label picked. -- Almost every answer goes back to the agent. The exception is an answer the agent marked as ending it — declining a plan being the one that matters, because the user's next move is fresh instructions and building on a plan they just rejected is the worst thing to do with the interval. That is a property of the question, so the agent says which answers mean it; it used to be inferred from a plan-approval gate kind, which made one question a special case instead of one among many. On a gate taking several picks, one stopping pick among several still stops. +- One resolution path: a gate shows its options, waits for the pick, and re-prompts the agent with the answer, repeating until the agent stops asking — capped so it cannot ask forever. A gate that takes several picks answers with the labels it got; every other gate answers with the one label picked. +- Almost every answer goes back to the agent. The exception is an answer the agent marked as ending it — declining a plan being the one that matters, because the user's next move is fresh instructions and building on a plan they just rejected is the worst thing to do with the interval. On a gate taking several picks, one stopping pick among several still stops. - With nobody to answer (headless, or stopped mid-question) a gate falls back to its recommended option so it never hangs. Which option that is belongs to the agent: handing over a browser recommends "could not handle it", because claiming a human cleared a login wall nobody saw sends the agent back to a blocked page. - Once the agent stops asking, live chat takes over: each user message resumes the same conversation; by default the agent finishes what queued and ends itself — only one whose own terminal is the single surface stays parked for the next message. +## Rationales + +- Whether an answer ends the agent is a property of the question, so the agent says which answers mean it; a dedicated plan-approval gate kind would make one question a special case instead of one among many. + ## 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/src/branch-links.SPEC.md b/packages/the-framework/src/branch-links.SPEC.md index 6cd0acd24..ad8ea135a 100644 --- a/packages/the-framework/src/branch-links.SPEC.md +++ b/packages/the-framework/src/branch-links.SPEC.md @@ -1,6 +1,6 @@ Keeps every session checkout reachable by its branch name: new checkouts live in `.the-framework/branches/` in a folder named as their branch, a symlink appears beside them whenever a branch gets renamed, and a `branches` shortcut at the repo root points there — so `cd branches/` opens any session's work by the name the dashboard shows. -## TLDR +## Flows - A new checkout's folder is already named as its branch, so most need nothing extra. When a session renames its branch (most do, early on), the background pass adds a link under the new name — a rename costs a link, never moving a checkout under a running session. - Only the framework's own links are ever created, replaced, or removed: a user's own file, folder, or symlink at any of these paths is left alone. diff --git a/packages/the-framework/src/browser-stream.SPEC.md b/packages/the-framework/src/browser-stream.SPEC.md index 715fbe425..ee0cbcfb1 100644 --- a/packages/the-framework/src/browser-stream.SPEC.md +++ b/packages/the-framework/src/browser-stream.SPEC.md @@ -1,6 +1,6 @@ Streams the agent's headless browser to a human — the latest screen frame as a simple image stream, with clicks, typing, scrolling and navigation posted back — so when an agent parks asking someone to deal with a login wall or captcha, there is actually a page to act on. -## TLDR +## Flows - Follows the agent: always shows the tab it is working in, re-attaching when it opens or switches tabs, and announces each real page change so the transcript can show the pane at the point of use. - A still page is exactly the parked case, so the newest frame is re-sent on a timer to keep the pane painted. diff --git a/packages/the-framework/src/browser.SPEC.md b/packages/the-framework/src/browser.SPEC.md index fa5fbbf0f..00063b440 100644 --- a/packages/the-framework/src/browser.SPEC.md +++ b/packages/the-framework/src/browser.SPEC.md @@ -1,6 +1,6 @@ The agent launches its own browser with a shared remote-control port, so the agent's browser tools and a human's preview can watch the very same page — a second viewer cannot attach to a browser that launched privately. -## TLDR +## Flows - Finds the machine's Chrome (explicit override first, then the well-known locations, then the PATH); none found means the agent's browser tools fall back to launching their own — a missing browser costs the preview, never the tools. - Headless, on a throwaway profile: an agent never inherits or dirties the user's real browser session. diff --git a/packages/the-framework/src/driver/SPEC.md b/packages/the-framework/src/driver/SPEC.md index eeeec61cb..687ccc8d1 100644 --- a/packages/the-framework/src/driver/SPEC.md +++ b/packages/the-framework/src/driver/SPEC.md @@ -1,19 +1,26 @@ The driver seam: the one abstraction a coding-agent CLI is wrapped behind, so the whole product works the same whether the work happens in a local Claude Code or Codex process, a Claude cloud session, a GitHub Actions job, or a deterministic fake. -## TLDR - -- A driver can start a session, prompt it turn by turn, read the resulting code, report the account's quota, and dispose. That is the entire contract — deliberately *the code and the outcome*, never the CLI's inner workings. Tool calls surface only as named actions for the watching human; their arguments are never seen and never branched on. -- Each turn runs the CLI's own loop to completion as a black box. A turn whose process exits abnormally fails, even if it streamed plausible text first — the product gates on outcomes, so a crash mid-work must not pass as a result. -- The session id is captured from the first thing the CLI says, not from the end of the turn, so a stopped or killed turn cannot take the resume handle with it. A resume that fails because the CLI forgot the conversation silently reruns fresh, with a notice, rather than losing the message the user already sent. -- Every CLI invocation runs as its own process tree, and stopping an agent (or the framework dying) kills the whole tree — no stray processes outlive the agent that spawned them. - ## Flows +- **The contract.** A driver can start a session, prompt it turn by turn, read the resulting code, report the account's quota, and dispose — that is the entire contract. Tool calls surface only as named actions for the watching human; their arguments are never seen and never branched on. +- **Black-box turns.** Each turn runs the CLI's own loop to completion as a black box. A turn whose process exits abnormally fails, even if it streamed plausible text first. +- **The resume handle.** The session id is captured from the first thing the CLI says, not from the end of the turn. A resume that fails because the CLI forgot the conversation silently reruns fresh, with a notice, rather than losing the message the user already sent. +- **Process hygiene.** Every CLI invocation runs as its own process tree, and stopping an agent (or the framework dying) kills the whole tree — no stray processes outlive the agent that spawned them. - **Local CLIs.** Claude Code runs with edits auto-accepted by default; skipping its permission system entirely is an explicit opt-in. Codex runs inside its own workspace sandbox and the bypass is never passed. Extra capabilities (e.g. the real browser) are merged alongside the user's own tools, never replacing them. - **Hands-off locations.** A cloud session is handed the task exactly once per agent — so one agent can never fan out into several cloud sessions racing on one repo — and it ends with the link: there is nothing to read back. Whether later phases run is a fact about *where* the turn executed, so it is settled by the location rather than declared by the driver. A GitHub Actions workflow run is dispatched, polled, and read back from the transcript the workflow uploads; continuity between turns is the branch the previous turn pushed plus the carried session id, and it requires a real user's token — a bot-triggered dispatch is refused. - **Quota and cost.** Drivers that can, report the account's quota window: read via the CLI's own usage command, and for free between turns from the telemetry the stream already carries. A transient failure (network, timeout, reworded readout) leaves the last good reading in force; "the CLI isn't installed / has no subscription" invalidates it. A driver that cannot price turns reports cost as unknown — never zero — so an unpriced agent never reads as free. - **The fake.** A scripted, offline driver that exercises every path deterministically — the product's whole lifecycle is testable without spending a token. +## Rationales + +- The contract is deliberately *the code and the outcome*, never the CLI's inner workings: gating on what the agent produced keeps each CLI's own loop untouched and swappable. +- An abnormal exit fails the turn even after plausible text because the product gates on outcomes — a crash mid-work must not pass as a result. +- The session id is captured at the start of the turn because a stopped or killed turn would otherwise take the resume handle down with it. + +## Glossary + +- **driver** — the wrapper that puts one coding-agent CLI behind this seam's single contract; swapping the driver swaps the agent, and nothing built on top changes. + ## 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/src/driver/actions-zip.SPEC.md b/packages/the-framework/src/driver/actions-zip.SPEC.md index f5241fa86..67022f8ec 100644 --- a/packages/the-framework/src/driver/actions-zip.SPEC.md +++ b/packages/the-framework/src/driver/actions-zip.SPEC.md @@ -1,4 +1,9 @@ -Reads the archive a GitHub Actions run uploads — the only channel the transcript can come back through — and refuses anything it does not fully understand, because a silently-short transcript would read as an agent that said less than it did. Internal to the driver: it is imported by module path, never through a barrel, so it cannot reach the package's public surface by accident again. +Reads the archive a GitHub Actions run uploads — the only channel the transcript can come back through — and refuses anything it does not fully understand. + +## Rationales + +- A refused read beats a partial one: a silently-short transcript would read as an agent that said less than it did. +- The reader is internal to the driver, deliberately off the package's public surface: an accidental export is a one-way door once released. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/actions.SPEC.md b/packages/the-framework/src/driver/actions.SPEC.md index fea6cd4d3..7dcf2979e 100644 --- a/packages/the-framework/src/driver/actions.SPEC.md +++ b/packages/the-framework/src/driver/actions.SPEC.md @@ -1,6 +1,6 @@ A driver that runs the agent on GitHub Actions instead of this machine: dispatch a workflow, wait for it, read back the transcript the agent uploads. -## TLDR +## Flows - Same contract, different tempo: every prompt is a fresh runner, turns take minutes, and progress replays in one burst at the end instead of trickling live. - Continuity across turns is the branch the previous turn pushed — the next one starts from it — plus the carried session id, so a multi-pass agent keeps building on its own work; produced code is read off that branch, because the runner is gone by the time we ask. diff --git a/packages/the-framework/src/driver/child-registry.SPEC.md b/packages/the-framework/src/driver/child-registry.SPEC.md index 9fa5eaaa2..105e1056a 100644 --- a/packages/the-framework/src/driver/child-registry.SPEC.md +++ b/packages/the-framework/src/driver/child-registry.SPEC.md @@ -1,10 +1,13 @@ Makes sure no driver process ever outlives the agent that spawned it: each spawned child leads its own process tree, and stopping an agent — or the framework itself dying — reaps the whole tree instead of orphaning it. -## TLDR +## Flows -- Signaling only the top agent process used to leave its helpers (workers, tool calls, servers) burning CPU forever; killing the whole tree at once is the fix. - Every live tree is tracked so even a hard crash of the framework still takes them all down on the way out. +## Rationales + +- The whole tree is killed at once because signaling only the top agent process leaves its helpers (workers, tool calls, servers) burning CPU 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/src/driver/claude-code-quota.SPEC.md b/packages/the-framework/src/driver/claude-code-quota.SPEC.md index ba0b2dd62..55d14b367 100644 --- a/packages/the-framework/src/driver/claude-code-quota.SPEC.md +++ b/packages/the-framework/src/driver/claude-code-quota.SPEC.md @@ -1,12 +1,16 @@ Asks Claude Code where the account's subscription quota stands, by running the agent's own usage readout and parsing the prose it prints. -## TLDR +## Flows -- The read costs nothing — the driver answers locally without prompting a model — and it runs with the driver's own credentials, so the product never touches the user's token. -- The readout is prose, so a reworded readout is a real failure mode: an unreadable answer reports "unrecognized", never an empty reading, because a silent zero would read as "nothing used" and let a consumption limit run the account dry. +- The read costs nothing — the agent answers locally without prompting a model — and it runs with the agent's own credentials, so the product never touches the user's token. +- The readout is prose, so a reworded readout is a real failure mode: an unreadable answer reports "unrecognized", never an empty reading. - An account with no subscription quota (API-key auth) is told apart from a readout we failed to read: one means no quota exists, the other means try again — and an account burning overage still reports its quota. - A missing agent, a refused fetch, and a hung read each get their own reason, so callers know whether to keep the last good reading. +## Rationales + +- An unreadable answer never reports an empty reading because a silent zero would read as "nothing used" and let a consumption limit run the account dry. + ## 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/src/driver/claude-code.SPEC.md b/packages/the-framework/src/driver/claude-code.SPEC.md index 99bf854f4..2c42240d6 100644 --- a/packages/the-framework/src/driver/claude-code.SPEC.md +++ b/packages/the-framework/src/driver/claude-code.SPEC.md @@ -1,11 +1,15 @@ The first real driver: wraps the Claude Code CLI so the product runs work on the user's own Claude subscription, one fresh non-interactive invocation per prompt. -## TLDR +## Flows - File edits are auto-accepted by default so agents never stall on a permission prompt; skipping the CLI's permission system entirely is a separate, explicit opt-in meant for sandboxes. - A chat turn can resume the agent's previous conversation so the message lands with full context; when that conversation no longer exists, the turn reruns fresh with a notice rather than losing the message the user already typed — and without showing a failed turn for one that recovered. - Extra capabilities (like the real browser) are offered as additional tool servers that merge with the user's own, never replacing them. -- The CLI's stream is mined for free telemetry: its session id — announced at the start of the turn, so a stopped or killed turn can't take the resume handle with it — assistant text, tool names, per-turn token/cost accounting, and the account's quota standing. +- The CLI's stream is mined for free telemetry: its session id (announced at the start of the turn), assistant text, tool names, per-turn token/cost accounting, and the account's quota standing. + +## Rationales + +- The session id is captured at the start of the turn because a stopped or killed turn would otherwise take the agent's resume handle down with it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/cli-session.SPEC.md b/packages/the-framework/src/driver/cli-session.SPEC.md index ff73df9bf..50fd49a46 100644 --- a/packages/the-framework/src/driver/cli-session.SPEC.md +++ b/packages/the-framework/src/driver/cli-session.SPEC.md @@ -1,12 +1,16 @@ The one way any driver CLI is run: spawn it over the workspace, hand it the prompt, stream its output through that CLI's own dialect parser, and settle the turn on how the process ended. -## TLDR +## Flows -- A turn fails on an abnormal exit even when the agent streamed plausible text first — the product gates on outcomes, and a crash mid-work must not pass as a result; what the agent said on the way down becomes the error detail. +- A turn fails on an abnormal exit even when the agent streamed plausible text first; what the agent said on the way down becomes the error detail. - Stopping a turn kills the agent's entire process tree: asked nicely first, forced after a grace period. - The prompt travels over the agent's input stream, so its length is unlimited and none of it can be misread as a command; an agent that dies before reading it fails the turn cleanly instead of crashing the product. - Only the parser knows which CLI is on the other end, so a second driver gets all of this for free. +## Rationales + +- An abnormal exit fails the turn despite plausible text because the product gates on outcomes: a crash mid-work must not pass as a result. + ## 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/src/driver/cloud.SPEC.md b/packages/the-framework/src/driver/cloud.SPEC.md index 65bc739fd..81fdf4d52 100644 --- a/packages/the-framework/src/driver/cloud.SPEC.md +++ b/packages/the-framework/src/driver/cloud.SPEC.md @@ -1,13 +1,21 @@ A driver that hands the whole task to Claude Code on the web: it starts a real cloud session on the user's own account and gives back the link where the work continues. -## TLDR +## Flows -- One agent, one cloud session — ever. The loop keeps prompting (plan, build, review), so without this guard a single agent would fan out into several cloud sessions racing on one repo; every prompt after the first just reports the hand-off that already happened. -- The agent ends at the hand-off: a cloud session offers no way to read status, replies, or output back — only its link, plus a command to pull the session back locally. The location says so, so later phases never mistake this driver's own summary for the agent's answer. -- The project root is trusted for the CLI before the hand-off — worktrees inherit the root's trust, and starting a web agent is itself the user's trust decision — so the CLI's interactive trust question, which a background run could never answer, does not fire. A dialog that appears anyway (the write failed or was rejected) still fails fast with the manual fix named instead of timing out with nothing to show. +- One agent, one cloud session — ever. Every prompt after the first just reports the hand-off that already happened, without spending another session. +- The agent ends at the hand-off: a cloud session offers no way to read status, replies, or output back — only its link, plus a command to pull the session back locally. That the agent ends there is a fact of the web location, not something this driver declares. +- The project root is trusted for the CLI before the hand-off, so the CLI's interactive trust question — which a background run could never answer — does not fire. A dialog that appears anyway (the write failed or was rejected) still fails fast with the manual fix named instead of timing out with nothing to show. - Nothing the user typed can ever reach a shell as syntax. -- The session it creates is repo-bound, not a bundle (#1320): with nonessential traffic disabled the CLI's server-side bundle experiment reads off, so a failed GitHub-App preflight falls through to a session that clones from GitHub and can push — instead of silently uploading a local bundle whose work can never leave the VM (anthropics/claude-code#81776). -- Before the hand-off, HEAD is pushed to origin under the agent's own id: the CLI's default revision pin is the current local branch — which an agent workspace's local-only branch fails — and a slash-carrying ref never resolves on the cloud side even when pushed (anthropics/claude-code#87235), so the ref is minted slash-free and handed over explicitly. A push that fails degrades to the old behavior and says so, naming `--teleport` as the recovery path. +- The session it creates is repo-bound — it clones from GitHub and can push — never a silently uploaded local bundle whose work could never leave the VM; the CLI's nonessential traffic is switched off for the invocation to keep it so. +- Before the hand-off, HEAD is pushed to origin under the agent's own slash-free id and the session is told to clone at that ref. A push that fails hands the ref choice back to the CLI's own default and says so, naming `--teleport` as the recovery path. + +## Rationales + +- The one-session guard exists because the loop keeps prompting (plan, build, review): without it a single agent would fan out into several cloud sessions racing on one repo. +- The hand-off ending lives on the location rather than on the driver so later phases never mistake the driver's own summary for the agent's answer. +- Trusting the project root on the user's behalf is sound because starting a web agent is itself the user's trust decision, and worktrees inherit the root's trust, so one grant covers every agent workspace. +- With nonessential traffic disabled, the CLI's server-side bundle experiment reads off, so a failed GitHub-App preflight falls through to a repo-bound session instead of a silent local-bundle upload (#1320, anthropics/claude-code#81776). +- The ref is minted slash-free and handed over explicitly because the CLI's default revision pin is the current local branch — which an agent workspace's local-only branch fails — and a slash-carrying ref never resolves on the cloud side even when pushed (anthropics/claude-code#87235). ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/codex.SPEC.md b/packages/the-framework/src/driver/codex.SPEC.md index e3da32cbb..268049779 100644 --- a/packages/the-framework/src/driver/codex.SPEC.md +++ b/packages/the-framework/src/driver/codex.SPEC.md @@ -1,6 +1,6 @@ The second real driver: wraps the Codex CLI so the product runs the same way on the user's own ChatGPT subscription. -## TLDR +## Flows - Codex works inside its own workspace sandbox — it can edit the workspace it was pointed at and nothing else — and the sandbox-bypass option is never passed. - Codex takes no separate system prompt, so role framing is prepended to the prompt; the same words reach the CLI. diff --git a/packages/the-framework/src/driver/fake.SPEC.md b/packages/the-framework/src/driver/fake.SPEC.md index 452f88910..2ac6deb4e 100644 --- a/packages/the-framework/src/driver/fake.SPEC.md +++ b/packages/the-framework/src/driver/fake.SPEC.md @@ -1,6 +1,6 @@ An in-memory driver that replays scripted turns deterministically, so the whole product — agents, gates, dashboard — works offline without spawning a process or spending a token. -## TLDR +## Flows - Emits the same events a real driver does and records every prompt it receives, so tests can assert both what the user saw and what the agent was told. - A short script never starves a long agent: once the turns run out, the last one repeats. diff --git a/packages/the-framework/src/driver/index.SPEC.md b/packages/the-framework/src/driver/index.SPEC.md index dc49cd7a8..6a478bb84 100644 --- a/packages/the-framework/src/driver/index.SPEC.md +++ b/packages/the-framework/src/driver/index.SPEC.md @@ -1,4 +1,8 @@ -The driver seam's public doorway: one import for the contract, every driver (Claude Code, Codex, GitHub Actions, cloud, fake), and the quota reader. What a driver needs internally does not pass through here — the root barrel re-exports this one wholesale, so anything listed is published, and an accidental export is a one-way door once released. +The driver seam's public doorway: one import for the contract, every driver (Claude Code, Codex, GitHub Actions, cloud, fake), and the quota reader — what a driver needs internally does not pass through here. + +## Rationales + +- The root barrel re-exports this doorway wholesale, so anything listed is published, and an accidental export is a one-way door once released. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/types.SPEC.md b/packages/the-framework/src/driver/types.SPEC.md index 399b45320..2722b6562 100644 --- a/packages/the-framework/src/driver/types.SPEC.md +++ b/packages/the-framework/src/driver/types.SPEC.md @@ -1,10 +1,10 @@ The contract every wrapped coding agent must fit: start a session in a workspace, prompt it turn by turn, read the code it produced, dispose — the agent itself stays a black box. -## TLDR +## Flows - The seam is deliberately the code and the outcome, never the agent's tool calls: tools surface only as named actions for the watching human, and control flow never branches on them. - Each prompt is the fresh-context unit; personas are framing text carried on the session, not separate agents. -- A turn always reports the tokens it spent, but a price only when the agent priced it — an unknown cost is omitted, never zero, so "free" and "unknown" can't be confused. +- A turn reports the tokens it spent, but a price only when the agent priced it — an unknown cost is omitted, never zero, so "free" and "unknown" can't be confused. - A quota reading is available-with-windows or unavailable-with-a-reason, and the reasons split "this attempt failed" (keep showing the last good reading) from "this setup has no quota" (drop it). - A driver answers only "which CLI do I spawn". Whether an agent ends at its first prompt is a fact about where that prompt executed, and lives with the location instead. From 37118a14eeb9e0771e770bca34098dd150e9c199 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:39:34 +0000 Subject: [PATCH 03/15] Migrate SPEC.md files to the new sdd.md template: src utilities and presets TLDR sections become Flows; history framings ("used to be three rules", "any more", incident stories) are reworded as present-tense rationales; self-references made self-contained. Behavior bullets stay byte-identical. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/event-stream.SPEC.md | 2 +- packages/the-framework/src/events.SPEC.md | 2 +- packages/the-framework/src/fake-script.SPEC.md | 2 +- .../the-framework/src/framework-gitignore.SPEC.md | 7 +++++-- packages/the-framework/src/gate-keepalive.SPEC.md | 10 +++++++--- packages/the-framework/src/git-exclude.SPEC.md | 2 +- packages/the-framework/src/handoff-level.SPEC.md | 12 ++++++++---- packages/the-framework/src/install.SPEC.md | 2 +- packages/the-framework/src/jsonl-tail.SPEC.md | 2 +- packages/the-framework/src/layout.SPEC.md | 7 ++----- packages/the-framework/src/loopback-host.SPEC.md | 2 +- packages/the-framework/src/maintenance.SPEC.md | 2 +- packages/the-framework/src/merged-worktrees.SPEC.md | 9 ++++++--- .../src/on-before-mergeable-prompt.SPEC.md | 2 +- .../the-framework/src/preference-defaults.SPEC.md | 8 ++++++-- packages/the-framework/src/preflight.SPEC.md | 5 +++-- packages/the-framework/src/preset-catalog.SPEC.md | 4 ++-- packages/the-framework/src/preset-prompt.SPEC.md | 2 +- packages/the-framework/src/presets.SPEC.md | 2 +- 19 files changed, 50 insertions(+), 34 deletions(-) diff --git a/packages/the-framework/src/event-stream.SPEC.md b/packages/the-framework/src/event-stream.SPEC.md index 5d38dd291..fc01c2514 100644 --- a/packages/the-framework/src/event-stream.SPEC.md +++ b/packages/the-framework/src/event-stream.SPEC.md @@ -1,6 +1,6 @@ A replayable, multi-consumer stream of events: every event is buffered, live consumers get async iterators, and history replays from any offset. -## TLDR +## Flows - One producer, many consumers: pushing an event wakes every waiting iterator, and a consumer that arrives late replays what it missed from an offset rather than starting blank. - Closing is final and idempotent: pushes after a close are ignored and every live iterator ends, so a finished agent cannot leave a reader hanging. diff --git a/packages/the-framework/src/events.SPEC.md b/packages/the-framework/src/events.SPEC.md index 26fb1b68f..b4130d262 100644 --- a/packages/the-framework/src/events.SPEC.md +++ b/packages/the-framework/src/events.SPEC.md @@ -1,6 +1,6 @@ The single event stream an agent narrates itself over: one timeline uniting the framework's own steps, the driver's progress, and the moments that need a human. -## TLDR +## Flows - The framework owns the stream rather than exposing the driver's transport, so every surface — terminal, dashboard, chat — renders the same story. - Events are the agent's durable record: anything a dashboard tab opened later must know (the ticket being implemented, the branch, the pull request opened for the work, what the end-of-work handoff is armed to do) travels as an event, because only events reach its stored history. diff --git a/packages/the-framework/src/fake-script.SPEC.md b/packages/the-framework/src/fake-script.SPEC.md index 7de14666f..47c287ada 100644 --- a/packages/the-framework/src/fake-script.SPEC.md +++ b/packages/the-framework/src/fake-script.SPEC.md @@ -1,6 +1,6 @@ The deterministic offline demo: a scripted fake agent builds a small orders app so the whole flow can be shown with no agent CLI and no model. -## TLDR +## Flows - Optional variants make the scripted build pause on each kind of interactive gate (single choice, checklist, plan approval), so the ask-and-resume flow is demonstrable offline too. - Each turn reports a small plausible spend, so the live cost readout has something to show. diff --git a/packages/the-framework/src/framework-gitignore.SPEC.md b/packages/the-framework/src/framework-gitignore.SPEC.md index 17b439b6b..e55c572cf 100644 --- a/packages/the-framework/src/framework-gitignore.SPEC.md +++ b/packages/the-framework/src/framework-gitignore.SPEC.md @@ -1,9 +1,12 @@ The `.the-framework/.gitignore`: everything under a project's framework directory is transient on main. -## TLDR +## Flows - One file with one content, written whole at install: ignore it all, keep only the ignore file itself and the layout marker. -- Nothing else under the framework directory is committed on code branches any more — the lasting records (the session archives) live on the data branch — so the layout marker is the only re-inclusion. + +## Rationales + +- Nothing else under the framework directory belongs on code branches — the lasting records (the session archives) live on the data branch — so the layout marker is the only re-inclusion. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/gate-keepalive.SPEC.md b/packages/the-framework/src/gate-keepalive.SPEC.md index 5636ca802..f22b946fa 100644 --- a/packages/the-framework/src/gate-keepalive.SPEC.md +++ b/packages/the-framework/src/gate-keepalive.SPEC.md @@ -1,9 +1,13 @@ Keeps an agent's process alive while it is parked waiting for a human's answer, and lets it exit the moment nothing is parked. -## TLDR +## Flows -- A background-started agent has nothing else keeping it running between turns, so without this it could silently exit mid-wait — leaving answers that arrive later with nothing to read them. -- Waiting for the answer is the agent's work at that moment, so holding the process open is right exactly then and nowhere else: overlapping waits share one hold, and the last to settle releases it. +- Overlapping waits share one hold, and the last to settle releases it. + +## Rationales + +- A background-started agent has nothing else keeping it running between turns, so without the hold it could silently exit mid-wait — leaving answers that arrive later with nothing to read them. +- Waiting for the answer is the agent's work at that moment, so holding the process open is right exactly then and nowhere else. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/git-exclude.SPEC.md b/packages/the-framework/src/git-exclude.SPEC.md index c575382b7..1eb6cf93a 100644 --- a/packages/the-framework/src/git-exclude.SPEC.md +++ b/packages/the-framework/src/git-exclude.SPEC.md @@ -1,6 +1,6 @@ Hides a path from git without touching any file the project owns, by writing the ignore rule into the repository's own exclude file. -## TLDR +## Flows - The rule lands in git's repository-level exclude, so it covers every worktree at once and never shows up as a change to the project. - Idempotent: a rule already present is left alone. diff --git a/packages/the-framework/src/handoff-level.SPEC.md b/packages/the-framework/src/handoff-level.SPEC.md index f108a5cb3..d31aefe82 100644 --- a/packages/the-framework/src/handoff-level.SPEC.md +++ b/packages/the-framework/src/handoff-level.SPEC.md @@ -1,11 +1,15 @@ How far a finished agent publishes itself — one ordinal covering keep it local, push the branch, open a pull request, merge it. -## TLDR +## Flows -- One ladder, not three switches. The stages are strictly nested — a pull request needs a pushed branch, a merge needs a pull request — so three independent booleans described eight states of which four were reachable, and the implication lived in a doc comment because the type could not carry it. -- The impossible combinations stop being representable. "A pull request without a push" was never something an agent could honour; it used to be resolved by turning the push back on, which meant a launcher offering "publish nothing" could not deliver it. +- 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 still shows three checkboxes converts both ways, and the conversion is where an impossible answer resolves *downward* rather than being quietly repaired upward. Settings written before the ladder are read through that same conversion, since forgetting them 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. 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. + +## Rationales + +- Three independent booleans describe eight states of which four are reachable, and the implication has to live in a doc comment because the type cannot carry it; the ladder carries the implication structurally. +- "A pull request without a push" is not something an agent can honour, and repairing it upward — turning the push back on — makes a "publish nothing" answer undeliverable; resolving downward keeps every answer deliverable. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/install.SPEC.md b/packages/the-framework/src/install.SPEC.md index 118887802..1ef2ed661 100644 --- a/packages/the-framework/src/install.SPEC.md +++ b/packages/the-framework/src/install.SPEC.md @@ -1,6 +1,6 @@ Activates a repo for the framework: it becomes a Git repo if it is not one yet, gets the framework's marker directory and its ignore rules, and the activation itself is committed. -## TLDR +## Flows - Pre-existing uncommitted changes are committed first, so the install commit is clean and none of the user's work is mixed into it. - The ignore rules keep every bit of agent state out of Git on the code branches (the durable records live on the data branch); the quality presets are materialized so their references resolve, but regenerate per install rather than being committed. diff --git a/packages/the-framework/src/jsonl-tail.SPEC.md b/packages/the-framework/src/jsonl-tail.SPEC.md index e60788d34..267d03e73 100644 --- a/packages/the-framework/src/jsonl-tail.SPEC.md +++ b/packages/the-framework/src/jsonl-tail.SPEC.md @@ -1,6 +1,6 @@ Follows an append-only line-per-record log as it grows — the seam through which agents, daemon, and dashboard talk via files rather than direct connections. -## TLDR +## Flows - Reads only what was appended since last time, holds back a half-written line until it completes, and starts over when a fresh agent truncates the log. - A tail can follow its log to a new home when the file is moved with content intact, without replaying what it already delivered. diff --git a/packages/the-framework/src/layout.SPEC.md b/packages/the-framework/src/layout.SPEC.md index b8f69857e..2c95240c1 100644 --- a/packages/the-framework/src/layout.SPEC.md +++ b/packages/the-framework/src/layout.SPEC.md @@ -1,19 +1,16 @@ The layout gate: a framework build refuses to run in a repo that records a different bookkeeping layout, instead of committing files under names the repo no longer uses. -## TLDR +## Flows - Every activated repo carries a small committed marker naming the layout its bookkeeping is on (the data branch's name, and where archives, tickets, and the queue live). - Before a session starts, the build compares the repo's marker against its own layout; a mismatch refuses the session outright with both sides named and the fix — no degraded mode. - A repo without the marker is not gated; installing writes it, so every newly activated repo is gated from the start. - -## Flows - - The marker's content is derived from the build itself, so renaming anything in the layout changes the marker by itself; a test pins the repo's checked-in marker to the derivation, so a rename cannot land without regenerating the marker in the same change. - The refusal message names the file, both layouts, and how to fix each direction (update the installed framework, or regenerate the marker when the build is the newer side). ## Rationales -- The failure this closes was caught live: the cloud environment installs the framework from npm, the published build predated a rename, and the web run committed its session archive under the old name — rejected hours later by the main branch's guard instead of seconds in, with a message about symptoms rather than the cause. +- The failure the gate closes: the cloud environment installs the framework from npm, so a published build can predate a repo-side rename; run unguarded, it commits its session archive under the old name and is rejected hours later by the main branch's guard — with a message about symptoms — when the session could have refused seconds in with the cause named. - Refuse rather than warn, same stance as the extension version gate: a skewed build does not fail loudly on its own — it half-works, and its wrong-layout commits look plausible to a human. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/loopback-host.SPEC.md b/packages/the-framework/src/loopback-host.SPEC.md index 9451e8795..ae0939eb8 100644 --- a/packages/the-framework/src/loopback-host.SPEC.md +++ b/packages/the-framework/src/loopback-host.SPEC.md @@ -1,6 +1,6 @@ Decides whether an address is truly local, so the dashboard skips its access token only when the browser never leaves the machine. -## TLDR +## Flows - A bind-all or routable address is not local and stays gated behind the shared token. - A registrable name that merely starts with "127." is rejected — that is exactly the rebound host a DNS-rebinding attack uses to look local. diff --git a/packages/the-framework/src/maintenance.SPEC.md b/packages/the-framework/src/maintenance.SPEC.md index 09b785cfb..0e76ca4a5 100644 --- a/packages/the-framework/src/maintenance.SPEC.md +++ b/packages/the-framework/src/maintenance.SPEC.md @@ -1,6 +1,6 @@ The maintenance sweep: a background job that keeps registered repos healthy by running the maintainability review on whatever new work each repo has grown since it was last reviewed. -## TLDR +## Flows - Each repo remembers its last-reviewed commit in a small local file, so a sweep only ever acts on commits added since then. - A repo seen for the first time is baselined at its current state — its pre-existing history is never reviewed retroactively. diff --git a/packages/the-framework/src/merged-worktrees.SPEC.md b/packages/the-framework/src/merged-worktrees.SPEC.md index 354779faa..3266cc982 100644 --- a/packages/the-framework/src/merged-worktrees.SPEC.md +++ b/packages/the-framework/src/merged-worktrees.SPEC.md @@ -1,14 +1,17 @@ Automatically reclaims an agent's checkout once its work is on the remote — the branch, the commits, and the agent's history are kept, so only disk is freed. -## TLDR +## Flows -- One rule decides every removal, and it lives in the shared operation rather than here: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it. Every deletion is therefore recoverable, because the remote holds a copy. -- Three interacting rules used to decide this instead — a clean finish removes the checkout, a failure or stop keeps it, a merged branch reclaims it later through two different "landed" signals — each asking *how did this agent end* rather than *is the work safe yet*. +- One rule decides every removal, shared with the dashboard's manual Remove button so the automatic path and the manual one cannot disagree: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it. Every deletion is therefore recoverable, because the remote holds a copy. - The sweep is what reaches the agents the teardown could not: a push that failed then (offline, no auth, a rejected non-fast-forward) simply succeeds on a later pass. There is one failure mode, and it is said out loud — once per kept checkout per reason, not once per pass: a checkout that can never be reclaimed would otherwise repeat the same line every ten minutes for as long as the daemon runs, while a reason that changes is a changed state and is said again. - Whether the repo has a remote at all is asked once per project: with none, every checkout is kept and accounted for without probing each one for a push that cannot land. - A live agent keeps its checkout: its driver is working in there, and Stop is how one ends. So does one the daemon has not finished retiring — an agent's records say `done` a beat before its teardown reclaims the checkout, and "not live" is not "nobody is holding this". - Every project is swept once at startup and every ten minutes after, on the daemon's shared clock rather than a timer of its own, and each removal is announced so it never reads as a bug. +## Rationales + +- The one rule asks *is the work safe yet* rather than *how did this agent end*: one predicate, checkable at any moment, with one failure mode — where per-ending rules (a clean finish removes the checkout, a failure or stop keeps it, a merged branch reclaims it later through separate "landed" signals) leave the kept checkouts accumulating, one per failed agent, 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/src/on-before-mergeable-prompt.SPEC.md b/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md index 9606a39fe..3ceabee9a 100644 --- a/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md +++ b/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md @@ -1,6 +1,6 @@ The prompt sent to an agent once its work is ready to merge: queue the quality reviews as follow-up work, and fold what it learned back into the project's business-knowledge docs. -## TLDR +## Flows - It queues the quality presets (maintainability and security audit) onto the project's backlog rather than running them on the spot; the backlog loop picks them up later. - Every queued entry targets the changes introduced by the finished agent, named explicitly. diff --git a/packages/the-framework/src/preference-defaults.SPEC.md b/packages/the-framework/src/preference-defaults.SPEC.md index 22b24b171..844cb312a 100644 --- a/packages/the-framework/src/preference-defaults.SPEC.md +++ b/packages/the-framework/src/preference-defaults.SPEC.md @@ -1,11 +1,15 @@ The shared defaults and bounds for user preferences, written down once so the dashboard and the daemon act on the same values. -## TLDR +## Flows -- Notifications are a 2×2 with the axes named: *how* one reaches you (browser, Discord) and *what it is about* (needs-you, plain activity). Nothing in the four stored key names said which axis a key belonged to, and composing them was open-coded per call site — which is how one got a category's polarity wrong by copying its sibling. Delivering a cell asks one question here instead. +- Notifications are a 2×2 with the axes named: *how* one reaches you (browser, Discord) and *what it is about* (needs-you, plain activity). Delivering a cell asks one question: both its method and its category must be on. - The polarities are not uniform, and that is the point of writing them once: the browser bell and the "needs you" baseline fire unless turned off, while anything that reaches outward (Discord) or is loosely informative is opt-in. - The bounds and defaults for the automatic-spend slider and the auto-PM concurrency — one number each that both the browser control and the daemon's sanitizer read. +## Rationales + +- Composing a delivery in one place is what prevents the copied-sibling mistake: nothing in the four stored key names says which axis a key belongs to, and a delivery decision open-coded per surface is how a category's polarity gets copied wrong from its sibling. + ## 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/src/preflight.SPEC.md b/packages/the-framework/src/preflight.SPEC.md index 8f9d8e19e..28f187052 100644 --- a/packages/the-framework/src/preflight.SPEC.md +++ b/packages/the-framework/src/preflight.SPEC.md @@ -1,7 +1,8 @@ -Pre-start checks so an agent is refused early and clearly when a prerequisite is missing, instead of spawning a broken one. Run once, by the dashboard, before it spawns anything — the agent itself does not check again. +Pre-start checks so an agent is refused early and clearly when a prerequisite is missing, instead of spawning a broken one. -## TLDR +## Flows +- The checks run once, by the dashboard, before it spawns anything — the agent itself does not check again. - Verifies the driver CLI the agent actually picked is installed and logged in — installed is not usable: a logged-out CLI kills every agent before it starts, while the machine keeps spending branches and checkouts on each attempt. - Only a clear "not logged in" blocks; a CLI that will not say gets the benefit of the doubt, because an agent that might work beats a refusal we cannot stand behind. - When the agent will publish (PR/merge), the GitHub CLI is checked too — as warnings only, since its own work needs no GitHub and only publishing would degrade. diff --git a/packages/the-framework/src/preset-catalog.SPEC.md b/packages/the-framework/src/preset-catalog.SPEC.md index f2145d5c3..0e0c5dcb4 100644 --- a/packages/the-framework/src/preset-catalog.SPEC.md +++ b/packages/the-framework/src/preset-catalog.SPEC.md @@ -1,12 +1,12 @@ The one table of every built-in preset — the prompts the product offers as one-click agents, from quality reviews to the product-management cluster. -## TLDR +## Flows - The quality reviews (research, maintainability, readability, security audit, UX) take a target, defaulting to the launching agent or the whole codebase. - The PM cluster scopes itself to the repo's own tickets and queue: update tickets from GitHub (an empty `tickets/` gets a full first import — there is no separate import preset), plan them, suggest new tickets or features, pick what to work on, triage into the queue, and drain the queue. - Presets that pause for a human are kept off unattended schedules; the scheduled triage pair pins its own session name so a firing aborts instead of triaging twice. - The GitHub-sync preset always opens an agent of its own — its work is about the repo, not the conversation it was clicked from. -- The launcher's menu is one ordered list here; the queue-drain preset is daemon-only and absent from it. +- The launcher's menu is one ordered list; the queue-drain preset is daemon-only and absent from it. - Recognising "the prompt that drains the queue" compares against the rendered preset itself, so rewording the preset cannot silently break the detection. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/preset-prompt.SPEC.md b/packages/the-framework/src/preset-prompt.SPEC.md index d43d6ad7c..0d2353c6b 100644 --- a/packages/the-framework/src/preset-prompt.SPEC.md +++ b/packages/the-framework/src/preset-prompt.SPEC.md @@ -1,6 +1,6 @@ How a preset is defined: only what actually differs between presets (name, prompt, what its one target means) is declared; parameter handling, defaults, and rendering are shared. -## TLDR +## Flows - A preset either takes one target ("what to run against") or scopes itself, in which case its prompt is used verbatim. - A blank or omitted target falls back to a dynamic default: the agent the preset was launched from, else the whole codebase. diff --git a/packages/the-framework/src/presets.SPEC.md b/packages/the-framework/src/presets.SPEC.md index fecd8c148..8aba09e66 100644 --- a/packages/the-framework/src/presets.SPEC.md +++ b/packages/the-framework/src/presets.SPEC.md @@ -1,6 +1,6 @@ Writes the built-in presets into a project as real files, so queued follow-up work can point the agent at a preset it can open. -## TLDR +## Flows - Derived from the one preset table, so a preset exists on disk exactly when it exists in the catalog. - The files keep their target blank unfilled — the queue entry that references a preset says what to aim it at. From 3071a94dc1905f1be185ea3cb8e92b44a5cc9000 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:39:52 +0000 Subject: [PATCH 04/15] Migrate SPEC.md files to the new sdd.md template: dashboard components (A-D), website, chrome extension TLDR sections become Flows with behavior bullets byte-identical; history clauses reworded as present-tense rationales; User Stories added on the extension and website directory specs (visitor/user-facing flows). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/SPEC.md | 2 +- packages/chrome-extension/SPEC.md | 20 +++++++++++++++---- packages/chrome-extension/background.SPEC.md | 13 ++++++++---- packages/chrome-extension/content.SPEC.md | 9 +++++++-- packages/chrome-extension/options.SPEC.md | 2 +- packages/the-framework.ai/SPEC.md | 9 ++++++++- packages/the-framework.ai/pages/SPEC.md | 2 +- .../pages/index/AutonomousAi.SPEC.md | 2 +- .../the-framework.ai/pages/index/Hero.SPEC.md | 2 +- packages/the-framework.ai/pages/index/SPEC.md | 10 ++++++++-- .../components/AddProjectPanel.SPEC.md | 2 +- .../components/AgentActionBar.SPEC.md | 2 +- .../components/AgentActionsMenu.SPEC.md | 2 +- .../dashboard/components/AgentChanges.SPEC.md | 2 +- .../components/AgentComposer.SPEC.md | 4 ++-- .../dashboard/components/AgentHandoff.SPEC.md | 2 +- .../dashboard/components/AgentHistory.SPEC.md | 2 +- .../dashboard/components/AgentView.SPEC.md | 2 +- .../dashboard/components/AiQueue.SPEC.md | 2 +- .../dashboard/components/BrowserPanel.SPEC.md | 2 +- .../dashboard/components/ChoicePanel.SPEC.md | 8 ++++++-- .../components/CloudAgentNotice.SPEC.md | 2 +- .../dashboard/components/Composer.SPEC.md | 2 +- .../components/DashboardPage.SPEC.md | 2 +- 24 files changed, 73 insertions(+), 34 deletions(-) diff --git a/packages/SPEC.md b/packages/SPEC.md index bad0674bb..13d29afc1 100644 --- a/packages/SPEC.md +++ b/packages/SPEC.md @@ -1,4 +1,4 @@ -The published family: the product (`the-framework`, which carries its own dashboard UI in `dashboard/`), the claude.ai browser extension (`chrome-extension`), and the product's website (`the-framework.ai`). Nothing depends up. +The published family: the product (`the-framework`, which carries its own dashboard UI in `dashboard/`), the claude.ai browser extension (`chrome-extension`), and the product's website (`the-framework.ai`) — nothing depends up. ## Before modifying/creating SPEC.md files diff --git a/packages/chrome-extension/SPEC.md b/packages/chrome-extension/SPEC.md index 86ae4d01f..9c98ee8a4 100644 --- a/packages/chrome-extension/SPEC.md +++ b/packages/chrome-extension/SPEC.md @@ -1,13 +1,25 @@ A Chrome extension bridging Claude Code cloud sessions on claude.ai to the local dashboard: the question a parked session is waiting on travels home, and the answer picked in the dashboard travels back into the session. -## TLDR +## User Stories + +- The user sees the question a parked cloud session is waiting on in the dashboard, without watching claude.ai. +- The user answers it in the dashboard, and the answer is typed back into the claude.ai session. +- The user follows the cloud session's transcript from the dashboard. + +## Flows - A cloud agent hands off and ends, so when its session later asks something, nothing streams back and the question strands on claude.ai; the extension reads the session page the user is already signed into and carries the question — plus a mirror of the transcript — to the daemon. -- Two halves with a strict trust line: the page script reads claude.ai and types into it but never holds a secret; the background worker holds the bridge token and is the only part that talks to the daemon — which refuses cross-origin calls on purpose, so no website the user visits can reach the dashboard. +- Two halves with a strict trust line: the page script reads claude.ai and types into it but never holds a secret; the background worker holds the bridge token and is the only part that talks to the daemon. - Answers go the long way round: dashboard pick → daemon queue → worker → typed into the session's composer and submitted; the extension can only ever type a label the session itself offered, and the pick was confirmed in the dashboard first. - It keeps one pinned background tab per session the daemon watches, so the bridge works with nobody looking at claude.ai — closing tabs when watching stops, and never reopening one the user closed. -- Reading is driven by page changes with a slow heartbeat backstop (it lives in background tabs), and every stage reports its status — on the page's panel and in the settings page's connection test — so a silent misconfiguration is visible. -- The extension and the daemon insist on matching versions: every call states the manifest version, and a daemon expecting another blocks it with an error naming both and the update path, because a version-skewed pair half-works in ways that read as bugs. +- Reading is driven by page changes with a slow heartbeat backstop, and every stage reports its status — on the page's panel and in the settings page's connection test — so a silent misconfiguration is visible. +- The extension and the daemon insist on matching versions: every call states the extension's version, and a daemon expecting another blocks it with an error naming both and the update path. + +## Rationales + +- **The trust line.** The daemon refuses cross-origin calls on purpose, so no website the user visits can reach the dashboard; only the background worker is exempt, and nothing sharing a tab with claude.ai should ever hold the secret — which is why the token and every daemon call live in the worker. +- **Built for background tabs.** The bridge lives in pinned tabs nobody looks at, where the browser throttles timers — so reading rides on page changes and the heartbeat is only a backstop. +- **Versions must match.** A version-skewed extension–daemon pair half-works in ways that read as bugs, so the daemon blocks it loudly instead of degrading. ## Before modifying/creating SPEC.md files diff --git a/packages/chrome-extension/background.SPEC.md b/packages/chrome-extension/background.SPEC.md index 98f4831a5..800b80fe7 100644 --- a/packages/chrome-extension/background.SPEC.md +++ b/packages/chrome-extension/background.SPEC.md @@ -1,14 +1,19 @@ The extension's daemon half: the only part holding the bridge token and talking to the local dashboard — it forwards what the page half finds, delivers the dashboard's answers into the right tab, and keeps a pinned tab open per watched session. -## TLDR +## Flows -- The token and every daemon call live here: the page half shares a tab with claude.ai and must never see the secret, and the daemon refuses cross-origin requests on purpose — only this half is exempt. - Questions forward with a dedupe: the page re-reports on every change, and an unchanged question for the same session costs nothing. -- Answers travel back: queued picks are fetched on a fast beat (a person is watching a spinner), handed to the page in that session's tab to type, and the outcome reported — typing before reporting, so a pick is never marked sent that a dying tab never typed; failed deliveries and reports are retried, not dropped. -- One pinned, inactive tab opens per session the daemon says to watch — the extension cannot know on its own that a cloud agent started; stale ones close, and a tab the user closed is never reopened. +- Answers travel back: queued picks are fetched on a fast beat, handed to the page in that session's tab to type, and the outcome reported — typing before reporting, so a pick is never marked sent that a dying tab never typed; failed deliveries and reports are retried, not dropped. +- One pinned, inactive tab opens per session the daemon says to watch; stale ones close, and a tab the user closed is never reopened. - Every sweep records why it did or didn't act, so "tabs are not opening" is answerable from the options page. - Every daemon call states this extension's version; a daemon expecting another refuses outright with both versions named, so a stale install blocks loudly instead of half-working. +## Rationales + +- **The token and every daemon call live here.** The page half shares a tab with claude.ai and must never see the secret, and the daemon refuses cross-origin requests on purpose — only this half is exempt. +- **Answers are fetched on a fast beat.** A person is sitting on the other end of a delivery, watching a spinner that says the pick is on its way. +- **The daemon names the sessions to watch.** The extension cannot know on its own that a cloud agent started — it only sees pages it is already injected into. + ## 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/chrome-extension/content.SPEC.md b/packages/chrome-extension/content.SPEC.md index 80b540e1b..7f86ff337 100644 --- a/packages/chrome-extension/content.SPEC.md +++ b/packages/chrome-extension/content.SPEC.md @@ -1,11 +1,16 @@ The extension's page half: injected into claude.ai session pages, it finds the question a parked session is waiting on, mirrors the transcript, types a delivered answer into the composer, and shows a status panel — never holding the token or talking to the daemon itself. -## TLDR +## Flows - The question is the JSON options block our agents emit, brace-matched out of surrounding prose wherever it hides — code elements, shadow roots, split across highlighter spans — and the page's rendered copy of our own protocol is a decoy three ways (#1568): nothing inside the transcript's opening message counts (that is the prompt rendering, examples included), a placeholder-shaped title is the spec talking even when punctuation joins the placeholders, and the protocol's two literal examples are matched verbatim; the last real question among what survives wins. - The transcript mirrors as per-message blocks when the page marks them, else as the visible conversation text from the newest end (application chrome and our own panel stripped); only what changed since the last look is sent. - Delivering an answer is the one action taken: the pick is typed into the composer and submitted, after patiently waiting for a slow page to render the composer; only the top frame types, so nothing submits twice — and only labels the session itself offered can ever be typed. -- It re-reads on page mutation with a slow heartbeat backstop (built to run in background tabs), and its collapsible panel reports every stage's status — found, sent, delivered, and why not — because silent failure was the recurring failure. +- It re-reads on page mutation with a slow heartbeat backstop, and its collapsible panel reports every stage's status — found, sent, delivered, and why not. + +## Rationales + +- **Built to run in background tabs.** The tabs the bridge lives in are pinned and hidden, where the browser throttles timers — and the session's own stream changes the page whenever anything happens, so reacting to page mutations is immediate and the heartbeat is only a backstop. +- **Every stage reports.** A stage that fails silently is indistinguishable from a bridge with nothing to do. ## Before modifying/creating SPEC.md files diff --git a/packages/chrome-extension/options.SPEC.md b/packages/chrome-extension/options.SPEC.md index e8f03d648..d509344b2 100644 --- a/packages/chrome-extension/options.SPEC.md +++ b/packages/chrome-extension/options.SPEC.md @@ -1,6 +1,6 @@ The settings page: the daemon address, the bridge token, and the tab-opening switch live in extension storage — never in the page — and saving immediately proves the connection instead of just storing it. -## TLDR +## Flows - The test names the exact failure: Chrome not actually granting site access (declared is not granted, and without it the daemon sees nothing), daemon unreachable, token rejected, a version the daemon refuses (its answer, naming both versions and the way out, is shown verbatim), bridge switched off, or a dashboard too old to have a bridge — whose look-alike success page is not accepted as connected. - Success also says how many cloud sessions the daemon is watching, so "connected but nothing happens" answers itself. diff --git a/packages/the-framework.ai/SPEC.md b/packages/the-framework.ai/SPEC.md index 7ebe5f613..dd7459ea4 100644 --- a/packages/the-framework.ai/SPEC.md +++ b/packages/the-framework.ai/SPEC.md @@ -1,6 +1,13 @@ The product's marketing website, the-framework.ai — it pitches The Framework and funnels visitors into trying it. -## TLDR +## User Stories + +- A visitor reads the pitch and copies one terminal command to try The Framework. +- A visitor picks their package manager once and every command on the site follows, this visit and the next. +- A visitor landing on the go-to-dashboard page learns the dashboard runs on their own machine and how to launch it. +- Someone writing about The Framework downloads logos, naming rules, and the banner from the press page. + +## Flows - One long landing page carries the whole pitch: stop babysitting AI, make the important decisions, let AI do the rest. - The main call to action everywhere is a copy-pasteable terminal command that runs The Framework; visitors pick their package manager once and every command site-wide shows in that flavor, remembered across visits. diff --git a/packages/the-framework.ai/pages/SPEC.md b/packages/the-framework.ai/pages/SPEC.md index d1fb5bbe5..a7ed208d2 100644 --- a/packages/the-framework.ai/pages/SPEC.md +++ b/packages/the-framework.ai/pages/SPEC.md @@ -1,6 +1,6 @@ The website's pages — the landing page plus three small side pages — and the head/settings they all share. -## TLDR +## Flows - / is the landing page carrying the whole pitch; it also hosts the shared building blocks the side pages reuse. - /press offers brand material, /go-to-dashboard explains how to open the locally-running dashboard, and /banner exists only to be screenshotted into the social-preview image. diff --git a/packages/the-framework.ai/pages/index/AutonomousAi.SPEC.md b/packages/the-framework.ai/pages/index/AutonomousAi.SPEC.md index 5f8d197fd..c03c29fc8 100644 --- a/packages/the-framework.ai/pages/index/AutonomousAi.SPEC.md +++ b/packages/the-framework.ai/pages/index/AutonomousAi.SPEC.md @@ -1,4 +1,4 @@ -The "Autonomous AI" section: two cards listing what AI handles autonomously — product management and coding — with reassurances that agents still ask before non-obvious decisions and that you pick the autonomy level. +The "Autonomous AI" section: two cards listing what AI does autonomously — product management and coding — with reassurances that agents still ask before non-obvious decisions and that you pick the autonomy level. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework.ai/pages/index/Hero.SPEC.md b/packages/the-framework.ai/pages/index/Hero.SPEC.md index ef5c57cfc..a9e8aafc1 100644 --- a/packages/the-framework.ai/pages/index/Hero.SPEC.md +++ b/packages/the-framework.ai/pages/index/Hero.SPEC.md @@ -1,6 +1,6 @@ The opening screen: the promise (strike through "Babysit AI", announce "Autonomous AI" — make the important decisions, let AI do the rest) plus the fastest possible path to trying the product. -## TLDR +## Flows - Leads with the trust badges (100% open source, free, local), the headline, and the tagline. - A one-shot "try" command takes center stage, with a smaller install command as alternative; one click copies either. diff --git a/packages/the-framework.ai/pages/index/SPEC.md b/packages/the-framework.ai/pages/index/SPEC.md index 1b2f0af2c..48ba93d9e 100644 --- a/packages/the-framework.ai/pages/index/SPEC.md +++ b/packages/the-framework.ai/pages/index/SPEC.md @@ -1,8 +1,14 @@ The landing page — the product pitch told top to bottom — plus the shared building blocks the other pages borrow. -## TLDR +## User Stories -- The pitch order: the promise and a try-it-now command (hero), the babysitting problems The Framework solves, what AI handles autonomously, how it works (the two building blocks: enhanced system prompt and queues, powered by open-source prompts), the feature list, why the framework is yours (flexible, local, open source), and a closing invitation to the community. +- A visitor scrolls the pitch from promise to community invitation and copies a try-it-now command on the way. +- A visitor shares a link that opens the page at the exact section they were reading. +- A visitor picks their package manager once and every command follows, on this page and every other. + +## Flows + +- The pitch order: the promise and a try-it-now command (hero), the babysitting problems The Framework solves, what AI does autonomously, how it works (the two building blocks: enhanced system prompt and queues, powered by open-source prompts), the feature list, why the framework is yours (flexible, local, open source), and a closing invitation to the community. - A sticky section menu follows the reader and keeps the page address in sync with the visible section, so any section can be shared as a link. - Every command appears in the visitor's chosen package manager, copies with one click, and the choice is remembered across pages and visits. - This directory doubles as the site's component library: the other pages reuse its navigation bar, footer, styling, copy-to-clipboard behavior, and package-manager machinery. diff --git a/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md b/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md index b7d1039e8..3d0c6c491 100644 --- a/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md @@ -1,6 +1,6 @@ The "Add project" modal: registers a repo — or every git repo directly under a folder — with the daemon so it joins the project list. -## TLDR +## Flows - Adding is a two-step act: submitting the path first shows a trust confirmation, because adding a repo lets the agent read its files and hidden instructions in an untrusted repo can hijack the agent (prompt injection); nothing is installed until trust is confirmed. - A folder add reports how many repos it registered (and how many already were) instead of finishing silently. diff --git a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md index 335c0f3f5..25eb9e29a 100644 --- a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md @@ -1,6 +1,6 @@ One agent's action bar: what the agent is on the left — its branch, state, and a summary of what the branch holds, as a disclosure — and what you can do to it on the right. -## TLDR +## Flows - The doing collapses into one overflow menu; only the handoff's next step (push / open PR) stays out as a visible button, since it is the one thing that moves the work forward. - The agent's state (exactly one of stopped, ready for merge, failed, building, finished) reads beside the branch facts instead of spending a banner row on one word. diff --git a/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md b/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md index 6c9b02eef..a836685fb 100644 --- a/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md @@ -1,6 +1,6 @@ The agent's one overflow menu: everything you can do to an agent — open it on GitHub, in the file manager, an editor or the driver's own app, copy the terminal resume command, stop it, arm a merge, remove its worktree, delete it. -## TLDR +## Flows - Items name what they will actually do: once a clean agent's worktree is gone, the folder item admits it opens the project folder, not an agent folder it can no longer reach. - The driver's session id is shown as the only handle on the conversation outside the dashboard; clicking it copies the command that reopens it in a terminal, recreating the vanished directory first, since that is how the agent's CLI finds the session. diff --git a/packages/the-framework/dashboard/components/AgentChanges.SPEC.md b/packages/the-framework/dashboard/components/AgentChanges.SPEC.md index 6110a5cfb..bbd21db39 100644 --- a/packages/the-framework/dashboard/components/AgentChanges.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentChanges.SPEC.md @@ -1,6 +1,6 @@ The live agent's changed-files panel: which files it has touched so far, each row expanding to its diff. -## TLDR +## Flows - Derived from the agent's own working copy, not from its tool calls — the outcome rather than the intent, and it works the same for every driver. - Diffs load only when a row is opened; the running totals (files, lines added/removed) are reported up so the action bar can show them while the list is collapsed. diff --git a/packages/the-framework/dashboard/components/AgentComposer.SPEC.md b/packages/the-framework/dashboard/components/AgentComposer.SPEC.md index daef2f7a8..0e83e2a86 100644 --- a/packages/the-framework/dashboard/components/AgentComposer.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentComposer.SPEC.md @@ -1,6 +1,6 @@ One composer for an agent, live or finished: the box stays put across its whole life, and only what a send does changes. -## TLDR +## Flows - Live, a send queues a message the agent reads between turns — and says so, since a queued message is otherwise invisible. Ended with a session to pick up, a send continues that same conversation on the same branch and row, on the agent's own driver (never the global preference). Ended without one, a send starts a fresh agent — the placeholder itself says so. - The empty box's submit slot doubles as the agent's control: Stop while live, Resume once stopped — both latched so a landed press cannot re-fire or flicker while the state change is still in flight. @@ -8,7 +8,7 @@ One composer for an agent, live or finished: the box stays put across its whole ## Rationales -- Replaced a pair of look-alike composers that swapped when the agent ended, remounting the editor under the user mid-typing and leaving un-resumable agents a dead end. +- One composer rather than one per state: swapping boxes at the ending would remount the editor under the user and take a half-typed message with it, and a box that exists only when resuming is possible would leave an un-resumable agent a dead end. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md b/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md index 640f542f3..182b4b442 100644 --- a/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md @@ -1,6 +1,6 @@ The end-of-work handoff riding the agent's action bar: what this agent left behind, and the next step offered rather than described. -## TLDR +## Flows - The one-line verdict beside the branch: "branch gone", "no changes", or commits · files · diffstat, plus whether the work is already pushed or merged. - While the agent works, one checkbox arms the ending — ticked by default, so finished work stops stranding on local branches nobody was told about. Its label names exactly what this agent will do (open a PR, open and merge, or push only); unticked, nothing is handed off. Re-ticking lands on the zero-config rung rather than restoring a merge the box never mentioned, so the label and the outcome always agree. diff --git a/packages/the-framework/dashboard/components/AgentHistory.SPEC.md b/packages/the-framework/dashboard/components/AgentHistory.SPEC.md index 9a3e25b6f..1df1fbd61 100644 --- a/packages/the-framework/dashboard/components/AgentHistory.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentHistory.SPEC.md @@ -1,6 +1,6 @@ The app's left sidebar, present on every route: brand, global navigation (New, Overview, Tickets, Projects), the recent-agents rail, and the utility footer. -## TLDR +## Flows - "New" adapts to what exists: no projects prompts to add one, one project starts there, several open a picker; inside a project it starts another agent there. - The rail lists the selected project's agents — or, on the Overview, every project's pooled newest-first, each row naming its project and jumping into it. diff --git a/packages/the-framework/dashboard/components/AgentView.SPEC.md b/packages/the-framework/dashboard/components/AgentView.SPEC.md index 40e488c61..91d9e11f1 100644 --- a/packages/the-framework/dashboard/components/AgentView.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentView.SPEC.md @@ -1,6 +1,6 @@ One agent's view, live or finished, in a stable frame — bar, details, changes/handoff, transcript and composer — whose contents change instead of remounting when the agent ends. -## TLDR +## Flows - The transcript is one log with two sources — the live channel while running, the archive once ended — swapped behind the events on screen, so an ending never blanks what you are reading. - An empty archive never replaces shown events, a stale one never hides a resumed leg, a foreign journal never beats the agent's own record; the archive is re-read once the feed outgrows it, which is how the epilogue's PR line arrives without a refresh. diff --git a/packages/the-framework/dashboard/components/AiQueue.SPEC.md b/packages/the-framework/dashboard/components/AiQueue.SPEC.md index 3046e72c0..9f6ea4f31 100644 --- a/packages/the-framework/dashboard/components/AiQueue.SPEC.md +++ b/packages/the-framework/dashboard/components/AiQueue.SPEC.md @@ -1,6 +1,6 @@ The Overview's AI Queue card: every project's open queue entries — the work the framework picks up on its own — shown in full, each readable and startable. -## TLDR +## Flows - An entry's title opens what it names: a queued ticket opens its own ticket page in-app, an external link opens in a new tab, and plain text pretends to be nothing. - The play button starts one unattended agent on that entry alone — the same work the automatic drain sweep would get to, on your click — then jumps to the agent it started. diff --git a/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md b/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md index ce3144273..381e34c1f 100644 --- a/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md @@ -1,6 +1,6 @@ The live view and remote control of the agent's own browser — in the right rail or inline in the transcript — which is what lets a human get the agent past a login wall. -## TLDR +## Flows - The browser streams in as live video, and clicks, scrolls, and typed keys go back to the real page, with clicks rescaled so they land where they look like they land. - An unreachable stream says so and offers Retry; a failure belongs to that one attempt, so retrying, switching agents, or coming back later always tries fresh instead of replaying it. diff --git a/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md b/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md index a9d768d57..7cf9f7f96 100644 --- a/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md +++ b/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md @@ -1,12 +1,16 @@ "Your call" — the question an agent parks on, answered in place and sent back to it. -## TLDR +## Flows -- One shape: a list of the agent's options with the recommended one highlighted, or — when the question takes several answers — a checklist whose Accept button says exactly what it will send (down to "Accept none"). An Approve/Decline approval had its own green-and-red card until it stopped being its own kind of question; it is two options like any other. +- One shape: a list of the agent's options with the recommended one highlighted, or — when the question takes several answers — a checklist whose Accept button says exactly what it will send (down to "Accept none"). An Approve/Decline approval is two options like any other. - It always asks. A gate only reaches a panel because somebody is watching; an agent nobody is watching resolves its gates to the recommended option without one, so the audience is the signal rather than a setting. - A sent answer parks the panel — buttons off, "waiting for the agent to pick it up" — until the agent confirms and the question goes away. - The first open gate also answers to Ctrl+Enter, so the shortcut is unambiguous with several gates open. +## Rationales + +- An approval gets no card of its own: it is a question with two options, and a dedicated card would only mean the agent has to know which of several kinds of question to emit. + ## 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/CloudAgentNotice.SPEC.md b/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md index 07a9a869b..2365791c9 100644 --- a/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md +++ b/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md @@ -1,6 +1,6 @@ What the agent view shows when the work was handed to a Claude web cloud session: where it went and how to reach it, plus — through the browser bridge — the question it is parked on and a mirror of its conversation. -## TLDR +## Flows - Nothing streams back from a cloud session, so instead of a feed that looks stalled the notice says the honest thing — the session asks its questions and opens its pull request over there — with a link out and the command that continues it on this machine. - A question the bridge reports is answerable here as pick-then-confirm: the send has the extension type into the user's own Claude tab, so a queued answer can still be withdrawn, and a failed delivery says so and re-offers the question. diff --git a/packages/the-framework/dashboard/components/Composer.SPEC.md b/packages/the-framework/dashboard/components/Composer.SPEC.md index 3bcfccf36..dc44c436c 100644 --- a/packages/the-framework/dashboard/components/Composer.SPEC.md +++ b/packages/the-framework/dashboard/components/Composer.SPEC.md @@ -1,6 +1,6 @@ The shared composer: the prompt editor plus its control row — presets, context, driver and model, options, send — used by the launcher to start agents and by an agent's chat to message it. -## TLDR +## Flows - A loaded preset prefills the editor and runs verbatim as its own kind; emptying the box falls back to a normal build, and a preset can insist on opening a new agent. - Agent, model, options, and target are shared preferences: every composer, including the compact navbar row, shows and writes the same state instead of silently using stored values. diff --git a/packages/the-framework/dashboard/components/DashboardPage.SPEC.md b/packages/the-framework/dashboard/components/DashboardPage.SPEC.md index 7dc3661f1..100fe9bea 100644 --- a/packages/the-framework/dashboard/components/DashboardPage.SPEC.md +++ b/packages/the-framework/dashboard/components/DashboardPage.SPEC.md @@ -1,6 +1,6 @@ The Overview landing page, shown when no project is picked: usage first, then what needs a human beside who is working now and what the AI takes up next, the routine jobs, and the hot tickets across every project — polled so it stays live. -## TLDR +## Flows - Every card is a projection of the same project files; selecting a row jumps into its project, straight into an agent, or onto a ticket's own page. - The Human Queue collects the cross-project things only a person can clear: open pull requests to review (merge to confirm, close to reject — each links out), agents parked mid-flight on a question (the row opens the agent to answer), and finished work that was never pushed. Notifications fire off the same set. From 33c4b48a54b7ff2863ebb0e8b86a8f86d443976b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:40:35 +0000 Subject: [PATCH 05/15] Migrate SPEC.md files to the new sdd.md template: src daemon, CLI, config TLDR sections become Flows; history framings reworded as present-tense rationales; why-clauses collected under Rationales; User Stories added on cli.SPEC.md (traced to FEATURES-SPEC.md). One grounded correction: each driver, not each agent, declares its CLI surface. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/ci-watch.SPEC.md | 5 +++-- .../the-framework/src/claude-trust.SPEC.md | 4 ++-- .../src/claude-trust.test.SPEC.md | 2 +- packages/the-framework/src/cli-exec.SPEC.md | 11 +++++++--- packages/the-framework/src/cli.SPEC.md | 22 ++++++++++++++----- .../src/cloud-scratch-refs.SPEC.md | 14 ++++++++---- .../the-framework/src/config-layers.SPEC.md | 11 +++++++--- packages/the-framework/src/config.SPEC.md | 7 +----- packages/the-framework/src/control.SPEC.md | 14 ++++++++---- .../the-framework/src/daemon-runtime.SPEC.md | 2 +- .../src/daemon-runtime.test.SPEC.md | 2 +- .../the-framework/src/daemon-services.SPEC.md | 11 +++++++--- .../the-framework/src/daemon-tick.SPEC.md | 22 +++++++++++++------ packages/the-framework/src/daemon.SPEC.md | 13 +++++++---- .../the-framework/src/data-branch.SPEC.md | 2 +- .../src/discord-credentials-store.SPEC.md | 8 +++++-- .../src/discord-credentials.SPEC.md | 15 ++++++++----- packages/the-framework/src/driver-cli.SPEC.md | 10 ++++++--- 18 files changed, 118 insertions(+), 57 deletions(-) diff --git a/packages/the-framework/src/ci-watch.SPEC.md b/packages/the-framework/src/ci-watch.SPEC.md index 6e37049f1..5c39b1265 100644 --- a/packages/the-framework/src/ci-watch.SPEC.md +++ b/packages/the-framework/src/ci-watch.SPEC.md @@ -1,14 +1,15 @@ Watches the pull requests the framework is waiting to land and acts on what their checks say: merge the green ones, start an unattended fix agent for the red ones. -## TLDR +## Flows -- Merge-on-green everywhere: the sweep polls a watched PR's checks about once a minute and merges once they pass — repos without GitHub's native auto-merge used to be merged seconds after opening, before their first check ran. +- Merge-on-green everywhere: the sweep polls a watched PR's checks about once a minute and merges once they pass. - Red checks start one fix agent per failing head commit, told to land the fix on the PR's own branch; at most two attempts per PR, then the failure is evidently not one an agent can fix and it is left to a human. - Conservative when unclear: a closed PR is left alone (an unmerged close is a human's rejection), pending checks wait, and "no checks" only counts as green once the PR has outlived the time a check suite takes to attach. - A PR stays watched for a week; a refused merge is remembered per head commit, so a new push earns exactly one more try. ## Rationales +- The sweep is what makes merge-on-green hold in a repo without GitHub's native auto-merge: without the sweep, an auto-merge-armed PR there could only be merged seconds after opening, before its first check ran. - Polling rather than webhooks: a local daemon has no public address GitHub could call, and every decision starts from a fresh read, so a hosted deployment could later swap the trigger without changing the handlers. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/claude-trust.SPEC.md b/packages/the-framework/src/claude-trust.SPEC.md index 0a69a8c88..92c4d72fb 100644 --- a/packages/the-framework/src/claude-trust.SPEC.md +++ b/packages/the-framework/src/claude-trust.SPEC.md @@ -1,13 +1,13 @@ Reads and grants Claude Code's own folder trust, so a web run never dies on the CLI's interactive trust question. -## TLDR +## Flows - The trust record written is the CLI's own — the same one the user's accepting of the dialog would leave behind — and everything else in the CLI's config survives the write. - A record that is missing or not understood reads as unknown; a config file that exists but cannot be parsed is never overwritten — it is the CLI's file, so the write refuses instead. ## Rationales -- Trust was read-only here at first: the framework warned and named the manual one-time fix. That manual step broke the click-and-it-works story for web runs, and starting a web agent on a project is itself the user's trust decision — so the write automates consent already given, it does not invent it. +- The write automates consent already given, it does not invent it: starting a web agent on a project is itself the user's trust decision. Left as a manual one-time fix, the trust question breaks the click-and-it-works story for web runs. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/claude-trust.test.SPEC.md b/packages/the-framework/src/claude-trust.test.SPEC.md index ec7e7487e..e2c1b2890 100644 --- a/packages/the-framework/src/claude-trust.test.SPEC.md +++ b/packages/the-framework/src/claude-trust.test.SPEC.md @@ -1,4 +1,4 @@ -Covers Claude Code's folder-trust record. Reading: trusted and declined folders, a never-seen folder reading as known-untrusted, and a missing or unintelligible record answering unknown. Writing: the trust entry lands while every other part of the CLI's config survives, a missing file is created, and an existing file that cannot be parsed is refused rather than replaced. +Covers Claude Code's folder-trust record — reading: trusted and declined folders, a never-seen folder reading as known-untrusted, and a missing or unintelligible record answering unknown; writing: the trust entry lands while every other part of the CLI's config survives, a missing file is created, and an existing file that cannot be parsed is refused rather than replaced. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/cli-exec.SPEC.md b/packages/the-framework/src/cli-exec.SPEC.md index 1afc10677..dba46d4c6 100644 --- a/packages/the-framework/src/cli-exec.SPEC.md +++ b/packages/the-framework/src/cli-exec.SPEC.md @@ -1,11 +1,16 @@ One shared way to run a command-line tool such as git or gh: hand back its output, and kill it if it outruns its time budget so a hung tool can never hang the framework. -## TLDR +## Flows -- The budget can depend on the operation — pushing to a remote deserves more time than reading a value — because one binary is not one operation. -- A kill is reported as a timeout, distinct from the tool refusing: a killed push says nothing on its own and would otherwise read as a rejected one. +- The budget can depend on the operation — pushing to a remote deserves more time than reading a value. +- A kill is reported as a timeout, distinct from the tool refusing. - A tool that explains itself on its error output (gh does) can have that shown instead of a generic failure. +## Rationales + +- Budgets attach to operations rather than to the binary because one binary is not one operation. +- A timeout is its own outcome because a killed push says nothing on its own and would otherwise read as a rejected one. + ## 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/src/cli.SPEC.md b/packages/the-framework/src/cli.SPEC.md index 6969a9691..920d40b65 100644 --- a/packages/the-framework/src/cli.SPEC.md +++ b/packages/the-framework/src/cli.SPEC.md @@ -1,17 +1,29 @@ The framework command: serves the dashboard in the foreground, and runs one agent from the spec the dashboard hands it. -## TLDR +## User Stories -- Four options and no verbs: `--host` and `--port`, because they are the two things a browser cannot be asked and a dashboard cannot serve about itself, plus `--help` and `--version`. Everything else is the dashboard, which is the product's user interface. -- One more, not for humans: `--agent ` runs the agent described by a JSON spec. That is how the dashboard spawns one, and it replaced sixty-seven flags — twenty-seven of which had no human user at all, being `StartAgentOptions` serialized onto a command line. +- The user starts the whole product with one command: `the-framework` serves the dashboard in the foreground. +- The user chooses where the dashboard listens with `--host` and `--port`. + +## Flows + +- Four options and no verbs: `--host` and `--port`, plus `--help` and `--version`. Everything else is the dashboard, which is the product's user interface. +- One more, not for humans: `--agent ` runs the agent described by a JSON spec. That is how the dashboard spawns one. - A live agent gets everything around the driver: settings resolved across config layers, persisted events, the steering channel, the browser, and live chat. It serves no dashboard of its own — the one that spawned it reads its event log off disk and steers it through the control file. - Three shapes share that wiring: the full build flow, a verbatim prompt (research included), and transparent mode — the driver completely raw. - Every agent settles identically: the quality follow-up if asked for and earned, then the handoff — commit what it left, push the branch, open a draft PR, merge only when authorized — then a project-log entry, written even for agents that stopped or crashed. -- Settings that cannot apply say so before the spending; a stopped agent never publishes — publishing what it happened to reach is the opposite of what stopping meant. -- A build whose bookkeeping layout differs from the one the repo records is refused before it writes anything, with both layouts and the fix named — the stale published build a cloud environment installs must fail in seconds with the cause, not hours later at the main branch's guard. +- Settings that cannot apply say so before the spending; a stopped agent never publishes. +- A build whose bookkeeping layout differs from the one the repo records is refused before it writes anything, with both layouts and the fix named. - Once an agent starts it is never interrupted for quota. The gate is on starting, and it lives with the daemon that decides whether to start unattended work at all. - Ctrl+C aborts the agent itself, the driver's process tree included; a second press force-quits. +## Rationales + +- `--host` and `--port` are the whole human option surface because they are the two things a browser cannot be asked and a dashboard cannot serve about itself. +- The agent spec is a file rather than command-line flags because an agent's options are a machine-written start request — most of them nothing a human would ever type. +- A stopped agent never publishes because publishing what it happened to reach is the opposite of what stopping meant. +- The layout check fails before anything is written because the stale published build a cloud environment installs must fail in seconds with the cause, not hours later at the main branch's guard. + ## 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/src/cloud-scratch-refs.SPEC.md b/packages/the-framework/src/cloud-scratch-refs.SPEC.md index 2a32fbac1..c43da5e8f 100644 --- a/packages/the-framework/src/cloud-scratch-refs.SPEC.md +++ b/packages/the-framework/src/cloud-scratch-refs.SPEC.md @@ -1,13 +1,19 @@ Deletes the two dead refs every Claude-web hand-off leaves on origin — the pre-hand-off `cloud-*` ref and the run branch — once it is provably safe, so they stop accumulating one pair per web run. -## TLDR +## Flows - A web run pushes a `cloud-*` ref for the cloud session to clone at, and its run branch reaches origin when the worktree is reclaimed. The session then works on its own branch and opens its PR from there, so nothing ever consumes either ref again. -- The driver must not delete its own ref: it only learns "session created", never "clone finished", and a ref deleted in between strands the session. So the daemon sweeps instead, hourly, and waits out the race. -- A ref goes only when every gate clears: it is about a day old, its commits are already on the default branch (the proof it holds no work — this is what protects a local run's branch carrying unmerged commits), it has no open pull request, and its agent is not one the daemon is still running. -- A run branch's age is in its name; a `cloud-*` ref's is not, so the sweep remembers when it first saw one and ages it from there — which also keeps refs pushed by another machine safe, since each machine only deletes what it has itself watched for a day. +- The daemon sweeps hourly; the driver that pushed a ref never deletes it itself. +- A ref goes only when every gate clears: it is about a day old, its commits are already on the default branch, it has no open pull request, and its agent is not one the daemon is still running. +- A run branch's age is in its name; a `cloud-*` ref's is not, so the sweep remembers when it first saw one and ages it from there. - Conservative and quiet: anything unprovable simply stays for the next pass, and only actual deletions (and failures) are announced. +## Rationales + +- The driver must not delete its own ref: it only learns "session created", never "clone finished", and a ref deleted in between strands the session — so deletion falls to the daemon's later sweep, which waits out the race. +- The commits-already-on-the-default-branch gate is the proof a ref holds no work; it is what protects a local run's branch carrying unmerged commits. +- First-seen ageing also keeps refs pushed by another machine safe: each machine only deletes what it has itself watched for a day. + ## 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/src/config-layers.SPEC.md b/packages/the-framework/src/config-layers.SPEC.md index 1b99bae4b..21a0f7548 100644 --- a/packages/the-framework/src/config-layers.SPEC.md +++ b/packages/the-framework/src/config-layers.SPEC.md @@ -1,11 +1,16 @@ Resolves an agent's settings across configuration tiers — this agent's own say, then the project's, the repo's, the account's — where the nearest tier that set something wins and a tier that said nothing does not participate. -## TLDR +## Flows -- Built this way because combining tiers with "or" could only ever turn things on: no tier could say no; now an explicit off in a nearer tier beats an on in a farther one. -- Nobody setting anything resolves to the shipped defaults: a finished agent hands itself back by opening a draft pull request, and merging is the one rung above that, because landing on the default branch is not reversible the way publishing a branch is. +- An explicit off in a nearer tier beats an on in a farther one. +- Nobody setting anything resolves to the shipped defaults: a finished agent hands itself back by opening a draft pull request, and merging is the one rung above that. - Each settled key remembers which tier decided it, so the agent can say out loud where every setting came from. +## Rationales + +- Nearest-tier-wins rather than combining tiers with "or": "or" could only ever turn things on — no tier could say no. +- Merging sits one rung above the default because landing on the default branch is not reversible the way publishing a branch is. + ## 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/src/config.SPEC.md b/packages/the-framework/src/config.SPEC.md index edfc22709..b1800d0c9 100644 --- a/packages/the-framework/src/config.SPEC.md +++ b/packages/the-framework/src/config.SPEC.md @@ -1,10 +1,5 @@ The per-repo defaults every agent starts from, persisted in a small YAML file (`the-framework.yml`) so a project's way of being built — its prompt switches, and what happens when an agent finishes — travels with the code instead of being retyped each time. -## TLDR - -- A project's settings travel with its code, and a bad file never fails an agent. -- One list of keys, and only today's spelling of each. - ## Flows - The file is read from the workspace root; a missing one yields nothing, and a malformed one is a warning and nothing. @@ -15,7 +10,7 @@ The per-repo defaults every agent starts from, persisted in a small YAML file (` ## Rationales - One key list drives parsing, layering and narration alike, so a new setting is declared once instead of added in three places. -- A setting spells its key and its polarity the same way here as everywhere else, so crossing the file boundary is a copy rather than a rename plus a negation — the one key that did neither had three separate comments apologising for it. +- A setting spells its key and its polarity the same way here as everywhere else, so crossing the file boundary is a copy rather than a rename plus a negation. - The publish rung is checked by value because silently ignoring a typo there would leave a repo that meant "keep it local" publishing. - A retired spelling is ignored rather than translated: those settings stop applying until someone rewrites the file by hand, which is the whole of this project's migration story. - A bad config must never fail an agent, so a malformed file degrades to nothing rather than to an error. diff --git a/packages/the-framework/src/control.SPEC.md b/packages/the-framework/src/control.SPEC.md index 1a131ac4b..6de66dde9 100644 --- a/packages/the-framework/src/control.SPEC.md +++ b/packages/the-framework/src/control.SPEC.md @@ -1,11 +1,17 @@ The steering channel from the dashboard to a live agent — the reverse of the event stream: the daemon appends an instruction to a file in the workspace and the agent tails it, with no direct connection between the two. -## TLDR +## Flows - The instructions: stop the agent, answer a parked gate, send a live chat message, move the end-of-work handoff, and a human's Merge — a pre-commitment that outranks the agent's own ready signal. -- The file is emptied when an agent starts, so a previous agent's answers can never fire into this one — gate names repeat across agents. -- The handoff instruction is one rung of the publish ladder, not a set of stage flags: a surface offering the stages as separate boxes resolves them on its own side, so an impossible combination can never arrive here for the receiving end to repair upward. -- Every line is shape-checked and a bad one is skipped: an entry naming no rung must not silently stop an agent publishing its work. +- The file is emptied when an agent starts, so a previous agent's answers can never fire into this one. +- The handoff instruction is one rung of the publish ladder, not a set of stage flags: a surface offering the stages as separate boxes resolves them on its own side. +- Every line is shape-checked and a bad one is skipped. + +## Rationales + +- The start-of-work reset is needed because gate names repeat across agents — an answer left behind would match a question it was never meant to answer. +- The handoff travels as one rung so an impossible combination of stages can never arrive here for the receiving end to repair upward. +- Bad lines are skipped rather than obeyed: an entry naming no rung must not silently stop an agent publishing its work. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon-runtime.SPEC.md b/packages/the-framework/src/daemon-runtime.SPEC.md index 5c6478143..9f92cd641 100644 --- a/packages/the-framework/src/daemon-runtime.SPEC.md +++ b/packages/the-framework/src/daemon-runtime.SPEC.md @@ -1,6 +1,6 @@ What the daemon does for a project: starting agents in isolated checkouts, retiring them when they end, and keeping every one of them recoverable. -## TLDR +## Flows - Each agent gets its own worktree and branch, so concurrent agents never touch each other or the user's checkout; a project that is not a Git repo falls back to its main checkout, one agent at a time. A repo whose worktree cannot be created fails the start rather than borrowing the user's working tree. - A start is refused when the chosen driver cannot run (not installed or not logged in), so a doomed agent spends no branch or worktree. diff --git a/packages/the-framework/src/daemon-runtime.test.SPEC.md b/packages/the-framework/src/daemon-runtime.test.SPEC.md index 3445132ab..72df2aa33 100644 --- a/packages/the-framework/src/daemon-runtime.test.SPEC.md +++ b/packages/the-framework/src/daemon-runtime.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the wait applied when a Resume races a just-finished agent's exit: a free slot returns at once, a leg still calling itself running is a real collision and is not waited on, a finished leg is waited out within a bounded grace period, and an in-flight or failed retirement never breaks the continuation awaiting it. A leg that cannot be read at all is a fourth answer, not a live one: it is asked again until it commits, so a meta caught mid-rewrite never costs the continuation its wait, while a leg that goes on to report itself running still reaches the guard without sitting out the grace. +Covers the wait applied when a Resume races a just-finished agent's exit: a free slot returns at once, a leg still calling itself running is a real collision and is not waited on, a finished leg is waited out within a bounded grace period, and an in-flight or failed retirement never breaks the continuation awaiting it; a leg that cannot be read at all is a fourth answer, not a live one — asked again until it commits, so a record caught mid-rewrite never costs the continuation its wait, while a leg that goes on to report itself running still reaches the guard without sitting out the grace. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon-services.SPEC.md b/packages/the-framework/src/daemon-services.SPEC.md index 22a0ef62f..418c9310f 100644 --- a/packages/the-framework/src/daemon-services.SPEC.md +++ b/packages/the-framework/src/daemon-services.SPEC.md @@ -1,8 +1,8 @@ Everything the daemon runs in the background beside serving the dashboard: Discord notifications, automatic project management, CI watching, data-branch syncing, and disk reclamation. -## TLDR +## Flows -- One clock runs every background job, each declaring how many ticks it wants between turns rather than owning an interval — six timers used to run side by side, with no single place to look when a sweep turned out not to be running. +- One clock runs every background job, each declaring how many ticks it wants between turns rather than owning an interval. - Every service re-reads its preference on each tick, so a dashboard toggle takes effect without restarting the daemon. - An agent the daemon starts resolves its options from the same two tiers the launcher uses — your settings, then the repo's committed file — so one nobody asked for and one someone clicked differ only in who asked. - Auto PM spends idle quota on the roadmap: it fans out up to the configured number of unattended agents, each pinned to one queue entry, and retires an entry on the data branch once its agent's ending reports the work published; the daemon, never the agent, writes queue check-offs and ticket locks. @@ -11,7 +11,12 @@ Everything the daemon runs in the background beside serving the dashboard: Disco - The Discord notification watchers are rebuilt when the webhook changes, so a value pasted into the dashboard works immediately. - The data branch is pulled eagerly, so this machine reads what other machines and cloud sessions pushed without waiting for its own next write. - Every background start forces unattended mode, so gates auto-answer instead of parking forever on an absent human. -- Shutdown quiesces everything that could start an agent before the daemon stops the agents it owns. Nothing is resumed on the next boot — Ctrl-C closed those agents deliberately. +- Shutdown quiesces everything that could start an agent before the daemon stops the agents it owns. Nothing is resumed on the next boot. + +## Rationales + +- One clock rather than a timer per service: a single schedule gives one place to look when a sweep turns out not to be running. +- Nothing is resumed on the next boot because Ctrl-C closed those agents deliberately. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon-tick.SPEC.md b/packages/the-framework/src/daemon-tick.SPEC.md index 9a23ac131..0d741a4b0 100644 --- a/packages/the-framework/src/daemon-tick.SPEC.md +++ b/packages/the-framework/src/daemon-tick.SPEC.md @@ -1,14 +1,22 @@ The daemon's one background clock: a single interval that runs a list of jobs, each saying how many ticks it wants between turns. -## TLDR +## Flows -- Every sweep used to own a timer — six of them, each with its own interval, unref call and overlap guard — so there was no single place to look when "nothing is happening" turned out to be a sweep that was not running. -- A job's cadence is a small integer rather than a duration: the base interval is the finest cadence anything needs, and everything slower is that many ticks. The ratios are exact by construction, where separate timers drift. +- A job's cadence is a small integer rather than a duration: the base interval is the finest cadence anything needs, and everything slower is that many ticks. - A missed turn is skipped, never queued: a slow job comes back to the next turn, not to a backlog of them. -- A job that throws costs its own turn and nothing else, and says so with its name — a sweep failing silently is indistinguishable from one that was never scheduled. -- The first tick fires at start-up rather than an interval later, because the case most of these jobs exist for is a machine that was off while something happened; a job that only makes sense once the daemon has been up says so. -- Awaiting a tick means the tick finished — overlapping ones join it rather than being dropped — which is what makes the schedule testable without waiting on wall-clock time. -- Stopping is likewise awaitable, and resolves only once the turn in flight has finished: these jobs commit and push, and clearing the interval stops the next turn, never the one already inside a job. A shutdown that did not wait for it would tear the repo down underneath a sweep. +- A job that throws costs its own turn and nothing else, and says so with its name. +- The first tick fires at start-up rather than an interval later; a job that only makes sense once the daemon has been up says so. +- Awaiting a tick means the tick finished — overlapping ones join it rather than being dropped. +- Stopping is likewise awaitable, and resolves only once the turn in flight has finished: clearing the interval stops the next turn, never the one already inside a job. + +## Rationales + +- One clock rather than a timer per sweep: a single schedule gives one place to look when "nothing is happening" turns out to be a sweep that was not running. +- Tick counts rather than durations keep the ratios exact by construction, where separate timers drift. +- A throwing job is named because a sweep failing silently is indistinguishable from one that was never scheduled. +- The start-up tick exists because the case most of these jobs exist for is a machine that was off while something happened. +- A tick you can await to completion is what makes the schedule testable without waiting on wall-clock time. +- Stopping waits out the turn in flight because these jobs commit and push: a shutdown that did not wait would tear the repo down underneath a sweep. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon.SPEC.md b/packages/the-framework/src/daemon.SPEC.md index bc8e09854..71aabd3f8 100644 --- a/packages/the-framework/src/daemon.SPEC.md +++ b/packages/the-framework/src/daemon.SPEC.md @@ -1,14 +1,19 @@ The process behind the dashboard: it serves the UI, spawns agents, and runs the background services. -## TLDR +## Flows - It runs in the foreground and only in the foreground. Ctrl-C closes the dashboard and every agent it is running, so there is no liveness record, no machine-global state file, and no second process to find, reuse or stop. -- The trade that buys: unattended work needs a window left open, the way any dev server does. The product's promise is spending idle quota while nobody is at the keyboard, and that still holds — but it is now visible and killable rather than invisible and persistent, which is the right direction for a tool that spends a subscription: nothing burns quota after you have closed it. - The dashboard is a projection of each project's on-disk event log, and steering flows back through an append-only control file — files are the seam, never a direct agent-to-dashboard connection. -- Bound to localhost by default; binding to the network requires a generated shared token, because a process that spawns agents would otherwise be remote code execution for whoever finds the port. -- At boot it registers the home project, marks agents a dead process left "running" as stopped, and starts the background services. It resumes nothing: Ctrl-C was deliberate. Every other project joins through the dashboard's "Add project" — the one onboarding path, so a repo is always installed before an agent can touch it. +- Bound to localhost by default; binding to the network requires a generated shared token. +- At boot it registers the home project, marks agents a dead process left "running" as stopped, and starts the background services; it resumes nothing. Every other project joins through the dashboard's "Add project" — the one onboarding path, so a repo is always installed before an agent can touch it. - Shutdown is ordered: background services quiesce first, live agents are stopped, their archives committed, then the dashboard goes. Each step is waited out rather than merely started, so the archives being committed are the finished ones — the sweeps are off the repo before the agents are torn down, and the teardowns are done before their work is committed. +## Rationales + +- Foreground-only is a deliberate trade: unattended work needs a window left open, the way any dev server does. The product's promise of spending idle quota while nobody is at the keyboard holds with that window open — and the spending is visible and killable rather than invisible and persistent, the right direction for a tool that spends a subscription: nothing burns quota after you have closed it. +- Binding to the network requires the token because a process that spawns agents would otherwise be remote code execution for whoever finds the port. +- Boot resumes nothing because the Ctrl-C that closed those agents was deliberate. + ## 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/src/data-branch.SPEC.md b/packages/the-framework/src/data-branch.SPEC.md index 1a0ea4344..cb2e9c9c0 100644 --- a/packages/the-framework/src/data-branch.SPEC.md +++ b/packages/the-framework/src/data-branch.SPEC.md @@ -1,6 +1,6 @@ All the data The Framework writes — the tickets, the task queue, the session archives — lives on one dedicated branch, `tf-data`, so the code history stays 100% code and the data can be pushed and pulled eagerly without ever touching anyone's working tree. -## TLDR +## Flows - The branch is checked out at `.the-framework/branches/tf-data`, and a `tickets` symlink at the repo root points into it, so the roadmap stays one `ls` away. The symlink is hidden from git through a repo-level exclude, so no sweeping commit ever drags it onto a code branch. - The daemon is the only local writer: every write is one serialized cycle — sync with origin, apply the change, commit, push — so a lost race re-reads the fresher state and re-applies the intent instead of clobbering what someone else landed. diff --git a/packages/the-framework/src/discord-credentials-store.SPEC.md b/packages/the-framework/src/discord-credentials-store.SPEC.md index b26b7b380..b2bbcab12 100644 --- a/packages/the-framework/src/discord-credentials-store.SPEC.md +++ b/packages/the-framework/src/discord-credentials-store.SPEC.md @@ -1,10 +1,14 @@ Reads and writes the two Discord credentials in the user's registry file, and tells the running daemon when they change so a pasted token works without a restart. -## TLDR +## Flows -- A credential set in the daemon's environment cannot be edited here: the write would be silently shadowed on the next read, so the save is refused with an explanation instead. +- A credential set in the daemon's environment cannot be edited here: the save is refused with an explanation instead. - A save validates first and applies all-or-nothing; the reload runs only after the write has landed, and a reload that fails never fails the save — the credential is stored and the next daemon start uses it. +## Rationales + +- A save over an env-owned credential is refused because the write would be silently shadowed on the next read. + ## 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/src/discord-credentials.SPEC.md b/packages/the-framework/src/discord-credentials.SPEC.md index 3fd54964d..a9bbd23ad 100644 --- a/packages/the-framework/src/discord-credentials.SPEC.md +++ b/packages/the-framework/src/discord-credentials.SPEC.md @@ -1,12 +1,17 @@ The rules for where the daemon's Discord notification webhook comes from: the environment first, then a value saved from the dashboard. -## TLDR +## Flows -- Enabling Discord used to require editing the daemon's environment and restarting it; now the credentials can also be saved from the dashboard and are picked up live. -- The environment wins over a stored value, and the dashboard says so rather than offering an edit that would not take effect — a browser must not quietly override how the machine was deployed. +- Credentials can be set in the daemon's environment or saved from the dashboard; a dashboard save is picked up live, without a restart. +- The environment wins over a stored value, and the dashboard says so rather than offering an edit that would not take effect. - The dashboard is only ever told which credential exists and where it came from, never the value: a stored credential cannot be read back. -- Validation is deliberately shallow — reject only what could never work (a non-URL webhook); whether it actually delivers is Discord's answer to give. -- Holds no credential and touches no file itself, so the browser shares exactly the rules the daemon enforces. +- Validation rejects only what could never work (a non-URL webhook). +- The rules hold no credential and touch no file themselves, so the browser shares exactly the rules the daemon enforces. + +## Rationales + +- The environment wins because a browser must not quietly override how the machine was deployed. +- Validation is deliberately shallow: whether a credential actually delivers is Discord's answer to give. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver-cli.SPEC.md b/packages/the-framework/src/driver-cli.SPEC.md index 684298f2c..fd81cb2c0 100644 --- a/packages/the-framework/src/driver-cli.SPEC.md +++ b/packages/the-framework/src/driver-cli.SPEC.md @@ -1,9 +1,13 @@ What the framework knows about each driver it can run — a whole coding-agent CLI the user already pays for, driven on their own subscription with no API key — and the one place an agent turns the picked driver into a live implementation. -## TLDR +## Flows -- Each agent declares its binary, an install hint, how to ask it "am I logged in?", and the one command that fixes a no — so a dead setup is caught before any quota is spent. -- Only a clear "logged out" fails the preflight check; an answer that cannot be read counts as unknown, because wrongly blocking a working setup is worse than the silent dead agent this exists to prevent. +- Each driver declares its binary, an install hint, how to ask it "am I logged in?", and the one command that fixes a no — so a dead setup is caught before any quota is spent. +- Only a clear "logged out" fails the preflight check; an answer that cannot be read counts as unknown. + +## Rationales + +- An unreadable login answer counts as unknown, not as logged out, because wrongly blocking a working setup is worse than the silent dead agent the check exists to prevent. ## Before modifying/creating SPEC.md files From 97defdeeb660a1eaf5e37a265402f326e4a068f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:40:49 +0000 Subject: [PATCH 06/15] Migrate SPEC.md files to the new sdd.md template: store, e2e, scripts, prompts TLDR sections become Flows; why-clauses and de-historied framings collected under Rationales; multi-sentence preambles reduced to the single mandated description sentence. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/prompts/SPEC.md | 6 +++--- packages/the-framework/scripts/SPEC.md | 12 ++++++++---- packages/the-framework/src/e2e/SPEC.md | 8 ++++++-- packages/the-framework/src/e2e/harness.SPEC.md | 8 ++++++-- packages/the-framework/src/store/SPEC.md | 15 +++++++++++---- .../src/store/agent-checkout.SPEC.md | 11 ++++++++--- .../the-framework/src/store/agent-store.SPEC.md | 9 ++------- .../src/store/agent-store.test.SPEC.md | 6 +++++- .../the-framework/src/store/worktree-deps.SPEC.md | 11 ++++++++--- packages/the-framework/src/store/worktree.SPEC.md | 9 +++++++-- 10 files changed, 64 insertions(+), 31 deletions(-) diff --git a/packages/the-framework/prompts/SPEC.md b/packages/the-framework/prompts/SPEC.md index 1f7d280b2..cd23a6081 100644 --- a/packages/the-framework/prompts/SPEC.md +++ b/packages/the-framework/prompts/SPEC.md @@ -1,9 +1,8 @@ Every word the framework says to a coding agent, authored as markdown: the built-in system prompt, the protocols agents answer back through, the preset task prompts, and the repo conventions they are taught. -## TLDR +## Flows - The system prompt wraps the user's prompt in a working discipline: analyze it and gate on ambiguity or large scope, keep every read and write under the working directory, name the session and do all work on a branch of that name, offer alternatives wherever the best solution is unclear, and signal ready-for-merge only when nothing is left — without that signal the work is never merged. -- The workspace boundary is spelled out because the layout invites crossing it: an agent's worktree is nested inside the repo, so the user's own checkout is a path prefix of its working directory, and the same file exists twice. An agent that edits the outer copy while committing the inner one finishes clean with a commit holding none of its work. - The protocols define the agent's side of the conversation: how to park on a gate (a choice, a multi-select, a document approval, handing the browser to a human at a login wall) — including marking the answers that end the agent rather than resume it, so a rejection is not something it is asked to build on — and how to emit the non-blocking signals (show a document, name the session, ready-for-merge); per-capability protocols adapt it — an agent with a real browser is told when to use it, a hands-off agent is told gates can never be answered, so assume the recommended option and carry on. - The presets are the one-click task prompts behind the dashboard's buttons: research, the quality reviews (readability, maintainability, security, UX), ticket triage and planning, and draining the queue. - The format docs teach the repo conventions: tickets as dated proposal files with plan and claim siblings, and the priority-ordered queue file of confirmed work. @@ -11,7 +10,8 @@ Every word the framework says to a coding agent, authored as markdown: the built ## Rationales -- Prompting lives as prose and is compiled into the code at build time, so a prompt change lands as a readable markdown diff that gets a review round like any other product change. These files are the source of truth: the system prompt used to be authored on a GitHub issue and copied here, with a daily job checking the two had not drifted — a second home for the text, and a checker to paper over having one. +- The workspace boundary is spelled out because the layout invites crossing it: an agent's worktree is nested inside the repo, so the user's own checkout is a path prefix of its working directory, and the same file exists twice. An agent that edits the outer copy while committing the inner one finishes clean with a commit holding none of its work. +- Prompting lives as prose and is compiled into the code at build time, so a prompt change lands as a readable markdown diff that gets a review round like any other product change. These files are the only source of truth: the compiled module is regenerated from them on every build, so there is no second home for the text to drift, and no checker needed to catch it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/scripts/SPEC.md b/packages/the-framework/scripts/SPEC.md index 268d1f5ab..cf9a7b00c 100644 --- a/packages/the-framework/scripts/SPEC.md +++ b/packages/the-framework/scripts/SPEC.md @@ -1,10 +1,14 @@ Build-time helpers that keep the prompting authored as markdown, and make the published package and the test suite self-contained. -## TLDR +## Flows -- Every prompt markdown file is compiled into a generated module of plain strings before each build, test, and typecheck; the markdown is the only source of truth, and strings — unlike a file read at run time — also work in the browser, where the dashboard shows the user a prompt before an agent starts. -- Tests run against a throwaway home for the machine's global state, so the developer's live daemon can never leak into the suite and hang it. There are two suites and two runners: `node --test` over the compiled `src/`, and the dashboard's own browser-shaped tests. -- Copying the dashboard bundle is no longer a step: the dashboard is part of this package now, and its build writes straight into the `dist/` the daemon serves from. +- Every prompt markdown file is compiled into a generated module of plain strings before each build, test, and typecheck; the markdown is the only source of truth. +- Tests run against a throwaway home for the machine's global state, so the developer's live daemon can never leak into the suite and hang it. There are two suites and two runners: Node's test runner over the compiled daemon suite, and the dashboard's own browser-shaped tests. + +## Rationales + +- Prompts are compiled to plain strings rather than read from disk at run time because strings also work in the browser, where the dashboard shows the user a prompt before an agent starts. +- The dashboard is part of this package and its build writes straight into the `dist/` the daemon serves from, so no helper has to copy a bundle around. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/e2e/SPEC.md b/packages/the-framework/src/e2e/SPEC.md index 27dbb9ffc..b629ba2ea 100644 --- a/packages/the-framework/src/e2e/SPEC.md +++ b/packages/the-framework/src/e2e/SPEC.md @@ -1,11 +1,15 @@ The product's end-to-end stories: each test walks a user journey through the daemon's real business logic — real spawned agent processes, real git repos, the same calls the dashboard makes — with only the coding-agent CLI replaced by the deterministic fake driver, so the whole lifecycle is provable offline. -## TLDR +## Flows - Four story files cover the journeys: the agent lifecycle (start, watch live, read the archived row, publish the branch), steering and gates (questions, chat, handoff, stop), projects and settings, and tickets and the work queue. -- Stories observe the product exactly where users do — the dashboard's reads and the live event feed; the one extra window is the recorded child invocation, since a detached spawn is otherwise unobservable. +- Stories observe the product exactly where users do — the dashboard's reads and the live event feed; the one extra window is the recorded child invocation. - The harness gives every story a throwaway world with its own global state and a daemon-shaped teardown, so stories are isolated, parallel-safe, and repeatable. +## Rationales + +- The recorded child invocation is the one extra window because a detached spawn is otherwise unobservable — it is the only way a story can prove a dashboard toggle reached the agent it started. + ## 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/src/e2e/harness.SPEC.md b/packages/the-framework/src/e2e/harness.SPEC.md index a6aaa3e4c..e1d6c45d3 100644 --- a/packages/the-framework/src/e2e/harness.SPEC.md +++ b/packages/the-framework/src/e2e/harness.SPEC.md @@ -1,12 +1,16 @@ Stands up a disposable copy of the product for one story: the daemon's business logic wired exactly as production wires it, real git-repo projects registered through the real add-project call, and agents spawned as real child processes with the fake driver in the driver seat. -## TLDR +## Flows - Stories drive the product only through the same calls the dashboard makes and watch it through the same live event feed, so what a test sees is what a user sees; the pieces the daemon runs as live loops (quota, auto-PM) are stubs a story controls directly. -- "Finished" means two different things — the agent's row says done, and its workspace has actually been retired — and a story can wait for either, because acting on an agent in between is the same race a fast-clicking user hits. +- "Finished" means two different things — the agent's row says done, and its workspace has actually been retired — and a story can wait for either. - Each world gets its own throwaway global state, so parallel stories never see each other's projects; a story can also park an agent on a scripted question, and read back exactly how each agent child was invoked. - Teardown mirrors daemon shutdown: stop the agents, wait out in-flight teardowns, then delete everything. +## Rationales + +- A story can wait for either kind of finished because acting on an agent in between — row done, workspace not yet retired — is the same race a fast-clicking user hits. + ## 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/src/store/SPEC.md b/packages/the-framework/src/store/SPEC.md index 731b42f81..817e0f657 100644 --- a/packages/the-framework/src/store/SPEC.md +++ b/packages/the-framework/src/store/SPEC.md @@ -1,12 +1,19 @@ Agent persistence and workspaces: the append-only event log every surface is a projection of, and the per-agent git worktrees they work in. -## TLDR +## Flows -- Persisting is logging: an agent's history is the events it appended, and everything else — status, name, branch, outcomes — is folded from that log into a small snapshot for cheap list reads. A restarted dashboard rehydrates by replaying the log; there is no second source of truth to drift. +- Persisting is logging: an agent's history is the events it appended, and everything else — status, name, branch, outcomes — is folded from that log into a small snapshot for cheap list reads. A restarted dashboard rehydrates by replaying the log. - An agent that crashed without saying goodbye is healed on a later boot or read: after checking its process is genuinely dead, the missing ending is written on its behalf, so none stays "running" — or keeps asking its last question — forever. -- Finished agents are archived into the repo under per-user directories and committed, because the live state is untracked and an ordinary `git clean` used to erase a project's entire history. Every user's archive is visible to the whole team — that is the point, not a leak — and live state beats the archive when both exist. +- Finished agents are archived into the repo under per-user directories and committed; every user's archive is visible to the whole team, and live state beats the archive when both exist. - Each agent works in its own worktree on its own branch (named after its id until it picks a session name); teardown commits leftover work first, so the branch outlives the checkout. The parent checkout's installed dependencies are symlinked in instead of reinstalled — instant, no extra disk — and hidden from git so a sweeping commit cannot drag them onto the PR. -- Nothing is resumed at boot. Ctrl-C closed the last dashboard and every agent it was running, which is a deliberate act rather than a crash to recover from; what the next boot does instead is mark as stopped anything a dead process left claiming to be running. +- Nothing is resumed at boot; what boot does instead is mark as stopped anything a dead process left claiming to be running. + +## Rationales + +- There is no second source of truth to drift: every read is a fold of the log, and a restart replays it rather than reconciling a separate state model. +- The archive is committed because untracked state does not survive an ordinary `git clean`: without the commit, one clean would erase a project's entire history. +- Every user's archive being visible to the whole team is the point, not a leak. +- Nothing is resumed at boot because Ctrl-C closed the last dashboard and every agent it was running — a deliberate act rather than a crash to recover from. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/agent-checkout.SPEC.md b/packages/the-framework/src/store/agent-checkout.SPEC.md index b940cd176..6f4146643 100644 --- a/packages/the-framework/src/store/agent-checkout.SPEC.md +++ b/packages/the-framework/src/store/agent-checkout.SPEC.md @@ -1,9 +1,14 @@ Resolves which checkout — and which event journal — a session id points at, one shared rule for every surface that addresses a session by id. -## TLDR +## Flows -- Order: the live agent's own recorded checkout first, then its worktree directory — which exists before it has written any state, and matters because a subscriber resolves its path once and would otherwise tail the wrong file for its whole life — and finally the project root, the sane thing to act on for an unknown or finished id. -- The events variant differs in one place: for an ended agent whose worktree is gone, its archived log wins over the project's shared journal — the archive is the agent's own record and proves it ended. +- Order: the live agent's own recorded checkout first, then its worktree directory — which exists before the agent has written any state — and finally the project root, the sane thing to act on for an unknown or finished id. +- The events variant differs in one place: for an ended agent whose worktree is gone, its archived log wins over the project's shared journal. + +## Rationales + +- The worktree-directory probe matters because a subscriber resolves its path once: falling back to the project root would have it tail the wrong file for its whole life. +- For an ended agent, the archived log wins because the archive is the agent's own record and proves it ended. - Shared on purpose, so the fallback rules cannot drift apart between the daemon and the dashboard. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/agent-store.SPEC.md b/packages/the-framework/src/store/agent-store.SPEC.md index a49abbb10..29a95b361 100644 --- a/packages/the-framework/src/store/agent-store.SPEC.md +++ b/packages/the-framework/src/store/agent-store.SPEC.md @@ -1,11 +1,5 @@ Agent persistence: an agent's history is its append-only event log, and everything shown about it is folded from that log. -## TLDR - -- The log is the truth; the snapshot beside it is a fold of the log, kept for cheap reads. -- Finished agents are archived onto the data branch per user, so a project's history is a team record that never touches the code history. -- An agent that died without saying goodbye is given its ending, wherever it is found. - ## Flows - Each event is appended to the log and folded into a small snapshot, so a list read costs one file instead of a replay. A restarted dashboard rehydrates by replaying the log itself. @@ -19,10 +13,11 @@ Agent persistence: an agent's history is its append-only event log, and everythi ## Rationales +- Archives live on the data branch, so a project's history is a team record that never touches the code history. - A live agent must never blink out of a listing, because readers act on that absence: it is why a torn snapshot is re-read, and why the write is a rename rather than a truncate. - The ending is written into the log too, or the agent's last question renders as answerable forever. - An owner on another machine, or one with no record of its process, is cleaned up only at boot: a routine read must not kill an agent another machine is still driving. -- Everything here is known by one name. What the snapshot and the archive directories were called before is not looked for, so state left under the old names reads as absent. +- Everything here is known by one name: when a file or directory here is renamed, no fallback to the previous name is added, so state left under it reads as absent. - A record is the right home for a late fact either way, so a surface reading it never has to know which path produced it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/agent-store.test.SPEC.md b/packages/the-framework/src/store/agent-store.test.SPEC.md index eef9a2226..713525da8 100644 --- a/packages/the-framework/src/store/agent-store.test.SPEC.md +++ b/packages/the-framework/src/store/agent-store.test.SPEC.md @@ -1,4 +1,8 @@ -The tests cover the persistence promises: events fold into the same snapshot whether appended live or replayed, agents archive on close and are rescued after a crash, per-user archives on the data branch are listed team-wide and deduplicated, dead agents are healed everywhere they can be found (the missing ending written into log, snapshot, and archive, while live owners and other machines' agents are left alone), and continuing an agent reopens the same log under its original intent. A snapshot is renamed into place rather than written over, so its own path is never the empty file a truncate leaves for a concurrent reader; a store whose filesystem cannot rename still writes in place, and a snapshot that arrives unparseable anyway is read again rather than reported missing — a live agent never blinks out of a listing mid-write — while one that stays unparseable is still given up on. +The tests cover the persistence promises: events fold into the same snapshot whether appended live or replayed, agents archive on close and are rescued after a crash, per-user archives on the data branch are listed team-wide and deduplicated, dead agents are healed everywhere they can be found (the missing ending written into log, snapshot, and archive, while live owners and other machines' agents are left alone), and continuing an agent reopens the same log under its original intent. + +## Flows + +- A snapshot is renamed into place rather than written over, so its own path is never the empty file a truncate leaves for a concurrent reader; a store whose filesystem cannot rename still writes in place, and a snapshot that arrives unparseable anyway is read again rather than reported missing — a live agent never blinks out of a listing mid-write — while one that stays unparseable is still given up on. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/worktree-deps.SPEC.md b/packages/the-framework/src/store/worktree-deps.SPEC.md index 06294a85a..04d0cc41c 100644 --- a/packages/the-framework/src/store/worktree-deps.SPEC.md +++ b/packages/the-framework/src/store/worktree-deps.SPEC.md @@ -1,11 +1,16 @@ Gives a fresh worktree a working dependency tree instantly, by symlinking the parent checkout's installed dependencies instead of copying or reinstalling them. -## TLDR +## Flows -- Linking whole dependency directories (the root's and each workspace package's) costs no disk and no wait, and one installed store serves every agent; an agent that changes the lockfile needs its own install anyway and runs it itself. -- The links are hidden from git through a repo-level exclude, because the usual ignore rule matches directories, not symlinks — without it the agent's sweeping commit would drag dangling links onto its branch and the PR. +- Whole dependency directories are linked — the root's and each workspace package's; an agent that changes the lockfile needs its own install anyway and runs it itself. +- The links are hidden from git through a repo-level exclude, so the agent's sweeping commit cannot drag dangling links onto its branch and the PR. - Best-effort throughout: a worktree without dependencies is a worse agent, not a failed one. +## Rationales + +- Linking whole directories costs no disk and no wait, and one installed store serves every agent. +- A repo-level exclude is needed because the usual ignore rule matches directories, not symlinks — without it the links would show as untracked in every worktree. + ## 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/src/store/worktree.SPEC.md b/packages/the-framework/src/store/worktree.SPEC.md index 5c0783077..15189c8db 100644 --- a/packages/the-framework/src/store/worktree.SPEC.md +++ b/packages/the-framework/src/store/worktree.SPEC.md @@ -1,12 +1,17 @@ The git-worktree lifecycle behind concurrent agents: each agent gets its own checkout on its own branch, so several on one repo never fight over the working tree. -## TLDR +## Flows - An agent's branch is first named after its id (the only name that exists at start) and renamed to its chosen name once the agent picks one — unless the agent already moved to a branch of its own, which is then left alone. - Continuing an agent re-attaches the branch its work is already on, rather than branching afresh and stranding what it did last time. -- Teardown commits whatever the agent left uncommitted before removing the checkout — the agent deliberately never commits its own final work, and removing without committing would destroy the diff — so the branch outlives the worktree; the commit retries briefly past a lock race that once made an agent's real work look like nothing. +- Teardown commits whatever the agent left uncommitted before removing the checkout, so the branch outlives the worktree; the commit retries briefly past a transient lock race. - Removal tries politely first and forces only as a told-about fallback; a checkout's size read is best-effort, since it only labels a delete button. +## Rationales + +- Teardown commits first because the agent deliberately never commits its own final work, and removing the checkout without committing would destroy the diff. +- The commit retries because the daemon itself commits in the same checkout and is busiest exactly when teardown runs: a first attempt can lose a lock race, and giving up there would make the agent's real work read as nothing. + ## Before modifying/creating SPEC.md files You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md From 0e3922e50cbc6756e3e2d5e8ab479cc95720dc44 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:41:18 +0000 Subject: [PATCH 07/15] Migrate SPEC.md files to the new sdd.md template: dashboard lib (Q-Z hooks) TLDR sections become Flows with behavior bullets byte-identical; design-why clauses move to Rationales; history framings reworded present-tense. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../the-framework/dashboard/lib/resume-command.SPEC.md | 5 ++++- packages/the-framework/dashboard/lib/route.SPEC.md | 9 ++++++--- .../the-framework/dashboard/lib/ticket-filter.SPEC.md | 2 +- .../dashboard/lib/use-agent-handoff.SPEC.md | 2 +- packages/the-framework/dashboard/lib/use-async.SPEC.md | 2 +- .../dashboard/lib/use-device-status.SPEC.md | 2 +- .../the-framework/dashboard/lib/use-live-events.SPEC.md | 2 +- .../dashboard/lib/use-notifications.SPEC.md | 2 +- packages/the-framework/dashboard/lib/use-route.SPEC.md | 7 +++++-- .../the-framework/dashboard/lib/use-route.test.SPEC.md | 2 +- 10 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/the-framework/dashboard/lib/resume-command.SPEC.md b/packages/the-framework/dashboard/lib/resume-command.SPEC.md index 0ab7f2dd9..00a6bc7d6 100644 --- a/packages/the-framework/dashboard/lib/resume-command.SPEC.md +++ b/packages/the-framework/dashboard/lib/resume-command.SPEC.md @@ -1,9 +1,12 @@ Builds the shell one-liner that picks a dashboard session back up in a terminal, so a conversation is reachable outside the dashboard. -## TLDR +## Flows - The command recreates the agent's working directory first — usually already deleted by cleanup — because that directory is how the driver CLI finds the session; an empty folder is enough. - With no directory on record, only the bare session id is offered. + +## Rationales + - Deliberately no permission preset: what a reopened agent may do is the call of the person at the terminal. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/route.SPEC.md b/packages/the-framework/dashboard/lib/route.SPEC.md index 64d3304d6..a5f541edd 100644 --- a/packages/the-framework/dashboard/lib/route.SPEC.md +++ b/packages/the-framework/dashboard/lib/route.SPEC.md @@ -1,12 +1,15 @@ The dashboard's address scheme — the URL is the selection: the Overview at the root, then a project, then one of its agents. -## TLDR +## Flows - Reserved words carve out the non-project views: settings, the cross-project tickets list, a project's tickets, one ticket's own page, and its plan view beneath that. -- Reserving them is safe because a real id can never be those bare words: project ids always carry a hash suffix and agent ids are derived from their start time. -- The third segment is the agent's own stable id, not its conversation id with the driver: only the agent id is ours, stable, and already the name of its worktree directory. - Anything unparseable is the Overview and stray extra segments are ignored, so a hand-typed URL cannot break the view. +## Rationales + +- Reserving the view words is safe because a real id can never be those bare words: project ids always carry a hash suffix and agent ids are derived from their start time. +- The third segment is the agent's own stable id, not its conversation id with the driver: only the agent id is ours, stable, and already the name of its worktree directory. + ## 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/lib/ticket-filter.SPEC.md b/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md index f160f01ba..f751e978a 100644 --- a/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md +++ b/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md @@ -1,6 +1,6 @@ The tickets page's filtering, sorting, and grouping model: every project's backlog as one pool, narrowed instantly in the browser, with the whole view carried by the URL so it can be shared, reloaded, and returned to. -## TLDR +## Flows - The facets: word search, priority/effort/uncertainty, topics, pipeline stage (unplanned / planned / claimed by an agent), project, and locally-written-only. Selections OR within a facet and AND across facets. - A numeric facet takes named buckets or a fine range — two ways to say the same thing, so picking one clears the other — while "names no value" composes with either: "critical or unprioritized" is a real triage lens. diff --git a/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md b/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md index d0137d873..bee85f9aa 100644 --- a/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md @@ -1,6 +1,6 @@ One shared reading of what a finished agent's branch has to hand off — push, open a PR, merge — feeding both the action bar's summary and its expanded detail so the two can never disagree. -## TLDR +## Flows - Not read while the agent is still running: a branch still being written to has nothing to hand off yet. - Re-asked slowly at rest, and quickly while the PR lookup is still out, since that answer decides which buttons to offer; the last answer stays on screen across the cadence switch instead of blanking. diff --git a/packages/the-framework/dashboard/lib/use-async.SPEC.md b/packages/the-framework/dashboard/lib/use-async.SPEC.md index 935733211..599e9d8c2 100644 --- a/packages/the-framework/dashboard/lib/use-async.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-async.SPEC.md @@ -1,6 +1,6 @@ The one guarded way every panel reads from the daemon — once, or again on a steady interval — instead of each panel hand-rolling the same fetch loop. -## TLDR +## Flows - A failed read keeps the last answer on screen rather than blanking it: an empty panel would read as "nothing there" when the truth is "no answer", and the next tick usually recovers. - An answer that lands after the panel moved on (switched target, closed) is dropped, so a slow read can never show the wrong target's data. diff --git a/packages/the-framework/dashboard/lib/use-device-status.SPEC.md b/packages/the-framework/dashboard/lib/use-device-status.SPEC.md index 6f68db142..381a2b14d 100644 --- a/packages/the-framework/dashboard/lib/use-device-status.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-device-status.SPEC.md @@ -1,6 +1,6 @@ The saved devices' online/offline dots, refreshed on a short interval. -## TLDR +## Flows - Device tokens are a per-browser secret the daemon never stores, so the browser hands over each device's address and token per check and the daemon does the ping. - A device whose first check has not come back reads as unknown, not offline. diff --git a/packages/the-framework/dashboard/lib/use-live-events.SPEC.md b/packages/the-framework/dashboard/lib/use-live-events.SPEC.md index 60c71d72f..d310f7652 100644 --- a/packages/the-framework/dashboard/lib/use-live-events.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-live-events.SPEC.md @@ -1,6 +1,6 @@ The live transcript feed: one subscription to the selected agent's own event log, replayed from the top and then followed as it grows, shared by every pane that renders it. -## TLDR +## Flows - The feed is addressed per agent, so selecting another switches to that agent's log — two agents never share a feed. - A dead stream is not silent: an errored drop flags the feed as possibly behind reality and retries with backoff, while a deliberate close by the server (watch stream over, unknown session) simply ends it, no alarm. diff --git a/packages/the-framework/dashboard/lib/use-notifications.SPEC.md b/packages/the-framework/dashboard/lib/use-notifications.SPEC.md index 0d231080e..931a44ba3 100644 --- a/packages/the-framework/dashboard/lib/use-notifications.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-notifications.SPEC.md @@ -1,6 +1,6 @@ Browser notifications for the two watched feeds: something new needs you, or an agent started or finished. -## TLDR +## Flows - What counts as "new" is the same logic the daemon's own notifier runs, so the browser and the other delivery channels can never disagree about which items were already announced. - The first observations after load are absorbed as a baseline: you hear only what happens while you are watching, never the backlog that already existed — and flipping the toggle does not replay the backlog either. diff --git a/packages/the-framework/dashboard/lib/use-route.SPEC.md b/packages/the-framework/dashboard/lib/use-route.SPEC.md index 094d1371b..706c8fc4b 100644 --- a/packages/the-framework/dashboard/lib/use-route.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-route.SPEC.md @@ -1,10 +1,13 @@ The address bar as the dashboard's selection: read which view, project, and agent the live URL names, and navigate by naming another — so Back/Forward work and an agent is a link you can paste, reload, and bookmark. -## TLDR +## Flows - Going where you already are adds no history entry, and a correction (adopting a just-started agent's real id) replaces the current entry rather than adding a step you could go Back to. - It reads the browser's live address, not what was baked in at build time — the shell is one static page served for every path, so only the address bar tells the truth. -- It is the History API and a subscription, nothing more. A client router used to own this; its whole contribution was exposing the pathname and pushing to it, alongside a catch-all route whose return value was deliberately never read. + +## Rationales + +- No client router library is involved: the whole routing need is reading the current path and navigating to another, which the browser's own history does directly. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/use-route.test.SPEC.md b/packages/the-framework/dashboard/lib/use-route.test.SPEC.md index 697676799..f2720b61d 100644 --- a/packages/the-framework/dashboard/lib/use-route.test.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-route.test.SPEC.md @@ -1,4 +1,4 @@ -Covers route-as-URL against the real browser history (there is no router left to mock): reading the live address, navigating and re-reading it, adding a history entry so Back returns, replacing that entry for corrections, adding none for where you already are, and following Back/Forward when the browser drives them. +Covers route-as-URL against the real browser history (there is no router to mock): reading the live address, navigating and re-reading it, adding a history entry so Back returns, replacing that entry for corrections, adding none for where you already are, and following Back/Forward when the browser drives them. ## Before modifying/creating SPEC.md files From a866ca55e9ba0d5a7c65fe2381975cc4bab1d1ac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:41:31 +0000 Subject: [PATCH 08/15] Migrate SPEC.md files to the new sdd.md template: dashboard lib (A-Q) TLDR sections become Flows with behavior bullets byte-identical; retired-UI history clauses dropped or reworded as present-tense rationales. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/dashboard/lib/SPEC.md | 2 +- packages/the-framework/dashboard/lib/agent-label.SPEC.md | 2 +- .../the-framework/dashboard/lib/agent-option-rows.SPEC.md | 2 +- packages/the-framework/dashboard/lib/agent-status.SPEC.md | 2 +- .../the-framework/dashboard/lib/draft-handoff.SPEC.md | 2 +- packages/the-framework/dashboard/lib/format-date.SPEC.md | 2 +- packages/the-framework/dashboard/lib/live-state.SPEC.md | 2 +- .../the-framework/dashboard/lib/notify-channels.SPEC.md | 2 +- packages/the-framework/dashboard/lib/preferences.SPEC.md | 8 ++++++-- packages/the-framework/dashboard/lib/profiles.SPEC.md | 2 +- packages/the-framework/dashboard/lib/queue-entry.SPEC.md | 8 ++++++-- packages/the-framework/dashboard/lib/quota-bar.SPEC.md | 2 +- 12 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/the-framework/dashboard/lib/SPEC.md b/packages/the-framework/dashboard/lib/SPEC.md index 4fa4c0d96..7c39a64d7 100644 --- a/packages/the-framework/dashboard/lib/SPEC.md +++ b/packages/the-framework/dashboard/lib/SPEC.md @@ -1,6 +1,6 @@ The dashboard's client-side logic layer: the dashboard owns no facts of its own, and this is where the daemon's answers and an agent's event stream become everything the pages show — no components here, only the state and rules they consume. -## TLDR +## Flows - Freshness comes two ways: one live feed streams the selected agent's transcript (replay the log, then follow it, never showing less than it already showed), and everything else re-asks the daemon on short intervals. All reads share one guarded pattern — a failure keeps the last answer, a late answer for an abandoned target is dropped, absence is never claimed before the first answer — and a separate heartbeat is what turns "daemon unreachable" into a visible fact instead of silently frozen panels. - Anything two surfaces show is computed once and shared so they cannot drift: an agent's options table with its cross-option rules, the one-word status pill, the status color vocabulary, agent and queue-entry labels, event badge wording, the quota week's bar arithmetic, and timestamp formatting that never shows a broken date. diff --git a/packages/the-framework/dashboard/lib/agent-label.SPEC.md b/packages/the-framework/dashboard/lib/agent-label.SPEC.md index 4c458e72f..cf9c2642f 100644 --- a/packages/the-framework/dashboard/lib/agent-label.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-label.SPEC.md @@ -1,4 +1,4 @@ -What to call an agent in a list: what the user typed, else the agent's own name for itself, else its branch, else its start time — so the line identifying a row always says something real instead of a bold "(no prompt)". +What to call an agent in a list: what the user typed, else the agent's own name for itself, else its branch, else its start time — so the line identifying a row always says something real. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md b/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md index 0f18cb988..d35cb4add 100644 --- a/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md @@ -1,6 +1,6 @@ An agent's options as one table with every rule between them already applied, so the launcher and the settings page render the same options and can never disagree. -## TLDR +## Flows - A box shows the option's effective value, not the stored one: an option overridden by another reads as off, because off is what the agent will do. - Transparent turns the whole framework off, so it disables every option below it. diff --git a/packages/the-framework/dashboard/lib/agent-status.SPEC.md b/packages/the-framework/dashboard/lib/agent-status.SPEC.md index 56d86630e..5b98c52d4 100644 --- a/packages/the-framework/dashboard/lib/agent-status.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-status.SPEC.md @@ -1,6 +1,6 @@ Reduces everything an agent has reported to the single word its status pill shows — one agent, one word. -## TLDR +## Flows - An agent can hold several facts at once (it can say ready-for-merge and then fail), so a ranking picks the word: how it ended outranks anything it said on the way, because a green "ready for merge" must never describe an agent that then failed or was stopped. - Between a clean end and the report that publishing finished, the pill says "publishing…" — pushing, opening the PR, or merging is what is actually happening then. diff --git a/packages/the-framework/dashboard/lib/draft-handoff.SPEC.md b/packages/the-framework/dashboard/lib/draft-handoff.SPEC.md index 6a201fcab..8e89913a7 100644 --- a/packages/the-framework/dashboard/lib/draft-handoff.SPEC.md +++ b/packages/the-framework/dashboard/lib/draft-handoff.SPEC.md @@ -1,6 +1,6 @@ Carries a half-typed prompt into the next screen — across a device hop or an in-app jump to the launcher — without ever letting it linger in the address bar. -## TLDR +## Flows - A prompt arriving from another device rides in on the URL; at boot it is moved into tab-local storage and stripped from the address bar, so it never sits in history or leaks to other sites. - An in-app navigation writes the same stash directly — it never leaves the tab, so there is no reason to put the prompt in a URL at all. diff --git a/packages/the-framework/dashboard/lib/format-date.SPEC.md b/packages/the-framework/dashboard/lib/format-date.SPEC.md index 29aa30704..ce454cf76 100644 --- a/packages/the-framework/dashboard/lib/format-date.SPEC.md +++ b/packages/the-framework/dashboard/lib/format-date.SPEC.md @@ -1,6 +1,6 @@ Every timestamp the dashboard shows is formatted here, so an absent or unparseable one reads as a quiet fallback instead of "Invalid Date". -## TLDR +## Flows - Full, short, and date-only forms for tables and rows; the short form doubles as a name for unnamed sessions. - Ages ("22s ago", "2w ago") and durations ("2h", "2 hours") are floored, so "1m" always means at least a full minute has really passed. diff --git a/packages/the-framework/dashboard/lib/live-state.SPEC.md b/packages/the-framework/dashboard/lib/live-state.SPEC.md index 2eb7f502e..1f9a0c3e1 100644 --- a/packages/the-framework/dashboard/lib/live-state.SPEC.md +++ b/packages/the-framework/dashboard/lib/live-state.SPEC.md @@ -1,6 +1,6 @@ Answers the live view's questions purely from an agent's event stream — the dashboard is a projection of the same journal the agent writes. -## TLDR +## Flows - The choice gates the agent is parked on: an answer closes one, the agent's end closes them all, so a dead agent's question stops looking answerable. - The markdown views the agent has shown, one entry each, updated in place when re-shown. diff --git a/packages/the-framework/dashboard/lib/notify-channels.SPEC.md b/packages/the-framework/dashboard/lib/notify-channels.SPEC.md index 991e519c9..7464a2f2a 100644 --- a/packages/the-framework/dashboard/lib/notify-channels.SPEC.md +++ b/packages/the-framework/dashboard/lib/notify-channels.SPEC.md @@ -1,6 +1,6 @@ One shared reading of which notification channels the daemon can deliver on, so every surface showing it — the bell, the settings rows, the onboarding checklist — agrees the moment one of them changes it. -## TLDR +## Flows - Loaded once and shared: several surfaces mounting together ask the daemon a single time. - After saving a credential, one reload settles every surface on the new state together. diff --git a/packages/the-framework/dashboard/lib/preferences.SPEC.md b/packages/the-framework/dashboard/lib/preferences.SPEC.md index 6a6616713..ab6002f5d 100644 --- a/packages/the-framework/dashboard/lib/preferences.SPEC.md +++ b/packages/the-framework/dashboard/lib/preferences.SPEC.md @@ -1,14 +1,18 @@ The dashboard's settings: your own options with the open project's committed settings file on top, resolved nearest-wins into the one value every control reads. -## TLDR +## Flows - Owned and persisted by the daemon, so settings follow the installation, not one browser. -- Two tiers, one of them writable: a repo-shaped setting is edited in the repo, so every control writes to the same place and there is no split to get wrong. A third tier — your own per-project overrides — used to sit on top, answering for one machine what the committed file already answers for everyone, and paying for it in a write split and per-tier write bookkeeping. +- Two tiers, one of them writable: a repo-shaped setting is edited in the repo, so every control writes to the same place and there is no split to get wrong. - 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. +## Rationales + +- There is no third tier of your own per-project overrides: it would answer for one machine what the committed file already answers for everyone, and pay for it in a write split and per-tier write bookkeeping. + ## 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/lib/profiles.SPEC.md b/packages/the-framework/dashboard/lib/profiles.SPEC.md index db4a5727a..477450c7f 100644 --- a/packages/the-framework/dashboard/lib/profiles.SPEC.md +++ b/packages/the-framework/dashboard/lib/profiles.SPEC.md @@ -1,6 +1,6 @@ The saved daemons this browser can hop between — each remembered as a label, an address, and a per-browser access token. -## TLDR +## Flows - Saved only in this browser, on purpose: the token is a per-browser secret and must never land in the daemon's shared registry. - Switching devices is a navigation: the browser goes to the other daemon's address carrying the token once (plus any half-typed prompt, unless oversized), after which everything is same-origin again. diff --git a/packages/the-framework/dashboard/lib/queue-entry.SPEC.md b/packages/the-framework/dashboard/lib/queue-entry.SPEC.md index 5e27cba4b..5dc9a9f0d 100644 --- a/packages/the-framework/dashboard/lib/queue-entry.SPEC.md +++ b/packages/the-framework/dashboard/lib/queue-entry.SPEC.md @@ -1,10 +1,14 @@ How a work-queue entry reads on screen: its leading link becomes the title, and the link's target decides where clicking goes. -## TLDR +## Flows - Only a link at the very start counts as the title; one mid-sentence is part of the sentence and the entry shows as-is. - A link into the tickets folder opens that ticket's own page; an external web link opens the web; any other target keeps the title but points nowhere rather than at a dead page. -- The agent's notes after the link are detail for the tooltip, not the one-line list — inline they pushed the title out of view entirely. +- The agent's notes after the link are detail for the tooltip, not the one-line list. + +## Rationales + +- Shown inline, the agent's notes push the title out of the truncated one-line entry entirely. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/quota-bar.SPEC.md b/packages/the-framework/dashboard/lib/quota-bar.SPEC.md index 05a40a184..9701fcad9 100644 --- a/packages/the-framework/dashboard/lib/quota-bar.SPEC.md +++ b/packages/the-framework/dashboard/lib/quota-bar.SPEC.md @@ -1,6 +1,6 @@ The arithmetic behind the usage bar — purely about drawing the week; where the spending boundary sits and what it gates is decided framework-side and never re-derived here. -## TLDR +## Flows - Draws the quota week as real calendar days: segments run local midnight to midnight, so a day's width is how much of it is actually in the week, and a mid-day start's split day is named once, at whichever end holds more of it. - Day labels are a fixed two-letter notation, not the viewer's locale — two letters of a localized weekday do not distinguish the days in every language. From 891d85db8fd1418431f1708c13b6e2c70b789cdf Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:41:44 +0000 Subject: [PATCH 09/15] Migrate SPEC.md files to the new sdd.md template: src quota, tickets, queue, worktrees TLDR sections become Flows; why-clauses and de-historied framings collected under Rationales; User Stories added on tickets.SPEC.md and todo-loop.SPEC.md (traced to FEATURES-SPEC.md); worktrees.SPEC.md split into 8 flow facts and 8 rationales. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../the-framework/src/project-presets.SPEC.md | 8 ++++-- packages/the-framework/src/project.SPEC.md | 11 +++++--- .../the-framework/src/prompt-template.SPEC.md | 8 ++++-- .../the-framework/src/quota-boundary.SPEC.md | 4 +-- .../the-framework/src/quota-poller.SPEC.md | 11 +++++--- packages/the-framework/src/registry.SPEC.md | 14 +++++++--- .../the-framework/src/stale-branch.SPEC.md | 14 +++++++--- packages/the-framework/src/steps.SPEC.md | 2 +- .../the-framework/src/system-prompt.SPEC.md | 8 ++++-- packages/the-framework/src/terminal.SPEC.md | 2 +- .../the-framework/src/ticket-locks.SPEC.md | 11 +++++--- packages/the-framework/src/tickets.SPEC.md | 15 ++++++++--- packages/the-framework/src/todo-loop.SPEC.md | 14 ++++++++-- packages/the-framework/src/turn-gate.SPEC.md | 8 ++++-- .../the-framework/src/turn-gate.test.SPEC.md | 2 +- packages/the-framework/src/worktrees.SPEC.md | 27 +++++++++++++------ 16 files changed, 116 insertions(+), 43 deletions(-) diff --git a/packages/the-framework/src/project-presets.SPEC.md b/packages/the-framework/src/project-presets.SPEC.md index 52730e9d7..a8ef8868a 100644 --- a/packages/the-framework/src/project-presets.SPEC.md +++ b/packages/the-framework/src/project-presets.SPEC.md @@ -1,11 +1,15 @@ Project-shared custom presets: saved into the repo rather than the user's home file, so a team's presets travel with the code and everyone who clones it gets them. -## TLDR +## Flows - Same shape and same sanitizer as the personal presets, so the dashboard renders both alike and a hand-edited or hostile file is cleaned on read and on write. -- Saving also un-ignores the file in the framework directory's gitignore — without that, git would never see the presets and they could not be shared. +- Saving also un-ignores the file in the framework directory's gitignore. - Reading is forgiving: a missing or malformed file means no presets, never an error; removing every preset keeps the file so the sharing setup stays in place. +## Rationales + +- The framework directory's gitignore hides everything by default, so without the un-ignore line git would never see the presets and they could not be shared. + ## 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/src/project.SPEC.md b/packages/the-framework/src/project.SPEC.md index ffbdfdf6f..e91a079de 100644 --- a/packages/the-framework/src/project.SPEC.md +++ b/packages/the-framework/src/project.SPEC.md @@ -1,13 +1,18 @@ 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. -## TLDR +## 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. That way a repo can never look activated while it still lacks the one protection activation is about; writing it is a separate concern. +- 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 — because killing a slow push or checkout mid-flight can corrupt real work, while a hung read must not hold the daemon for minutes. +- 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. +## Rationales + +- The ignore file is the activation marker so a repo can never look activated while it still lacks the one protection activation is about. +- The time budget is fitted to each git operation because killing a slow push or checkout mid-flight can corrupt real work, while a hung read must not hold the daemon for minutes. + ## 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/src/prompt-template.SPEC.md b/packages/the-framework/src/prompt-template.SPEC.md index fa2adc0dd..4e8358292 100644 --- a/packages/the-framework/src/prompt-template.SPEC.md +++ b/packages/the-framework/src/prompt-template.SPEC.md @@ -1,10 +1,14 @@ Renders the built-in system prompt's template by evaluating the small code expressions embedded in it, so one verbatim prompt text can adapt itself to each agent's settings. -## TLDR +## Flows - A fragment that fails or comes out undefined (almost always a typo) stops with a loud error naming the fragment, instead of silently degrading the prompt. - Fragments are real executable code, so only the trusted built-in prompt is ever rendered this way — never user- or repo-supplied text. -- A fragment ends at the first *adjacent* `}}`, so a nested brace must never close right against another one — a space between them is the whole fix, and without it the expression is cut short and fails on a syntax error rather than doing something subtly wrong. A rule of this language rather than a bug: replacing the language was considered and declined, and two prompts already work around it. +- A fragment ends at the first *adjacent* `}}`, so a nested brace must never close right against another one — a space between them is the whole fix, and without it the expression is cut short and fails on a syntax error rather than doing something subtly wrong. + +## Rationales + +- The adjacent-`}}` limit is a rule of this language, not a bug to fix: the language stays, and a prompt that needs two braces to close together bends around the limit on purpose — two already do. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/quota-boundary.SPEC.md b/packages/the-framework/src/quota-boundary.SPEC.md index 707267507..c2843e99b 100644 --- a/packages/the-framework/src/quota-boundary.SPEC.md +++ b/packages/the-framework/src/quota-boundary.SPEC.md @@ -1,6 +1,6 @@ Decides how much of the account's weekly allowance The Framework may have spent by now: the share of the week that has already elapsed. -## TLDR +## Flows - One policy, nothing to configure: the boundary rises continuously with the clock and reaches the full allowance exactly as the week resets, so a quiet week still gets spent instead of expiring. - Work the user asks for may borrow ahead; unattended work stands down once usage passes the limit — the boundary plus an optional user-set offset (by default a small cushion beyond it). @@ -9,7 +9,7 @@ Decides how much of the account's weekly allowance The Framework may have spent ## Rationales -- Continuous rather than stepping once a day: a step handed out a whole day's allowance the instant a new day began, inviting a burst; continuous stays honest about what has actually elapsed. +- Continuous rather than stepping once a day: a step hands out a whole day's allowance the instant a new day begins, inviting a burst; continuous stays honest about what has actually elapsed. - The limit and the boundary are separate values because moving the user's slider must not redraw the boundary it is measured against. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/quota-poller.SPEC.md b/packages/the-framework/src/quota-poller.SPEC.md index 3b155ce49..bdc4fa856 100644 --- a/packages/the-framework/src/quota-poller.SPEC.md +++ b/packages/the-framework/src/quota-poller.SPEC.md @@ -1,11 +1,16 @@ Keeps a recent reading of the account's quota on hand, polling slowly and backing off on failure so the number is available without being refused upstream. -## TLDR +## Flows -- Reading the quota is expensive (it launches the whole agent) and gets refused when asked too often, so polling is deliberately slow and backs off rather than retrying into the refusal — an eager loop would keep the number permanently unavailable. -- The last good reading survives transient blips: a usage bar going empty would read as "nothing used", the one thing this must never imply. +- Polling is deliberately slow and backs off on failure rather than retrying into the refusal. +- The last good reading survives transient blips. - An authoritative answer — no subscription, no agent installed — stops polling for good and drops the retained reading; an unrecognized readout does not, so one odd answer never kills the usage bar for the daemon's whole life. +## Rationales + +- Reading the quota is expensive (it launches the whole agent) and gets refused when asked too often — an eager retry loop would keep the number permanently unavailable. +- The last good reading is retained through blips because a usage bar going empty would read as "nothing used", the one thing this must never imply. + ## 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/src/registry.SPEC.md b/packages/the-framework/src/registry.SPEC.md index fd6415961..2917a467b 100644 --- a/packages/the-framework/src/registry.SPEC.md +++ b/packages/the-framework/src/registry.SPEC.md @@ -1,16 +1,22 @@ The user's one home file: the projects The Framework is installed into, the user's dashboard preferences, the daemon's access token, and third-party credentials. -## TLDR +## Flows - A single JSON file per machine in the user's home, owned by the daemon, so the dashboard never needs browser storage. - Reads are forgiving — a missing, malformed, or old-shaped file yields an empty registry — and every value is sanitized on the way in (clamped numbers, known-set strings, capped lists), so a hand-edited or hostile file can neither break anything nor smuggle junk into an agent. - One shape, and one spelling per setting. A file in an older shape reads as no projects and no preferences at all, and a setting under an older name is simply not there — nothing is translated on the way in, so a file left behind by a rename is brought up to date by hand. -- A setting whose states are strictly nested is stored as the one ordinal it is, not as a flag per stage: how far an agent publishes itself is a single rung, so the file cannot hold a combination no agent could honour. -- One tier of preferences lives here, and it is the user's. Repo-shaped settings belong in the repo's committed file, so a per-project block in a home file was a second answer to the same question that only one machine could see. +- How far a finished agent publishes itself is stored as the single rung it is. +- One tier of preferences lives here, and it is the user's; repo-shaped settings belong in the repo's committed file. - Patch writes touch only the keys the caller changed, so a stale dashboard tab cannot silently revert someone else's setting. -- Writes are atomic, serialized, and owner-only readable: the file carries the daemon token and credentials, and a half-written or world-readable registry would lose or leak everything. +- Writes are atomic, serialized, and owner-only readable. - The token and secrets live outside the preferences so they can never reach the browser; clients are only ever told a credential is present. +## Rationales + +- The publish setting is one ordinal rather than a flag per stage, so the file cannot hold a combination no agent could honour. +- A per-project block in a home file would be a second answer to a question the repo's committed file already answers — and an answer only one machine could see. +- The file carries the daemon token and credentials, so a half-written or world-readable registry would lose or leak everything. + ## 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/src/stale-branch.SPEC.md b/packages/the-framework/src/stale-branch.SPEC.md index c66674f8e..567e3f4fb 100644 --- a/packages/the-framework/src/stale-branch.SPEC.md +++ b/packages/the-framework/src/stale-branch.SPEC.md @@ -1,10 +1,16 @@ Releases a pinned routine branch its closed or merged PR left behind, so a recurring job stops reporting a pending agent that no longer exists. -## TLDR +## Flows -- Routine agents pin their branch name so two firings never run at once — but nothing released the name, so the first PR closed without deleting its branch jammed the routine forever. -- A branch existing is not evidence of pending work; an open PR is. The branch goes only when its PR history proves the work is over: some PR existed and none is open. Deleting is safe exactly then — the closed PR preserves the diff, so the branch is a leftover name, not the last copy of anything. -- An open PR keeps the branch (genuinely busy); no PR history keeps it too (either an agent still heading for its handoff, or the PR lookup hiccuped — deleting on a hiccup would discard work). A failed release never throws; the next sweep retries. +- Routine agents pin their branch name so two firings never run at once; a firing that finds the branch already existing stands down. +- A branch existing is not evidence of pending work; an open PR is. The branch goes only when its PR history proves the work is over: some PR existed and none is open. +- An open PR keeps the branch (genuinely busy); no PR history keeps it too (either an agent still heading for its handoff, or the PR lookup hiccuped). A failed release never throws; the next sweep retries. + +## Rationales + +- Without the release, a branch left behind by a closed PR would stand its routine down forever: every firing finds the branch and reports a pending agent that does not exist. +- Deleting is safe exactly when the PR history proves the work over — the closed PR preserves the diff, so the branch is a leftover name, not the last copy of anything. +- A branch with no PR history is kept because deleting on a lookup hiccup would discard work. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/steps.SPEC.md b/packages/the-framework/src/steps.SPEC.md index 41261c8dc..257df32e0 100644 --- a/packages/the-framework/src/steps.SPEC.md +++ b/packages/the-framework/src/steps.SPEC.md @@ -1,6 +1,6 @@ The prompts a build agent opens with, and the one check that decides between them. -## TLDR +## Flows - The framing states the one thing the agent cannot infer: build from scratch in an empty workspace, or work within the codebase that already exists — an existing project is extended, never re-scaffolded. - A workspace holding no source the agent could have written counts as empty; lockfiles, dotfiles, and dependency or output directories do not. diff --git a/packages/the-framework/src/system-prompt.SPEC.md b/packages/the-framework/src/system-prompt.SPEC.md index cf1f01a39..75fef756f 100644 --- a/packages/the-framework/src/system-prompt.SPEC.md +++ b/packages/the-framework/src/system-prompt.SPEC.md @@ -1,11 +1,15 @@ Composes, in one place, the entire system channel every agent runs under, so every surface agrees on exactly what it was told. -## TLDR +## Flows -- Fixed order: the project context (goal, knowledge docs, tickets, queue — with the ticket and queue format specs inlined into the channel itself, because pointing at a file the agent may not have left the formats unfollowable), then the built-in prompt, then the user's own instructions, then the emit protocols, signal protocol always last. +- Fixed order: the project context (goal, knowledge docs, tickets, queue — with the ticket and queue format specs inlined into the channel itself), then the built-in prompt, then the user's own instructions, then the emit protocols, signal protocol always last. - Vanilla mode drops everything framework-authored but keeps the emit protocols — the agent still has to be able to signal. Transparent mode is the master off-switch: an empty channel, the agent runs raw. - An agent with a real browser is told it has one; a hands-off agent is told the ask-gates are unavailable, so it takes its most plausible reading instead of parking on a question nobody can answer. +## Rationales + +- The ticket and queue format specs travel inlined in the channel because a pointer to a file the agent may not have leaves the formats unfollowable. + ## 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/src/terminal.SPEC.md b/packages/the-framework/src/terminal.SPEC.md index c9535d0e5..294a62abc 100644 --- a/packages/the-framework/src/terminal.SPEC.md +++ b/packages/the-framework/src/terminal.SPEC.md @@ -1,6 +1,6 @@ Renders the agent's event stream as one human-readable terminal line per event — the CLI's counterpart to the dashboard's views over the same stream. -## TLDR +## Flows - Consequences over flags: the handoff line says what will happen ("push the branch, open a PR, and merge it"), and every merge outcome is spoken — after "auto-merge is on", silence would read as "it merged". - Refusals are phrased as reasons in the reader's terms (why a merge was withheld, why the handoff or cleanup did nothing), so a setting never reads as a bug. diff --git a/packages/the-framework/src/ticket-locks.SPEC.md b/packages/the-framework/src/ticket-locks.SPEC.md index ba27590d8..05bc4397c 100644 --- a/packages/the-framework/src/ticket-locks.SPEC.md +++ b/packages/the-framework/src/ticket-locks.SPEC.md @@ -1,12 +1,17 @@ Claims a ticket for a single agent by committing a lock file beside it on the data branch, so agents on other machines and in the cloud cannot double-work the same ticket. -## TLDR +## Flows -- The claim cannot live in the daemon's memory — cloud agents outlive the local process, and other machines never shared it — so it lives where every agent already looks: a lock sibling in the tickets folder naming the holder, which the stock prompts already skip. +- A claim is a lock sibling in the tickets folder naming the holding agent — the place every agent already looks, and a file the stock prompts already skip. - A lock is one more data-branch write: the shared write funnel syncs, commits the batch, and pushes the branch, so a claim reaches every machine the way all framework data does — and a lost push race re-judges the batch against what actually landed instead of overwriting anyone's claim. - A batch says which side of the ticket's life it claims for: a planning batch skips a ticket that already has a plan (the work it came for is done), while an implementing batch reads the plan as its input and is stopped only by an existing lock. - The claim is the committed state: a batch whose cycle failed whole claimed nothing, while one that committed but could not push still guards local agents and says so out loud. -- No timed expiry — an agent may legitimately hold a ticket for days. The lock lifts when the agent retires it on the data branch with its finished work, when a human releases it, or when the daemon frees a claim it made for an agent that ended with nothing to hand off — the one claim it can know is dead rather than guess by a clock. The daemon only ever frees a lock still naming the exact agent it made it for. +- There is no timed expiry. The lock lifts when the agent retires it on the data branch with its finished work, when a human releases it, or when the daemon frees a claim it made for an agent that ended with nothing to hand off. The daemon only ever frees a lock still naming the exact agent it made it for. + +## Rationales + +- The claim cannot live in the daemon's memory: cloud agents outlive the local process, and daemons on other machines never see it — a committed file is what every machine and every agent shares. +- No timed expiry because an agent may legitimately hold a ticket for days, and a lock released under a live agent re-opens the exact double-work window it exists to close; an agent that ended with nothing to hand off is the one claim the daemon can know is dead rather than guess by a clock. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/tickets.SPEC.md b/packages/the-framework/src/tickets.SPEC.md index b0739c7ea..24ae49e83 100644 --- a/packages/the-framework/src/tickets.SPEC.md +++ b/packages/the-framework/src/tickets.SPEC.md @@ -1,11 +1,20 @@ The repo's ticket and queue conventions: tickets are plain markdown files in a root `tickets/` folder, the confirmed-task queue is the one root `TODO_AGENTS.md` file, and this defines how the two reference each other. -## TLDR +## User Stories -- A ticket put on the queue keeps its identity as a markdown link back to the ticket file; only a plain file directly inside the tickets folder counts — the value is rendered and opened by people, so traversal, absolute paths, URLs, and nesting are all refused. +- The user queues a ticket into the AI queue, and the queue entry stays linked back to the ticket file. +- The user marks a ticket's priority, and queueing the ticket lands it in the queue's matching ranked section. +- The user links a ticket to a GitHub issue, and merging the ticket's work closes that issue. + +## Flows + +- A ticket put on the queue keeps its identity as a markdown link back to the ticket file; only a link to a plain file directly inside the tickets folder counts — traversal, absolute paths, URLs, and nesting are all refused. - A ticket's written priority (0-10) maps straight onto the queue's numbered sections; anything else — words, out-of-range, fractions — lands in the middle rather than being guessed at. - A ticket's GitHub header names the issue it tracks, which is what lets merging the work close that issue. -- One queue location; the older spellings are no longer read. + +## Rationales + +- Only a link to a plain file directly inside the tickets folder counts because the value is rendered and opened by people; an entry linking anywhere else is treated as plain text rather than followed. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/todo-loop.SPEC.md b/packages/the-framework/src/todo-loop.SPEC.md index 45294dc3c..a4bc2a690 100644 --- a/packages/the-framework/src/todo-loop.SPEC.md +++ b/packages/the-framework/src/todo-loop.SPEC.md @@ -1,13 +1,23 @@ Drains the project's task queue one entry per turn until it is empty, and owns the queue plumbing other features lean on — the queue itself living on the data branch, with the framework as its writer. -## TLDR +## User Stories + +- The user queues confirmed work, and an unattended agent drains it one entry per turn until the queue is empty. +- The user watching an agent is asked before each queue entry and can stop the loop. +- The user queues an entry with a priority, and it is worked in rank order rather than arrival order. + +## Flows - The framework drives: read the queue's first open entry fresh off the data branch, gate ("start the next item?" — an agent nobody is watching takes the recommended answer and carries on), prompt the agent to complete exactly that entry, then check it off on the data branch itself — the queue is not the agent's file to edit, and an entry someone else already retired is simply found done. - Safe to leave unattended: a hard item cap, the agent's stop and budget signal, and a write guard — a check-off that cannot land stops the loop rather than re-serving finished work. - A backlog turn is a full turn: ask-gates and signals are honored there too, with ready-for-merge fired once across the whole backlog. -- An entry queued with a priority lands in its numbered section, not at the end — the queue drains front to back, so placement is priority. Every queue write, the paused agent's resume note included, goes through the data branch's write funnel. +- An entry queued with a priority lands in its numbered section, not at the end of the file. Every queue write, the paused agent's resume note included, goes through the data branch's write funnel. - An agent's own session TODO file (a checkout file, not the queue) with open entries withholds auto-merge — a temporary belt under the agent's own ready signal. +## Rationales + +- The queue drains front to back, so placement is priority: an entry appended at the end would be worked last, behind everything already queued. + ## 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/src/turn-gate.SPEC.md b/packages/the-framework/src/turn-gate.SPEC.md index 246c171da..6c6b89c59 100644 --- a/packages/the-framework/src/turn-gate.SPEC.md +++ b/packages/the-framework/src/turn-gate.SPEC.md @@ -1,13 +1,17 @@ The turn-boundary contract with the wrapped agent: each turn runs as a black box, so everything the framework learns — the agent stopping to ask, views to show, its chosen session name, ready-for-merge — is a tagged block parsed out of the turn's final message. -## TLDR +## Flows - The protocol texts appended to the system channel pin how to emit, not when: one blocking ask-gate and the non-blocking signals (markdown views, session name, ready-for-merge). -- There is one gate block, not four. A single choice, several at once, an approval, and handing over a browser were four tags with four parsers; each is a question with options, and what distinguishes them is what the agent writes in one — two options for an approval, a file for a plan, a flag for several picks, a mark on the options that end the agent rather than resuming it. A new kind of question needs no new code. +- There is one gate block, not four: every gate is a question with options, and what distinguishes the kinds is what the agent writes in one — two options for an approval, a file for a plan, a flag for several picks, a mark on the options that end the agent rather than resuming it. - Parsing is tolerant on purpose: a malformed block is ignored rather than crashing an agent, the block appearing latest in the turn wins (falling back past a broken one), and missing ids and titles get sensible defaults. A block with nothing pickable in it is not a gate — the agent carries on rather than parking on an empty question. - One continuation wording resumes the agent after any answered gate, and a shared cap on ask-rounds stops an agent that keeps asking. - Signal emission is deduped across a span of turns: ready-for-merge fires once, and a session name re-emits only on a real rename. +## Rationales + +- One gate block instead of a tag, a parser, and a card per question kind: a single choice, several picks, an approval, and handing over a browser are all a question with options, so a new kind of question needs no new code. + ## 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/src/turn-gate.test.SPEC.md b/packages/the-framework/src/turn-gate.test.SPEC.md index fade1552d..662ec2e3b 100644 --- a/packages/the-framework/src/turn-gate.test.SPEC.md +++ b/packages/the-framework/src/turn-gate.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the turn-signal parsing: the one ask-gate block with its tolerant defaults and latest-block-wins (falling back past a malformed block, and refusing a block with nothing pickable in it), the several-picks and plan-file variants that used to be gate kinds of their own, an option marked as ending the agent, markdown views, session-name slugging (a session legitimately named "view" is kept), ready-for-merge detection, and the single continuation wording shared by every path. +Covers the turn-signal parsing: the one ask-gate block with its tolerant defaults and latest-block-wins (falling back past a malformed block, and refusing a block with nothing pickable in it), the several-picks and plan-file gate variants, an option marked as ending the agent, markdown views, session-name slugging (a session legitimately named "view" is kept), ready-for-merge detection, and the single continuation wording shared by every path. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/worktrees.SPEC.md b/packages/the-framework/src/worktrees.SPEC.md index 01462f5b7..76ffc2d8c 100644 --- a/packages/the-framework/src/worktrees.SPEC.md +++ b/packages/the-framework/src/worktrees.SPEC.md @@ -1,16 +1,27 @@ Cleans up the per-agent checkouts a project retains — one implementation behind the dashboard's buttons, the teardown and the automatic sweep, so none of them can drift. -## TLDR +## Flows -- **One rule: only what is on the remote may go.** Removing a checkout commits whatever it is still holding to the agent's branch, pushes that branch, and deletes the checkout only once the remote has it — so nothing local is ever the last copy of work, and every deletion is recoverable with `git worktree add`. -- A repo with nowhere to push keeps every checkout, which is the honest answer rather than a special case: there is nowhere for the work to be recoverable from. -- A session set to publish nothing (`handoff: local`) keeps its unpushed checkout the same way: the push exists to make removal recoverable, not to publish work the session said must stay local. That decision comes before anything commits — a kept checkout is a place someone works, and grabbing their half-typed edits as a commit on the way to a refusal would repeat every sweep pass — so its checkout goes only from a clean tree on a tip already on the remote, where removing it publishes nothing. -- A record that cannot be read keeps the checkout too: "no record was ever written" is a boot death and takes the recoverable default, but unreadable cannot tell a publish-nothing session from any other, so removal refuses rather than guesses and a later pass retries. -- One failure mode, and it is legible: the push did not land, so the checkout stays and the reason says why. It replaced a retention policy that asked what state the agent ended in, which is a question with three answers and no bearing on whether the work is safe. -- Deleting an agent is the other thing entirely: its archived records leave the dashboard for good and uncommitted work is discarded with the checkout — but the branch and its commits stay, because silently deleting a branch that may carry merged work or an open pull request is not a dashboard's call. -- Both refuse while the agent is live: Stop is how one ends, not pulling the floor out from under it. +- **One rule: only what is on the remote may go.** Removing a checkout commits whatever it is still holding to the agent's branch, pushes that branch, and deletes the checkout only once the remote has it. +- A repo with nowhere to push keeps every checkout. +- A session set to publish nothing (`handoff: local`) keeps its unpushed checkout; that decision comes before anything commits, so its checkout goes only from a clean tree on a tip already on the remote, where removing it publishes nothing. +- A record that cannot be read keeps the checkout too, and a later pass retries; a record that was never written is a boot death and takes the ordinary commit-push-remove path. +- One failure mode, and it is legible: the push did not land, so the checkout stays and the reason says why. +- Deleting an agent is the other thing entirely: its archived records leave the dashboard for good and uncommitted work is discarded with the checkout — but the branch and its commits stay. +- Removal and deletion both refuse while the agent is live. - The prune sweep offers every non-live checkout to the same rule and reports each one it could not reclaim, so a checkout that stays is always accounted for rather than silently kept. +## Rationales + +- Committing and pushing before deletion means nothing local is ever the last copy of work: every removed checkout can be recreated from its branch. +- A repo with nowhere to push keeping every checkout is the honest answer rather than a special case: there is nowhere for the work to be recoverable from. +- A publish-nothing session's branch is never pushed to make removal possible: the push exists to make removal recoverable, not to publish work the session said must stay local. +- The publish-nothing decision comes before anything commits because a kept checkout is a place someone works, and grabbing their half-typed edits as a commit on the way to a refusal would repeat every sweep pass. +- An unreadable record cannot tell a publish-nothing session from any other, so removal refuses rather than guesses. +- Retention asks one question — is this work recoverable yet — never what state the agent ended in: how an agent ended has no bearing on whether its work is safe. +- Deletion keeps the branch because silently deleting a branch that may carry merged work or an open pull request is not a dashboard's call. +- A live agent's checkout is where it is working: Stop is how an agent ends, not pulling the floor out from under it. + ## Before modifying/creating SPEC.md files You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md From 051eb935ea67f653582642e8296631ebde0877b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:41:56 +0000 Subject: [PATCH 10/15] Migrate SPEC.md files to the new sdd.md template: dashboard components (P-Z) TLDR sections become Flows; history clauses grounded against the components and reworded present-tense; fuzzy phrases replaced with the actual behavior; User Stories added on the components directory spec. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../dashboard/components/ProjectHome.SPEC.md | 2 +- .../dashboard/components/PromptEditor.SPEC.md | 2 +- .../the-framework/dashboard/components/Quota.SPEC.md | 4 ++-- .../dashboard/components/RightRail.SPEC.md | 7 +++++-- .../dashboard/components/RoutineWork.SPEC.md | 2 +- packages/the-framework/dashboard/components/SPEC.md | 11 ++++++++++- .../dashboard/components/SettingsPage.SPEC.md | 8 ++++++-- .../dashboard/components/StartAgentForm.SPEC.md | 4 ++-- .../components/SystemPromptDisclosure.SPEC.md | 2 +- .../dashboard/components/TicketDetailPage.SPEC.md | 4 ++-- .../dashboard/components/TicketFilterBar.SPEC.md | 2 +- .../dashboard/components/TicketsPage.SPEC.md | 2 +- .../dashboard/components/TicketsPanel.SPEC.md | 2 +- .../dashboard/components/TicketsPanel.test.SPEC.md | 2 +- 14 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/the-framework/dashboard/components/ProjectHome.SPEC.md b/packages/the-framework/dashboard/components/ProjectHome.SPEC.md index baeb06273..0a6b06571 100644 --- a/packages/the-framework/dashboard/components/ProjectHome.SPEC.md +++ b/packages/the-framework/dashboard/components/ProjectHome.SPEC.md @@ -1,6 +1,6 @@ The project home / launcher: the page for starting an agent, plus everything worth answering or reading about the project. -## TLDR +## Flows - Never consumed by an agent: starting one adds its own view alongside, and this page stays put so you can launch again. - Below the start form: every agent's open questions in one answerable place, then the project's docs — shown in this column rather than in the right rail, which withholds them while this page is up. diff --git a/packages/the-framework/dashboard/components/PromptEditor.SPEC.md b/packages/the-framework/dashboard/components/PromptEditor.SPEC.md index ec6ce24c9..222c06478 100644 --- a/packages/the-framework/dashboard/components/PromptEditor.SPEC.md +++ b/packages/the-framework/dashboard/components/PromptEditor.SPEC.md @@ -1,6 +1,6 @@ The rich prompt editor: `/` opens commands, `<` inserts the agent's tag macros, `@` references a project, `#` references a file — each inserted as a chip that still serializes to the exact prompt text the agent reads, so nothing downstream changes. -## TLDR +## Flows - `/` loads a preset (built-in, yours, or the project's) or inserts an agent action, and can open the create-preset panel; loading over a typed draft is allowed, one undo away, and the caller is told so its note can say so. - Referencing a project or file also focuses the context on it, and deleting the chip undoes that focus — the prompt and the context set can never silently disagree. diff --git a/packages/the-framework/dashboard/components/Quota.SPEC.md b/packages/the-framework/dashboard/components/Quota.SPEC.md index e58689a33..60e0f98fd 100644 --- a/packages/the-framework/dashboard/components/Quota.SPEC.md +++ b/packages/the-framework/dashboard/components/Quota.SPEC.md @@ -1,6 +1,6 @@ The Usage panel: the account's quota week as one bar, so "am I ahead or behind?" is a glance — the fill is what has been spent, a tick marks how much may be gone by now, and the colour compares the two. -## TLDR +## Flows - The bar splits into the used fill and, dimmed, the budget left for autonomous AI; dragging the dimmed segment's own edge sets where unattended work stops, and the legend says whether autonomous AI currently has room (enabled) or none (disabled). - The headline is a duration, not a percentage: how far ahead of or behind the week's pace consumption runs. @@ -9,7 +9,7 @@ The Usage panel: the account's quota week as one bar, so "am I ahead or behind?" ## Rationales -- A week the panel cannot place is a loud error quoting the text it could not read — a quiet fallback once hid a real defect for weeks. Each of the three ways of failing gets its own sentence: an unreadable reset phrasing shows that phrasing; a week line carrying no reset time at all says that, and does not claim a parse failure it did not have, since that readout parsed exactly as printed and simply left the week with only one end of its span; and a readout with no week names the line that is missing and lists the labels that arrived instead. The readout is prose from another program, so those labels are the whole diagnosis — and a message that borrows the wrong case is worse than none, since it denies a week the panel is listing directly below it. +- A week the panel cannot place is a loud error quoting the text it could not read — a quiet fallback would hide a real defect for weeks. Each of the three ways of failing gets its own sentence: an unreadable reset phrasing shows that phrasing; a week line carrying no reset time at all says that, and does not claim a parse failure it did not have, since that readout parsed exactly as printed and simply left the week with only one end of its span; and a readout with no week names the line that is missing and lists the labels that arrived instead. The readout is prose from another program, so those labels are the whole diagnosis — and a message that borrows the wrong case is worse than none, since it denies a week the panel is listing directly below it. - Numbers that outlive failed refreshes are dated, since an undated bar claims to be current. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/RightRail.SPEC.md b/packages/the-framework/dashboard/components/RightRail.SPEC.md index 33708fd9a..6171a0c15 100644 --- a/packages/the-framework/dashboard/components/RightRail.SPEC.md +++ b/packages/the-framework/dashboard/components/RightRail.SPEC.md @@ -1,12 +1,15 @@ The right sidebar: tabs for the project's files, the documents the agent pushed up as it worked, the live browser view, and the workspace docs — every tab earned by its content. -## TLDR +## Flows - A tab that could only say "nothing yet" is not offered, and a rail with no tabs left is not shown at all; while a first read is still out the tab stays, so switching projects does not blink the rail. - Only the first pushed view pulls focus; after that an explicit pick is never overridden, and a tab that loses its content falls back to one that still has some. - The browser tab appears only when the agent actually drives a browser — never for agents executing somewhere no browser exists. - While the launcher shows the docs in its own column, the rail withholds that tab. -- It had a History tab too, rendering a committed markdown re-narration of what the agent archive already holds exactly; the agents themselves are the history. + +## Rationales + +- No History tab: a committed markdown re-narration of what the agent archive already holds exactly adds nothing — the agents themselves are the history. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/RoutineWork.SPEC.md b/packages/the-framework/dashboard/components/RoutineWork.SPEC.md index dba139e9d..fcf6ede5c 100644 --- a/packages/the-framework/dashboard/components/RoutineWork.SPEC.md +++ b/packages/the-framework/dashboard/components/RoutineWork.SPEC.md @@ -1,6 +1,6 @@ The Overview's Routine work card: the jobs the scheduled sweep fires, each with a Run now that starts it against a chosen project immediately. -## TLDR +## Flows - The list is read straight from the definition the daemon runs, so screen and schedule cannot drift; Run now starts the work at once rather than asking the sweep to come sooner. - The queue-draining routine's Run now fires a drain-only sweep — the only path that can fan out several agents, up to the concurrency setting; card-fired routines run unattended, like the sweep's own. diff --git a/packages/the-framework/dashboard/components/SPEC.md b/packages/the-framework/dashboard/components/SPEC.md index 397af029f..caf7a2144 100644 --- a/packages/the-framework/dashboard/components/SPEC.md +++ b/packages/the-framework/dashboard/components/SPEC.md @@ -1,6 +1,15 @@ The dashboard's React component catalog: every page, panel and control the browser app is assembled from. -## TLDR +## User Stories + +- The user starts an agent from the launcher — a typed prompt for an attended run, a preset for unattended routine work. +- The user watches a live agent's transcript, answers its questions inline, steers it, stops it, and resumes it. +- The user scans the Overview board for quota pace, running agents, queued work, routine jobs and hot tickets, and answers any agent's open question from there. +- The user filters, sorts and groups every project's tickets on one page, opens a ticket's detail and plan, and queues or starts work from a row. +- The user changes every preference — appearance, driver and model, where agents execute, agent options, notifications, automation — on one settings page. +- A teammate opens a shared link and watches one agent's live feed read-only. + +## 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. - 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. diff --git a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md index 68e278af5..154ae7935 100644 --- a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md +++ b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md @@ -1,12 +1,16 @@ The settings page: every setting in one findable place — appearance, driver and model, where agents execute, agent options, notifications, automation, saved devices and the browser bridge — plus the non-dismissible onboarding checklist. -## TLDR +## Flows - Everything here writes the one writable tier — your own settings, which is the default every project starts from. A value that belongs to a repo is committed in that repo's own settings file, and is edited there. - The agent-options table is the very one the launcher renders, its rules applied identically, and a rule-disabled row stays visible, greyed with its reason — the whole point of the page is being where you come to look. - A toggle is a preference; whether it can deliver is a capability: blocked browser notifications and unconfigured Discord channels read as such, with their setup dialogs right beside the toggle. - Typed automation values are clamped to the same bounds the daemon enforces, and an untouched value shows the real default in force rather than a zero nothing is using. -- A row with nothing to pick renders nothing at all: an empty dropdown is a control you can open and not use, which reads as broken rather than as "no choices here". Every list here is static today, so the guard is for the next dynamic one. +- A row with nothing to pick renders nothing at all: an empty dropdown is a control you can open and not use, which reads as broken rather than as "no choices here". + +## Rationales + +- Every list here is static today, so the guard against empty dropdowns is for the next dynamic one. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md index 03cbdaa20..a619d5334 100644 --- a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md +++ b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md @@ -1,10 +1,10 @@ The launcher form that starts an agent in the selected project: the shared composer plus the actual submit, the Context selector, and the system-prompt preview. -## TLDR +## Flows - A typed prompt starts an attended conversation; a preset starts unattended routine work that ends at settle with its armed handoff firing. - The options sent and the prompt previewed come from the same mapping the agent uses, so the form cannot disagree with the agent it starts; a picked device relays it there, its secret token riding in memory only. -- Preflight warnings spend words before the agent is spent, never blocking: a driver CLI that cannot start (the GitHub CLI checked only when a PR or merge is armed, nothing probed for Actions or device targets), a repo whose disabled auto-merge makes an armed merge land immediately, and Haiku's known failure to finish properly. +- Preflight warnings spend words before the agent is spent, never blocking: a driver CLI that cannot start (the GitHub CLI checked only when a PR or merge is armed, nothing probed for Actions or device targets), a repo whose disabled auto-merge makes an armed merge land immediately, and Haiku's known skipping of the finish step, which leaves a publishing run an unmerged draft PR. - A start answers immediately: an optimistic rail row appears and the view jumps to the agent before its record exists. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md b/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md index 81a5903c8..47028648f 100644 --- a/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md +++ b/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md @@ -1,6 +1,6 @@ The "Enhanced System Prompt" disclosure on the launcher: the entire system prompt the agent will send, readable before it runs, so a user can see what The Framework wraps their prompt in rather than take our word for it. -## TLDR +## Flows - It renders through the same composition the agent itself uses — no second copy of the wrapping logic to drift — and states that nothing else is appended when it starts. - Its two checkboxes are the two real axes, not new settings: the built-in anti-laziness block, and the framework integration as a whole; transparent mode is the master off-switch that empties the prompt entirely, and the rows always read the way the agent will actually behave. diff --git a/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md b/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md index 127f2f9a3..17102b5f4 100644 --- a/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md @@ -1,10 +1,10 @@ One ticket's own page: its full markdown and every known fact, plus the Queue action and the manual release of a claim. -## TLDR +## Flows - Addressed directly by the same ticket filename the list row and the route carry; a ticket deleted or mistyped says "does not exist" rather than rendering blank. - Queue files the ticket into the AI queue with its priority; once queued it reads so and cannot be pressed twice, while a failed write surfaces and leaves it pressable. -- Nothing times a claim out anymore, so a dead agent's claim stands until a human lifts it here: a claimed ticket names its holder inline and offers Release lock. +- Nothing times a claim out, so a dead agent's claim stands until a human lifts it here: a claimed ticket names its holder inline and offers Release lock. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md b/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md index 8aa5789c9..3847f2263 100644 --- a/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md @@ -1,6 +1,6 @@ The Tickets page's toolbar: search, faceted filters, sort and grouping — the page's whole viewing state in one row, every control handing an edited copy back to the caller, with option counts read from the unfiltered pool so a pick's yield is visible before it is made. -## TLDR +## Flows - Numeric facets (priority, effort, uncertainty) offer quick buckets, a fine min–max slider and a "names no value" row; buckets and slider drive one selection — a contiguous bucket pick mirrors onto the slider, and only a selection skipping a middle span dims it (still live, not dead). - Facets appear only when they have something to say: effort and uncertainty once some plan recorded numbers, Project with two or more projects, "Not linked" while unlinked tickets exist. diff --git a/packages/the-framework/dashboard/components/TicketsPage.SPEC.md b/packages/the-framework/dashboard/components/TicketsPage.SPEC.md index 787de3fa3..ad2c47c86 100644 --- a/packages/the-framework/dashboard/components/TicketsPage.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketsPage.SPEC.md @@ -1,6 +1,6 @@ The cross-project Tickets view: every registered project's backlog on one full-width page — filterable, sortable, groupable — because the backlog is worth seeing whole rather than scoped to whichever project is selected. -## TLDR +## Flows - The whole viewing state lives in the URL, so a filtered view is a shareable link; changes mirror to the address without adding history steps, and the shown/total tally rides the page title. - Grouped by project by default, each section its own panel with its GitHub update bar; the flat list is the one view that answers "the single highest-priority ticket anywhere", rows carrying their project and still starting work or plans in it. diff --git a/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md b/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md index d0b8a3ba1..ac394d82e 100644 --- a/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md @@ -1,6 +1,6 @@ A project's tickets as scannable one-liner rows — start column, title, topics, claim, effort, priority, age, plan column, GitHub link — plus the GitHub update. -## TLDR +## Flows - The start column spins up an unattended agent on that one ticket, the ticket named on the agent so its record says what it implements; opening the row goes to the detail page instead — starting is not opening. - The plan column links an existing plan, or starts an agent to write one — attended, because a plan is written for a human to review. diff --git a/packages/the-framework/dashboard/components/TicketsPanel.test.SPEC.md b/packages/the-framework/dashboard/components/TicketsPanel.test.SPEC.md index 7cd8a5afd..21859b6af 100644 --- a/packages/the-framework/dashboard/components/TicketsPanel.test.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketsPanel.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the one-liner rows (meta and its order, the claim marker with inline holder, topic and claim clicks filtering without navigating, the GitHub link not hijacking the row), the start and plan columns sending their exact exported prompts with the right attended/unattended split, the one GitHub update offered empty and filled with its verbatim preset text, last-caught-up stamp and refusal handling, and the filtered-empty vs genuinely-empty distinction. +Covers the one-liner rows (meta and its order, the claim marker with inline holder, topic and claim clicks filtering without navigating, the GitHub link not hijacking the row), the start and plan columns sending their exact exported prompts with the right attended/unattended split, the one GitHub update offered empty and filled with its verbatim preset text, last-caught-up stamp and refusals saying why, and the filtered-empty vs genuinely-empty distinction. ## Before modifying/creating SPEC.md files From 29c37d96bc7304938edaf481e90efd0d5e0e37c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:42:42 +0000 Subject: [PATCH 11/15] Migrate SPEC.md files to the new sdd.md template: daemon dashboard reads (G-Z) TLDR sections become Flows with bullets unchanged; the two history passages (overview backlog, RPC framework) reworded as present-tense rationales grounded in the code. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/dashboard/git-status.SPEC.md | 2 +- .../the-framework/src/dashboard/interventions.SPEC.md | 2 +- .../the-framework/src/dashboard/keyed-watcher.SPEC.md | 2 +- packages/the-framework/src/dashboard/open-in-app.SPEC.md | 2 +- .../the-framework/src/dashboard/open-questions.SPEC.md | 2 +- packages/the-framework/src/dashboard/overview.SPEC.md | 8 ++++++-- packages/the-framework/src/dashboard/projects.SPEC.md | 2 +- packages/the-framework/src/dashboard/queue.SPEC.md | 2 +- packages/the-framework/src/dashboard/quota.SPEC.md | 2 +- .../the-framework/src/dashboard/relay-endpoints.SPEC.md | 2 +- packages/the-framework/src/dashboard/remote-run.SPEC.md | 2 +- packages/the-framework/src/dashboard/rpc-serve.SPEC.md | 8 ++++++-- packages/the-framework/src/dashboard/server.SPEC.md | 2 +- packages/the-framework/src/dashboard/tickets.SPEC.md | 2 +- 14 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/the-framework/src/dashboard/git-status.SPEC.md b/packages/the-framework/src/dashboard/git-status.SPEC.md index 0a1a465a6..7e1a11204 100644 --- a/packages/the-framework/src/dashboard/git-status.SPEC.md +++ b/packages/the-framework/src/dashboard/git-status.SPEC.md @@ -1,6 +1,6 @@ A project's git status for the panel: the current branch, whether there are uncommitted changes, and the pull request linked to that branch. -## TLDR +## Flows - Branch and dirtiness are instant local reads; the PR is the slow lookup, served through the cache and allowed to arrive late as "not known yet" rather than holding the row back on every poll. - Read for an agent's checkout, the PR is attributed using the agent's start time, so a reused branch does not show a predecessor's merged PR as the agent's own. diff --git a/packages/the-framework/src/dashboard/interventions.SPEC.md b/packages/the-framework/src/dashboard/interventions.SPEC.md index c649effab..3c2b9cdb7 100644 --- a/packages/the-framework/src/dashboard/interventions.SPEC.md +++ b/packages/the-framework/src/dashboard/interventions.SPEC.md @@ -1,6 +1,6 @@ The cross-project "needs you" queue: everything currently waiting on the human, gathered from every registered project. -## TLDR +## Flows - Three kinds of item: an open pull request to review, an agent parked on a question, and a finished agent whose commits were never pushed. - Hand-opened draft PRs stay off the queue (they are not asking for review yet); an agent's own draft stays on it — the automatic handoff opens drafts precisely so reviewers are not pinged, and the queue is then the only place the work is visible at all. diff --git a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md index ac8dd5fac..145f8720e 100644 --- a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md +++ b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md @@ -1,6 +1,6 @@ The notification engine: a background poll over the registered projects that announces only what newly appeared, so a Discord message fires even when no dashboard is open. -## TLDR +## Flows - The first look only takes a baseline — whatever already existed when the daemon started is never announced; you only hear about what happens while it watches. - What makes two items "the same" is the caller's decision, so one engine serves both the needs-you queue and the activity feed. diff --git a/packages/the-framework/src/dashboard/open-in-app.SPEC.md b/packages/the-framework/src/dashboard/open-in-app.SPEC.md index a98c16d63..0e0e1245a 100644 --- a/packages/the-framework/src/dashboard/open-in-app.SPEC.md +++ b/packages/the-framework/src/dashboard/open-in-app.SPEC.md @@ -1,6 +1,6 @@ Opens a project in the OS file manager or the user's editor, and detects which known editors are installed so the picker can offer them. -## TLDR +## Flows - Local machine only: the opened path is the project's own registered one, never something the browser sent, and a public host has no local checkout to open anyway. - The stored editor preference wins, then an environment override, then VS Code; a missing command comes back as a friendly failure, never a crash. diff --git a/packages/the-framework/src/dashboard/open-questions.SPEC.md b/packages/the-framework/src/dashboard/open-questions.SPEC.md index 5ae1c19eb..9ffd3ab71 100644 --- a/packages/the-framework/src/dashboard/open-questions.SPEC.md +++ b/packages/the-framework/src/dashboard/open-questions.SPEC.md @@ -1,6 +1,6 @@ Every agent's open question gathered into one hub, so a human can answer any parked agent from one place instead of hunting through their individual views. -## TLDR +## Flows - The full question — options, recommendation, whether several may be picked — is read back from each parked agent's own log, because its summary record only carries the title. - Longest-waiting first: the agent blocked on its human the longest is the one to unblock first. diff --git a/packages/the-framework/src/dashboard/overview.SPEC.md b/packages/the-framework/src/dashboard/overview.SPEC.md index 0dee88cf5..84b15132a 100644 --- a/packages/the-framework/src/dashboard/overview.SPEC.md +++ b/packages/the-framework/src/dashboard/overview.SPEC.md @@ -1,13 +1,17 @@ The cross-project glance: what the agent is working on right now, how much is queued, which projects and agents were recently active, and which tickets are hot. -## TLDR +## Flows - Active agents come from each project's live records — every concurrent one — most recently updated first; recent agents pool every project's history into one capped, newest-first rail. - Hot tickets sort into three lanes with strict precedence: being worked on (a live agent is implementing it — hard evidence — or it has a plan), sitting in the AI queue, or merely flagged high priority; everything else stays off the card, which is a shortlist, not the backlog. -- "High priority" follows the ticket format's 0-10 scale (7 and up) — reading it as the P-numbers convention once kept a whole urgent backlog off the card. +- "High priority" follows the ticket format's 0-10 scale (7 and up). - The cross-project tickets page keeps one list per project, present even when empty, so importing stays reachable there. - Forgiving throughout: an unreadable project contributes nothing. +## Rationales + +- The priority floor reads the ticket format's own 0-10 scale, never the P-numbers convention: P-numbers put the most urgent work at the low numbers, so applying them here would keep a whole backlog of high-numbered urgent tickets off the card. + ## 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/src/dashboard/projects.SPEC.md b/packages/the-framework/src/dashboard/projects.SPEC.md index 9c84cdc96..37d426add 100644 --- a/packages/the-framework/src/dashboard/projects.SPEC.md +++ b/packages/the-framework/src/dashboard/projects.SPEC.md @@ -1,6 +1,6 @@ Turns the registered-project list into what the dashboard shows per project: its name, whether it is still set up, when it was last active, and the agent defaults its repo commits. -## TLDR +## 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. - One provider, the real registry: with one dashboard host there is no per-agent scope to substitute and no public host to blank out. diff --git a/packages/the-framework/src/dashboard/queue.SPEC.md b/packages/the-framework/src/dashboard/queue.SPEC.md index 0e8fd315f..49f8dd70b 100644 --- a/packages/the-framework/src/dashboard/queue.SPEC.md +++ b/packages/the-framework/src/dashboard/queue.SPEC.md @@ -1,6 +1,6 @@ The cross-project view of the AI work queue: every project's open TODO entries rolled up in one place, most-loaded project first. -## TLDR +## Flows - What counts as an entry deliberately matches the sweep that drains the queue — otherwise the card could say "nothing queued" while the sweep works the same file. - Any list item is an open entry unless its checkbox is checked; link-style entries without a checkbox count as open too. diff --git a/packages/the-framework/src/dashboard/quota.SPEC.md b/packages/the-framework/src/dashboard/quota.SPEC.md index 8b6b15638..f9e9572b9 100644 --- a/packages/the-framework/src/dashboard/quota.SPEC.md +++ b/packages/the-framework/src/dashboard/quota.SPEC.md @@ -1,6 +1,6 @@ Feeds the dashboard's usage panel: the account's quota windows and where they stand against the spending boundary. -## TLDR +## Flows - The daemon polls for the dashboard's whole life, not just while an agent is up — the panel must answer while nothing is running. - A failed reading never blanks the panel: the last good reading is kept and marked stale, and "no reading at all" is reported as such rather than as zero usage. diff --git a/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md b/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md index b460bee83..00354dcf2 100644 --- a/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md +++ b/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md @@ -1,6 +1,6 @@ The device side of running an agent on another machine: the few endpoints a daemon exposes so a trusted peer daemon can start an agent here, watch its events, and make agent-scoped calls. -## TLDR +## Flows - Everything sits behind the shared-token guard, so a caller without the device's token is refused before reaching any of this. - A reachability ping answers even when the relay is not wired and starts nothing — it only proves "I'm here and your token works", which is what the device list's status dots poll. diff --git a/packages/the-framework/src/dashboard/remote-run.SPEC.md b/packages/the-framework/src/dashboard/remote-run.SPEC.md index 00e3020c0..01dfa35b0 100644 --- a/packages/the-framework/src/dashboard/remote-run.SPEC.md +++ b/packages/the-framework/src/dashboard/remote-run.SPEC.md @@ -1,6 +1,6 @@ The local half of running an agent on a saved device: this daemon — never the browser — drives the remote daemon and streams the agent back so it looks and behaves like a local one. -## TLDR +## Flows - The device's token stays between the two daemons and only ever in memory; the browser watches the agent over its normal same-origin channel, so nothing crosses origins in the browser and the token never reaches a page. - Short health pings are how the device list's online/offline dots know what is reachable. diff --git a/packages/the-framework/src/dashboard/rpc-serve.SPEC.md b/packages/the-framework/src/dashboard/rpc-serve.SPEC.md index db7a0c142..2f029717a 100644 --- a/packages/the-framework/src/dashboard/rpc-serve.SPEC.md +++ b/packages/the-framework/src/dashboard/rpc-serve.SPEC.md @@ -1,13 +1,17 @@ Mounts the dashboard's RPC surface on the daemon's server and keeps other websites out of it. -## TLDR +## Flows - Two browser guards: calls from another website's page are refused (it must not start or steer agents on the user's machine), and so are requests whose named host betrays the DNS trick that makes a hostile page look same-origin. - The RPCs run inside the daemon's own process and reach its capabilities through wiring set once at start-up, which carries all of them — there is no second host to wire a different subset, and nothing about the wiring varies per caller. -- Calls are addressed by name over plain HTTP, and the live feed is Server-Sent Events. There used to be an RPC framework here: it needed a build-time transform over every RPC file, a registration table pinning each call to the client-baked path of the file it was re-exported from, and a shim per module to keep those paths stable. What it bought was type-safety across a package boundary that no longer exists. +- Calls are addressed by name over plain HTTP, and the live feed is Server-Sent Events. - The feed's response simply ending is how a viewer tells "the server is done" from "the connection dropped" — the first is a finished agent, the second is worth retrying and worth saying out loud. - A malformed request is answered, and so is one whose RPC throws: a failing call is a failing call, never a dead daemon. A name that is not an RPC is one of those answers, including the names every object carries whether anyone registered them or not. +## Rationales + +- Plain HTTP calls rather than an RPC framework: what a framework buys over them is type-safety across a package boundary, and the server and the dashboard app live in one package — there is no boundary to protect. + ## 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/src/dashboard/server.SPEC.md b/packages/the-framework/src/dashboard/server.SPEC.md index 7eb53d986..7697df499 100644 --- a/packages/the-framework/src/dashboard/server.SPEC.md +++ b/packages/the-framework/src/dashboard/server.SPEC.md @@ -1,6 +1,6 @@ The dashboard's front door: one small web server that serves the app, mounts its RPC surface, and routes the special channels — browser preview, device relay, cloud-session bridge. -## TLDR +## Flows - Bound to this machine only by default; on a reachable address one shared token guards every route, because a daemon that spawns processes on an open port is remote code execution. A valid token in a link becomes a cookie and leaves the URL after one hop. - The cloud-session bridge is the only route in front of that guard: it is meant to be called from another origin, so it authenticates with its own token instead. diff --git a/packages/the-framework/src/dashboard/tickets.SPEC.md b/packages/the-framework/src/dashboard/tickets.SPEC.md index a69783573..492d745ad 100644 --- a/packages/the-framework/src/dashboard/tickets.SPEC.md +++ b/packages/the-framework/src/dashboard/tickets.SPEC.md @@ -1,6 +1,6 @@ Reads a project's ticket backlog for the dashboard, so what the agent plans from is visible without opening the repo. -## TLDR +## Flows - A ticket is one file; its plan and its claim are sibling files folded into it, never rows of their own — "planned" means a plan exists, "locked" means an agent holds a claim (an unreadable claim still locks; the holder's name is only display sugar). - Deliberately tolerant of tickets predating the format: a missing heading, summary, or key falls back rather than dropping the ticket, and the plan's effort/uncertainty values ride along when named. From 84d8fbb842972b7e5f814641f0f5382f976f386f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:42:54 +0000 Subject: [PATCH 12/15] Migrate SPEC.md files to the new sdd.md template: dashboard components (D-P) TLDR sections become Flows with bullets byte-identical; design-reason bullets move to Rationales; history framings reworded or dropped after grounding against the components; stale "agent" vocabulary corrected to "driver". Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../dashboard/components/DevicesSettings.SPEC.md | 2 +- .../dashboard/components/DiscordDialogs.SPEC.md | 2 +- .../dashboard/components/DriverModelMenu.SPEC.md | 12 ++++++++---- .../components/DriverModelMenu.test.SPEC.md | 2 +- .../dashboard/components/ErrorBoundary.SPEC.md | 4 ++-- .../dashboard/components/EventList.SPEC.md | 2 +- .../dashboard/components/FilePreview.SPEC.md | 2 +- .../dashboard/components/FileTree.SPEC.md | 7 +++++-- .../dashboard/components/GitStatusBar.SPEC.md | 7 +++++-- .../dashboard/components/HotTickets.SPEC.md | 2 +- .../dashboard/components/InlineBrowser.SPEC.md | 2 +- .../dashboard/components/Markdown.SPEC.md | 2 +- .../dashboard/components/NotificationsMenu.SPEC.md | 2 +- .../dashboard/components/OnboardingChecklist.SPEC.md | 2 +- .../dashboard/components/OpenQuestions.SPEC.md | 2 +- .../dashboard/components/OptionsMenu.SPEC.md | 2 +- .../dashboard/components/PresetCreatePanel.SPEC.md | 2 +- .../dashboard/components/PresetsMenu.SPEC.md | 7 +++++-- .../dashboard/components/PresetsMenu.test.SPEC.md | 2 +- 19 files changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md b/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md index 2155906d8..7f68c1e29 100644 --- a/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md +++ b/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md @@ -1,6 +1,6 @@ The settings section for the saved devices — other machines running The Framework that an agent can run on — listed with live online/offline badges, plus add and remove. -## TLDR +## Flows - Unlike everything else in settings these are not preferences: a device carries its own access token, so the roster lives in this browser only and does not follow you to the next one — and the section says so. - Removing the device currently chosen as the target also clears that choice, so the next agent cannot point at a machine no longer on the list. diff --git a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md index 253205be6..c17f5dee9 100644 --- a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md +++ b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md @@ -1,6 +1,6 @@ The two Discord setup dialogs — the bot and notifications — that explain the integration, take its credential, and toggle the matching preference, so Discord is set up inside the product instead of by editing the daemon's environment and restarting it. -## TLDR +## Flows - A credential is write-only: it goes to the daemon and never comes back, so a stored one reads "saved" with Replace and Remove instead of a field holding a secret. - A credential set in the daemon's environment wins over a stored one, so that case is reported as fixed rather than offering an edit the daemon would ignore; a host that stores no credentials says so instead of offering a field. diff --git a/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md b/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md index 1c80fcd8b..b79d429f5 100644 --- a/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md @@ -1,10 +1,14 @@ One menu picking the driver and its model together — drivers at the top level, each opening a submenu of only its own models, so a pick sets both at once and an incompatible pair can never be chosen; the trigger wears the current driver's logo and, when one is pinned, its model. -## TLDR +## Flows -- Every listed model is a real model id. There is no "Default" entry: picking it stored nothing, so the menu's own answer to "which model is this" was "we do not know". -- Not choosing is still a state, and the trigger says so rather than naming the first model in the list — which is what it did while that entry existed, so an unset preference read as whichever model happened to be listed first. -- The trigger carries its own accessible name, because with no model pinned its rendered content is a logo and a chevron. It used to be named by the model text incidentally, and that text was "Default". +- Every listed model is a real model id; there is no "Default" entry. +- Not choosing is still a state, and the trigger says so rather than naming the first model in the list. +- The trigger carries its own accessible name, because with no model pinned its rendered content is a logo and a chevron. + +## Rationales + +- A "Default" entry would store nothing, so the menu could not answer "which model is this"; naming the first model in the list instead would present an unset preference as whichever model happens to be listed first — a model the agent does not actually pass. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/DriverModelMenu.test.SPEC.md b/packages/the-framework/dashboard/components/DriverModelMenu.test.SPEC.md index 70e1cf817..836d7d7dc 100644 --- a/packages/the-framework/dashboard/components/DriverModelMenu.test.SPEC.md +++ b/packages/the-framework/dashboard/components/DriverModelMenu.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the agent+model tree: the trigger shows the current agent's logo and model (spelled out on hover), picking a model inside an agent's submenu sets both together, and a submenu never lists another agent's models. +Covers the driver+model tree: the trigger shows the current driver's logo and model (spelled out on hover), picking a model inside a driver's submenu sets both together, and a submenu never lists another driver's models. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/ErrorBoundary.SPEC.md b/packages/the-framework/dashboard/components/ErrorBoundary.SPEC.md index 177509b2b..d7bc3c747 100644 --- a/packages/the-framework/dashboard/components/ErrorBoundary.SPEC.md +++ b/packages/the-framework/dashboard/components/ErrorBoundary.SPEC.md @@ -1,6 +1,6 @@ -The net under the whole dashboard: a crash while drawing any view shows a recoverable "Something went wrong" card instead of the blank white screen that used to take the entire app down. +The net under the whole dashboard: a crash while drawing any view shows a recoverable "Something went wrong" card instead of the blank white screen that would otherwise take the entire app down. -## TLDR +## Flows - The error is shown on the card and its details logged to the browser console — the one trace a data-driven "random" crash leaves, since the daemon never sees it. - "Try again" redraws the view, which recovers when the cause was transient; Reload sits beside it as the sure way out — and the agent and daemon keep running either way. diff --git a/packages/the-framework/dashboard/components/EventList.SPEC.md b/packages/the-framework/dashboard/components/EventList.SPEC.md index 9a40fd545..23dc51403 100644 --- a/packages/the-framework/dashboard/components/EventList.SPEC.md +++ b/packages/the-framework/dashboard/components/EventList.SPEC.md @@ -1,6 +1,6 @@ The agent's transcript, shared by the live stream and the replay of a finished one: every event as a human-readable line, with the conversation and the agent's interactive surfaces rendered as themselves. -## TLDR +## Flows - Your prompts read YOU (blue) and the agent's replies AGENT, both as Markdown; a long message collapses to its first line and expands in place, and the system prompt hides behind a character count. - The agent's first prompt is hoisted to the very top, so the log opens with what you asked rather than the machinery that preceded it; later turns stay where they happened. diff --git a/packages/the-framework/dashboard/components/FilePreview.SPEC.md b/packages/the-framework/dashboard/components/FilePreview.SPEC.md index ae2e67f60..ce943baf9 100644 --- a/packages/the-framework/dashboard/components/FilePreview.SPEC.md +++ b/packages/the-framework/dashboard/components/FilePreview.SPEC.md @@ -1,6 +1,6 @@ Hovering a file in the tree shows what is in it — a changed file's diff, an unchanged file's contents — read from the selected agent's worktree. -## TLDR +## Flows - Nothing is read until a card actually opens, so the tree's many hover targets cost nothing for files never pointed at. - An open card re-reads every few seconds, keeping up with an agent that is still editing instead of freezing at hover time. diff --git a/packages/the-framework/dashboard/components/FileTree.SPEC.md b/packages/the-framework/dashboard/components/FileTree.SPEC.md index 59c62277e..88dfd82fa 100644 --- a/packages/the-framework/dashboard/components/FileTree.SPEC.md +++ b/packages/the-framework/dashboard/components/FileTree.SPEC.md @@ -1,13 +1,16 @@ The project panel's file tree — a context picker, not an editor: clicking a file toggles it in the Context, the same set the other context pickers feed. -## TLDR +## Flows - Per-file git-status marks, read from the selected agent's worktree and refreshed as it edits, roll up to folders so dirty work is spottable even while a folder is closed. A file says which change it is; a folder only says that something under it changed. -- Folders are the browser's own disclosure element, so open/closed state and keyboard operation are not ours to implement. They used to be a vendored animation library — a copied component registry rather than a dependency, and exempted from type-checking — for an expand animation on a side panel. - A filter box narrows to matching files, and zero matches say so instead of rendering an empty pane that reads as broken. - Every file previews on hover — its diff when changed, its contents when not — with the tree's own status deciding which. - Localhost-only: with no checkout to list (the relay), the tree renders nothing. +## Rationales + +- Folders are the browser's own disclosure element, so open/closed state and keyboard operation are not ours to implement. + ## 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/GitStatusBar.SPEC.md b/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md index 7af374deb..e493e9714 100644 --- a/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md +++ b/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md @@ -1,13 +1,16 @@ The checkout-in-play status line — active branch, a clean/dirty dot, the linked PR — shared by the project home and the agent page, so the same facts cannot drift into two looks. -## TLDR +## Flows - On an agent it reports that agent's own worktree, adding what only a worktree has: its size on disk, and honesty that uncommitted changes there are the agent's, not yours. - Refreshed on a slow cadence, but sped up while a PR lookup is still settling, so that answer appears in seconds rather than after a full cycle; nothing renders when there is no checkout to report. - The agent's name leads and truncates last — it is the stable identity, where the branch gets renamed by the agent — and other facts drop out whole as the bar narrows rather than squeezing. -- Clean is deliberately neutral, not green: green means "changed/added" one pane away, and a clean tree has nothing to announce. - It can double as the disclosure for the branch detail below it, so an agent's branch is spoken about in exactly one place. +## Rationales + +- Clean is deliberately neutral, not green: green means "changed/added" one pane away, and a clean tree has nothing to announce. + ## 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/HotTickets.SPEC.md b/packages/the-framework/dashboard/components/HotTickets.SPEC.md index 239c75cb3..14d61df3a 100644 --- a/packages/the-framework/dashboard/components/HotTickets.SPEC.md +++ b/packages/the-framework/dashboard/components/HotTickets.SPEC.md @@ -1,6 +1,6 @@ The Overview's cross-project shortlist of the tickets that matter right now: what is being worked on, what sits in the AI Queue, and what is flagged high priority. -## TLDR +## Flows - Three lanes, every qualifying ticket listed in full — never "+N more" — and the empty state names the lanes rather than claiming the backlog is empty. - A ticket an agent is implementing right now says so — live work outranks the "planned" mark older work left behind — and its row opens that agent. diff --git a/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md b/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md index 7611f41cf..3a96ae662 100644 --- a/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md +++ b/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md @@ -1,6 +1,6 @@ The browser preview inline in the transcript — the same proxied screencast the rail's Browser tab shows, so the two surfaces can never disagree about what the browser is doing. -## TLDR +## Flows - When the agent ends under the reader, the pane degrades in place: the last frame stays with a "preview ended" note, and with no frame ever captured the row is just its one-line text — never a dead stream or a spinner. - The kept frame lives only in this viewer's memory, upholding the rule that frames never enter the log. diff --git a/packages/the-framework/dashboard/components/Markdown.SPEC.md b/packages/the-framework/dashboard/components/Markdown.SPEC.md index 04aecd2cc..2f58459e2 100644 --- a/packages/the-framework/dashboard/components/Markdown.SPEC.md +++ b/packages/the-framework/dashboard/components/Markdown.SPEC.md @@ -1,6 +1,6 @@ A tiny, dependency-free Markdown renderer for agent-written content: the surfaced PLAN/TODO docs, pushed views, and conversation messages. -## TLDR +## Flows - It builds display elements directly and never injects raw HTML, and links render only for web addresses — so agent-written text can never smuggle markup or scripts into the page. - It covers what that content actually uses (headings, bullet and task lists, code, bold/italic, links, pipe tables); anything else falls through as a plain paragraph, so nothing is ever dropped. diff --git a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md index c10e808d2..03230cf5a 100644 --- a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md @@ -1,6 +1,6 @@ The shell header's single notifications bell, making the model legible: where notifications are delivered, and which categories trigger them. -## TLDR +## Flows - The bell lights only when a method would actually deliver: browser needs its permission granted, Discord needs a configured credential on top of the toggle — a switch that delivers nothing must not read as "you will be paged". - "Human Queue" (an agent awaiting you, a PR to review) is the default-on baseline but a real toggle; "New activity" (an agent started or finished) is opt-in on top. diff --git a/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md b/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md index bcb1b826f..308404568 100644 --- a/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md +++ b/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md @@ -1,6 +1,6 @@ The onboarding checklist: what a new install needs, each step shown in the state it is actually in rather than as a list to read past. -## TLDR +## Flows - Every "done" derives from a real fact — a registered project, a filled AI queue, tickets on disk, a granted notification permission, saved Discord credentials — so a step cannot be ticked by clicking it, and one done outside the dashboard ticks itself. - Only adding a project and filling the AI queue are essential; the rest are marked optional because nothing breaks without them. diff --git a/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md b/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md index 176191806..4da558b2f 100644 --- a/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md +++ b/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md @@ -1,6 +1,6 @@ The launcher's questions hub: every agent's open question across all projects, answerable in one place, longest-waiting first. -## TLDR +## Flows - Each card names the agent asking and offers the way into it; an answer posts against that agent's own record. - An answered card collapses in place to a ✓ line that re-expands to show what was picked — it never vanishes under the cursor — and the memory is per-visit, so a reload starts clean. diff --git a/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md b/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md index fb0ecb7ce..7ff7f44ca 100644 --- a/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md @@ -1,6 +1,6 @@ The options gear: an agent's options as one checkbox dropdown that writes each preference straight through, topped by the single-choice "Run on" picker for where it executes. -## TLDR +## Flows - "Run on" is one flat list with exactly one checkmark: this machine, a fresh GitHub Actions runner, a hand-off to a Claude web cloud session, then the saved devices — each with a reachability dot and removable in place — and "Add a device". - Picking a device makes it the target where you are, no navigation; "This machine" while browsing a remote daemon means "go home" instead. diff --git a/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md b/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md index 36a17311d..bfb7ed92f 100644 --- a/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md +++ b/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md @@ -1,6 +1,6 @@ The "New preset" dialog: name and save a prompt as a reusable preset, prefilled with what is currently typed in the composer since the common path is saving what you just wrote. -## TLDR +## Flows - Saving needs both a name and a prompt. - With a project open you choose where the preset lives: private to you on every project, or committed into the project's repo and shared with the team; with no project open it is always private. diff --git a/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md b/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md index 3bcad4f84..9f020e18d 100644 --- a/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md @@ -1,11 +1,14 @@ The composer's presets button: the one visible place to load, create, and delete preset prompts — typing `/` in the editor stays as the fast path for those who know it. -## TLDR +## Flows - Three groups: the built-in presets, your own saved presets, and the open project's shared presets; saved presets load their prompt verbatim, and each saved row carries its own delete. -- Exists because loading used to hide behind typing `/` and deleting lived in a different menu, so a first-time user saw an empty box and no sign that presets exist. - Some built-ins always run as an agent of their own, even when loaded from inside one. +## Rationales + +- Typing `/` is invisible until known, so without a visible button a first-time user sees an empty box and no sign that presets exist. + ## 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/PresetsMenu.test.SPEC.md b/packages/the-framework/dashboard/components/PresetsMenu.test.SPEC.md index 8ffd73719..a6b0c131d 100644 --- a/packages/the-framework/dashboard/components/PresetsMenu.test.SPEC.md +++ b/packages/the-framework/dashboard/components/PresetsMenu.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the presets menu: built-ins load their rendered prompt, saved and project presets load verbatim, a row's delete deletes (via the right tier's handler) without loading, an empty project group is hidden, and "New preset…" appears only where a create panel exists. +Covers the presets menu: built-ins load their rendered prompt, saved and project presets load verbatim, a row's delete deletes (from its own group) without loading, an empty project group is hidden, and "New preset…" appears only where a create panel exists. ## Before modifying/creating SPEC.md files From a7d45cf0bbb0ec22b6e6944b4708d84b762fa830 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:44:57 +0000 Subject: [PATCH 13/15] Migrate SPEC.md files to the new sdd.md template: dashboard root, ui, prompt-editor, design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard root spec gains User Stories (traced to FEATURES-SPEC.md) and a Flows — TL;DR over its ten flows; TLDR sections elsewhere become Flows; tooling history reworded as present-tense rationales; implementation terms replaced with plain words. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/dashboard/App.SPEC.md | 6 ++-- packages/the-framework/dashboard/SPEC.md | 35 ++++++++++++++++--- .../components/prompt-editor/SPEC.md | 2 +- .../prompt-editor/SuggestionList.SPEC.md | 2 +- .../prompt-editor/suggestion.SPEC.md | 2 +- .../components/prompt-editor/tokens.SPEC.md | 2 +- .../dashboard/components/ui/SPEC.md | 2 +- .../the-framework/dashboard/design/SPEC.md | 2 +- .../dashboard/design/previews.SPEC.md | 6 +++- .../the-framework/dashboard/index.SPEC.md | 8 +++-- packages/the-framework/dashboard/main.SPEC.md | 9 +++-- .../dashboard/vite.config.SPEC.md | 12 ++++++- .../dashboard/vitest.config.SPEC.md | 6 +++- 13 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/the-framework/dashboard/App.SPEC.md b/packages/the-framework/dashboard/App.SPEC.md index 7fbf34834..4d9324c02 100644 --- a/packages/the-framework/dashboard/App.SPEC.md +++ b/packages/the-framework/dashboard/App.SPEC.md @@ -1,16 +1,16 @@ The entire dashboard is this one page: it reads the selection from the address, routes the main view accordingly, and owns everything the views share. -## TLDR +## Flows - The address is the selection — overview, project home, one agent, settings, tickets and ticket pages — so every view is a link to paste, reload, or bookmark. -- Owns what the views share: the agent list, project files, the cross-project needs-you queue, and the one live event stream the main view and right rail both read. +- The page owns what the views share: the agent list, 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. ## Rationales -- The selection used to be several pieces of state reconciled at render, and every disagreement was a bug; a route cannot disagree with itself. +- The address is the only selection state because a route cannot disagree with itself — selection kept as several pieces of state reconciled at render can disagree, and every disagreement is a bug. - "No such project" and "this agent is gone" appear only after the relevant list actually loaded, so a slow read never looks like a missing thing. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/SPEC.md b/packages/the-framework/dashboard/SPEC.md index f54129436..cbb6c597a 100644 --- a/packages/the-framework/dashboard/SPEC.md +++ b/packages/the-framework/dashboard/SPEC.md @@ -1,16 +1,37 @@ The dashboard UI: a browser app served by the daemon that renders everything the daemon knows and steers agents through it — holding no authoritative state of its own. -## TLDR +## User Stories + +- The user starts an agent from the composer — typing a prompt for an attended one, picking a preset for an unattended one — and is warned before spending when something would block the handoff. +- The user watches an agent as a live transcript, answers the questions it parks on right where they happened, chats with it, and stops or resumes it. +- The user reviews an agent's changed files and diffs and hands the work off — push, open a PR, merge. +- The user sees on one overview everything that needs them: the quota bar, every unanswered question across all projects, agents working now, the AI queue, routine work, and the hottest tickets. +- The user browses every project's tickets in one filterable list, shares the filtered view as a URL, and starts an agent straight from a ticket. +- The user follows an onboarding checklist whose steps tick themselves off real facts, not clicks. +- The user shares a watch link that shows one agent read-only. +- The user tunes every preference in settings, from appearance to automation and spend. + +## Flows — TL;DR + +- Every read is a projection of daemon state; every write is a call into the daemon. +- The URL is the selection, so every view is a link to paste, reload, or bookmark. +- A live agent streams its events; everything else polls; a finished agent reads from the archive. +- The dashboard is a plain client-side app behind one static page the daemon serves. +- A shared watch link renders one agent read-only. +- The overview leads with the quota bar, then everything that needs the user. +- The composer starts agents and is a live agent's control. +- The agent view is the transcript with every control inline. +- Tickets are the cross-project roadmap surface, filterable and shareable by URL. +- Settings gathers every preference on one page. + +## Flows - Every read is a projection of state the daemon assembles (agent logs, tickets, the queue, git and GitHub state, preferences); every write is a call into the daemon. The one exception is saved remote devices: their access tokens stay in this browser only, handed to the daemon per call. - 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. -- It is a plain client-side bundle: one static page the daemon serves for every address, and all the behaviour in the browser. There is no server rendering and no framework between the HTML and the app — plain Vite, and the calls are plain HTTP handlers. -- What it reads out of the rest of the package comes from that package's *source*, not its build output. Nearly all of it is type-only and reaches no bundle; the runtime part is the browser-safe barrel, compiled here like any other file. Pointing at the build instead made a fresh clone fail to type-check until something had run `tsc`, and type-checked the browser against the last build rather than the current source. +- 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. -## Flows - **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. @@ -21,6 +42,10 @@ The dashboard UI: a browser app served by the daemon that renders everything the **Settings** covers appearance, driver and model defaults, the options every new agent starts with, saved devices, notification channels, automation (the idle sweep and the spend slider), and the cloud-session bridge token. +## Rationales + +- What the dashboard reads out of the rest of the package comes from that package's *source*, not its build output; nearly all of it is type-only and reaches no bundle, and the little that is runtime code is compiled into the bundle like any other file. Reading the build instead would type-check the browser against the last build rather than the current source, and a fresh clone would not type-check until a build had run. + ## 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/prompt-editor/SPEC.md b/packages/the-framework/dashboard/components/prompt-editor/SPEC.md index 70ac0efa5..2f0798ae4 100644 --- a/packages/the-framework/dashboard/components/prompt-editor/SPEC.md +++ b/packages/the-framework/dashboard/components/prompt-editor/SPEC.md @@ -1,6 +1,6 @@ The machinery behind the composer's in-editor triggers and token chips: typing a trigger character opens a menu at the caret, and picked or typed tokens become pills that always submit as the exact plain text the agent parses. -## TLDR +## Flows - The composer wires four triggers with it: `/` for commands and presets, `<` for the agent's tags, `@` for project references, `#` for file references. - A trigger menu filters as you type, picks with Enter/Tab or a click, and closes on a non-match, so stray trigger characters in prose are never a trap. diff --git a/packages/the-framework/dashboard/components/prompt-editor/SuggestionList.SPEC.md b/packages/the-framework/dashboard/components/prompt-editor/SuggestionList.SPEC.md index 578be8c18..804776b1a 100644 --- a/packages/the-framework/dashboard/components/prompt-editor/SuggestionList.SPEC.md +++ b/packages/the-framework/dashboard/components/prompt-editor/SuggestionList.SPEC.md @@ -1,6 +1,6 @@ The floating menu the composer's triggers open: arrow keys move the highlight, Enter or Tab picks, the mouse works too, and items can sit under group headers. -## TLDR +## Flows - Focus never leaves the text being typed; the menu reads as a listbox to assistive tech, pointing at the highlighted option. - With no items it shows the trigger's explanatory note when one is given, and each item can carry a short hint and a hover explanation. diff --git a/packages/the-framework/dashboard/components/prompt-editor/suggestion.SPEC.md b/packages/the-framework/dashboard/components/prompt-editor/suggestion.SPEC.md index 924cc5a5e..aaebaeb56 100644 --- a/packages/the-framework/dashboard/components/prompt-editor/suggestion.SPEC.md +++ b/packages/the-framework/dashboard/components/prompt-editor/suggestion.SPEC.md @@ -1,6 +1,6 @@ Turns a trigger character typed in the composer into a floating menu at the caret: it filters as you keep typing, inserts the pick, and hides when nothing matches. -## TLDR +## Flows - Each trigger is built independently from its own character, item source, and insert action, so several menus coexist without clashing. - The menu tracks the caret through scrolling and resizing, flipping above it near the bottom of the screen. diff --git a/packages/the-framework/dashboard/components/prompt-editor/tokens.SPEC.md b/packages/the-framework/dashboard/components/prompt-editor/tokens.SPEC.md index 7ca63e509..dffeddd13 100644 --- a/packages/the-framework/dashboard/components/prompt-editor/tokens.SPEC.md +++ b/packages/the-framework/dashboard/components/prompt-editor/tokens.SPEC.md @@ -1,6 +1,6 @@ The composer's token chips: pills that read nicely in the editor but always serialize back to the exact plain text the agent parses — macros like ``, action calls like `showChoices()`, and project/file references. -## TLDR +## Flows - Holds the catalogs of insertable macros and agent-action calls, each with a one-line hint for the trigger menus. - Typing a complete token by hand turns it into a chip on the spot, and a known token's casing is normalized to the canonical form the agent expects. diff --git a/packages/the-framework/dashboard/components/ui/SPEC.md b/packages/the-framework/dashboard/components/ui/SPEC.md index 6cacb77bc..4fe18e1a4 100644 --- a/packages/the-framework/dashboard/components/ui/SPEC.md +++ b/packages/the-framework/dashboard/components/ui/SPEC.md @@ -1,6 +1,6 @@ The dashboard's shared kit of basic interface pieces — buttons, inputs, menus, dialogs, tooltips, scroll areas, the sidebar shell — hand-ported shadcn-style components rather than product logic, so every panel looks and behaves the same. -## TLDR +## Flows - Two dialog tiers on purpose: an irreversible action goes through a focus-trapped confirm dialog a stray click cannot dismiss, while ordinary forms and drawers close on Esc or a click outside. - Scrollbars are the app's own — thin, themed, visible only while content overflows — never the OS overlay bar that hides itself. diff --git a/packages/the-framework/dashboard/design/SPEC.md b/packages/the-framework/dashboard/design/SPEC.md index b66b792c6..fe6986707 100644 --- a/packages/the-framework/dashboard/design/SPEC.md +++ b/packages/the-framework/dashboard/design/SPEC.md @@ -1,6 +1,6 @@ The dashboard's design gallery: static pages that show the design foundations (colors, status vocabulary, type, radii) and the app's building blocks in both themes, for design review outside the running app. -## TLDR +## Flows - Cards render the shipped components themselves against the app's own compiled stylesheet, so what the gallery shows is what the dashboard ships — silent drift between gallery and app is exactly what this exists to catch. - The few surfaces that cannot render statically (popups that appear only at runtime) are hand-copied and visibly flagged as replicas. diff --git a/packages/the-framework/dashboard/design/previews.SPEC.md b/packages/the-framework/dashboard/design/previews.SPEC.md index 8b59e80ec..d22c40422 100644 --- a/packages/the-framework/dashboard/design/previews.SPEC.md +++ b/packages/the-framework/dashboard/design/previews.SPEC.md @@ -1,4 +1,8 @@ -The gallery's card registry — the design foundations (color tokens, status palette, type and radius scales) and the dashboard's building blocks — where every card renders the real shipped component so it cannot drift from the app, and the few that cannot (popups that render nowhere statically) are hand-copied and flagged as replicas on the card itself. A card is not a place to park a component the app stopped rendering: two of them sat here after the page that drew them was cut, and being on a card is what made them look alive. +The gallery's card registry — the design foundations (color tokens, status palette, type and radius scales) and the dashboard's building blocks — where every card renders the real shipped component so it cannot drift from the app, and the few that cannot (popups that render nowhere statically) are hand-copied and flagged as replicas on the card itself. + +## Rationales + +- A card is not a place to park a component the app stopped rendering — being on a card is what makes an orphaned component look alive. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/index.SPEC.md b/packages/the-framework/dashboard/index.SPEC.md index f17e9dd58..c5247007c 100644 --- a/packages/the-framework/dashboard/index.SPEC.md +++ b/packages/the-framework/dashboard/index.SPEC.md @@ -1,10 +1,14 @@ -The static shell the daemon serves for every address: the root element the app mounts into, the product's typefaces and tab icon, and the module script that boots it. It is a plain Vite entry — the framework that used to sit between this file and the app was doing nothing that a static page and a client-side router do not. +The static shell the daemon serves for every address: the root element the app mounts into, the product's typefaces and tab icon, and the module script that boots it. -## TLDR +## Flows - Served for every path, not just `/`: the app owns routing, so an unknown path is the shell plus a client-side decision, never a 404 from the daemon. - The tab icon carries its own dark-mode ramp inside the file, because a favicon sits on browser chrome — which follows the OS theme, not the in-app theme choice. +## Rationales + +- No framework sits between this page and the app: a static shell plus the app's own client-side routing is all the dashboard needs. + ## 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/main.SPEC.md b/packages/the-framework/dashboard/main.SPEC.md index a0a129b54..8f357c053 100644 --- a/packages/the-framework/dashboard/main.SPEC.md +++ b/packages/the-framework/dashboard/main.SPEC.md @@ -1,9 +1,12 @@ The dashboard's entry point: mount the app into the static shell's root element, inside the theme-and-error-boundary frame, with the global stylesheet. -## TLDR +## Flows -- Everything here is the browser's. The daemon serves one static `index.html` plus fingerprinted assets and answers RPCs; the app routes itself off the address. -- There is no framework between the HTML and the app. A prerendering meta-framework used to sit here, and its entire net contribution was emitting that one `index.html` — the rest was scaffolding for a prerender with a single page in it. +- Everything here is the browser's: the daemon serves the one static page plus its assets and answers the app's calls, and the app routes itself off the address. + +## Rationales + +- There is no framework between the static page and the app: with a single always-served page and client-side routing, a prerendering meta-framework's entire net contribution is emitting that one page — the rest is scaffolding for a prerender with one page in it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/vite.config.SPEC.md b/packages/the-framework/dashboard/vite.config.SPEC.md index 20115a716..85f286d33 100644 --- a/packages/the-framework/dashboard/vite.config.SPEC.md +++ b/packages/the-framework/dashboard/vite.config.SPEC.md @@ -1,4 +1,14 @@ -Dev-server and build setup for the dashboard: a plain Vite SPA, entered from the `index.html` beside it. Its root is pinned to this directory rather than inherited from the cwd, because the scripts that run it live in the package root one level up; the build writes the bundle straight into that package's `dist/`, where the daemon serves it from, which is what used to be a separate copy step. Its one behavior of note is an opt-in dev mode that brings up a real daemon and forwards the dashboard's calls to it, so starting agents works from the live-reload UI (the plain dev server can only read). That is also the only thing here that wants the package's build rather than its source: it runs in this file's own Node process, outside any transform, so it imports the built entry — typed against the source it is built from, so a signature change is still caught. +Dev-server and build setup for the dashboard: a plain Vite SPA, entered from the `index.html` beside it. + +## Flows + +- The build writes the bundle straight into the package's `dist/`, where the daemon serves it from. +- An opt-in dev mode brings up a real daemon and forwards the dashboard's calls to it, so starting agents works from the live-reload UI (the plain dev server can only read). + +## Rationales + +- The root is pinned to this directory rather than inherited from the cwd, because the scripts that run it live in the package root one level up. +- The dev mode is the only thing here that wants the package's build rather than its source: it runs in this file's own Node process, outside any transform, so it imports the built entry — typed against the source it is built from, so a signature change is still caught. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/vitest.config.SPEC.md b/packages/the-framework/dashboard/vitest.config.SPEC.md index 2ddb5d734..a9cd7e7b5 100644 --- a/packages/the-framework/dashboard/vitest.config.SPEC.md +++ b/packages/the-framework/dashboard/vitest.config.SPEC.md @@ -1,4 +1,8 @@ -Test-runner setup for the dashboard's unit tests: jsdom and the JSX transform, with the root pinned to this directory because the package's `test` script runs from one level up. It is the second of the package's two test runners — `node --test` covers the compiled `src/`, this covers the browser half. +Test-runner setup for the dashboard's unit tests: jsdom and the JSX transform, with the root pinned to this directory because the package's `test` script runs from one level up. + +## Rationales + +- The package runs two test suites: `node --test` covers the compiled `src/`, and this one covers the browser half. ## Before modifying/creating SPEC.md files From dfe65eadb50e058421345bec5ab70e48fd977de1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 19:47:10 +0000 Subject: [PATCH 14/15] Migrate SPEC.md files to the new sdd.md template: daemon dashboard reads (A-G), bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The directory spec gains User Stories (traced to FEATURES-SPEC.md) and a Flows — TL;DR over its nine flows; TLDR sections elsewhere become Flows; history sentences reworded as present-tense rationales; a dead PR-resolution mechanism trimmed from the handoff flow. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/dashboard/SPEC.md | 33 +++++++++++++++---- .../src/dashboard/activity.SPEC.md | 2 +- .../src/dashboard/agent-handoff.SPEC.md | 8 +++-- .../src/dashboard/bridge-endpoints.SPEC.md | 2 +- .../dashboard/bridge-endpoints.test.SPEC.md | 2 +- .../src/dashboard/bridge-store.SPEC.md | 8 +++-- .../src/dashboard/browser-proxy.SPEC.md | 2 +- .../the-framework/src/dashboard/cache.SPEC.md | 4 +-- .../src/dashboard/dashboard.SPEC.md | 7 ++-- .../the-framework/src/dashboard/docs.SPEC.md | 2 +- .../src/dashboard/file-diff.SPEC.md | 2 +- .../src/dashboard/file-read.SPEC.md | 2 +- .../the-framework/src/dashboard/gh.SPEC.md | 2 +- 13 files changed, 54 insertions(+), 22 deletions(-) diff --git a/packages/the-framework/src/dashboard/SPEC.md b/packages/the-framework/src/dashboard/SPEC.md index 463931562..f24ea0fba 100644 --- a/packages/the-framework/src/dashboard/SPEC.md +++ b/packages/the-framework/src/dashboard/SPEC.md @@ -1,14 +1,35 @@ The daemon's serving and projection layer: the HTTP host behind the dashboard, the read models it assembles from disk and git, the git handoff (push → PR → merge), and the notification watchers. -## TLDR - -- Everything the dashboard shows is assembled here from what is already on disk — agent logs, tickets, the queue, git state, GitHub state. Reads are forgiving: whatever fails yields an empty result, never a crash at the view. -- Serving is guarded by where the daemon is bound: on localhost, browser calls must come from the dashboard's own origin; on a reachable address, everything demands a shared token, because a daemon that spawns processes on an open port is remote code execution. -- Expensive questions (a GitHub lookup costs many times a git read) go through a cache that asks once for all concurrent callers, serves the last good answer while refreshing, and answers "pending" — not "failed" — when a cold ask exceeds its time budget, so a caller that must not act on a half-answer can hold off. A failure never overwrites the last good value. +## User Stories + +- The user reviews finished work as pull requests: an agent that produced real work pushes it and opens a PR by itself, and a merge waits for the agent's own ready signal. +- The user is notified on Discord when an agent starts or finishes, and when something needs a human — an open PR to review, a parked question, unpushed commits. +- The user answers every agent's parked question from one hub, across all projects. +- The user runs an agent on a saved remote device and watches and steers it from the local dashboard. +- The user answers a cloud agent's question from the dashboard, and it is typed back into claude.ai. +- The user watches an agent's live browser and clicks into it from the dashboard. + +## Flows — TL;DR + +- Everything shown is assembled from what is already on disk; a failed read yields an empty result. +- Localhost serving demands the dashboard's own origin; a reachable bind demands a shared token on every route. +- Slow GitHub questions go through a cache: one lookup for all concurrent callers, the last good answer while refreshing, "pending" over a half-answer. +- A cleanly settled agent's work is committed, pushed and opened as a PR — unless it is empty, which is never published. +- Configuration arms auto-merge; only the agent's ready signal authorizes it, and a withheld merge is recorded with its reason. +- Two Discord watchers post only what is genuinely new: activity, and what needs a human. +- A saved device's agents are started and streamed through the local daemon, never the browser. +- A browser extension posts parked claude.ai questions in; picked answers queue back out. +- The daemon relays an agent's live browser screencast and clicks, on a port from the agent's own record. ## Flows -**The handoff.** When an agent settles cleanly, decide whether it is empty (no commits, or only bookkeeping changes) — empty agents are never published. Otherwise commit what it left uncommitted, push the branch, open the PR. The PR number is then recorded on the agent, so every surface reads the same integer instead of re-deriving it from three candidate branch names filtered by a start time; its *state* is still read live, because that changes without the agent doing anything. A PR opened after the process is gone is recorded too, by patching the archive. +**The read models.** Everything the dashboard shows is assembled here from what is already on disk — agent logs, tickets, the queue, git state, GitHub state. Reads are forgiving: whatever fails yields an empty result, never a crash at the view. + +**Serving.** Serving is guarded by where the daemon is bound: on localhost, browser calls must come from the dashboard's own origin; on a reachable address, everything demands a shared token, because a daemon that spawns processes on an open port is remote code execution. + +**The cache.** Expensive questions (a GitHub lookup costs many times a git read) go through a cache that asks once for all concurrent callers, serves the last good answer while refreshing, and answers "pending" — not "failed" — when a cold ask exceeds its time budget, so a caller that must not act on a half-answer can hold off. A failure never overwrites the last good value. + +**The handoff.** When an agent settles cleanly, decide whether it is empty (no commits, or only bookkeeping changes) — empty agents are never published. Otherwise commit what it left uncommitted, push the branch, open the PR. The PR number is then recorded on the agent, so every surface reads the same integer instead of re-deriving it; its *state* is still read live, because that changes without the agent doing anything. A PR opened after the process is gone is recorded too, by patching the archive. **Merging.** Arming and authorizing are separate: configuration arms auto-merge, and only the agent's ready-for-merge signal plus an empty backlog of its own authorizes it — an armed-but-unauthorized merge is recorded as withheld, with the reason. On a repo without native auto-merge the PR is handed to the CI watch to merge once checks pass, and the merge outcome lands on the agent's record so every surface can say what happened. diff --git a/packages/the-framework/src/dashboard/activity.SPEC.md b/packages/the-framework/src/dashboard/activity.SPEC.md index 1ea990bbf..8523cb176 100644 --- a/packages/the-framework/src/dashboard/activity.SPEC.md +++ b/packages/the-framework/src/dashboard/activity.SPEC.md @@ -1,6 +1,6 @@ The activity feed: the cross-project stream of agent lifecycle moments — an agent started, an agent finished — that notify without needing the human. -## TLDR +## Flows - Each project's recent agents contribute one item apiece: "started" while it runs, "finished" once it ends, tagged by how it ended so a stop reads differently from a success. - A start and a finish are separate events, so an agent notifies at most twice — and one that starts and ends between two looks notifies once, as finished. diff --git a/packages/the-framework/src/dashboard/agent-handoff.SPEC.md b/packages/the-framework/src/dashboard/agent-handoff.SPEC.md index dab41c751..03461b2df 100644 --- a/packages/the-framework/src/dashboard/agent-handoff.SPEC.md +++ b/packages/the-framework/src/dashboard/agent-handoff.SPEC.md @@ -1,15 +1,19 @@ How a finished agent's work is handed back to the human: measure what its branch holds, push it, open a pull request, and decide whether it may merge. -## TLDR +## Flows - Branch-addressed: an agent reads the same whether or not its checkout still exists, and a locally-gone branch still reports its PR — a hands-off cloud agent only ever pushed remotely. - An agent that produced nothing — no commits, or only the framework's own bookkeeping — is said so and never published. - Push and a draft PR are armed by default; drafts keep the automatic path out of reviewers' inboxes, and uncommitted leftovers are swept into a commit first (guarded so only the agent's own checkout and branch are ever committed). -- The PR number is recorded on the agent the moment one is opened for it, so every surface reads the same integer instead of re-deriving it. It used to be re-resolved from three candidate branch names filtered by the agent's start time — a guess assembled at read time, standing in for one fact nobody had written down. Its *state* is still read live, because that changes without the agent doing anything. +- The PR number is recorded on the agent the moment one is opened for it, so every surface reads the same integer instead of re-deriving it. Its *state* is still read live, because that changes without the agent doing anything. - A pull request opened after the agent's process is gone is recorded too, by patching its archive: it is the same fact, and a surface should not have to know which of the two paths produced it. - The branch's own PR history is still consulted for a different question — does this branch already have one — because a branch name pinned by a prompt is reused across agents, so a reused branch never wears an old PR and a branch with one never gets a second. - Configuration arms an automatic merge; only the agent's declared-done signal plus an empty backlog of its own authorizes it, and a withheld merge still pushes and opens the draft for a human. +## Rationales + +- The PR number is written down rather than derived at read time: a lookup across candidate branch names filtered by the agent's start time is a guess standing in for one fact nobody wrote down. + ## 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/src/dashboard/bridge-endpoints.SPEC.md b/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md index aeac39a96..e29c04a8e 100644 --- a/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md @@ -1,6 +1,6 @@ The daemon's doorway for the browser extension in the user's own claude.ai tab: questions a cloud session is parked on (and what it said) come in, and answers picked in the dashboard go back out. -## TLDR +## Flows - The one surface meant to be reached from another origin, so every call must present a shared secret, checked before anything else is read. - Payloads are tiny and validated field by field — no paths, no commands, no free text — so a stolen secret buys at worst a bogus question card. diff --git a/packages/the-framework/src/dashboard/bridge-endpoints.test.SPEC.md b/packages/the-framework/src/dashboard/bridge-endpoints.test.SPEC.md index ee2cb81fd..47a141f9e 100644 --- a/packages/the-framework/src/dashboard/bridge-endpoints.test.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-endpoints.test.SPEC.md @@ -1,4 +1,4 @@ -Covers the bridge doorway: a valid question is recorded and timestamped by the daemon (never the caller), missing or wrong secrets and malformed, oversized, or off-shape payloads are refused with nothing recorded, no cross-origin access is offered, queued answers are served and their delivery acknowledgements accepted, and everything answers "not found" when the bridge is off. A version-skewed or version-silent extension is refused on every route with both versions named while the matching one passes, the gate sits behind the token so an unauthenticated caller learns nothing, and the expected version stays in lockstep with the extension's manifest. +Covers the bridge doorway: a valid question is recorded and timestamped by the daemon (never the caller), missing or wrong secrets and malformed, oversized, or off-shape payloads are refused with nothing recorded, no cross-origin access is offered, queued answers are served and their delivery acknowledgements accepted, and everything answers "not found" when the bridge is off; a version-skewed or version-silent extension is refused on every route with both versions named while the matching one passes, the gate sits behind the token so an unauthenticated caller learns nothing, and the expected version stays in lockstep with the extension's manifest. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/bridge-store.SPEC.md b/packages/the-framework/src/dashboard/bridge-store.SPEC.md index 48a8665bf..63a76d1e5 100644 --- a/packages/the-framework/src/dashboard/bridge-store.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-store.SPEC.md @@ -1,12 +1,16 @@ The daemon's memory of the cloud-session bridge: each session's parked question and transcript, and the dashboard-picked answer on its way back. -## TLDR +## Flows - One parked question per session, replaced when the session moves on; a new question also discards any undelivered answer to the old one, so a stale pick is never typed into a fresh question. - An answer must be a label the question itself offered; once delivered it resolves the question, and re-reports of that question are ignored so it cannot resurface right after being answered. A failed delivery keeps the question for a retry. - Acknowledgements name the answer they are about, so a stale one from a dead tab cannot resolve a newer answer. - Transcript entries are kept one per position, bounded — the page is re-read constantly, so the same message arrives many times. -- All in memory on purpose: a question is only answerable while its session is parked. Even refused contacts are remembered, telling a misconfigured extension apart from an absent one. +- Even refused contacts are remembered, telling a misconfigured extension apart from an absent one. + +## Rationales + +- The store is all in memory on purpose: a question is only answerable while its session is parked. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/browser-proxy.SPEC.md b/packages/the-framework/src/dashboard/browser-proxy.SPEC.md index 06e159bf0..582970258 100644 --- a/packages/the-framework/src/dashboard/browser-proxy.SPEC.md +++ b/packages/the-framework/src/dashboard/browser-proxy.SPEC.md @@ -1,6 +1,6 @@ Lets the dashboard watch and steer an agent's live browser by relaying the screen stream and the clicks through the daemon. -## TLDR +## Flows - The dashboard cannot reach an agent's browser directly (wrong origin), so the pane talks to the daemon and the daemon talks to the agent's own preview channel on the same machine. - The client never names the port: it comes from the agent's own live record, so the relay cannot be pointed at anything else on the machine, and a finished agent's port is never reused. diff --git a/packages/the-framework/src/dashboard/cache.SPEC.md b/packages/the-framework/src/dashboard/cache.SPEC.md index 6dc04ee44..e543e50c1 100644 --- a/packages/the-framework/src/dashboard/cache.SPEC.md +++ b/packages/the-framework/src/dashboard/cache.SPEC.md @@ -1,6 +1,6 @@ -A read-through cache for the dashboard's slow questions — mostly GitHub ones, which cost hundreds of times a local git read — so a polling page never buys the same answer twice. +A read-through cache for the dashboard's slow questions — mostly GitHub ones, which cost many times a local git read — so a polling page never buys the same answer twice. -## TLDR +## Flows - Concurrent asks for the same thing share one lookup. - A known answer is served instantly and refreshed behind the scenes once it ages; a failed refresh keeps the last good answer rather than dropping it. diff --git a/packages/the-framework/src/dashboard/dashboard.SPEC.md b/packages/the-framework/src/dashboard/dashboard.SPEC.md index a30d91fa9..74b6c0730 100644 --- a/packages/the-framework/src/dashboard/dashboard.SPEC.md +++ b/packages/the-framework/src/dashboard/dashboard.SPEC.md @@ -1,11 +1,14 @@ Assembles the Overview page: the cross-project, at-a-glance rollup of what is running, what recently happened, and how much work is waiting. -## TLDR +## Flows - What a reader actually asks of it, and nothing else: how many projects there are and how much work is waiting, which agents are going right now, the per-project queue, and which projects have tickets — ordered most-recently-active first, because the onboarding checklist acts on the head of that list. -- It carried more for a release after the surfaces that drew them were cut: a status rollup, a two-week activity window, a per-project agent count. Each of those needed every project's whole archive listed, so the page paid a fan-out over all of it on every poll to render nothing. What a payload costs is a reason to keep it honest about who reads it. - A pure projection of what is already on disk; a project whose records cannot be read simply contributes nothing. +## Rationales + +- The payload is pinned to what its readers ask for: a rollup over past agents costs a fan-out over every project's whole archive on every poll, and what a payload costs is a reason to keep it honest about who reads it. + ## 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/src/dashboard/docs.SPEC.md b/packages/the-framework/src/dashboard/docs.SPEC.md index 5aeaf87b3..42340f0e2 100644 --- a/packages/the-framework/src/dashboard/docs.SPEC.md +++ b/packages/the-framework/src/dashboard/docs.SPEC.md @@ -1,6 +1,6 @@ Surfaces the plan documents at a project's root and the backlog off the data branch, so the human can read them in the dashboard beside the agent. -## TLDR +## Flows - Plans come before backlogs, with each category's flat file first and its per-agent variants after; anything else at the root is ignored. - Filenames come from listing the directory and matching fixed patterns — never from the client — so nothing can escape the workspace. diff --git a/packages/the-framework/src/dashboard/file-diff.SPEC.md b/packages/the-framework/src/dashboard/file-diff.SPEC.md index 57d5ae142..8ffcf0ef9 100644 --- a/packages/the-framework/src/dashboard/file-diff.SPEC.md +++ b/packages/the-framework/src/dashboard/file-diff.SPEC.md @@ -1,6 +1,6 @@ Shows what actually changed in a checkout's files: one file's diff for the tree's hover card, and every changed file's line counts for the agent's Changes list. -## TLDR +## Flows - Tracked files diff against the last commit, so a change the agent already staged still shows; an untracked file, having nothing to diff against, renders as all-added from its contents. - A binary change says so instead of dumping bytes, and a long patch is cut for display and says it was cut. diff --git a/packages/the-framework/src/dashboard/file-read.SPEC.md b/packages/the-framework/src/dashboard/file-read.SPEC.md index 33eb77bf5..32e76b2b4 100644 --- a/packages/the-framework/src/dashboard/file-read.SPEC.md +++ b/packages/the-framework/src/dashboard/file-read.SPEC.md @@ -1,6 +1,6 @@ The safe way the dashboard reads one file out of a checkout, used by the hover card to preview an unchanged file. -## TLDR +## Flows - One guard for every client-supplied path: repo-relative only, no traversal, no absolute paths, and never into git's own folder, where credentials live. - Confinement is real, not textual: links are resolved before checking the file sits inside the checkout, so a link pointing outside is refused. diff --git a/packages/the-framework/src/dashboard/gh.SPEC.md b/packages/the-framework/src/dashboard/gh.SPEC.md index 35ed0616f..8fde825ca 100644 --- a/packages/the-framework/src/dashboard/gh.SPEC.md +++ b/packages/the-framework/src/dashboard/gh.SPEC.md @@ -1,6 +1,6 @@ Everything the dashboard asks or tells GitHub — pull request lookups, merging, CI status — in one place. -## TLDR +## Flows - Reads are quick, cached, and forgiving: an unreachable GitHub reads as nothing, never a failed page. - An agent's PR is picked from its branch's whole history — an open PR always counts, a closed one only if created after the agent began — so a reused branch name cannot wear a predecessor's merged PR. From b560f9d3c56d9fe212e9c5b8c83201d2d10ebde2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 22:16:12 +0000 Subject: [PATCH 15/15] Review round: drop user stories derived from the removed watch relay The shared watch link was removed in #1536; the migration's derived user stories on the dashboard and components specs promoted that stale flow into a headline promise, and the root spec overpromised "steering" cloud agents. The stories go, the root story now says follow-and-answer, and the stale flow sentences stay preserved for the queued relay-orphan cleanup. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- SPEC.md | 2 +- packages/the-framework/dashboard/SPEC.md | 2 -- packages/the-framework/dashboard/components/SPEC.md | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/SPEC.md b/SPEC.md index cad671706..9d06c5540 100644 --- a/SPEC.md +++ b/SPEC.md @@ -7,7 +7,7 @@ Autonomous AI programming: humans make the important decisions while The Framewo - The user reviews finished work as pull requests: an agent that produced real work pushes it and opens a PR by itself. - The user walks away and the product keeps working: it drains the confirmed queue, refills it by triaging and planning tickets, fixes red CI on its own PRs, and merges them on green. - The user never budgets: unattended work spends only the share of the subscription week that has already elapsed, and work the user asks for is never starved. -- The user runs agents on another machine, a GitHub Actions runner, or a Claude cloud session, and steers them from the same dashboard — a browser extension bridges claude.ai cloud sessions back to it. +- The user runs agents on another machine, a GitHub Actions runner, or a Claude cloud session, follows them from the same dashboard, and answers the questions they park on — a browser extension bridges claude.ai cloud sessions back to it. - The user is notified — browser or Discord — whenever an agent needs a human. ## Flows diff --git a/packages/the-framework/dashboard/SPEC.md b/packages/the-framework/dashboard/SPEC.md index cbb6c597a..504e72cbe 100644 --- a/packages/the-framework/dashboard/SPEC.md +++ b/packages/the-framework/dashboard/SPEC.md @@ -8,7 +8,6 @@ The dashboard UI: a browser app served by the daemon that renders everything the - The user sees on one overview everything that needs them: the quota bar, every unanswered question across all projects, agents working now, the AI queue, routine work, and the hottest tickets. - The user browses every project's tickets in one filterable list, shares the filtered view as a URL, and starts an agent straight from a ticket. - The user follows an onboarding checklist whose steps tick themselves off real facts, not clicks. -- The user shares a watch link that shows one agent read-only. - The user tunes every preference in settings, from appearance to automation and spend. ## Flows — TL;DR @@ -17,7 +16,6 @@ The dashboard UI: a browser app served by the daemon that renders everything the - The URL is the selection, so every view is a link to paste, reload, or bookmark. - A live agent streams its events; everything else polls; a finished agent reads from the archive. - The dashboard is a plain client-side app behind one static page the daemon serves. -- A shared watch link renders one agent read-only. - The overview leads with the quota bar, then everything that needs the user. - The composer starts agents and is a live agent's control. - The agent view is the transcript with every control inline. diff --git a/packages/the-framework/dashboard/components/SPEC.md b/packages/the-framework/dashboard/components/SPEC.md index caf7a2144..e0670b203 100644 --- a/packages/the-framework/dashboard/components/SPEC.md +++ b/packages/the-framework/dashboard/components/SPEC.md @@ -7,7 +7,6 @@ The dashboard's React component catalog: every page, panel and control the brows - The user scans the Overview board for quota pace, running agents, queued work, routine jobs and hot tickets, and answers any agent's open question from there. - The user filters, sorts and groups every project's tickets on one page, opens a ticket's detail and plan, and queues or starts work from a row. - The user changes every preference — appearance, driver and model, where agents execute, agent options, notifications, automation — on one settings page. -- A teammate opens a shared link and watches one agent's live feed read-only. ## Flows