# tooler Framework Implementation Plan > **For Hermes:** Multi-repo, multi-agent build. Pick up ONLY on explicit user > request. Build starts ONLY after explicit user approval (cost control). > Red-team review precedes execution and runs mid-build (owner convention). **Goal:** Build a modular framework ("tooler") from the reusable components and methods found across the Tangled ATProto tool portfolio, so new ATProto tools scaffold quickly. Each component gets its own Tangled repo (web_themes model) so multiple agents can improve repos in parallel with tests, coordinated by an orchestrator agent, verified by a QA tester agent, and audited across all repos by a red-team agent. **Architecture:** Component-per-repo git libraries, ESM, dependency-pinned to the proven line (atproto/api 0.20.41 + oauth-client-browser 0.5.4). Consumer apps (verifier, altifier, slicer, sifter, timer) adopt the libs repo-by-repo, proving each extraction. A meta repo ("tooler") holds the registry, shared conventions, agent role docs, and review notes. Theme stays in the existing web_themes repo (already component-per-repo; consumers pull at build time). **Tech Stack:** JavaScript/ESM libraries; vitest (proposed) for new repos; CRA 5 apps as integration consumers; Tangled git remotes (public-only); web_themes theme tokens; atproto/api + oauth-client-browser pinned versions. --- ## 1. Background and scope (locked) SOURCES (locked 2026-09-07): this framework extracts exclusively from these six repos: 1. verifier 2. altifier 3. slicer 4. sifter 5. timer 6. web_themes No other repo is a source. The 2026-09-06 portfolio review covered more repos; those reviews are provenance only and do not feed this framework (owner decision: keep this plan scoped to the six). Consequences of scope: - Baseline pins: atproto/api 0.20.41 + oauth-client-browser 0.5.4 (timer-proven ESM under CRA/webpack, zero Jest hacks). - ATProtocol-Playground is NOT a source (owner decision, option c, 2026-09-06): bsky.app DOM automation is out of scope; no tooler-dom module. - The six source repos are the sole extraction material for every module in section 2; consumer migrations (phase 6) also touch only these repos. ## 2. Component inventory (one repo per row) | Repo | Module | Extract from | Key source files | Deliverable | |---|---|---|---|---| | tooler-oauth | M1 OAuth SPA kit | timer (canonical 0.5.4 line), verifier/slicer/sifter/altifier (0.3.30 copies) | AuthContext.js, client-metadata.json, Login/LoginCallback/ProtectedRoute/Logout, url.js safeReturnUrl | Core: client bootstrap, session init/login/logout, safe-return-url guard. react/ subpath: AuthProvider, route quartet. Version-aware callback (explicit route vs 0.5.4 auto). | | tooler-xrpc | M2 XRPC data layer | verifier (paginate, applyWrites-200), timer lists.js (ensure-records), altifier (in-place swapRecord edit), slicer+sifter rateLimit.js | Verifier.js:15-33, lists.js, altApplier.js, rateLimit.js | Cursor paginator, applyWrites batcher, getProfiles chunker, ensure-records, in-place record editor with swapRecord+retry, rate-limit honoring (withBackoff, header parsing, sequentialWithPacing). Pure, no React. | | tooler-scan | M3 account-analysis engine | sifter scan.js + extract.js; altifier resumable-scan | src/lib/scan.js, src/lib/extract.js | DI-injected fail-closed scan engine (listBlobs/describeRepo/listRecords/listMissingBlobs), generic blob-ref extractor (all shapes incl. hydrated CIDs), resumable-scan state helper. Pure. | | tooler-blob | M4 blob lifecycle | sifter cleanup.js + backup.js | src/lib/cleanup.js, src/lib/backup.js | Reference-then-delete cleanup (temp-NSID record, deleteRecord, listBlobs verify, validate:false retry, sweep), backup zip (getBlob normalization + jszip manifest + download). Pure. | | tooler-publish | M5 publishing | slicer publish.js + verify.js + alt.js | src/lib/publish.js, verify.js, alt.js | Gallery pipeline (uploadBlob xN, $type items, selfLabels, lexicon pre-validation), publish-then-hydration-poll + auto-delete, alt-text composer + a11y gating. Pure + gallery lexicon asset. | | tooler-image | M7 image pipeline | slicer encode.js geometry.js cropViewport.js carousel.js | src/lib/*.js | Single-drawImage encode (WebP q search, global long-edge reduction, EXIF), N-divisible grid math, pure pan/zoom viewport, carousel index helpers. Rules: never upscale, identical dims, integer px. Pure. | | tooler-bot | M6 headless bot template | timer bot/ | bot/scheduler.ts, bot/schema.sql | App-password login, sqlite WAL poll loop, paginated reads, INSERT OR IGNORE idempotency, DM send, opt-out on 400/403. Ships as template/ + lib/, not a deployed app. | | tooler (meta) | Registry + governance | conventions distilled from the six source repos' AGENTS.md in this plan | n/a | README + AGENTS.md, registry.md (repo -> module -> status -> versions), docs/review/ (preserved review notes), roles/ (orchestrator, component-agent, QA, red-team instructions), pins.md (dependency baseline), DECISIONS.md. | | web_themes | M8 theme | existing repo (no new repo) | themes/witchsky-cyan, scripts/sync-theme.sh | Consumed as-is: 15-token :root contract, var()-only gate, build-time pull. tooler apps adopt sync hook. | ## 3. Agent operating model (owner-specified) Four roles; all agents are Hermes sessions/subagents following the conventions in the role docs (tooler/roles/). - Orchestrator agent: owns the registry and sequencing. Creates per-repo task lists (TODO.md per repo at pickup), dispatches component agents, enforces interface contracts and pin versions, approves merges after QA, surfaces all decisions to the owner as terse a/b/c. Exactly one writer active per repo at a time (repos are small; agents never write the same repo concurrently). - Component agents (one or more per repo, parallel across repos): implement TDD task-by-task in their repo, write tests first, commit per task, never touch other repos, never deploy. - QA tester agent: independent. Runs each repo's test suite, runs integration harness against consumer apps after each migration, verifies acceptance criteria, files defect reports to the orchestrator. Never fixes code. - Red-team agent: cross-cutting. Adversarial review BEFORE execution (this plan + target repos) and periodically mid-build across ALL repos (security, secrets, supply chain, open-redirect, XRPC misuse, public-repo hygiene). Findings co-located with targets in each repo's red-team-output/ (gitignored); doer resolves inline `> RESOLVED/REJECTED `; findings never deleted; open questions surface to owner as a/b/c. Never fixes code. Handoff: orchestrator -> component agent per task; component agent -> QA on completion; QA verdict -> orchestrator merge decision; red team scans at phase boundaries (pre-build and per-phase). Inter-agent state lives in the tooler registry.md + per-repo TODO.md (committed), so any session can resume. ## 4. Repo scaffold standard (applies to every tooler-* repo) - Public Tangled remote (psingletary.com/); public-only is accepted. - AGENTS.md generated from tooler/AGENTS.common.md + per-repo block (overlay pattern observed in the six source repos' AGENTS.md; first-gen by hand). - .gitignore: key blocklist from the shared red-team lineage (*.pem, *.key, *-key.txt, private-key*, keys*, *keypair*, *.jwk, *.hex, .env*), plus node_modules/, dist/, red-team-output/. - No emojis in code/docs. No hardcoded hex colors (themes via tokens). - Secrets: Bitwarden/Keychain only; never quoted; never committed (public repo). - Tests mandatory per module; vitest (proposed) + fixtures; pure-lib layout so UI shells stay thin (proven DI-injected engine pattern). - Pins: atproto/api 0.20.41, oauth-client-browser 0.5.4, react 18 only where react/ subpath exists. No version change without owner approval. - Commits: one type-scoped commit per task; `git add -A` forbidden (stage by name); no force-push without owner approval. - wispctl deploy: N/A for libs (no sites). Consumer migrations deploy per existing per-app conventions (--path build --site --spa). ## 5. Phases and tasks Phase 0 — Meta repo + baseline review (tooler) - Task 0.1: Create tooler repo on Tangled (git init, README, AGENTS.md from common template, LICENSE MIT, scaffold standard .gitignore). Verify: ls-remote. - Task 0.2: Copy the committed review notes from ~/dev/_shared/docs/plans/framework-review/*.md into tooler/docs/review/ (the source of THIS committed copy, never /tmp). The binder's SOURCES are ONLY the six repos in section 2's inventory table; notes covering other repos (zodiac.md, ATProtocol-Playground.md, extra-repos.md) are provenance for the record, not extraction sources. Commit. Verify: file count 8, grep confirms no real secret values (reviewers already confirmed clean). - Task 0.3: Write registry.md (rows from section 2 table, status=planned), pins.md, DECISIONS.md (open questions from section 8). - Task 0.4: Write roles/orchestrator.md, roles/component-agent.md, roles/qa.md, roles/red-team.md from section 3. - Task 0.5: Red-team agent reviews plan + the 6 in-scope source repos (read-only). Findings to tooler/red-team-output/. RESOLVED inline before Phase 1 starts; open questions to owner. - Gate: owner approves Phase 0 output + naming + decisions (a/b/c). Phase 1 — tooler-oauth (M1, highest value: 5 identical copies exist) - Task 1.1: Scaffold tooler-oauth (package.json ESM, vitest, exports map: core + ./react). Test infra green (1 smoke test). - Task 1.2: Core client bootstrap from timer AuthContext + fetched client-metadata.json; session init (0.5.4 auto-callback), login/logout. TDD: unit tests with mocked BrowserOAuthClient. - Task 1.3: safeReturnUrl/isSafeReturnUrl open-redirect guard (from slicer url.js, SEC-01 lineage). TDD incl. attack-vector cases. - Task 1.4: React bindings: AuthProvider/useAuth, ProtectedRoute, Login/ LoginCallback (0.5.4 auto-callback) + Logout. Component tests with mocks. - Task 1.5: client-metadata.json template (dual-host redirect_uris, scope parameterized: transition:generic default / chat.bsky for DM tools, dpop_bound_access_tokens true) + docs. - Task 1.6: QA: run suite; integration = migrate TIMER (single consumer) to tooler-oauth; timer OAuth flow still logs in/out on timer.psingletary.com. Red team: oauth scope/redirect/open-redirect pass. - Task 1.7: Commit + registry.md row (status=released-oauth). Phase 2 — tooler-xrpc (M2) - Tasks 2.x: paginator (paginate(fn,params,onPage), auto list-key detection); chunk + applyWrites-200 batcher w/ progress; getProfiles x25 chunker; ensure-records (data-driven creation + duplicate reporting, from lists.js); in-place editor (getRecord -> mutate -> putRecord swapRecord guard, 1 retry on InvalidSwap/409); rate-limit honoring (withBackoff, retryDelayFromError, sequentialWithPacing 250ms). TDD each (slicer's 10 rate-limit tests port). - Task 2.n: QA integration: migrate verifier applyWrites + paginate paths. Red team pass. Phase 3 — tooler-scan (M3) then tooler-blob (M4) (sifter core; extract together, ship as two repos) - Task 3.1: scan.js port: DI-injected paged scan, fail-closed trustworthy flag; extract.js: recursive blob-ref walker ($link, hydrated CIDs, legacy {cid}, dedupe — fixtures F1-F6); resumable-scan state (cursor+hits in localStorage keyed by DID, resume/restart). - Task 3.2: cleanup.js: reference-then-delete via temp NSID, deleteRecord, listBlobs verify, outcomes enum, validate:false retry, temp-collection sweep; backup.js: getBlob normalization, jszip manifest zip, download helper. - QA integration: migrate sifter scan + cleanup + backup paths; run against a throwaway test account (author-in-the-loop for destructive live test). - Red team: destructive-operation safeguards, DID scoping, no cross-account risk. Phase 4 — tooler-publish (M5) + tooler-image (M7) (slicer pair) - Task 4.1: gallery pipeline (uploadBlob xN -> createRecord w/ BlobRef $type items, selfLabels, lexicon pre-validation via lexicon validator); verify-post hydration poll (10s) + auto-delete on failure; alt composer buildAlts/ allAltsValid + publish gating. Lexicon asset: lexicons/gallery.json. - Task 4.2: encode.js (single drawImage, WebP q binary search, global long-edge reduction, EXIF decode), geometry.js (N-divisible grid, suggestCrop), cropViewport.js pure pan/zoom, carousel.js helpers. TDD; port slicer's existing 129 tests' pure-lib parts. - QA integration: migrate slicer publish path; publish one test gallery to a throwaway account (author-in-the-loop). Red team pass. Phase 5 — tooler-bot (M6) - Task 5.1: scheduler core from timer bot/scheduler.ts (app-password login, better-sqlite3 WAL, poll loop, paginated getLists/getList, dueAt math, convo.sendMessage DM, INSERT OR IGNORE idempotency, 400/403 -> active=0 opt-out), schema.sql + v_due_reminders view; template/ with .env.example. - QA: integration against timer deployment (bot dry-run mode; author runs live). - Red team: app-password handling, sqlite WAL hygiene, DM abuse vectors. Phase 6 — Consumer migration, QA sweep, docs, decision log - Task 6.1: remaining consumer migrations (altifier, slicer, sifter, verifier) onto released tooler libs; remove vendored duplicates per repo; verify app deploys (wispctl per-app conventions, live-hash check; edge caches ~10 min). - Task 6.2: full QA suite across all repos + integration harness run. - Task 6.3: red-team full pass across all repos; ledgers current; open items to owner. - Task 6.4: tooler README (framework guide: repo table, agent model, scaffold standard, how to add a component), DECISIONS.md updated, registry.md final. - Task 6.5: report to owner; archive PLAN to docs/plans/archive/ + REGISTRY row per intake convention (after pickup of the _shared copy). ## 6. Interface contracts (cross-repo, orchestrator-enforced) - All libs: ESM, zero runtime deps except where noted (tooler-xrpc may depend on atproto/api; tooler-oauth on oauth-client-browser; everything else zero-dep or peer-dep). Pure libs never import React; react bindings under ./react subpath. - Pins: atproto/api 0.20.41, oauth-client-browser 0.5.4, react 18. No version drift across tooler repos. Consumer apps keep their own pins until migrated. - Error shape: throw typed errors; rate-limit info on XRPC write errors where headers exist. Scan/cleanup engines return outcome enums, never throw for expected per-item failures (fail-closed only for whole-scan trust). - Every repo: package.json test script (vitest run), AGENTS.md, README.md with the module's interface table, LICENSE MIT, registry.md row maintained by orchestrator only. ## 7. Tests and validation (QA agent) - Unit: vitest per repo; fixtures ported from proven suites (slicer 129 tests' pure parts, sifter F1-F6 fixtures, slicer rate-limit 10 tests). Every module requires fixture-tested pure core. - Integration: consumer app swap per phase (listed above); QA runs app test suites + manual flow on live app for auth/CRUD paths. - Live (author-in-the-loop, never agent-run): destructive blob cleanup, real gallery publish, bot DM — per the standing owner HARD-STOP convention. - Acceptance evidence: per-repo test run output + QA verdict logged in registry.md (status field per row). ## 8. Open decisions (owner, at Phase 0 gate) a) Prefix/naming: tooler- for all new repos? Alternatives: atproto-tools-, or flat names (oauth-kit, xrpc-layer). Owner named the plan "tool-er" — confirm the family name spelling used on repos. b) Test runner for new lib repos: (a) vitest (ESM-native; recommended), (b) jest aligned to CRA react-scripts for maximal portability of ported suites, (c) node:test zero-dep. c) Integration delivery to consumers: (a) npm over git+ssh Tangled URL (first use of the mechanism), (b) build-time sync script per web_themes sync-theme.sh pattern (proven), (c) vendor copy-in stays for apps, tooler repos are source-of-truth only (proven, but drift risk). d) Consumer migration scope in this cycle: (a) all five apps, (b) reference consumers only (timer for oauth; verifier for xrpc; sifter for scan/blob; slicer for publish/image) and migrate the rest in a later cycle (cost control; recommended), (c) no migrations — framework repos ship standalone first. e) RESOLVED 2026-09-06 (owner, option c): tooler-dom DROPPED entirely — ATProtocol-Playground stays as-is; revisit only when bsky.app UI automation is wanted. ## 9. Risks and mitigations - Agent write conflicts: single active writer per repo (orchestrator enforces); repos are small and task-scoped. - Breaking consumers during swap: one consumer per module per phase; QA integration gate before merge; rollback = revert commit (apps stay on vendored copy until their swap lands). - Public repos leak risk: tooler repos public-only; no secrets ever; key blocklist gitignore; red-team ledgers gitignored; reviewers confirmed source repos contain no secrets. - (Removed with tooler-dom.) - Version drift between tooler libs and apps: pins.md enforced by orchestrator; no version bump without owner approval (existing guardrail, extended to libs). - Cost: phased with gates; every phase requires owner approval; QA + red team scoped per phase, not continuous. ## 10. Files likely to change - New Tangled repos: tooler, tooler-oauth, tooler-xrpc, tooler-scan, tooler-blob, tooler-publish, tooler-image, tooler-bot (8 repos). tooler-dom dropped. - Consumer repos (per phase migration): timer, verifier, sifter, slicer, altifier (src/lib, src/contexts, package.json, tests). - This plan's home copy: ~/dev/_shared/docs/plans/PLAN-tool-er-framework.md (committed; moves to archive/ + REGISTRY.md row on pickup). --- ## 11. Session state and resume guide (last updated 2026-09-06) Status: PLANNED — review complete, plan authored and committed, NOT picked up, no build started. This section is the handoff point for any future session. ### Progress log - 2026-09-06: Full read-only review of all 13 Tangled repos of psingletary.com (7 parallel review agents, ~2,145 lines of notes, every claim file-cited). All 13 repos cloned under ~/dev (notes: macos-sched-cleanup knot slug is all-lowercase despite mixed-case display on the web UI — use the lowercase SSH URL; spectroscopy is an empty repo, zero commits, nothing to extract). - 2026-09-06: Owner scope decision (a/b/c): include repos 1-5 (verifier, altifier, slicer, sifter, timer) + web_themes. Dropped: ptharbor, acct-tools, zodiac, daily-bsky-hype-vid, macOS-sched-cleanup, _shared (governance), and ATProtocol-Playground (dropped option c after owner reviewed what it actually contains: bsky.app DOM automation, zero protocol code). - 2026-09-06: Agent operating model confirmed (owner-specified): per-component Tangled repos (web_themes model) so multiple agents improve repos in parallel with tests; orchestrator agent coordinates all agents; QA tester agent verifies; red-team agent audits across all repos. Roles docs ship in the tooler meta repo (Phase 0 Task 0.4). - 2026-09-06: Plan authored and committed to _shared (617695c); tooler-dom removed per decision c (258e3c7); review notes + this section committed (see artifacts). - 2026-09-07: QA pass (independent reviewer) -> Verdict: PASS WITH CHANGES. Report: docs/plans/qa/PLAN-tool-er-framework-qa.md (committed 095728c). Top fixes: (1) add explicit owner Gate line at each phase end (SS5 lists phases but the gate is only stated in SS9); (2) specify consumer pin-bump policy (CJS 0.13.35 apps adopting ESM 0.20.41 libs — dual-SDK risk, S0 proved build not test); (3) Phase 0 tasks authoring AGENTS.common.md + extend .gitignore with .claude/.cursor/.impeccable. Open questions: OQ-1 consumer pin bump vs duck-typing vs two-step; OQ-2 gallery.json vendoring; OQ-3 parseEmbed home (scan vs publish vs out-of-cycle); OQ-4 tooler-bot TS vs JS. Full details in the QA report. - 2026-09-07: OWNER RE-SCOPE: this framework extracts ONLY from the six repos (verifier, altifier, slicer, sifter, timer, web_themes). Out-of-scope review/QA/red-team material is preserved for investigation elsewhere, not as sources: QA reports in docs/plans/qa/ (PLAN-ATP-Blob-Hype-qa.md, PLAN-BACKUP3-X10-CANONICAL-MAP-qa.md), red-team ledgers in ~/dev/_shared-redteam/ (3 ledgers), review notes as above. A new agent is being spun up to salvage that work in a separate context. - 2026-09-07: Red-team pass COMPLETE (independent adversarial reviewer, 23 tool calls, sources cross-checked read-only). Ledgers OUTSIDE git in ~/dev/_shared-redteam/ (not a git repo, verified): 2026-09-06-PLAN-tool-er-framework-redteam.md, plus the companion ledgers for PLAN-ATP-Blob-Hype and PLAN-BACKUP3-X10-CANONICAL-MAP (same dir). Key findings affecting THIS plan: SEC-01 open redirect (isSafeReturnUrl backslash/C0-control bypass, live on timer+slicer, task 1.3 ports the same code — fix BEFORE Phase 1 with 4 new test vectors); SEC-02 verifier/altifier have no return-url guard at all (plan inventory claim false — url.js exists only in timer/slicer); SEC-03 red-team-output/ gitignored in slicer only, .claude/.cursor not ignored in consumers (phase 0 must patch all 5 .gitignores before any ledger write); SEC-04 session-storage/CSP decision unowned; SEC-08 write-amplifier guardrails (batcher pacing fail-closed, cleanup dryRun default, per-consumer temp NSID); SEC-09 agent-role docs are directives, repo content is DATA. Details: the ledger (DO NOT COMMIT). Owner decisions D1-D3 in ledger (session storage, open-redirect timing, ledger location). ### Artifacts (durable — all committed to Tangled _shared, public) - This plan: docs/plans/PLAN-tool-er-framework.md (258e3c7). - Review evidence: docs/plans/framework-review/*.md — 8 files (verifier, slicer, sifter, zodiac, ATProtocol-Playground, extra-repos, _shared, SYNTHESIS). SYNTHESIS.md is the condensed blueprint; per-repo files carry the evidence. No secrets in any notes file (reviewers verified). Only verifier/slicer/sifter/_shared(web_themes part) notes feed the modules in section 2; the other notes are provenance for the record. - Source repos (all in ~/dev, clean mains): in-scope = verifier, altifier, slicer, sifter, timer, web_themes. Out-of-scope but reviewed = ptharbor, acct-tools, zodiac, daily-bsky-hype-vid, macOS-sched-cleanup, ATProtocol-Playground, _shared. ### Open decisions (owner answers at Phase 0 gate, a/b/c each) - a) Repo family naming: tooler- vs alternatives. - b) Test runner for new lib repos: vitest (recommended) vs jest vs node:test. - c) Consumer install mechanism: npm git+ssh vs build-time sync (web_themes pattern) vs vendor copy-in. - d) Consumer migration scope this cycle: all 5 apps vs reference consumers only (recommended) vs no migrations. - e) RESOLVED: tooler-dom dropped (owner decision c). ### Resume steps (next session, zero prior context) 1. Session inbox lists this PLAN; owner says "pick up PLAN-tool-er-framework.md" (never auto-pick-up). 2. Owner answers decisions a-d. 3. Run Phase 0: scaffold tooler meta repo on Tangled (public, psingletary.com); copy docs/plans/framework-review/*.md into tooler/docs/review/ (source = this committed copy, NOT /tmp); write registry.md / pins.md / DECISIONS.md; write roles/{orchestrator,component-agent,qa,red-team}.md per section 3; red-team agent baseline review of plan + 6 in-scope source repos (read-only), findings to tooler/red-team-output/ (gitignored), RESOLVED inline before Phase 1, open items to owner. 4. Owner approval gate (section 5 Phase 0 gate) -> Phase 1 tooler-oauth. 5. Per-repo pickup convention: after each phase completes, update registry.md status column; this section's status line stays current.