From 5fd0a0fb4b1e04a118792ad82adac5a95cebab03 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:37:45 +0000 Subject: [PATCH 1/3] A failed first poll no longer floods the notification watchers The watcher's baseline must come from a successful read: a failed scan or projection used to be caught into an empty result that still warmed the tracker up, so the next good poll announced every pre-existing item as new. A failed cycle now counts for nothing, with a regression test pinning the backlog's silence. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../src/dashboard/keyed-watcher.SPEC.md | 2 +- .../src/dashboard/keyed-watcher.test.ts | 25 +++++++++++++++++++ .../src/dashboard/keyed-watcher.ts | 14 ++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md index 6ff6e14b6..abe4b6835 100644 --- a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md +++ b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md @@ -9,7 +9,7 @@ The notification engine: a background poll over the registered projects that ann - The first look only takes a baseline — whatever already existed when the daemon started is never announced; the user only hears about what happens while it watches. - What makes two items "the same" is the caller's decision, so one engine serves both callers: the "needs you" queue (open PRs, parked questions, unpushed work) and the activity feed (agents started and finished). -- Forgiving: a failed scan or projection simply announces nothing that cycle. +- Forgiving: a failed scan or projection simply announces nothing that cycle, and never counts as the baseline — the first *successful* look is the one that seeds it. - It owns no timer of its own — the daemon's one clock calls it — so its cadence is declared where every other background job's is. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/keyed-watcher.test.ts b/packages/the-framework/src/dashboard/keyed-watcher.test.ts index e080837b6..06e0fe20e 100644 --- a/packages/the-framework/src/dashboard/keyed-watcher.test.ts +++ b/packages/the-framework/src/dashboard/keyed-watcher.test.ts @@ -77,3 +77,28 @@ test('startKeyedWatcher yields no new items when the scan or the projection fail watcher.stop() } }) + +test('a failed first poll does not seed the baseline, so the backlog is not announced as new', async () => { + const projects = async (): Promise => [{ id: 'a', path: '/a', name: 'a', activated: true }] + let failing = true + const announced: number[][] = [] + const watcher = startKeyedWatcher({ + projects, + build: async (): Promise => { + if (failing) throw new Error('projection failed') + return [pr(1, 'u1')] + }, + keyOf: interventionKey, + onNew: items => void announced.push(items.map(i => i.number!)), + }) + try { + await watcher.poll() // fails — must NOT count as the baseline + failing = false + await watcher.poll() // first real read: this is the baseline, pre-existing pr(1) stays silent + assert.deepEqual(announced, []) + await watcher.poll() // and it stays silent on the next poll too — seen, not new + assert.deepEqual(announced, []) + } finally { + watcher.stop() + } +}) diff --git a/packages/the-framework/src/dashboard/keyed-watcher.ts b/packages/the-framework/src/dashboard/keyed-watcher.ts index 2e3bb4a4c..b3f34b09f 100644 --- a/packages/the-framework/src/dashboard/keyed-watcher.ts +++ b/packages/the-framework/src/dashboard/keyed-watcher.ts @@ -50,8 +50,10 @@ export interface KeyedWatcherOptions { } /** - * Watch a projection and hand each poll's new items to `onNew`. The first poll only seeds the - * baseline. Forgiving — a failed project scan or projection just yields no new items that cycle. + * Watch a projection and hand each poll's new items to `onNew`. The first successful poll only + * seeds the baseline. Forgiving — a failed project scan or projection just yields no new items + * that cycle, and never counts as a poll: the baseline must come from a real read, or a failed + * first poll would make the next good one announce everything pre-existing as new. * * Owns no timer (E4): the daemon's one clock calls {@link KeyedWatcher.poll}, so the cadence is * declared where every other background job's is. @@ -65,8 +67,12 @@ export function startKeyedWatcher(opts: KeyedWatcherOptions): KeyedWatcher if (stopped || running) return running = true try { - const projects = await opts.projects().catch(() => []) - const items = await opts.build(projects).catch(() => []) + let items: T[] + try { + items = await opts.build(await opts.projects()) + } catch { + return + } const fresh = tracker.observe(items) if (fresh.length > 0 && !stopped) await opts.onNew(fresh) } finally { From 5b0974056ab70fb85cbc82e40bd713e2396e9e79 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:37:45 +0000 Subject: [PATCH 2/3] Five stale code comments catch up with the code they describe The auto-merge note describes merge-on-green (not the pre-#1418 immediate merge), Discord holds one credential and one dialog, the notifications bell lives in the sidebar's utility footer, and the on-before-mergeable prompt queues into the project's queue file. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../dashboard/components/DiscordDialogs.tsx | 12 ++++++------ .../dashboard/components/NotificationsMenu.tsx | 12 ++++++------ packages/the-framework/src/dashboard-rpc/projects.ts | 7 ++++--- packages/the-framework/src/discord-credentials.ts | 6 +++--- .../the-framework/src/on-before-mergeable-prompt.ts | 4 ++-- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/the-framework/dashboard/components/DiscordDialogs.tsx b/packages/the-framework/dashboard/components/DiscordDialogs.tsx index aca0358d7..f177aa467 100644 --- a/packages/the-framework/dashboard/components/DiscordDialogs.tsx +++ b/packages/the-framework/dashboard/components/DiscordDialogs.tsx @@ -5,7 +5,7 @@ import { Dialog } from './ui/dialog.js' import { Button } from './ui/button.js' import { usePreferences, updatePreferences, discordEnabled } from '../lib/preferences.js' -// The two Discord setup dialogs (#958, credentials in #1095). +// The Discord setup dialog (#958, credentials in #1095). // // #958 shipped these as explainers: they described what to set and told you to edit the daemon's // environment and restart it, which is what made Discord the one onboarding step you could not @@ -23,7 +23,7 @@ import { usePreferences, updatePreferences, discordEnabled } from '../lib/prefer export const DISCORD_WEBHOOK_DESCRIPTION = 'Delivers notifications to Discord, so an agent waiting on you reaches you with no dashboard open.' -/** What both dialogs take from their host: what the daemon holds, and a reload for after a save. */ +/** What the dialog takes from its host: what the daemon holds, and a reload for after a save. */ interface DialogProps { open: boolean onOpenChange: (open: boolean) => void @@ -70,10 +70,10 @@ interface ToggleSpec { } /** - * The shell both dialogs are: explain, take the credential, toggle the preference. One component - * rather than two near-copies, because what differs between them is a credential name and its - * words — while everything that could drift (what "configured" means, what an env-set credential - * does to the form, how a save is reported) is behaviour they have to share. + * The dialog's shell: explain, take the credential, toggle the preference. Kept apart from the + * wiring above so the behaviour that must not drift (what "configured" means, what an env-set + * credential does to the form, how a save is reported) lives in one place, whatever credential + * a dialog is for. */ function CredentialDialog({ open, diff --git a/packages/the-framework/dashboard/components/NotificationsMenu.tsx b/packages/the-framework/dashboard/components/NotificationsMenu.tsx index e9c25e4fb..ecf1e0abd 100644 --- a/packages/the-framework/dashboard/components/NotificationsMenu.tsx +++ b/packages/the-framework/dashboard/components/NotificationsMenu.tsx @@ -15,12 +15,12 @@ import { DropdownMenuSeparator, } from './ui/dropdown-menu.js' -// One "Notifications" bell in the shell header (#676), replacing the three loose icons (bell / -// Discord / activity). It makes the model legible: the bell and Discord are *delivery methods* -// (where a notification goes), "New activity" is a *category* on top of the always-on "needs you" -// pings. The trigger shows an active state + dot when a method is effectively on; the popover -// groups and labels every toggle. The underlying prefs and hooks are unchanged — this is purely -// the header control that writes them. The Discord *bot* (#680) sits in its own "Chat" group +// One "Notifications" bell in the sidebar's utility footer (#676), replacing the three loose +// icons (bell / Discord / activity). It makes the model legible: the bell and Discord are +// *delivery methods* (where a notification goes), "New activity" is a *category* on top of the +// always-on "needs you" pings. The trigger shows an active state + dot when a method is +// effectively on; the popover groups and labels every toggle. The underlying prefs and hooks are +// unchanged — this is purely the control that writes them. The Discord *bot* (#680) sits in its own "Chat" group // rather than under a delivery method: it is the one control here that takes messages in. export function NotificationsMenu() { diff --git a/packages/the-framework/src/dashboard-rpc/projects.ts b/packages/the-framework/src/dashboard-rpc/projects.ts index ba574b022..0c2c3566a 100644 --- a/packages/the-framework/src/dashboard-rpc/projects.ts +++ b/packages/the-framework/src/dashboard-rpc/projects.ts @@ -50,9 +50,10 @@ export async function onOnboarding(): Promise { } /** - * Whether this project's repo allows GitHub auto-merge (#1417): the launcher warns when the merge - * rung is armed on a repo that does not, because the armed merge silently degrades to an immediate - * direct merge (#1216) — the PR lands before CI has run (#1406). Read-only and cached (#1028). + * Whether this project's repo allows GitHub auto-merge (#1417): the launcher notes when the merge + * rung is armed on a repo that does not, because the merge is then handled by the daemon's CI + * watch (merge on green, #1216/#1418) — sound, but only while the daemon runs, unlike GitHub's + * server-side auto-merge. Read-only and cached (#1028). * `null` when the project is unknown here; `known: false` when `gh` could not say (not installed, * not a GitHub repo), which renders nothing rather than crying wolf — the no-crying-wolf stance (#1318). */ diff --git a/packages/the-framework/src/discord-credentials.ts b/packages/the-framework/src/discord-credentials.ts index 8319a56fd..34d0542c6 100644 --- a/packages/the-framework/src/discord-credentials.ts +++ b/packages/the-framework/src/discord-credentials.ts @@ -1,11 +1,11 @@ import type { RegistrySecrets } from './registry.js' /** - * Where the daemon's two Discord credentials come from (#1095). + * Where the daemon's Discord credential — the notifications webhook — comes from (#1095). * - * They used to be environment variables and nothing else, which made "enable Discord" the one + * It used to be an environment variable and nothing else, which made "enable Discord" the one * onboarding step you could not finish from the dashboard: you had to edit the daemon's - * environment and restart it. They are now also settable from the UI, stored in the registry file + * environment and restart it. It is now also settable from the UI, stored in the registry file * beside the daemon token, and picked up without a restart. * * The values only ever move daemon-side. This module is the rules — resolution, precedence, diff --git a/packages/the-framework/src/on-before-mergeable-prompt.ts b/packages/the-framework/src/on-before-mergeable-prompt.ts index fb23000d3..922e58092 100644 --- a/packages/the-framework/src/on-before-mergeable-prompt.ts +++ b/packages/the-framework/src/on-before-mergeable-prompt.ts @@ -6,8 +6,8 @@ import { presetContext } from './presets.js' * The on-before-mergeable prompt (#326), in `prompts/on_before_mergeable_prompt.md` (#551). * * It does not *run* the quality presets, it *queues* them: one agent turn that appends - * "Apply with tf.params.what set to ..." entries to the session's TODO - * file, which the backlog loop (#323/#538) picks up later. That is the whole point of + * "Apply with tf.params.what set to ..." entries to the project's queue + * file (`TODO_AGENTS.md`), which a later drain picks up. That is the whole point of * #556 — the previous suite executed maintainability, readability and security-audit as * three child runs on the spot, which does not compose with the queue. * From ff391a83a04da0efd1a3ea42627e61b90d7a227f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:37:45 +0000 Subject: [PATCH 3/3] Close the style pass's remaining spec-vs-code findings The flagship spec catches up with #1582: the queue lives on the data branch, check-offs are the framework's, claims are ticket locks plus an in-memory pin, the refill rotation starts with updating tickets, reclaim keys on work-on-remote, archives land at close through the write cycle, the config file records the handoff rung and two switches, and the busy- project guard only refuses duplicates. The quota-stop claims leave the agent specs (spending is decided before a start), the launcher specs state the daemon-side merge-on-green truth, delete vs remove tell their real stories, Discord is one credential and one dialog, the preset list is complete with the data-branch convention covered, e2e isolation is per story file, and the auto-pm window's mechanics name the data branch. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/SPEC.md | 20 +++++++++---------- .../components/AgentActionBar.SPEC.md | 2 +- .../components/DiscordDialogs.SPEC.md | 4 ++-- .../components/NotificationsMenu.SPEC.md | 2 +- .../dashboard/components/SPEC.md | 2 +- .../components/StartAgentForm.SPEC.md | 2 +- .../dashboard/lib/agent-option-rows.SPEC.md | 2 +- packages/the-framework/prompts/SPEC.md | 3 ++- packages/the-framework/src/SPEC.md | 4 ++-- .../the-framework/src/agent-telemetry.SPEC.md | 3 +-- packages/the-framework/src/agent.SPEC.md | 1 - packages/the-framework/src/agent.test.SPEC.md | 2 +- packages/the-framework/src/auto-pm.SPEC.md | 4 ++-- .../src/dashboard-rpc/control.SPEC.md | 4 ++-- .../src/dashboard-rpc/projects.SPEC.md | 4 ++-- .../src/discord-credentials-store.SPEC.md | 2 +- packages/the-framework/src/e2e/SPEC.md | 2 +- .../the-framework/src/e2e/harness.SPEC.md | 2 +- 18 files changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/the-framework/SPEC.md b/packages/the-framework/SPEC.md index 50ebf31a2..46cd7c734 100644 --- a/packages/the-framework/SPEC.md +++ b/packages/the-framework/SPEC.md @@ -29,7 +29,7 @@ The product: turnkey AI orchestration that wraps a coding-agent CLI (Claude Code - Configuration arms auto-merge, the agent's own ready signal authorizes it, and the CI watch merges green PRs and puts one fix agent on red ones. - Unattended work stands down past the elapsed share of the account's quota week; work the user asked for is never starved. - One daemon per machine serves the dashboard from the files agents write, and agents can also run on a saved device, GitHub Actions, or a Claude cloud session — or drive a real browser the user can take over. -- What must outlive a process — agent history, tickets, the queue, the project log — lands in git. +- What must outlive a process — agent history, tickets, the queue — lands in git on the framework's data branch. - One path composes every agent's recorded system prompt, presets are one catalog, and two switches (vanilla, transparent) dial the wrapping down. ## Flows @@ -48,9 +48,9 @@ flowchart TD Handoff --> Teardown["Teardown & retention"] ``` -**Setup.** When the user activates a repo, any dirty state is committed first — so the activation commit is clean — then the `.the-framework/` state directory is created, the project log is seeded, and `.gitignore` is taught which parts stay untracked. The project is registered in a per-machine registry in the user's home, which also holds the user's dashboard preferences (project settings override user settings, only for keys a project may override) and the daemon's secrets. Before an agent spawns anything, a preflight probes the driver CLI it picked, so a missing prerequisite fails early and clearly; a guard the picked driver cannot honor is announced rather than silently ignored. +**Setup.** When the user activates a repo, any dirty state is committed first — so the activation commit is clean — then the `.the-framework/` state directory is created and `.gitignore` is taught which parts stay untracked. The project is registered in a per-machine registry in the user's home, which also holds the user's dashboard preferences (project settings override user settings, only for keys a project may override) and the daemon's secrets. Before an agent spawns anything, a preflight probes the driver CLI it picked, so a missing prerequisite fails early and clearly; a guard the picked driver cannot honor is announced rather than silently ignored. -**Start.** The user starts an agent from the dashboard composer, a routine's "Run now", a queue entry's play button, onboarding, or the CLI. The daemon resolves the project, allocates the workspace, guards against a busy project, and spawns the agent as a detached process. An agent aimed at a saved remote device is relayed to that device's daemon. +**Start.** The user starts an agent from the dashboard composer, a routine's "Run now", a queue entry's play button, onboarding, or the CLI. The daemon resolves the project, allocates the workspace, refuses only a duplicate agent on the same checkout — concurrent worktree agents are the normal case — and spawns the agent as a detached process. An agent aimed at a saved remote device is relayed to that device's daemon. **Workspace.** The user's checkout — uncommitted work included — is never touched: every agent gets its own git worktree on its own branch, so concurrent agents never fight. 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. @@ -58,23 +58,23 @@ flowchart TD **Gates and chat.** When the agent stops to ask, the user finds the question where it happened: the agent 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 (a file in its workspace the daemon appends to and the agent tails), and the agent continues from there. The one exception is an answer the agent marked as ending it — which is how declining a plan stops the work rather than building on a rejected one. The user 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 its main task is done, the agent drains its queue file one entry per turn — read, complete exactly one entry, check it off, repeat. The user is asked before each entry — a per-entry gate that an unattended agent answers itself. +**The agent's own backlog.** Once its main task is done, the agent works what is queued for it one entry per turn: the framework hands it the next entry, the agent completes exactly that one, and the framework — never the agent — checks it off. The user is asked before each entry — a per-entry gate that an unattended agent answers itself. **Settle and handoff.** The user receives finished work as a pull request: the handoff runs only on the success path — pending work is committed, the branch pushed, and a PR opened. The handoff first decides whether the agent is *empty* — no commits, or only bookkeeping files changed — and empty agents are never published. 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. 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.** Whatever the user removes, the work survives: 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 roadmap the daemon works while the user is away lives in one file: the repo-root queue file (`TODO_AGENTS.md`), the durable, priority-ordered list of confirmed work. Agents write it directly; a *ticket*, by contrast, 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. +**Autonomy.** The roadmap the daemon works while the user is away lives in one file: the queue file (`TODO_AGENTS.md`), the durable, priority-ordered list of confirmed work, kept on the framework's own data branch. Agents write it directly; a *ticket*, by contrast, is a proposal for a human to accept. Every change to it — an agent's addition, a drain's check-off — goes through the data branch's serialized write cycle, so every machine and cloud session converges on the same queue. An entry tied to a ticket is claimed through the ticket's own lock file, which agents on other machines see; an entry without a ticket is pinned only in this daemon's memory while its agent runs — 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 updating tickets from GitHub → 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, 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.** When the user arms auto-merge, configuration only *arms* it; what *authorizes* the merge is the agent's own ready-for-merge signal plus an empty backlog of its own. 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. +**Merging and CI watch.** When the user arms auto-merge, configuration only *arms* it; what *authorizes* the merge is the agent's own ready-for-merge signal plus an empty backlog of its own. 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 is safe: checkouts whose work is already on the remote are reclaimed, and a pinned routine branch left behind by a closed PR is released so the routine can fire again. -**Spending limits.** The user never sets a budget, because the whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. There is nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line — but for work the user asked for, the slider only ever *loosens* the gate, and it is re-read live, so raising it unparks a waiting agent without a restart. The two gates fail in opposite directions on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise — by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part. +**Spending limits.** The user never sets a budget, because the whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. There is nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line for unattended work — by default it sits just past the boundary, and it is re-read live, so raising it takes effect without a restart; work the user asks for is never gated on quota at all. The two directions fail differently on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise — by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part. **Surfaces.** One daemon per machine serves the dashboard, and everything the user sees in it is read 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. When the user targets a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin. The device's token is saved only in the user's own browser and handed to the local daemon per call. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed). A browser extension inside the user's own claude.ai tab bridges 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 user 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, kept in git where the user can always find it: 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, kept in git on the framework's own data branch (`tf-data`) where the user can always find it: each agent's own event log, archived at close 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. Tickets — `tickets/_.md`, the human-facing roadmap, with optional plan and claim siblings, parsed tolerantly. And the queue file. Every one of these writes goes through the same serialized sync-apply-commit-push cycle, so machines converge instead of clobbering each other. -**Prompts and presets.** The user can read precisely what an agent ran under, because 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. 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. +**Prompts and presets.** The user can read precisely what an agent ran under, because 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. 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 (`the-framework.yml`) records the handoff rung and the two switches a project works under, resolved layer over layer. ## Rationales @@ -94,7 +94,7 @@ flowchart TD - **composer** — the dashboard's prompt box: type a prompt or pick a preset to start an agent, or send the next message to a live one. - **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`. +- **queue entry** — one item of confirmed work in the queue file `TODO_AGENTS.md`, kept on the framework's data branch. - **routine** — a recurring unattended job the daemon fires on its own — the queue drain, the ticket-refill rotation, the maintenance sweep; each can be switched off individually, and "Run now" fires one by hand. - **preset** — a cataloged prompt that starts an unattended agent (triage, research, security audit, …). - **empty agent** — an agent whose run left no commits (or only bookkeeping changes); it publishes nothing. diff --git a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md index 8a8d02f40..39e4059f2 100644 --- a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md @@ -3,7 +3,7 @@ One agent's action bar: what the agent is on the left — its branch, state, and ## Flows - Everything the user can do to the agent collapses into one overflow menu; only the end-of-work hand-off'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. +- The agent's state (exactly one of stopped, ready for merge, failed, building, publishing, finished) reads beside the branch facts instead of spending a banner row on one word. - The user sees one bar whether the agent is live or finished, and always one row: the label gives up width before the controls ever wrap. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md index dad91bb69..f3dc42b39 100644 --- a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md +++ b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md @@ -1,4 +1,4 @@ -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. +The Discord notifications setup dialog: it explains the integration, takes the webhook credential, and toggles the matching preference, so Discord is set up inside the product instead of by editing the daemon's environment and restarting it. ## Flows @@ -6,7 +6,7 @@ The two Discord setup dialogs — the bot and notifications — that explain the - A credential set in the daemon's environment wins over a stored one, so that case is reported as set-by-environment and not editable here, rather than offering an edit the daemon would ignore. A host that stores no credentials says so instead of offering a field. - Obviously-wrong input is refused before it is sent, and anything typed is wiped when the dialog closes so no secret lingers in a field. - The enable toggle is independent of the credential: it can be turned on first and starts working once the credential is set. -- Each dialog's one-line description is shared with the onboarding checklist row that opens it, so the two never tell different stories. +- The dialog's one-line description is shared with the onboarding checklist row that opens it, so the two never tell different stories. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md index c75a6987e..8c3bd7625 100644 --- a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md @@ -1,4 +1,4 @@ -The shell header's single notifications bell, making the model legible: where notifications are delivered, and which categories trigger them. +The single notifications bell in the sidebar's utility footer, making the model legible: where notifications are delivered, and which categories trigger them. ## Flows diff --git a/packages/the-framework/dashboard/components/SPEC.md b/packages/the-framework/dashboard/components/SPEC.md index da2a51563..4feab134f 100644 --- a/packages/the-framework/dashboard/components/SPEC.md +++ b/packages/the-framework/dashboard/components/SPEC.md @@ -10,7 +10,7 @@ The dashboard's React component catalog: every page, panel and control the brows ## Flows -- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, and not-found. +- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of the agent's files, agent-pushed views, its browser, and surfaced docs. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, and not-found. - The agent surface is a transcript with its controls inline: an action bar carrying the branch / PR / handoff and the one menu of agent actions, the event feed rendering its questions as answerable cards and its browser screencast in place, the changes and handoff panels, and one composer that starts, steers, stops and resumes — in a stable frame, so an ending never blanks what you are reading. - The Overview's widgets each show one slice of what the daemon knows: quota pace, agents working now, the Human Queue (what currently waits on a person — an agent's question, a PR to review), the AI queue (every project's open `TODO_AGENTS.md` items), routine work, hot tickets, activity and outcomes, and an onboarding checklist whose steps tick off real facts rather than clicks. - The launcher's controls — presets, driver/model and option menus, the Context selector, the system-prompt preview — read and write the same preferences and mappings the agent itself uses, so no surface can disagree with the agent it configures. diff --git a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md index 8455cad5f..340095050 100644 --- a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md +++ b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md @@ -4,7 +4,7 @@ The launcher form that starts an agent in the selected project: the shared compo - A typed prompt starts an attended conversation. A preset starts unattended routine work: it ends on its own once the agent's work settles, firing the hand-off it was armed with — how far the work publishes, up to push, PR, or merge. - 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 the start to that machine, the device's secret token riding in memory only — never persisted. -- Preflight warnings spend words before the agent is spent, and never block. They cover: a driver CLI that cannot start — the GitHub CLI checked only when a PR or merge is armed, and 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. +- Preflight warnings spend words before the agent is spent, and never block. They cover: a driver CLI that cannot start — the GitHub CLI checked only when a PR or merge is armed, and nothing probed for Actions or device targets; a repo whose disabled GitHub auto-merge means an armed merge is handled by the daemon's own merge-on-green, which works only while the daemon runs; and Haiku's known skipping of the finish step, which leaves a publishing run an unmerged draft PR. - A start answers immediately: an optimistic row for the run appears in the sidebar and the view jumps to the agent before its record exists. ## 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 f3262cb7d..329fb0d9f 100644 --- a/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md @@ -3,7 +3,7 @@ An agent's options as one table with every rule between them already applied, so ## 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. +- Transparent turns the framework's own wrapping off, so it disables the options that ride on it — the system-prompt switch, post-merge cleanup, and the browser — while the publish ladder stays the user's to set. - Publishing is a strict ladder — push branch, open PR, auto-merge — each rung alive only while the one below is on, which makes "publish nothing" expressible and the contradictory PR-without-push state unreachable. Auto-merge is the one rung off by default: publishing a branch is reversible, landing it is not. - The three publishing boxes store one rung between them: each box writes the rung it means, so unticking one lowers the whole ladder instead of leaving a merge armed over a pull request nobody asked for. - The browser option is offered only under Claude Code, the one agent it is wired to; every disabled row says why. diff --git a/packages/the-framework/prompts/SPEC.md b/packages/the-framework/prompts/SPEC.md index 7be1a6bd0..4d8159933 100644 --- a/packages/the-framework/prompts/SPEC.md +++ b/packages/the-framework/prompts/SPEC.md @@ -10,7 +10,8 @@ Every word the framework says to a coding agent, authored as markdown: the built - 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 protocols define the agent's side of the conversation. One teaches how to park on a gate — a question that blocks the agent until the user answers: a choice, a multi-select, a document approval, handing the browser to a human at a login wall — and how to mark the answers that end the agent rather than resume it, so a rejection is not something it is asked to build on. Another teaches the non-blocking signals: show a document, name the session, ready-for-merge. Per-capability protocols adapt the rest: an agent with a real browser is told when to use it, and a hands-off agent is told gates can never be answered, so it assumes the recommended option and carries 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 presets are the one-click task prompts behind the dashboard's buttons: research, market research, the quality reviews (readability, maintainability, security, UX), ticket triage and planning, updating tickets from GitHub, the three suggestion passes (new features, new tickets, which tickets to work on), the maintenance sweep, and draining the queue. +- One convention teaches how the framework's own records are read and written — the data branch (`tf-data`) — so an agent's bookkeeping never lands on a code branch. - The format docs teach the repo conventions: tickets as dated proposal files with plan and claim siblings (`.plan.md`, `.lock.md`), and the priority-ordered queue file (`TODO_AGENTS.md`) of confirmed work. - The before-mergeable prompt is the final quality turn: queue follow-up refactor and security passes when the changes warrant them, and fold what the agent learned into the project's knowledge base (`knowledge-base/*.md`). diff --git a/packages/the-framework/src/SPEC.md b/packages/the-framework/src/SPEC.md index c29bf9f53..8762039b0 100644 --- a/packages/the-framework/src/SPEC.md +++ b/packages/the-framework/src/SPEC.md @@ -1,4 +1,4 @@ -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. +The engine of The Framework: everything that turns an idea, a ticket, or a queue entry into a pull request for human review — the CLI, the per-machine daemon, the agent runtime that drives the wrapped coding-agent CLI, and the surfaces that watch and steer it. ## User Stories @@ -27,7 +27,7 @@ flowchart LR - The pull request the user gets back is the agent publishing itself: an agent that ends with real work commits, pushes, and opens a PR; an empty one publishes nothing. Merging is authorized by the agent's own ready signal plus an empty backlog of its own, never by configuration alone. How far an agent publishes is one ordinal — each rung including the ones below it — 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. +- 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 and the queue — all on the framework's own data branch. - 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-telemetry.SPEC.md b/packages/the-framework/src/agent-telemetry.SPEC.md index b7c1746cc..e83f9db5e 100644 --- a/packages/the-framework/src/agent-telemetry.SPEC.md +++ b/packages/the-framework/src/agent-telemetry.SPEC.md @@ -5,8 +5,7 @@ The accounting every agent shares, whatever kind it is: naming the session, foll - The handle for resuming the conversation survives a stop or crash mid-turn: the agent's real session id is surfaced the moment a turn starts, not only when it ends. - Each turn's spend is folded into a running total as the turn reports, so the dashboard's per-agent spend readout is live rather than final. - One self-stop exists: an answer that says to stop. It is composed with the stop signal from outside (the Stop button, Ctrl+C), 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. +- One shared classification of how an agent ended — a user stop or a real failure — so every surface agrees on what "stopped" means. ## Rationales diff --git a/packages/the-framework/src/agent.SPEC.md b/packages/the-framework/src/agent.SPEC.md index f4cfef75e..4a4fab00e 100644 --- a/packages/the-framework/src/agent.SPEC.md +++ b/packages/the-framework/src/agent.SPEC.md @@ -18,7 +18,6 @@ One agent: frame it, send it one prompt, honor the gates it answers with, work t - When a turn stops to ask, the user sees 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. - An answer the agent marked as ending it does exactly that, and cleanly: a declined plan reads as a stop rather than a failure, and the agent is never resumed with it. It stops through the same signal a Stop does, so a decline cannot read as a finished agent on one path and a stop on another. - Once the opening exchange settles, the user's own chat messages each continue the same conversation. An agent whose chat queue goes idle ends itself — unless its own surface is the only one there is, with no dashboard to resume through, in which case it stays parked for the next message. -- The budget and quota stops hold even when nothing runs after the opening turn. - 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. - When the user resumes a stopped agent, the message is sent verbatim into the same conversation — the old transcript already carries the framing — while the surrounding flow still runs. diff --git a/packages/the-framework/src/agent.test.SPEC.md b/packages/the-framework/src/agent.test.SPEC.md index d8b418eae..721fde637 100644 --- a/packages/the-framework/src/agent.test.SPEC.md +++ b/packages/the-framework/src/agent.test.SPEC.md @@ -1,4 +1,4 @@ -Tests the whole agent flow offline: the agent's question gates — one pick, several at once, and plan approval, which is that same gate with two options — the auto-accept fallback when nobody is there to ask, an answer the agent marked as ending it doing exactly that while the same label unmarked stays an ordinary answer, budget and quota stops with resume notes, session links and usage totals, the backlog and chat phases, greenfield versus existing-codebase framing and the once-only scaffold retry, a prompt agent running its text unframed and working no backlog, hand-off agents ending at the hand-off, and resuming a stopped agent verbatim. +Tests the whole agent flow offline: the agent's question gates — one pick, several at once, and plan approval, which is that same gate with two options — the auto-accept fallback when nobody is there to ask, an answer the agent marked as ending it doing exactly that while the same label unmarked stays an ordinary answer, session links and usage totals, the backlog and chat phases, greenfield versus existing-codebase framing and the once-only scaffold retry, a prompt agent running its text unframed and working no backlog, hand-off agents ending at the hand-off, and resuming a stopped agent verbatim. ## 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 79e946398..b5ef77a62 100644 --- a/packages/the-framework/src/auto-pm.SPEC.md +++ b/packages/the-framework/src/auto-pm.SPEC.md @@ -14,7 +14,7 @@ Auto PM spends leftover subscription quota on the product's own roadmap: while t - Draining and planning fan out, one pinned queue entry or locked ticket per agent, so concurrent agents do disjoint work; every other routine stays one per pass since concurrent copies would undo each other. - Both phases claim their ticket with a pushed lock file before the agent starts, so agents on other machines cannot double-book it: planning locks the ticket it will plan, and draining locks the ticket its queue entry links back to. An entry claimed elsewhere is dropped from the batch, and an entry with no ticket behind it keeps the queue itself as the coordination point. - 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. 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. +- The queue coordinates a ticketless entry only once its check-off is on the data branch, and that leaves a window: an agent handed off to a cloud session settles locally before its published work is adopted, so until the check-off lands 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 pass 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. @@ -23,7 +23,7 @@ Auto PM spends leftover subscription quota on the product's own roadmap: while t - 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 pass. - "Run now" skips only the master switch: the click is the consent the preference exists to record; every other stand-down holds. - A switched-off draining routine falls through to the rotation rather than standing the pass 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. +- 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 — for a race whose cost is a duplicated attempt, never lost work. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/control.SPEC.md b/packages/the-framework/src/dashboard-rpc/control.SPEC.md index 94cd52fc5..da51205e2 100644 --- a/packages/the-framework/src/dashboard-rpc/control.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/control.SPEC.md @@ -14,9 +14,9 @@ Every dashboard action that changes something: steering a live agent, starting o - When the user steers a live agent — stop, answer a choice, send a message, arm the handoff, merge — the click 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 no local process to steer: the user's pick is queued for the browser extension to type into claude.ai, and only as a label of the question actually parked. - Starting an agent and opening a checkout in an editor call straight into the daemon's own wiring: there is one host and it wires everything, so a missing capability is a wiring bug that names itself rather than a state a request can find. -- When the user publishes a finished agent — push its branch, open a PR, merge — the work it left uncommitted is committed first, and a lock is held across the commit *and* the push, since teardown publishes the same branch under the same lock: a click racing it must neither lose the work nor collide creating the ref. +- When the user pushes a finished agent's branch or opens its PR, the work it left uncommitted is committed first, and a lock is held across the commit *and* the push, since teardown publishes the same branch under the same lock: a click racing it must neither lose the work nor collide creating the ref. Merging acts on the already-published PR and needs neither. - Merge is one button for two states: it steers a live agent to merge at its natural end, and merges a finished one's PR directly. -- When the user removes a kept checkout or deletes an agent, the action refuses while the agent is live, saves the work as a commit, and stops any preview serving the tree first. +- When the user removes a kept checkout, the work is saved as a commit and pushed before the checkout goes; deleting an agent discards what its checkout still held uncommitted, while the branch and its commits stay. Both refuse while the agent is live and stop any preview serving the tree first. - When the user queues a ticket, the entry is written straight into the project's backlog, under the ticket's priority and linking back to the ticket, so the next agent working the queue picks it up; a stuck ticket claim can be released by hand. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md index 4a9c9217d..7fbac844b 100644 --- a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md @@ -4,13 +4,13 @@ The project list and the launcher's pre-flight answers: which projects are regis - The user sees every registered project, and a project in trouble wears its error right in the sidebar and on its page. - The user adds a project from the dashboard — one repo, or every repo under a folder. -- The user is warned before a doomed start: a merge armed on a repo that cannot auto-merge, a driver CLI that is missing or logged out. +- The user is warned before a doomed start — a driver CLI that is missing or logged out — and told when an armed merge will be handled by the daemon itself because the repo disallows GitHub auto-merge. ## Flows - Each listed project carries what the daemon's background jobs currently find wrong with it — a data branch that cannot reach origin — and the one list every project surface already polls is how that error reaches the sidebar's red dot and the project page's banner. - When the user adds a project (one repo, or every repo under a folder), the daemon installs and registers it, so it lands in the shared registry; the onboarding hint offers the daemon's own directory as the first project. -- The launcher — the form that starts an agent — warns 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. Only problems the user can act on are reported — never account details a visitor on a network-bound host has no business seeing. +- The launcher — the form that starts an agent — warns before a doomed start rather than after: whether the chosen driver's CLI is installed and logged in, and whether the repo allows GitHub auto-merge (an armed merge on a repo that does not is merged by the daemon's own CI watch on green, which works only while the daemon runs). Only problems the user can act on are reported — never account details a visitor on a network-bound host has no business seeing. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/discord-credentials-store.SPEC.md b/packages/the-framework/src/discord-credentials-store.SPEC.md index dc5ce8ae7..c6f4c021f 100644 --- a/packages/the-framework/src/discord-credentials-store.SPEC.md +++ b/packages/the-framework/src/discord-credentials-store.SPEC.md @@ -1,4 +1,4 @@ -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. +Reads and writes the Discord notifications webhook in the user's registry file, and tells the running daemon when it changes so a pasted credential works without a restart. ## User Stories diff --git a/packages/the-framework/src/e2e/SPEC.md b/packages/the-framework/src/e2e/SPEC.md index 29600e442..1e68525ce 100644 --- a/packages/the-framework/src/e2e/SPEC.md +++ b/packages/the-framework/src/e2e/SPEC.md @@ -4,7 +4,7 @@ The product's end-to-end stories: each test walks a user journey through the dae - 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: a capture of how each agent process was launched. -- 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. +- The harness gives every story a throwaway world and a daemon-shaped teardown — global state is fresh per story file, so parallel files never see each other — making stories repeatable. ## Rationales diff --git a/packages/the-framework/src/e2e/harness.SPEC.md b/packages/the-framework/src/e2e/harness.SPEC.md index 8ac8e014f..83c114a42 100644 --- a/packages/the-framework/src/e2e/harness.SPEC.md +++ b/packages/the-framework/src/e2e/harness.SPEC.md @@ -4,7 +4,7 @@ Stands up a disposable copy of the product for one story: the daemon's business - 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. -- 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. +- Each story file's process gets its own throwaway global state — shared by the worlds that file stands up — so parallel story files 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