You are implementing the full agent-readability and organization feature set for fullrefit.io, a Cloudflare Worker + D1 Markdown publishing service. REPOSITORY: /Users/paul/projects-4/fullrefit-io-markdown-service Read AGENTS.md or CLAUDE.md in this repo before changing anything, if either exists. Read src/index.ts, src/documents.ts, src/studio.ts, src/session.ts, src/ratelimit.ts, migrations/, test/worker.test.ts, and package.json before writing your first line of code. ============================================================================= GROUND TRUTH — verified 09/01/26. Do not re-derive. Do verify anything you intend to contradict. ============================================================================= - Last commit is b12308d, 2026-08-29 22:18:25 UTC. Cloudflare's last deployment was 2026-08-29 22:49:54 UTC. They correspond. - src/index.ts was modified on disk 2026-09-01 21:03 UTC and is NOT COMMITTED. The uncommitted working tree already contains: /llms.txt, /sitemap.xml, .md and .txt suffix routing, Vary: Accept, Link rel=alternate, and Accept: application/json negotiation. None of it is live. - Therefore production currently 404s on /llms.txt, /sitemap.xml, /wrap.md, /wrap.txt. Audits dated 08/31 that report those 404s are CORRECT about production and STALE about the working tree. Both are true. Do not "fix" what is already written. - /robots.txt returns 200 in production, served by Cloudflare's managed robots.txt, which intercepts before the Worker. It carries: Content-Signal: search=yes,ai-train=no,use=reference - GET /wrap returns 200, text/plain, 1871 bytes, revision 2, sha256 08c31681531adb8517481d4aba7cdd4c6b38d1a0ec914c066321723ec678e4a7. - HEAD /wrap returns 404. HEAD / returns 302 to /studio/login. HEAD /features and HEAD /how-to return 404. Cause: every public branch in src/index.ts is gated on request.method === 'GET', so non-GET falls through to Studio or not-found. - Default Python urllib receives HTTP 403 with a 17-byte body "error code: 1010" from Cloudflare, at the edge, before the Worker. NO CODE CHANGE CAN FIX THIS. It is Paul's dashboard action. Do not attempt it. Do not work around it. - There are zero production npm dependencies. Keep it that way. Do not add a runtime dependency for anything in this prompt. If you believe one is required, stop and say so. ============================================================================= THE CONSTRAINT THAT OVERRIDES EVERYTHING ELSE ============================================================================= PUBLIC DOCUMENT URLS MUST NOT CHANGE. EVER. /aoc, /wrap, and /persist are cited inside AI operating contracts and are stored in at least two agents' permanent memory. A change to the public path shape is a breaking change to systems you cannot see or notify. Specifically: folders are a Studio-only organization layer. A folder name must never appear in a public document URL. /{slug} stays flat and canonical. A document moved between folders keeps its address byte-for-byte. If any instruction below appears to conflict with this, this constraint wins and you stop and report the conflict. ============================================================================= EXECUTION MODEL — one phase at a time, tested, committed, then the next ============================================================================= Work through the phases in order. For EACH phase: 1. Implement it. 2. Run: npm run validate (this is typecheck + tests + local D1 migrations + wrangler deploy --dry-run) 3. Add tests for the new behavior. Every phase adds tests. A phase with no new test is not complete. 4. Run npm run validate again until clean. 5. Commit with a message naming the phase. 6. Report: what changed, what the tests now cover, what you verified, and what you could not verify from this surface. 7. STOP and wait for Paul before starting the next phase. Do not batch phases. Do not deploy. Deployment is Paul's action at every phase boundary; say when a phase is ready to deploy and let him run it. Exception: if a phase's work is trivially small, say so and propose merging it with the next, but wait for agreement before doing it. ============================================================================= PHASE 0 — Ship what already exists ============================================================================= The working tree is three days ahead of production and uncommitted. Before adding anything, get the existing work into a shippable, tested state. TASKS 0.1 Read the full uncommitted diff. Summarize every behavior change it introduces. 0.2 Verify each new route works in local dev: /llms.txt, /sitemap.xml, /{slug}.md, /{slug}.txt, Accept: text/markdown, Accept: application/json. 0.3 Verify /llms.txt does not advertise any path that 404s. An llms.txt pointing at a missing document is worse than no llms.txt. 0.4 Add tests covering every route and every content-negotiation branch above. 0.5 npm run validate until clean. Commit. DONE MEANS Local dev serves all of the above correctly, tests cover them, validate is clean, and the work is committed on a branch ready for Paul to deploy. WRONG LOOKS LIKE Rewriting the existing implementation because you would have done it differently. Deploying. Adding features from later phases. ============================================================================= PHASE 1 — Folders (Paul's top-priority feature) ============================================================================= Studio-only organization for documents. No public URL impact whatsoever. 1.1 MIGRATION — migrations/0003_folders.sql CREATE TABLE IF NOT EXISTS folders ( id TEXT PRIMARY KEY, name TEXT NOT NULL, parent_id TEXT REFERENCES folders(id) ON DELETE RESTRICT, sort_order INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); ALTER TABLE documents ADD COLUMN folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL; CREATE INDEX IF NOT EXISTS idx_documents_folder ON documents(folder_id); CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id); Rationale you must preserve: - folder_id nullable. "Unfiled" is a valid default. Existing rows need no backfill. - ON DELETE SET NULL on documents. Deleting a folder MUST NEVER delete a document. - ON DELETE RESTRICT on parent_id. A folder with subfolders cannot be silently removed. - Folder id is an opaque token, never a slug. Names are display-only and freely renamable. 1.2 MODEL — extend src/documents.ts Add: createFolder, listFolders (as a tree), renameFolder, moveFolder, deleteFolder, setDocumentFolder. Extend listDocuments to return folder_id and folder name. CYCLE AND DEPTH SAFETY, both required, both tested: - Before assigning parent_id, walk the ancestor chain from the proposed parent. Reject if the folder being moved appears in that chain. - Enforce maximum nesting depth of 3. Reject deeper. 1.3 RESERVED SLUGS — src/documents.ts Add 'folders' to RESERVED_SLUGS before the route ships, or a document named "folders" will shadow the Studio route. While you are in that set: 'robots.txt', 'favicon.ico', and 'sitemap.xml' can never match SLUG_PATTERN, which forbids dots. They are dead entries. Do not remove them, but add a one-line comment saying so. Also add 'robots' (no extension) — see Phase 2.4. 1.4 STUDIO ROUTES — src/studio.ts GET /studio library grouped by folder; Unfiled section last GET /studio?folder={id} filter to one folder GET /studio/folders manage: list, create form, rename, delete POST /studio/folders create POST /studio/folders/{id} rename / re-parent / delete via a _action field Every POST uses the SAME guards the document writes already use: originAllowed, csrfValid, and the WRITE_LIMIT rate check. Do not invent a new guard path. Add a folder