Skip to content

Simplification review, all 35 approved proposals, and the work queue drained - #1536

Merged
suleimansh merged 99 commits into
mainfrom
claude/simplification-review-ht0sjl
Aug 17, 2026
Merged

Simplification review, all 35 approved proposals, and the work queue drained#1536
suleimansh merged 99 commits into
mainfrom
claude/simplification-review-ht0sjl

Conversation

@brillout

@brillout brillout commented Aug 12, 2026

Copy link
Copy Markdown
Member

A clean-slate simplification review of the whole repo — and its implementation. Every proposal
the maintainer approved: 35 approved, 6 rejected, all 35 landed — then the six entries standing
in TODO_AGENTS.md, which is now empty, and then a review of the whole branch, whose findings are
fixed here too.

Net: +27,774 / −94,019 across ~2,260 files, and the product does the same thing.

The test applied throughout: can this be removed? Not "is this good code" — most of it was. The
question is whether the concept earns a place in a product whose stated goal is one sentence long.

What was removed

The stack's bottom two layers were ballast. the-framework and framework-dashboard imported
zero symbols from @gemstack/ai-sdk (30.8k LOC, 15+ providers, evals, computer-use, vector
stores, React bindings). Only ai-autopilot touched it — five symbols — and every file that did was
itself unreachable from the product. Both packages are gone (A1, A2); the used slice of
ai-autopilot was absorbed.

Two products were wired in parallel. --deploy cloudflare|dokploy, --serve*,
--sandbox docker and the Local/Docker/WebContainer/Fake runners described "idea to a running
app"
; the root SPEC describes "idea, ticket, or queue entry → reviewed pull request" on existing
repos. The second product won (A4). With it went the Bootstrap spine, which had degenerated to
one function call (A3), and the domain-preset review loop (A5).

The CLI is one command with no arguments. 67 flags → four options and no verbs (D4): 27 were
the daemon's own process API serialized onto a command line, and are now a JSON spec on a temp file.
It runs in the foreground and only in the foreground (D4b), which deletes the liveness record,
the machine-global state file and the second-process problem.

One package, not four. The dashboard merged in (A7), which dissolved a 406-symbol export
surface with one consumer; turbo went with it (A8), then Vike for plain Vite (F1), Telefunc
for plain HTTP handlers (F3), and the vendored animate-ui (F4).

Concepts that were one thing written several ways. Four gate mechanisms → one (D6). Four
records of what happened → one (B3). Three claim mechanisms → two (E2). Three spending gates
→ one, on starting (E1) — nothing interrupts work already in flight. Four background sweeps on
four timers → one daemon tick (E4). Three retention rules → one: only remove what is on the
remote
(E5). Two run paths that could drift → one (D2). Three prompt modes → two switches
(C1).

Three words for one thing → three concepts with three names (D5): the unit of work is an
agent, the CLI that drives it is a driver, and where it executes is its location. The
rename runs all the way through — every identifier, the module names, the on-disk layout
(agents/, agent.json), the branch prefix the-framework/agent-{id}, the spawn flag
--agent {path}, all 520 SPEC files, every doc comment, and every visible label in the dashboard,
the CLI and the daemon's log.

The work queue, drained

With the review's list finished, the six entries in TODO_AGENTS.md were worked front to back:

  • Spike & plan can never fire while the AI queue has entries #1432Spike & plan was unreachable. The sweep stood down whenever the queue had work and
    the draining routine was switched off, so with a standing backlog every work-inventing routine
    was unreachable. That switch means "do not work the queue"; the rotation fills it. It now
    falls through. A drain-only click still stands down — it asked for the queue by name.
  • readZip/ZipEntry leak onto the public API via the driver barrel #947readZip/ZipEntry left the published API surface, both steps at once: nothing has
    ever shipped to npm, so the barrel removal costs no consumer anything.
  • Bug: cannot select Fable #1143 — the model picker lists only real models. Dropping the "Default" entry alone would have
    been worse than keeping it: the label fell back to the first model, so an unset preference would
    have started reading as Fable. That fallback went with it — and it turned out to be hiding a
    trigger with no accessible name at all.
  • Empty settings dropdown #1172 — a settings row with nothing to pick renders nothing. The reported control did not
    survive the deletions above, so this makes the class impossible rather than hunting the instance.
  • Agent escaped its run worktree: edits landed in the main checkout, its commit claims work it does not contain #1276 (fix 2 only, as scoped) — the system prompt now says where the workspace ends. Worktrees
    nest inside the repo, so the user's checkout is a path prefix of the agent's cwd and the same file
    exists twice; the reproduced failure had an agent editing the outer copy while committing the
    inner one, finishing done with a commit holding none of the work.
  • Idea: also show daily % used #1367 — the usage panel says what was spent as quota time, and what share of the pro-rata
    allowance that is. It declines to read before any allowance has elapsed rather than dividing by
    zero.

The branch, reviewed

A pass over the whole diff. Six findings, each fixed on this branch:

  • The RPC handler table inherited from Object. Its key is a path segment off an
    unauthenticated request, so POST /_rpc/constructor found Object and answered 200 with whatever
    it was handed, and __proto__ / toString / valueOf reached members that are not handlers.
    Null-prototype table; none of them is an RPC, so all of them are the 404 they are.
  • B5 silently started publishing for anyone who had opted out. It replaced autoPushBranch /
    autoOpenPr / autoMerge with the handoff ordinal and read neither the stored preferences nor a
    committed the-framework.yml written in the old spelling. Ignoring those keys does not leave
    anyone where they were: absent means the default, and the default pushes the branch and opens a
    PR. Both readers migrate the old triple now, on the defaults those keys carried.
  • D5's on-disk rename made every earlier archive invisible. The committed archive (Session history gets lost #1179) exists
    because git clean -fdx used to delete a project's whole history; renaming its directory without
    reading the old name reproduced exactly that, one upgrade later. Both names are read and both are
    un-ignored — rewriting the .gitignore with only the new one would have ignored a pre-rename
    archive. Same for the transient runs/, which is the one history with no second copy anywhere.
  • ~93 comments still described Telefunc and Vike as the live mechanism, including three that
    justify code by a constraint Telefunc imposed and that no longer exists, and a dozen suites
    explaining their mocks by a failure mode that is gone.
  • D5's prose rename had not happened. Identifiers, disk layout and SPECs said "agent"; ~1,700
    lines of doc comment still said "run". run as a verb and GitHub Actions runs stay.
  • SessionOptions / SessionKind sat in the public barrel beside runAgent /
    RunAgentOptions, naming the same unit of work in the vocabulary D5 retired.

Two defects found by using the product were fixed alongside them: the usage panel's unplaceable-week
error contradicted the readout it was printing directly below (a week line with no reset time — what
an untouched account prints — fell through to the "no week at all" wording), and the handoff panel's
push ran outside the lock that teardown holds across the same branch's push, so the two collided
creating one ref and a lost teardown stranded the worktree. That race is what had been flaking
publish a finished session in CI.

What was kept, and why

Six proposals were rejected: B1 and B4 (data model), F5 (dashboard feature surface),
E3 (the routine rotation), G1 (SDD — the SPEC.md beside every source file stays, which is
why every deletion here takes its specs with it and every behaviour change rewrites them), and
C4 (the ${{ }} template language). Each one's live consequences are written into the SPEC.md
of the thing it is about, which is where SDD puts a decision — the permanent fork-point ambiguity in
the queue promoter, the template language's scan limit, the foreground daemon's trade.

Behaviour changes, called out

One from the review, and it is a restoration rather than a change: declining a plan stops the
agent
— the behaviour D6 dropped when the gate kind that carried it was collapsed. It is back as
a property of the gate: an option the agent marks stop ends the session instead of resuming it.
That is strictly more general than what it replaces, which only worked for plan approvals.

Two breaks, both the same call the maintainer already made explicitly for the on-disk layout —
break rather than carry a compatibility branch: the spawn flag --session {path} is now
--agent {path}, and agents branch the-framework/agent-{id} rather than run-{id}.

Everything else preserves behaviour. Where it could not, the commit message says so in its own
words.

Verification

Every commit on this branch was verified the same way: both typechecks, a full build, 1,418 node
tests
, 768 dashboard tests (80 files), and a non-root run that reproduces CI's environment.

Three more, raised by the review and then acted on

  • The dashboard imported the package's own build output. Not a decision — A7 merged the two
    packages by rewriting @gemstack/the-framework into the path that name resolved to, leaving the
    dashboard depending on dist/. pnpm typecheck failed on a fresh clone with 160 errors until
    something had run tsc, and editing src/ type-checked the browser against the last build.
    Of ~120 imports, all but the browser-safe barrel's are type-only and reach no bundle at all, so
    they now come from ../src: 160 errors to 0 with no dist present, and a byte-identical bundle,
    because the module graph never changed — only whether it was read pre-compiled. One import
    genuinely wants the build and says so: the dev:daemon plugin runs the real daemon in the Vite
    config's own Node process.
  • Two components nothing rendered. ActivityChart and AgentOutcomes were drawn only by the
    design gallery, whose own rule is that a card renders the real shipped component. Deleting them
    was the smaller half: Improve dashboard #1139 cut the page that drew them and left DashboardData still carrying
    agentsByStatus, activity, totalAgents and most of ProjectStat — every one of which needed
    listAgents over every project's entire archive, on a payload two pollers ask for every 5 and 10
    seconds. The daemon was walking every project's whole history twice a cycle to compute a
    fourteen-day histogram nobody has seen since. A test pins the payload's shape.
  • TODO_SIMPLIFY.md is gone, after the decisions still live in it moved into the SPECs where
    SDD puts them: the template language's scan limit (C4's rejection asked for exactly this, and
    reading the code to state it corrected the claim — it is "never two closing braces adjacent", not
    "no braces"), and the foreground daemon's trade from D4b. Git holds the document; this description
    holds what it decided.

Claude and others added 15 commits August 12, 2026 11:58
A clean-slate review of the repo at the goal, system, high and mid levels,
proposing subtractions rather than refactors. No code changes.

Headline findings:
- packages/the-framework imports zero symbols from @gemstack/ai-sdk (30.8k LOC);
  only ai-autopilot touches it, and only in the parts the product never reaches.
- ai-autopilot's Bootstrap spine degenerates to a single build call: `scope` is a
  constant, `checklist`/`improve` are omitted without a preset (#1372), `deploy`
  needs an explicit flag.
- The deploy/serve/sandbox/runner stack belongs to the older "idea to a running
  app" product, which the root SPEC has since replaced with "idea to a reviewed PR".
- Five on-disk representations of "work to do", four committed records of one
  session, three claim mechanisms, and four spending gates.
- 26 contradictions catalogued with a resolution each, favouring subtraction.

A1-A6 alone would remove roughly a third of the repo's ~132k LOC without
touching anything the stated business goal needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Part 2 is new: all 160 features as a user meets them, each with a
Keep / Simplify / Remove verdict and a pointer to the proposal that
argues it. 91 Keep, 26 Simplify, 43 Remove.

Features proposed for deletion are listed beside the ones that survive,
so each proposal's cost is legible in user terms rather than in lines of
code, and a closing section states plainly what a user loses: building an
app from nothing, running work off this machine, sharing a live session,
driving the agent's browser, Discord chat, and deploying.

The suggestions become Part 3; nothing in them changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Sharpens D4. The dashboard is the product's only user interface, so every
CLI setting is either a duplicate of a dashboard control or an encoding of
something no human types. Sorting all 67 flags by who supplies them:

- 27 are the daemon's process API — StartRunOptions serialized onto a
  command line (daemon-runtime.ts:155), which is why they must also be
  human-facing, tri-stated and documented. A JSON blob removes them, and
  the seven --no-* pairs with them.
- The same 27 seen from the human side duplicate a dashboard control.
  Keeping both is what forces config-layers.ts to exist; dropping the flag
  tier leaves two tiers, too few to need a resolver.
- ~23 belong to features already proposed for removal.
- 7 survive: --port, --host, --daemon, --cwd, --fake, -h, -v. Plus three
  verbs. These are the chicken-and-egg set — the dashboard cannot
  configure itself into existence.

Names the one real loss plainly: no starting a session from a terminal.
Feature inventory updated (165 features, 94 keep / 22 simplify / 49
remove) and D4 added to the order of work, staged after the steps that
delete flags outright so it is deletion rather than migration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
D4 stopped one step short. The "bootstrap set" it kept — port, host, cwd,
foreground, help, version, plus stop and doctor — does not survive the same
test applied to everything else:

- --port: bind an ephemeral loopback port and record it where the daemon
  already records itself. The codebase does exactly this three times
  (browser.ts:106, browser-stream.ts:266, preview.ts:316), and it is better
  than a flag because no EADDRINUSE is left to work around.
- --host: once A6 removes the relay and remote devices, nothing wants a
  non-loopback bind. It is also the sole creator of the daemon token
  (daemon.ts:355), so dropping it deletes ensureDaemonToken, the daemonToken
  registry key, the 401 gate on every route and most of loopback-host.ts.
  Cutting the remote surfaces is what makes the security machinery guarding
  them dead code.
- --cwd: AddProjectPanel already takes an absolute path via sendAddProject.
- --fake: never typed by a human; e2e/fake-agent-bin.ts appends it to its
  own child's argv.
- doctor: already three implementations of one question (AgentReady, the
  onboarding checklist, the per-session preflight); the verb is the one
  nobody sees.

67 flags -> 0, 10 verbs -> 1. Residual risk named rather than waved off:
no CLI lever recovers a daemon that will not start, mitigated by the
existing heartbeat and by the ephemeral port removing the common cause.

Inventory now 169 features: 90 keep / 22 simplify / 57 remove.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
MEMORY.md (per the ai-memory format it points at):
- The CLI always runs in the foreground; Ctrl-C closes everything.
- The CLI keeps exactly four options: --host, --port, --help, --version.

TODO_SIMPLIFY D4 revised to four options and no verbs. Keeping --host
keeps the authentication story with it: daemon.ts:355 makes a non-loopback
bind the sole reason the daemon token exists, so ensureDaemonToken, the
daemonToken registry key, the 401 gate and loopback-host.ts all stay live.
That is a coherent price -- a process spawner reachable from the network is
remote code execution -- but worth recording, so ~200 LOC of security
machinery never reads as unexplained weight.

New D4b: foreground-only deletes more than the flag cleanup does. The
global daemon state file, the 5s heartbeat with its self-heal and
yield-to-a-live-peer logic, ensureDaemon's find-or-spawn-detached path, and
"one daemon per machine, discovered from any repo" all lose their reason to
exist. Sessions become ordinary children rather than detached ones spawned
"so it survives the CLI that asked for it" (daemon-runtime.ts:155), which
also retires the stderr-to-a-file workaround and the crash-went-nowhere
problem it papered over.

Flagged rather than decided: store/suspend.ts resumes mid-flight sessions
after a restart, which arguably contradicts a deliberate Ctrl-C. Flagged as
a consequence: unattended work now needs a terminal left open -- visible and
killable instead of invisible and persistent.

Inventory now 173 features: 93 keep / 22 simplify / 58 remove.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
MEMORY.md gains three rules: only remove what has been pushed to the git
remote; never interrupt a running session for quota; remote execution is
core to the product, not a distribution channel.

A6 is mostly withdrawn. Remote devices, the Chrome extension driving Claude
Code Web, the browser screencast and takeover, GitHub Actions runners and
the Discord chatbot all stay -- running agents elsewhere is how work happens
when the laptop is closed, which is the point of a tool that spends idle
quota. What still goes: the relay, the preview server, the Discord reply
mirror.

Correcting a claim made while rewriting A6: keeping remote execution does
not preserve the capability-probing machinery. Reading context.ts seam by
seam, eventsSource is set only by the relay, preferences and the Discord
credential store are unset only on a public host, and preview goes with the
preview server -- every optional seam describes the relay and dies with it.
`remote` is wired only by the daemon, and a remote device is not a dashboard
host: the dashboard is always served locally and forwards. So D3 keeps its
full win -- one host, no degradation matrix -- with remote execution intact.

E2 answers the .lock.md question: a lock file cannot do the triage job. It
is a `tickets/<STEM>.lock.md` sibling, and triage has no stem because its
job is to create the ticket set; and creating a branch ref is atomic where
read-then-write on a file is not. Keep .lock.md and the branch lock, delete
the queue-entry pin -- the one that re-derives a claim from live runs, open
PRs and other machines' PR diffs.

Also: E1 keeps the budget slider and deletes consumption-guard; E5 becomes
the single pushed-to-remote rule; B3 keeps exact session logs and deletes
conversations/ and LOGS.md; C1 keeps --vanilla and --transparent as two
genuinely different questions and deletes the whole eco setting; E3 keeps
per-routine off-switches. Contradiction rows 4 and 5 now resolve against the
SPEC text rather than the feature.

Inventory now 105 keep / 20 simplify / 48 remove of 173.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
E3 is deferred rather than adopted: all five routines, the drain job, the
six ticket-shaped presets and the per-routine off-switches stay exactly as
they are. The analysis stays on the record as an observation, not a queued
action.

Two things follow from deferring it. The P4 ticket about the rotation being
unreachable with a standing backlog now has to be fixed rather than
dissolved -- it was going to disappear along with the rotation, and with the
rotation staying it is a live bug against live code. And stale-branch.ts was
listed under E3 as collateral when it belongs to E2, where the pinned-branch
lock is kept deliberately and the sweep is what makes that lock releasable;
row 123 flips to Keep accordingly, which it should have done when E2 was
settled.

Row 4 (auto-register repos under a configured directory) returns to Keep,
reachable from the dashboard only, matching row 3.

Also noted on E4: two of the four timers it consolidates are already deleted
by settled decisions -- conversation-committer with conversations/ (B3), the
heartbeat with the background daemon (D4b) -- so that proposal is smaller
than its own list suggests, while stale-branch stays on it.

Inventory now 114 keep / 13 simplify / 46 remove of 173.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
"Keep" against a proposal means keep today's behaviour -- the same sense the
feature rows use -- not adopt the proposal. B1, B4, F5 and E3 are rejected,
not settled. Their sections stay as recorded observations, marked rejected,
and the ten feature rows they had marked Simplify or Remove revert to Keep:
the five work-item files and the session's own TODO backlog (B1), the
knowledge-base directories (B4), faceted filtering, sorting and the
shareable filter URL (F5).

The rejections change what the surviving proposals mean, which the document
now says explicitly. B1 staying rejected is why E2 and B3 are contained
changes rather than the deep ones they were written as -- the ticket format
is untouched, so E2 deletes one derivation and B3 deletes one rendering. It
is also why contradiction #14 loses its resolution: the ambiguity
queue-promote.ts documents in its own SPEC stays, and the fork-point diffing
remains how it is disambiguated.

Added a decision-status table at the top of Part 3. Twelve of 39 proposals
are now ruled on across three rounds, and a reader could not otherwise tell
which sections are queued actions and which are observations that were
argued and declined. Verified that every proposal appears in it exactly once.

Inventory now 120 keep / 8 simplify / 45 remove of 173.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
…count

There are 32 keys, not 35, and the earlier "removable outright" list named
six that settled decisions have since protected: vanilla and transparent
(C1 keeps both), browser (#173), target (#151/#152), bridge (#155) and
notifyBrowser (#156-158).

Checked key by key, five go: technical, eco, ecoPlanning, ecoResearch,
ecoMaintenance. autopilot is renamed to autoAcceptGates rather than removed.
That is not a purge, and the remaining keys are mostly genuinely distinct
settings rather than duplicates.

The count was the wrong thing to measure. Two shapes carry the complexity:

The handoff ladder is one ordinal wearing three booleans. autoPushBranch →
autoOpenPr → autoMerge are nested stages of one pipeline, and autoOpenPr's
doc has to say it "implies autoPushBranch" because the type cannot. Three
booleans describe eight states of which four are reachable; one ordinal has
exactly four and makes the implication structural.

The notification cluster is a 2x2 flattened into four booleans with four
different defaults, where nothing in the names says which axis a key belongs
to. discordNotificationEnabled's own comment records what that cost: the
composition "was open-coded per call site and got the category's polarity
wrong by copying its sibling" -- a defect caused by the shape, not the count.

Net 32 to 25 keys.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
C1 had proposed keeping autopilot as an autoAcceptGates boolean, on the
reasoning that its remaining effect was the choice-gate countdown. Checking
await-gate.ts first: a headless run already resolves gates without it. There
is a recommended PROCEED fallback applied whenever a gate cannot get a real
answer, the interactive handler is explicitly "omit for a headless run", and
the code approves so a headless or aborted run proceeds. A parked gate never
hangs, autopilot or not -- so unattended work does not depend on this mode
and removing it cannot deadlock Auto PM.

What it actually deletes is auto-accept at an interactive gate: the case
where a human is at the dashboard and the mode decided not to ask them. The
gate's audience is the real signal and it is already available structurally
-- somebody is watching, so ask; nobody is, so take the recommended option.
That is a property of the run, not a setting, which is why no boolean has to
survive.

Flagged as still open, because it is prompt content rather than code: the
system prompt branches on tf.params.autopilot to relax the maintenance
stance. With the mode gone that has to resolve to one stance. Recommend the
relaxed one -- autopilotEnabled is `preferences.autopilot ?? true`, so
relaxed is already what nearly every run gets, and taking the strict branch
would quietly change behaviour for everyone while looking like a deletion.

Five combinable modes become two booleans. Preference keys 32 to 24.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
The inbound/outbound split is already clean in the code, so this is a cut
rather than an untangling. Discord's outbound half is webhook posts living
in dashboard/{discord-webhook,activity,interventions}.ts and ci-watch.ts --
nothing under discord/ -- while everything in discord/ exists for the
inbound half. rest.ts states the reason in its own header: a webhook can
only speak into one channel and cannot reply, so a bot that answers where it
was asked has to go through the API with its token.

That deletes gateway.ts, bot.ts, routing.ts, live-run.ts, rest.ts and
reply-mirror.ts with their tests (~1,840 LOC), the discordBot preference,
the discordBotToken secret, DISCORD_BOT_TOKEN, DISCORD_CHANNEL_ID, the bot's
wiring in daemon-services.ts, and the "token is set but the preference is
off" warning. discord-credentials.ts shrinks to the webhook alone.
Notifications are untouched.

One thread to unpick, noted rather than hidden: dashboard/discord-webhook.ts
imports clampContent from discord/rest.ts, the ~6-line helper enforcing
Discord's 2,000-character limit. It belongs to the webhook path anyway; move
it there and discord/ deletes cleanly.

A6's original complaint about Discord dissolves rather than being managed --
it called Discord three subsystems with two independent transports, and what
is left is one transport, one credential, one direction.

Preference keys 32 to 23.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Picks up #1518 and #1537, so CI re-runs against the current base.
Everything not explicitly rejected is approved. That leaves four rejections
-- B1, B4, F5 and E3 -- and 35 approved proposals, up from the 12 previously
ruled on individually.

The decision-status table now separates what was argued point by point from
what was approved wholesale, and names the five wholesale items that change
the most and are hardest to reverse: A4 deletes the idea-to-running-app
product the README still advertises; G1 retires SDD as practised here; G5
cuts test volume in a repo whose tests are the safety net for everything
else on the list; F1/F3 swap out Vike and Telefunc across every route and
RPC; A1/A2 delete two packages the rest of the code still imports from.
Naming them is not a re-litigation -- it is cheaper to pull one back before
the work starts than after.

Three sub-questions flagged inside sections rather than as proposals are
settled the same way, each taking the recommendation its section already
made: D4b deletes store/suspend.ts, since a deliberate Ctrl-C should not
have work resume behind it; C1 resolves the system prompt's maintenance
stance to the relaxed branch, which is what nearly every run gets today and
so changes nothing; #77 keeps the browser hand-off at a login wall.

The order of work covered 20 of the 35. It now sequences all of them in
eleven steps, with the dependencies that matter made explicit: F3 is much
cheaper after one host remains, E4 shrinks once D4b and B3 have deleted two
of its timers, D6 wants C1 first, and G5 goes last on purpose -- cutting
tests earlier would remove the evidence that the preceding steps landed.

Verified mechanically: every approved proposal appears in the order of work,
no rejected one is scheduled, and every proposal appears in the status table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x

Copy link
Copy Markdown
Member Author

CI build is failing on this PR, and I can't attribute it to the change

Flagging rather than fixing, because I've been unable to reproduce it and I don't want to push a speculative CI change on a docs-only PR.

This PR's entire diff is two markdown filesTODO_SIMPLIFY.md and MEMORY.md. No source, no config, no test files:

 MEMORY.md        |    7 +-
 TODO_SIMPLIFY.md | 1267 ++++++++++++++++++++++++++++++++++++++++++++++++++++++

Nothing under test reads either file (grep for TODO_SIMPLIFY|MEMORY.md across packages/ and e2e/ returns nothing), so there is no mechanism by which they affect a test run.

What I checked

Result
main at 6b211c9 (the base this branch now contains) green — as are all recent runs on main
Full CI pipeline locally on the failing commit 8c73f15 greenbuild, typecheck, test; 7/7 turbo tasks, 1,722 node tests + 803 dashboard tests
Clean-room repro: fresh clone at 8c73f15, fresh pnpm install --frozen-lockfile, CI=true green, exit 0
Without a global git identity (runners have none) green, 1,722 pass
Node version CI pins 22; local is 22.22.2
Turbo cache masking a failure on main Ruled out — no remote cache, no globalDependencies; both start cold

It has now failed twice in a row (f49a9e6 and 8c73f15), so it isn't a one-off. I merged main in between, which did not change the outcome.

What the log suggests, and what I can't see

Turbo reports Failed: @gemstack/the-framework#test, but there is no not ok line, assertion error, or stack trace anywhere in the retrievable portion. The dashboard suite also ends without its Test Files … passed summary — it stops right after components/PreviewBar.test.tsx and goes straight to [ELIFECYCLE]. That pattern reads as a process aborting (crash, OOM, or the 60s per-test timeout in run-tests.mjs) rather than a test asserting false.

I could not confirm this: the failing task's output is at the head of the job log, and the tooling available to me only returns the tail (~21 KB), with direct Actions-API log download disabled for this session.

What would settle it

Anyone with Actions access can open the full log and search for not ok or ##[error] above the turbo summary — that single line is what I'm missing. Alternatively, a re-run with --concurrency=1 would show whether it's contention between the two test suites running in parallel.

Happy to fix it once the actual failure is visible. I've deliberately not pushed CI config changes to chase it blind.


Generated by Claude Code

Claude and others added 14 commits August 14, 2026 12:31
G1 rejected: every SPEC.md remains, including the per-file, per-test and
per-config ones. Contradiction #24 changes from resolved to accepted --
specs describing test files and Vite configs are a deliberate practice now,
not an inconsistency to fix.

G1's rejection changes the shape of the remaining work rather than its
scope, which is worth planning for. At roughly one spec per source file,
every approved deletion takes its SPEC.md files with it -- discord/ alone is
six modules plus siblings, and the same holds for relay.ts, preview.ts,
consumption-guard.ts, store/suspend.ts, conversations.ts, logs.ts and the
ai-sdk package. Every approved behaviour change rewrites specs too:
daemon.SPEC.md's "one daemon per machine, recorded in a single global state
file" becomes false under D4b. So G3 stops being a tidy-up at the end and
becomes a requirement of every step; the order of work now says so.

G2 expanded from the 77 pending changesets to the whole apparatus: the
changesets tooling and release.yml, four CHANGELOG.md files totalling 2,861
lines, the ai-sdk migration note, and semver versions dropping to 0.0.0. Two
of the four changelogs leave anyway with A1 and A2.

One ordering constraint found while writing it up, with this review as the
evidence: the changelog is currently the only written explanation of some
live behaviour. C1's prompt-stance decision in this document is sourced from
CHANGELOG.md:1339 -- that tf.params.autopilot relaxes the maintenance stance
is recorded there and nowhere else. That is the same defect G4 describes
from the other side, so the two go together: sweep the changelogs for what
explains why live code behaves as it does, move it into the code or its
SPEC.md, and only then delete.

34 approved, 5 rejected. Verified every approved proposal is sequenced and
no rejected one is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
…ections

Every proposal is now decided, so the argumentation existed to win decisions
already made. What survives should be what implementing needs.

Three changes. A "work list" table opens Part 3: all 34 approved items, one
line each, which is the whole plan on one screen and doubles as the
implementation checklist. Every proposal heading gains a one-sentence TL;DR
directly under it, so a section can be skipped once its TL;DR is read. The
five rejected sections collapse from 147 lines of argument to their outcome
plus only what other sections depend on -- B1 keeps the queue-promote
ambiguity note behind contradiction #14, E3 keeps the P4 ticket that must now
be fixed and the correction that stale-branch.ts belongs to E2, G1 keeps the
spec-tax note that touches every other item.

A6 also loses its withdrawn-proposal narrative and its keep/remove table
becomes a delete list plus a keep sentence.

Deliberately not trimmed: file names, LOC counts, line references, ordering
constraints and gotchas. D4's 27-flag list and A6's seam-by-seam context.ts
analysis look verbose but are exactly what the implementation needs, and
re-deriving them costs more than reading them.

Verified: 39 proposals, 39 TL;DRs, every heading followed by one, work list
covers all 34 approved and no rejected, tally 119/7/47 of 173, all
cross-references resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Structural validation had been passing throughout, but it only checked
counts and cross-references, never whether a claim still matched the
decision it described. Auditing for meaning found ten stale statements.

Six contradictions-ledger rows still proposed the original fix rather than
the decided one. Row 8 said the two prompt off-switches collapse to one --
C1 keeps both. Row 13 offered "delete the remote drivers (A6)", which A6 no
longer does. Row 15 said three claim mechanisms become one; E2 keeps two,
deliberately. Row 16 promised "one committed projection"; B3 deletes the
projection entirely. Row 18 said one gate one policy, without the slider
surviving. Row 21 still described dropping the release machinery "or moving
ai-sdk out", where G2 now deletes the whole history.

D1's action contradicted both A6 and D1's own TL;DR: it read "if remote
execution survives A6 at all" and recommended dropping cloud and actions.
Remote execution is kept, so handsOff cannot be deleted with them -- it has
to become a property of the location instead. A3 was still conditional on A5
being rejected, which it was not.

The document also still opened by calling itself an unapplied proposal list,
and the losses section closed by claiming every loss was a distribution or
execution channel -- false once it included eco, quota pausing and the CLI.
Four decided removals were missing from that list: the preview server, topic
runs, gates auto-accepting while someone watches, and work resuming after a
restart. The last two are behaviour changes rather than removals, and are
now named as such.

Added a check that no ledger row cites a rejected proposal as its fix, and
that every row cites a proposal or is explicitly marked unresolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Adopting "agent" for the unit of work hits a collision worth naming: the
word is already taken for a different concept. --agent <claude|codex>
selects which CLI drives the work, across agent-names.ts, AGENTS,
isAgentName, preferences.agent, dashboard/types.ts and driver/agent-cli.ts
-- roughly 156 lines. cli.ts:170 reads "Which agent CLI drives the session",
using both words in one line for different things.

Clearing that is what makes this rename worth more than run->session. The
CLI axis becomes driver, which the code already calls it: the type is Driver
and the directory is driver/. --agent becomes --driver, preferences.agent
becomes preferences.driver, agent-names.ts becomes driver-names.ts. D4
deletes --agent as a flag anyway, so the CLI cost is nil.

That gives three concepts three distinct names -- agent for the unit of
work, driver for which CLI, location for where it executes -- which is what
D1 is trying to achieve, so D5 now settles the vocabulary D1 splits.

Recorded the cost rather than hiding it: this diverges from claude.ai/code,
which says "session" and which the dashboard otherwise imitates, per the
counter-argument in the ticket via #772. Deliberate -- the product sells
autonomous agents, and the word is the pitch.

Also noted explicitly that the rest of the document still says session and
run on purpose. It describes the code as it exists, and the rename is step
10, deliberately late so it renames as little code as possible; reading
"session" elsewhere is the state the rename starts from, not a contradiction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
…than merge

The four packages do become one, but by three different routes and only one
of them is a merge. The dependency chain is linear -- dashboard →
the-framework → ai-autopilot → ai-sdk -- and the product never imports
ai-sdk directly.

ai-sdk is deleted rather than merged (A1), and checking this made A1 cleaner
than it was written: every runtime coupling to ai-sdk lives in ai-autopilot
modules A2 already discards (overview/agent.ts, supervisor.ts, planner.ts,
synthesizer.ts, runner/tools.ts, decisions/tools.ts) or A4 deletes
(bootstrap/deploy.ts). The only residue touching the surviving slice is two
type-only imports -- Agent in prompts/bridge.ts, Agent and TokenUsage in
types.ts -- which vanish at runtime. Nothing needs rewriting to delete it.

ai-autopilot is reduced to near-nothing by A3/A4/A5 before A2 absorbs the
remainder. framework-dashboard is the actual merge.

Merging the dashboard dissolves F2 rather than solving it: F2 exists only
because the-framework exports 406 symbols to exactly one consumer, and
removing the boundary means there is no export surface left to trim. F2 is
marked dissolved and its work-list row now says so. F3 also gets cheaper --
its own argument is that Telefunc's value here is type-safety across a
boundary that could share types directly, and A7 removes the boundary. The
merge is largely mechanical: 94 type-only against 31 value imports.

The build also loses a step, since bundle-dashboard.mjs's copy into
dist/dashboard-client becomes a Vite output path.

Recorded the one real cost: two test runners in one package, node --test
over dist-test plus vitest. Mixed browser/node code is not an added risk,
because Vite already builds the dashboard against @gemstack/the-framework
and is already what enforces node-free.

40 proposals now: 35 approved, 5 rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Every task in turbo.json is declared dependsOn ["^build"] -- upstream
packages' build. Once A7 makes the product a single package, ^build resolves
to nothing and turbo is sequencing a one-node graph.

Checked what it actually contributes rather than assuming. Cross-package
ordering goes with A7, including the bundle:dashboard copy step that Vite
replaces with an output path. Remote caching was never configured -- no
TURBO_TOKEN, no TURBO_TEAM, no remoteCache -- so CI starts cold every run.
Local caching only helps within one job, and once bundle:dashboard is gone
neither typecheck (tsc --noEmit) nor test (its own tsc -p tsconfig.test.json)
consumes the build output. The release --filter script goes with G2.

The website is the tell: packages/the-framework.ai is a workspace member,
but the root scripts already bypass turbo for it -- website, website:build
and website:test are all plain cd + pnpm run. Only typecheck reaches it
through turbo, and nobody has missed the orchestrator for the rest.

Recorded two packages the four-package framing missed, so "one package" is
not read too literally. packages/the-framework.ai is the marketing site: it
shares no code with the product and stays separate. packages/chrome-extension
is kept by A6 and is not a workspace member at all -- no package.json, just
manifest.json and plain JS -- so turbo never touched it. Also noted that
pnpm-workspace.yaml globs examples/*, which does not exist.

41 proposals: 36 approved, 5 rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
C4 rejected -- prompt-template.ts keeps evaluating JS fragments inside
prompt markdown against TfContext.

That collides with C2, whose action ended "delete check-prompt-drift.mjs,
its workflow, the snapshot file, and the ${{ }} nesting limitation that
forced the flattening". The renderer stays, so the limitation stays: the
scanner still cannot nest, stopping at the first }}.

C2 is corrected rather than weakened. Deleting the drift checker and the
snapshot still stands on its own -- the snapshot exists only to compare
against issue #326, so it goes once the issue stops being canonical. What
changes is that nothing has to be re-flattened by hand to match an issue any
more; the block just lives in the repo in whatever form the renderer takes.

The no-nesting rule is now permanent, so C4 records that it should be
documented as a rule of the template language rather than left as a trap.
Two places already bend around it: the block that "cannot ship verbatim" in
C2, and the maintenance preset's workaround comment.

41 proposals: 35 approved, 6 rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
…tes, semver

Removes 77 changesets, the changesets tooling (.changeset/config.json, the
@changesets/cli dev dependency, the changeset/changeset:version/release
scripts, .github/workflows/release.yml), four CHANGELOG.md files totalling
2,861 lines, and the ai-sdk 0.3.0 migration note. Package versions drop to
0.0.0.

The pre-delete sweep found the opposite of what the plan predicted, so the
plan is corrected in the same commit. It claimed CHANGELOG.md:1339 was the
only written explanation of live behaviour -- that tf.params.autopilot
"relaxes the maintenance stance". Grepping every prompt file shows no such
branch exists: the only ${{ }} fragments are tf.params.what, tf.presets.*,
tf.session_name, tf.prompt and two tf.settings.technical_control ternaries.
A later changelog entry records that #556 removed that section. The
changelog was not preserving an explanation, it was preserving a
contradiction between two of its own entries.

C1 is corrected accordingly: its flagged "which maintenance stance wins?"
question does not exist. Two real items replace it -- run.ts:319 still
passes params: { autopilot } that nothing consumes, and --technical does
have prompt content that autopilot never had, in
on_before_mergeable_prompt.md:7 and presets/maintenance.md:6.

One collision the version reset exposed: cli.test.ts asserted
frameworkVersion() !== '0.0.0', because 0.0.0 was the fallback for a failed
package.json read. With the real version now 0.0.0 that guard could not
distinguish success from failure. Fixed at the sentinel rather than the
assertion -- a failed read now yields 'unknown', so --version (a kept CLI
option) still cannot print a plausible-looking wrong answer.

Build, typecheck and all tests green: 7/7 turbo tasks, 803 dashboard tests,
the-framework suite fail 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Three failures on this PR have been undiagnosable because turbo interleaves
both test tasks into one output stream and the dashboard's vitest run lasts
~78s, so the framework suite's failure scrolls past the readable window. The
turbo summary names @gemstack/the-framework#test as the failure but the
assertion itself sits at the head of the log.

Running them as separate steps keeps each suite's output attributable, and
puts the failing one last where it can be read. It costs nothing now that
they no longer race for the same cores, and it is close to where A8 lands
anyway once turbo goes.

Ruled out locally before changing CI, each reproducing nothing: node version
(CI pins 22, local 22.22.2), missing git identity, CI=true, stale build
artifacts, a fresh clone with a clean install, machine size (4-core/15GB
either way), running as root vs a non-root user, and turbo cache masking a
failure on main. The full suite passes locally in every one of those
configurations.

Both filters verified locally: 803 dashboard tests pass, framework suite
1722 pass / 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Main fixed the CI flakes at the source (#1541): a torn session snapshot in
run-store and a test that could dial itself. That was the real cause, not
the resource contention between concurrently-running test suites I had
hypothesised -- splitting the suites into separate steps made CI go green
for the wrong reason, and one green run was never proof.

So the split is reverted and ci.yml is now identical to main's. Its stated
justification was a diagnosis that turned out to be wrong, and carrying
diagnostic scaffolding whose diagnosis is finished is exactly the kind of
accretion this branch exists to remove. The readability benefit was real but
does not justify diverging from main on its own, and A8 restructures CI
anyway.

Also re-applied G2 over the merge: main added two changesets
(browser-proxy-port-recycle, torn-meta-read-refuses-resume) that came back
with it, and G2 deletes the changeset system.

Merged tree verified green: build, typecheck, 7/7 turbo tasks, 803 dashboard
tests, framework suite fail 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Removes the deploy half of the from-scratch product: decideDeploy and
deployWith from steps.ts, DeployDecision and the deploy/deployTarget run
options, the deploy step in the Bootstrap chain, buildDeployTarget and the
--deploy/--cf-project/--dokploy-url/--dokploy-app flags with their help text
and CliOptions fields, FAKE_DEPLOY from the offline demo, and host-exec.ts
(hostExecutor existed only to give cloudflareTarget a shell).

Per G1, the deletions take their specs with them: host-exec's two SPEC files
are gone, and steps.SPEC.md, steps.test.SPEC.md and run.SPEC.md no longer
describe a deploy phase they no longer have.

The --fake demo's flow test asserted `deploy: SSR` in its output; that
assertion goes rather than the test, since the demo still exercises the
scope -> build -> done path it exists to cover.

282 lines removed. Build, typecheck and all tests green: 7/7 turbo tasks,
803 dashboard tests, framework suite fail 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Removes ServeConfig and AppPreview, withServeCheck, provisionServeRunner,
startAppPreview and syncThenServe from run.ts; the serve/sandbox/runner run
options and the preview field on the result; the --serve, --serve-install,
--serve-build, --serve-port, --serve-path and --sandbox flags with their
help text, CliOptions fields and parsing; the preview branch of settleRun,
which no longer has an app to keep alive alongside the dashboard; and
sandbox.ts and serve-gate.test.ts entirely.

Per G1 the specs follow: sandbox's and serve-gate's SPEC files are deleted,
and run.SPEC.md no longer describes a boot-and-serve check or a live app
preview in its flow.

Two test adjustments rather than deletions where the test still earns its
keep: the --sandbox-without--serve note test goes with the flag pair it
asserts on, and serve-gate.test.ts goes with the feature it covers.

One trap worth noting for the next steps: tsc leaves compiled output for
deleted sources, so dist-test kept running the deleted serve-gate suite and
reported five phantom failures. Removing dist-test before the run is the fix.

613 lines removed. Build, typecheck and all tests green: 7/7 turbo tasks,
803 dashboard tests, framework suite fail 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
Removes buildReview, the LoopEngine the domain preset was materialized into,
the reviewChecklist it drove, the preset and modes run options, the loop
field on the result, and the "Domain preset: … N-loop review policy in
effect" narration with its modes event. The build turn is now the whole run,
which is what #1372 already made the default for every run without a preset.

Also drops the dockerAvailable import left behind by the serve removal, and
the autopilot param handed to the prompt template — nothing reads it, which
the C1 correction established.

preset-run.test.ts goes with the feature; five domain-preset tests come out
of run.test.ts (the two review-loop tests, the two build-event tests, and
the prototype-scope test, which existed to assert the full-fledged loop was
skipped). run.SPEC.md no longer describes a review phase.

Twice now a pattern-based deletion has damaged run.ts: a brace-counter left
orphaned bodies, and a regex whose leading .*? matched from the file's first
doc comment truncated it from 496 lines to 90. Both times reverting and
recomputing exact line ranges from the declaration map was faster than
repairing. Pattern deletion is off the table for the remaining files.

Build, typecheck and all tests green: 7/7 turbo tasks, 803 dashboard tests,
framework suite fail 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WmoAzowxoxB8bJv74SVM7x
brillout added a commit that referenced this pull request Aug 17, 2026
…he exception to (#1545)

* Delete the per-project settings notice: on a clean slate it is a message to nobody

It only ever spoke when `~/.the-framework.json` held a `projectPreferences`
block — a key nothing has written since B5 deleted that tier. No such file, or
any file written since, and it reads nothing, prints nothing, renders nothing.
With no old data anywhere, there is no reader for it and never will be.

What it cost to keep: a module, an RPC, a banner in the app shell, two test
files and four SPECs — ~440 lines whose whole purpose was a message about data
that does not exist, on a branch whose point was deleting things.

The argument for it still holds where it was aimed: the tier's removal is the
one that fails permissively, so an affected user starts publishing branches and
opening pull requests they had opted out of. That is a fact about *having* the
block. Nobody has it, so the risk it guards is empty and the guard is weight.

The reverse of e0ba45f, which #1536 squashed into main. Nothing else on that
branch is touched: every deletion from the zero-migration commit stands, and
this removes the single exception to it — which leaves one rule, stated in
MEMORY.md, with nothing standing beside it.

Verified on a clean build (`dist-test` removed first — a stale one keeps
running the deleted suite): both typechecks, a full build, 1422 node tests,
772 dashboard tests (80 files). Those are the counts from before the notice.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* Sharpen the zero-migration memory entry against its own format rules

Two problems with what I first wrote. It restated AGENTS.md's "no users, so
breaking changes are fine" — the format this file points at says to store a
fact in one place and not to repeat what is documented elsewhere, and the
rationale is already the first line every agent reads. And it named only the
shape of migration code I had deleted that hour: reading an old name.

What it says now is the rule as it was given — "manual migration is good, but
zero migration code" — and the shapes that are out: a fallback to the old name,
a key alias, an old-format branch, an upgrade step. Plus the one that cost this
branch two rounds and was not obvious to anyone, me included: a *notice* about
what was dropped is migration code too. It reads the retired key, it ships a
module, an RPC and a banner, and on a checkout that never held the old data it
is a message to nobody.

Docs only: nothing under test or in CI reads MEMORY.md.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

---------

Co-authored-by: Claude <noreply@anthropic.com>
suleimansh added a commit that referenced this pull request Aug 17, 2026
The questioned history panel (ProjectLogPanel, fed by .the-framework/LOGS.md)
was already deleted by #1536 along with LOGS.md itself, resolving four of the
ticket's five gripes. The plan verifies each gripe against HEAD, flags the one
survivor (the sidebar rail's STOPPED badge, a maintainer taste call), and
recommends closing the ticket with an optional sweep of ~12 stale LOGS.md
comments.

Releases the planning lock.


Claude-Session: https://claude.ai/code/session_01HXu7peqp243hPGms6qUE21

Co-authored-by: Claude <noreply@anthropic.com>
brillout added a commit that referenced this pull request Aug 18, 2026
* The barrel ends where its consumers end: 341 exports become 41

`src/index.ts` exported 341 names. Nothing imports this package by name, nothing
inside `src/` imports the barrel, and `bin.ts` — the only thing that ships —
takes `runCli` from `cli.js` directly. Its consumers were the dashboard, which
imports it 85 times and every one of them `import type`, and the dev-mode Vite
plugin, which pulled the whole barrel out of `dist/index.js` to get `runDaemon`.

269 of the 341 were referenced nowhere outside the module that defines them.

That is the same shape A7 dissolved one level down — a 406-symbol export surface
with one consumer — surviving at the top. It costs more than tidiness: an
exported name reads as "in use" to anyone grepping, so 269 of them made every
future deletion pass start by proving a negative. That fog is where the
`run.json` and `sessions/` back-reads sat unnoticed until someone went looking.

So the file is now the 41 types the dashboard actually names, grouped by the
module that defines each, and nothing else:

- type-only, because a value re-exported here would pull its module into the
  browser bundle — the failure `client.ts` exists to prevent;
- the dev plugin imports `dist/daemon.js`, the module that has `runDaemon`,
  rather than a barrel that had it among 340 others;
- `main`, `types` and the `exports` map go with it. They described a library API
  for a package that publishes a CLI: `bin` and `files` stay, and what someone
  installs is unchanged.

The rule the file now states about itself is the part worth keeping: every name
in it is imported by a file under `dashboard/`, so a name nothing renders cannot
quietly live on in it.

Verified: both typechecks, a full build, 1445 node tests, 774 dashboard tests,
and `dist/daemon.js` imported in a real Node process — the dev plugin's path is
the one line here that no test covers.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* The meta's version field goes, with the old-records guard that was its only reader

Correcting my own review as I fix it: I reported this field as written and never
read, off a grep that covered `src/` and not `dashboard/`. It had exactly one
reader — `isMetaPublishing` in `dashboard/lib/live-state.ts` — and its own
comment says what it is: "the version gate is the old-records guard".

That makes it the same class #1545 removed everywhere else, not dead weight.
A meta written before #1455 folded `handoffReport` has no report even though its
handoff answered long ago, so the gate stopped a list surface from reading that
absence as "still publishing". With nothing carrying a pre-fold record, the gate
only ever answers true, and the field only exists to be gated on.

So both go: the guard, then `version` itself — the constant, the interface field,
the three writes, and the fixtures that carried it through eleven suites.

The rail's rule is now the one that describes today's records: an agent that
ended, armed a push, and has not reported yet is still publishing. The test that
pinned the pre-fold record's behaviour is gone with the behaviour; the case that
replaces it is the live one — an agent that never armed a push has no window to
wait on.

Records already on disk keep an extra `version` key. Nothing reads it, and
nothing rejects it: an unknown key in a parsed snapshot has never been an error.

Verified: both typechecks, a full build, 1445 node tests, 774 dashboard tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* Nothing removes a project, so nothing keeps removeProject

`removeProject` had no caller: no RPC, no CLI path, no control in the dashboard.
A project is registered by activating a repo and stays registered — the only
thing that ever removed one was its own three tests.

Its doc gave away how long it had been that way: "the project's own overrides
(#840) go with it, so re-adding the path starts clean" — the tier that promise
was about was deleted in B5.

The token-survival test kept it as one of "the other registry mutators"; it now
uses a preference patch, which is a mutator that exists.

If unregistering a project is wanted, it is a feature with a surface, not a
function waiting in a file for someone to find it.

Verified: both typechecks, 1442 node tests, 774 dashboard tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* One name for the archive directory, not two spelling the same string

`AGENTS_DIR` and `ARCHIVE_DIR` were both `'agents'`, both exported, both
re-exported through the store barrel. D5 renamed `runs/` and `sessions/` into one
name and left the two constants behind — in the module whose whole rename was
about not having several words for one thing.

What actually differs between the two placements is the user segment, not the
name: `<user>/agents/` is the committed copy, top-level `agents/` is the transient
one, and `archiveDir(dir, user?)` is where a caller picks. That is now what the
one constant's doc says.

Verified: both typechecks, 1442 node tests, 774 dashboard tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* The archive rule is named for the archive, not for what it used to be called

`SESSIONS_RULE` is `ARCHIVE_RULE`. Its value was already built from `ARCHIVE_DIR`
— only the identifier still said `sessions`, which is the word D5 retired.

The same word survived in the prose around it, so this takes that with it: the
modules that write, commit and ignore the archive called it "the session
archive" throughout, and the line the install writes into every user's repo said
"session state is transient; the session archive is committed". It says agent
now, in the vocabulary MEMORY.md pins.

Two places still speak the old word and are deliberately left alone, because
changing them changes what a user sees rather than what a reader reads: the
commit message the committer writes ("[The Framework] a session"), and the
daemon's no-op reason ("no session changes"). Both are worth renaming; neither
is a comment.

Verified: both typechecks, 1442 node tests, 774 dashboard tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

* Four comments describe HTTP routes that no longer exist

F3 replaced the daemon's `/api/*` reads with RPCs called by name, and the
comments around them kept describing the routes: `GET /api/projects`, the
`/api/docs` payload, "each read endpoint (`/api/logs`, `/api/runs`, `/api/docs`)
resolves a `?project=<id>`", and a read model "backed by the same readers the
daemon's legacy /api/* endpoints use".

`daemon.test.ts` already called them retired. Same class as the ~93 Telefunc and
Vike comments #1536 swept — these four were just missed, and a reader following
them goes looking for a mount that isn't there.

Verified: both typechecks, 1442 node tests, 774 dashboard tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw

---------

Co-authored-by: Claude <noreply@anthropic.com>
suleimansh added a commit that referenced this pull request Aug 18, 2026
* [The Framework] a session

* [The Framework] a session

* [The Framework] a session

* Triage: queue three effort-1 quick-win tickets in TODO_AGENTS

Add the three agent-doable quick wins (effort 1, uncertainty 2 — the
lowest in tickets/) to the agent queue at priority 8:

- ux-gh-auto-merge-disabled: reword stale launcher warning (#1417)
- unclear-ux-what-now: close #1173, everything already shipped
- useless-history-new-session-page: close #1495 (resolved by #1536),
  bumped from ticket priority 7 as lowest-effort work

choices-not-working-cc-web (also effort 1) is deliberately excluded:
its plan is marked Outdated and the remaining verification needs the
maintainer's browser + extension.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BbtzJcHM7VzZhjwGLJSw3

* Re-home the session archives under agents/: the cloud run used a pre-rename build (#1575)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
suleimansh added a commit that referenced this pull request Aug 18, 2026
…/ gitignore rules

Cloud runs still execute the pre-rename npm build (#1575), so their
mirrored archives land under the retired sessions/ path; main's guard
test rightly rejects any tree carrying them. Two pairs were exact
duplicates of copies already landed under agents/ and are dropped. The
seeded .gitignore no longer un-ignores sessions/ either — the #1536
rename's repair path appends rules but never removes old ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
suleimansh added a commit that referenced this pull request Aug 18, 2026
… by #1536, sweep stale LOGS.md comments (#1586)

The questioned history panel (ProjectLogPanel) and the LOGS.md writer were
deleted by #1536, which resolves the ticket's gripes: no history section on
the launcher, no wordy caption, no [Build] tag, and only one history surface
left (the sidebar rail). The rail's STOPPED badge stays as-is — honest
status wording is deliberate (a stopped run is not a done run).

Sweep the comments that still described LOGS.md as existing: the launcher
header, the dashboard read-model and projection notes, the delete-session
leaves-behind lists, the gitignore allowlist note, the agent-store dir doc,
the cli settle/prompt-agent docs, the handoff paper-trail predicate doc, the
dashboard README, and format-date's examples. Deliberate past-tense B3
history notes and legacy-repo test fixtures stay.

Remove the ticket, its plan, and its lock per the ticketing convention.


Claude-Session: https://claude.ai/code/session_01197dFdmEoxKR5eymD2aUkV

Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Member Author

Post-merge review of the refactor

Reviewed the refactor as it stands on main (6ec7850), tracing each candidate end-to-end against current code, with the pre-refactor tree (acb7f30) as the comparison. Excluded up front: everything a follow-up already fixed (#1543's back-read removal, #1545's notice deletion, #1580/#1581's branch names), and everything the zero-migration policy settles — e.g. retired autoPushBranch/autoOpenPr/autoMerge keys in a committed yml going silently inert is pinned as deliberate (config.test.ts:44-45, config.SPEC.md:20, MEMORY.md) and ruled on twice, so it is not a finding. Nine findings, ranked by how much they can hurt. File references are packages/the-framework/src/ unless noted.

1. Teardown pushes a handoff: 'local' session's branch to origin — every session end, automatically

removeProjectWorktree pushes any branch not already on the remote before removing the checkout (worktrees.ts:133-143pushAgentBranch, dashboard/agent-handoff.ts:418), and neither it nor any caller reads the handoff level. It runs on every daemon-spawned session endsettletearDownWorktreeremoveProjectWorktree unconditionally, whatever state the agent ended in (daemon-runtime.ts:743-760, :564) — plus the startup/10-minute sweep and the dashboard's Remove button. So on any remoted repo, a session explicitly configured handoff: 'local' — the rung that exists to mean "publish nothing", the deliverable of B5/#1379 (handoff-level.ts:8-9) — gets its tf-agent-… branch pushed to origin under the user's credentials seconds after it finishes. The agent-side handoff honors the rung (agent-handoff.ts:620 skips as not-armed); the daemon then publishes at teardown anyway.

The same push overrides two of the code's own intents: a stopped agent ("publishing what it happened to reach is the opposite of what stopping meant", cli.ts:975-977 — then Stop → exit → teardown → push), and the daemon's own CI-fix agents, started handoff: 'local' precisely because "this agent's handoff must not push or open anything of its own" (daemon-services.ts:277-279).

Push-as-removal is documented policy (worktrees.SPEC.md:5, MEMORY's removal rule), but no SPEC, MEMORY entry or this PR's "behaviour changes, called out" section addresses the collision with handoff: 'local' — and this PR's own R2 section states the standard it breaks: someone "who had declined to publish would have come back … to branches on the remote … under their name, from a tool they had told not to." Pre-refactor teardown committed and removed without ever pushing. The retention rule doesn't force the push: keeping the checkout is the rule's own documented fallback, already what happens for a remote-less repo (worktrees.ts:137-141). Treating an unpushed handoff-local worktree the same way (keep it, don't publish it) satisfies both.

2. E2 re-fans-out ticketless queue entries mid-handoff — the deleted claim's own doc named this exact case

E2 deleted RunMeta.queueEntry + claimedQueueEntries + the cross-machine PR-diff reader, whose seam doc said verbatim: "a hands-off web run's local process ends at the hand-off while the cloud session still works the entry — both would otherwise put the same entry back on the market and fan it out to a second agent." That is now what happens for entries with no ticket link. Trace on main: preferences target: 'web' rides into unattended drains (agent-options.ts:67,86, daemon-services.ts:156-159); a web agent's local meta settles done at the hand-off while the cloud session still works (agent-location.ts:39-41, agent.ts:272-275); the next tick promotes, gets already up to date (the local branch never touched the queue file, queue-promote.ts:76), settles, and drops the in-memory pin (auto-pm.ts:650-668); the entry is still open — its check-off lands only when the cloud PR merges (auto-pm.ts:758-763) — so after the 30-minute cooldown the sweep starts a second agent, and a second cloud session, on the same entry, repeating until the first PR's merge reaches the checkout. A daemon restart drops the pin the same way.

Ticket-linked entries are protected by #1573's pushed .lock.md (verified: ticket-locks.ts:130-143, failed locks dropped at auto-pm.ts:791-802); a plain-text entry gets pinnedDrainJob unconditionally (auto-pm.ts:794). Neither queue-promote.SPEC.md nor auto-pm.SPEC.md owns the in-flight window as an accepted cost — auto-pm.SPEC.md:8 says a ticketless entry "keeps the queue itself as the coordination point", but during a hand-off the queue file demonstrably doesn't coordinate. And the comment at daemon-services.ts:223-224 still claims the pinned entry "rides along (#1253) … and outlives … the agent's local process" — a protection E2 removed. Either the SPEC owns the window, or ticketless entries need the same lock shape #1573 gave tickets; the stale comment goes either way.

3. A Start landing during shutdown spawns an agent nothing ever stops

Shutdown order (daemon.ts:268-287): quiesce → stopAgentsflushAgents → … → dashboard.close() last. The HTTP surface keeps accepting until that final step, and neither sendStart (dashboard-rpc/control.ts:177-193) nor /_relay/start (dashboard/relay-endpoints.ts:83) checks any closing flag. The spawn is detached: true + unref() (daemon-runtime.ts:105-123) — its own process group, so the terminal's Ctrl-C never reaches it. stopAgents snapshots activeAgents once (daemon-runtime.ts:836-845); an agent in the starting window (registered only at :761, after an awaited spec write) is in neither the snapshot nor the wait. Result: a Start clicked (or relayed) during the multi-second stop span produces an orphan on ppid 1 holding a worktree, burning quota after "everything closed" — and next boot's reconcileOrphanedAgents only flips agents whose pid is dead (store/agent-store.ts:917-946), so it stays running.

The window itself pre-dates this PR (same ordering at acb7f30), but the PR recorded the invariant it violates — MEMORY's "Ctrl-C closes everything", daemon.SPEC.md:6 "nothing burns quota after you have closed it", and stopAgents' own doc describing exactly this orphan. auto-pm.ts:841-851 already fixes the identical shape for its own spawns (#983); onStart has no equivalent. Cheap fix: a closing flag checked in onStart, or close the server before the stop pass.

4. The startup footer advertises a command that exits 2

Every daemon start prints "Type a prompt on the dashboard to start an agent, or use: framework "<what to build>"" (cli.ts:1311-1314), but D4 removed the positional path — framework "build me X" now answers unknown command: build me X, exit 2 (cli.ts:286, :646-650). The line was accurate pre-D4 (positionals became opts.intent); the refactor deleted the path and left the advertisement. Nothing pins the text (the footer test asserts other lines). One-line fix.

5. Agent specs leak their temp directory — and the whole prompt on spawn failure

writeAgentSpec mkdtemps framework-session-*/session.json per spawn (agent-spec.ts:46-47); readAgentSpec removes only the file, never the directory (agent-spec.ts:57-59), and no sweeper exists — one empty dir per session, forever, under a long-lived auto-PM daemon. On the spawn-error path neither writer cleans up at all (cli.ts:1358-1362; daemon-runtime.ts:728-740,759settle tears down the worktree but never touches the spec), leaving the full prompt on disk. Both contradict the module's own claim that "a spec never outlives the session it started" (agent-spec.ts:43, agent-spec.SPEC.md:7 — the rationale being a device token in the options). Fix: rm the spec's dirname recursively in readAgentSpec, plus cleanup on the error paths.

6. preset: / event: are a zombie config surface

The keys are still parsed, type-validated, resolved and confirmed to the user — the ◆ config: preset=… echo (config.ts:47, config-layers.ts:83-89,112-115, cli.ts:704-705) and launcher chips (dashboard/components/ResolvedOptions.tsx:27-28) — but A5 deleted every functional consumer (resolveDomainPreset, the review loop, domainLoopChecklist: zero hits on main). Two regressions ride along: an unknown preset used to error and now any string is accepted and narrated as if in force, and config.ts:14 still documents event by "the preset's review loop" A5 deleted, with config.SPEC.md:1 still presenting preset as live surface. This violates the CLI's own stated standard four hundred lines away: "A setting that silently does nothing is worse than one that errors" (cli.ts:361-363). Either the keys go, or something consumes them.

7. Dead option plumbing left behind by D4

Six fields survived the collapse with no transport: StartAgentOptions.autopilot/.technical (dashboard/types.ts:56,58) are written and read by nothing, under a header still claiming "each maps to a run flag" that no longer exists; AgentOptions.sessionLink has a live reader (cli.ts:87-90) that no writer can reach — while cli.ts:72-73 and session-link.ts:9 both instruct embedders to "set sessionLink on the spec", an instruction that is silently discarded; AgentOptions.preset/.buildEvent feed flagConfigLayer (cli.ts:384-385) but nothing can set them; todoMaxItems is read live (cli.ts:1233agent.ts:248) and settable nowhere — the suite itself asserts the void (cli.test.ts:116). Same-vein residue: an orphaned doc comment for the removed eco field (dashboard/types.ts:63) and a modes event kind + OPEN_LOOP_MODES = ['autopilot','technical'] (events.ts:316,336) that nothing outside tests emits.

8. The "not enabled on this server" guards are unreachable, and control.SPEC.md still describes them as live

contextStartAgent()/contextAddProject() throw on an unwired field by documented design ("Unwired is a bug rather than a degraded host", dashboard-rpc/context.ts:27-37; every context field required since D3), so the crafted { ok:false, error: 'starting a session is not enabled on this server' } branches (dashboard-rpc/control.ts:183-184, projects.ts:27-28,42-43) can never run — a hypothetically unwired host answers 500. Pre-F3 these guards were live (hosts wired subsets; the relay really got the friendly error). The right cleanup is deleting the guards — and fixing control.SPEC.md:6, which still says "exist only where the daemon wired them; other hosts refuse", directly contradicting context.SPEC.md:5 and the code.

9. The sweep grinds forever on checkouts that can never push

Mostly documented policy (remote-less repos keep every checkout; each failed reclaim is announced — worktrees.SPEC.md:6,10, merged-worktrees.SPEC.md:7), but the undocumented residue: the sweep is stateless (merged-worktrees.ts:65-79), never consults hasRemote (which the handoff path does check — agent-handoff.ts:277,637), and so re-runs ~5 git spawns and prints the identical "kept the worktree" line per retained checkout every 10 minutes, forever, for a condition that cannot change. New consequence worth stating somewhere: post-E5 a remote-less repo accumulates one permanent checkout per session (pre-refactor, clean finishes were removed locally), and the dashboard's Remove can never succeed there — only the history-destroying Delete reclaims disk. Distinguishing "no remote configured" from retryable failures (or deduplicating the repeat line) is the whole fix.


Held up under attack (verified, no finding): the retired-yml silence (settled policy, pinned), E5's push-as-removal mechanism itself (SPEC-owned — finding 1 is only its collision with handoff: 'local'), #1573's drain locks for ticket-linked entries, and the throw-on-unwired context design.

Happy to fix any subset of these — say which. 1, 4, 5 and the stale comments are small and self-contained; 2 and 3 each want a design call (own the window in the SPEC vs. close it) before code.


Generated by Claude Code

@suleimansh

Copy link
Copy Markdown
Member

Take 1, 4, 5 and the stale comments first. 1 is confirmed live on our side too — teardown pushed a declined session's branch to origin during the 2026-08-17 dogfood — and the keep-the-worktree fallback (the remote-less behavior) is the right fix, no new mechanism.

2: fix the stale daemon-services.ts comment now, but own the window in the SPEC rather than building a ticketless lock — #1582 moves the queue to the eagerly-pushed data branch with the check-off landing daemon-side, which closes this window structurally, so any new claim shape here would be deleted weeks later.

3: close it with the closing-flag check in onStart (the #983 shape) rather than reordering shutdown.

6, 7, 8: delete it all per the zero-users rule — unless you want preset: live soon, in which case say so and it needs a consumer, not a guard.

9: check hasRemote once and log the kept-checkout line once per checkout, not per sweep.

Copy link
Copy Markdown
Member Author

Done — #1594, one commit per instruction, in your order.

The two calls you left open, resolved as directed: 1 is the keep-the-worktree fallback with the armed handoff read off the agent's meta (so the daemon's own handoff: local CI-fix agents are covered too, and an already-pushed branch still lets the checkout go); 2 is the comment fix plus auto-pm.SPEC.md owning the window as accepted-until-#1582, no new claim shape. 6–8 deleted throughout — nothing wants preset: live. One consequence stated out loud: the lifecycle e2e story now pins the new contract (a local-handoff session ends with its checkout kept and nothing on origin), and the publish story runs push-armed so the click-vs-teardown ref collision it exists to pin still happens.

Verified on the head: both typechecks, a full build, 1466 node / 771 dashboard (81 files).


Generated by Claude Code

brillout pushed a commit that referenced this pull request Aug 20, 2026
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma
brillout added a commit that referenced this pull request Aug 20, 2026
* Migrate SPEC.md files to the new sdd.md template: root, package, .github, dashboard-rpc

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* Migrate SPEC.md files to the new sdd.md template: dashboard root, ui, prompt-editor, design

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* Migrate SPEC.md files to the new sdd.md template: daemon dashboard reads (A-G), bridge

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma

---------

Co-authored-by: Claude <noreply@anthropic.com>
suleimansh added a commit that referenced this pull request Aug 21, 2026
* Remove the code the watch relay's deletion left behind

#1536 deleted the hosted watch relay and #1611 cleaned up after it, but three
things it was the only reason for stayed:

- AgentFeed's showSessionLink/showName/showStatus were passed `false` by
  AgentView, its only caller, so the AgentOverview they configured hit its
  `if (!sessionLink && !status) return null` on every render. The props and
  that render are gone; AgentOverview itself stays, since ProjectHome uses it
  with the defaults.
- The `enabled` parameter on useWorking/useDaemonHealth/useFavicon was never
  passed `false` by anything — the relay view had been its only caller. Gone,
  along with the two tests that existed only to cover it.
- Comments and specs across nine files still explained live behaviour by
  naming the deleted relay: FileTree "has no checkout", GitStatusBar "on the
  relay", EventList's "read-only relay watch", RoutineWork's sweep note,
  favicon, use-daemon-health's tests. The behaviour is real in each case, only
  the reason was a ghost — reworded to state the condition itself.

The device relay (#1067) is untouched: relay-endpoints, relay-dispatch and
relay-agent are a different feature that shares the word.

No FEATURES-SPEC.md change: none of this was user-visible.

Suite 1489 + 774, typecheck clean.

* AgentFeed's projectId is required: an open gate must never render as log text

The last of the same shape #1615 removes. AgentView is AgentFeed's only caller
and always passes projectId, so the optional prop had exactly one production
value — and the conditional forward below it could only ever take one branch.

Required is the point, not tidiness: with it optional, a future caller that
omits it gets an open choice gate silently rendered as plain log text, which is
a run parked with nothing to answer it (#846). A required prop makes that a
compile error instead of a dead-quiet downgrade.

EventList's own projectId stays optional. It is a leaf presentational component
with 35 direct test renders, and its browser pane already degrades on a
genuinely nullable agentId, so the absent case is a real render mode there
rather than a fiction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants