Simplification review, all 35 approved proposals, and the work queue drained - #1536
Conversation
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
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
CI
|
| 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 |
green — build, 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
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
…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>
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>
* 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>
* [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>
…/ 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>
… 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>
Post-merge review of the refactorReviewed the refactor as it stands on 1. Teardown pushes a
|
|
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 3: close it with the closing-flag check in 6, 7, 8: delete it all per the zero-users rule — unless you want 9: check |
|
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 Verified on the head: both typechecks, a full build, 1466 node / 771 dashboard (81 files). Generated by Claude Code |
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
* 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>
* 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.
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 arefixed 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-frameworkandframework-dashboardimportedzero symbols from
@gemstack/ai-sdk(30.8k LOC, 15+ providers, evals, computer-use, vectorstores, React bindings). Only
ai-autopilottouched it — five symbols — and every file that did wasitself unreachable from the product. Both packages are gone (A1, A2); the used slice of
ai-autopilotwas absorbed.Two products were wired in parallel.
--deploy cloudflare|dokploy,--serve*,--sandbox dockerand the Local/Docker/WebContainer/Fake runners described "idea to a runningapp"; the root SPEC describes "idea, ticket, or queue entry → reviewed pull request" on existing
repos. The second product won (A4). With it went the
Bootstrapspine, which had degenerated toone 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 prefixthe-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.mdwere worked front to back:Spike & planwas unreachable. The sweep stood down whenever the queue had work andthe 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/ZipEntryleft the published API surface, both steps at once: nothing hasever shipped to npm, so the barrel removal costs no consumer anything.
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.
survive the deletions above, so this makes the class impossible rather than hunting the instance.
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
donewith a commit holding none of the work.%used #1367 — the usage panel says what was spent as quota time, and what share of the pro-rataallowance 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:
Object. Its key is a path segment off anunauthenticated request, so
POST /_rpc/constructorfoundObjectand answered 200 with whateverit was handed, and
__proto__/toString/valueOfreached members that are not handlers.Null-prototype table; none of them is an RPC, so all of them are the 404 they are.
autoPushBranch/autoOpenPr/autoMergewith the handoff ordinal and read neither the stored preferences nor acommitted
the-framework.ymlwritten in the old spelling. Ignoring those keys does not leaveanyone 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.
because
git clean -fdxused to delete a project's whole history; renaming its directory withoutreading the old name reproduced exactly that, one upgrade later. Both names are read and both are
un-ignored — rewriting the
.gitignorewith only the new one would have ignored a pre-renamearchive. Same for the transient
runs/, which is the one history with no second copy anywhere.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.
lines of doc comment still said "run".
runas a verb and GitHub Actions runs stay.SessionOptions/SessionKindsat in the public barrel besiderunAgent/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 sessionin 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.mdbeside every source file stays, which iswhy 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 theSPEC.mdof 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
stopends 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 branchthe-framework/agent-{id}rather thanrun-{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
packages by rewriting
@gemstack/the-frameworkinto the path that name resolved to, leaving thedashboard depending on
dist/.pnpm typecheckfailed on a fresh clone with 160 errors untilsomething had run
tsc, and editingsrc/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 nodistpresent, 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:daemonplugin runs the real daemon in the Viteconfig's own Node process.
ActivityChartandAgentOutcomeswere drawn only by thedesign 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
DashboardDatastill carryingagentsByStatus,activity,totalAgentsand most ofProjectStat— every one of which neededlistAgentsover every project's entire archive, on a payload two pollers ask for every 5 and 10seconds. 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.mdis gone, after the decisions still live in it moved into the SPECs whereSDD 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.