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:
- 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 <date>; findings never deleted; open questions surface to owner as a/b/c. Never fixes code.
- 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
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 -Aforbidden (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) #
- Session inbox lists this PLAN; owner says "pick up PLAN-tool-er-framework.md" (never auto-pick-up).
- Owner answers decisions a-d.
- 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.
- Owner approval gate (section 5 Phase 0 gate) -> Phase 1 tooler-oauth.
- Per-repo pickup convention: after each phase completes, update registry.md status column; this section's status line stays current.