agentleFS
igorkasyanchukigorkasyanchuk/diffnotes/AGENTS.md

diffnotes

AGENTS.md4 starsChanged 11 days agoMITView on GitHub

See README.md for what it is. Same stack and habits as specdrop. - Server: Node 24 + Hono, built-in node:sqlite, .ts run directly through type stripping (no build step): relative imports need .ts, import type for types, no enum / namespace / parameter properties. - Web: Vite + React 19, no router lib (web/main.tsx switches on location.pathname), plain CSS tokens in web/styles.css. - Extension: Chrome MV3, plain JS, no build. extension/markdown.js is the one Markdown renderer, also imported by the…

  • Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.

    Of the AGENTS.md files we read, the 31,843 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository2 other files it gives its agents.

The file

We split this AGENTS.md into the rules it gives an agent

3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Keep it small: few files, no dependency for what a few lines do.

    line 39
  2. Very specific:

    Every limit is an env var (MAX_*, docs/install.md).

    line 59
  3. Very specific:

    Keep docs/ in sync when routes, limits or features change (and the skill, skill/diffnotes/SKILL.md).

    line 62

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

DiffNotes

See README.md for what it is. Same stack and habits as specdrop.

Stack

  • Server: Node 24 + Hono, built-in node:sqlite, .ts run directly through type stripping (no build step): relative imports need .ts, import type for types, no enum / namespace / parameter properties.
  • Web: Vite + React 19, no router lib (web/main.tsx switches on location.pathname), plain CSS tokens in web/styles.css.
  • Extension: Chrome MV3, plain JS, no build. extension/markdown.js is the one Markdown renderer, also imported by the web app.

Layout

server/db.ts      schema, password hashing, bootstrap (first admin + account), tokens, purgeExpired
server/app.ts     all routes: auth (password, GitHub OAuth), invites, accounts, repos, tokens, /api/v1 pulls (skill + extension), /mcp (MCP tools over the same savePull/findPull), /files, /install.sh
server/storage.ts file bytes on disk or in an S3-compatible bucket (STORAGE_TYPE + provider env vars, SigV4 signing, retries, bucket check);
                  PRICES: hard-coded list prices (dated PRICES_AS_OF) for /admin's monthly cost estimate
server/index.ts   start, hourly purge, SIGTERM
docs/             user docs linked from README (install, deploy, skill, extension, accounts, storage, security, api) + images;
                  docs/store/ the Chrome Web Store listing and its images
web/main.tsx      routing, public + app (sidebar) layouts, login, invite, privacy, dashboard, account, pull preview, settings
web/landing.tsx   landing page (GitHub walkthrough, install), plus the Code/Copy block; its CSS is web/landing.css
web/ssr.tsx       prerenders the public pages (/, /docs, /badge, /terms) for crawlers: `npm run build` bundles it to dist-ssr/,
                  page() (app.ts) puts the HTML in #root; react is a runtime dependency for this
skill/diffnotes/  the agent skill, served at /skill/SKILL.md with {{HOST}}, {{VERSION}} and the limits filled in; /skill/version
                  is the release plus a hash of that skill, which an installed copy compares with its own (it then asks the user to re-run install.sh)
.diffnotes.yml    this repository's own annotation rules, which the skill reads from a PR's base branch
extension/        manifest, demo.html (sandbox for .html demos), background (holds the tokens, asks every connected server), content (draws on GitHub, GitLab and Bitbucket diffs:
                  box above the diff, floating dock with a notes switch once it scrolls away), popup (servers list)
Dockerfile        image (ghcr.io/igorkasyanchuk/diffnotes, pushed from the owner's machine with `npm run image` after each release:
                  amd64 + arm64, :X.Y.Z and :latest; no CI on GitHub, the checks below run locally); docker-entrypoint.sh gives /data to `node` and drops root. docker-compose.yml, render.yaml, fly.toml: deploy templates
SECURITY.md       how to report a vulnerability (GitHub private advisories)
CHANGELOG.md      one version for server, web, extension (package.json + extension/manifest.json), tagged vX.Y.Z

Rules

  • Keep it small: few files, no dependency for what a few lines do. Mark deliberate shortcuts with // ponytail:.
  • Access: everything about a PR is visible only to members of the account that owns its repository; other accounts' rows answer 404. Tokens and sessions are stored as sha256 only; the extension keeps its tokens in its own IndexedDB (vault, background.js), never in storage.local, which content scripts can read.
  • Roles per account (memberships.role): owner (made it; can't be removed or demoted; deletes the account), admin (invites, changes roles, manages repositories, renames), member (reads, uploads, removes its own notes; every note and file has its author, annotations.user_id / files.user_id, and an upload replaces only the uploader's own). users.admin = super admin: runs the server, sees /admin (/api/admin/*, 404 for others) and may open and manage any account, but reads notes and files (isMember()) only in accounts it belongs to. The first user (bootstrap) is a super admin.
  • Annotation Markdown is untrusted: render it only through dnMarkdown (escapes everything, keeps http(s) and relative URLs), deploy cards through dnDeploy; any other text from a server goes in as text (textContent, JSX), never as HTML.
  • A PR's preview is https only (previewUrl) and a note's try a path (tryPath), joined only by dnPreviewUrl (markdown.js): it stays under the preview's own path (no other host or scheme, no ../). The extension re-checks what every connected server sends: a bad value draws nothing instead of breaking the box. External links get rel=noopener noreferrer.
  • /files/* is served with CSP: sandbox + nosniff; text uploads as text/plain. .html demos are the exception: text/html with DEMO_CSP (app.ts; opaque sandbox, no network). The extension runs them only in its sandboxed extension/demo.html (manifest sandbox + the same CSP), never in github.com's page. Keep the two policies equal. Note videos (.mp4/.webm) play in that demo.html too: github.com's CSP refuses data:/blob: media in its page.
  • Uploads: allow-list in EXT_TYPES (db.ts), checked by content in fileType() (app.ts), all through savePull() (PUT and MCP). Every limit is an env var (MAX_*, docs/install.md); the served skill shows the server's values ({{MAX_FILE_MB}}…). Every file has a files row (size, type, backend disk|s3); stats and quotas read the rows, never the disk or the bucket.
  • Deleting PRs goes through dropPulls() so their files are removed from whichever backend holds them.
  • Keep docs/ in sync when routes, limits or features change (and the skill, skill/diffnotes/SKILL.md). In the skill's shell snippets, values from a PR (branch names, titles) come from the CLI into quoted variables ("$B"), never typed into a command: a branch can be named x$(…). The skill never fetches and follows instructions on its own. A new env var: a row in docs/install.md's Environment table (the one reference) and a line in docker-compose.yml and the templates.
  • A PR's own fields (title, head_sha, summary, deploy card, preview, stack) are its latest upload's; fields_by says whether one person wrote them all (the live demo, DEMO_PR, shows only a PR whose fields and notes are all a super admin's; it is public: /api/demo and its files answer signed-out visitors).
  • Repositories: owner/name = GitHub, host/path = GitLab (any host but bitbucket.org) or Bitbucket; repoName() (app.ts) normalizes URLs and clone addresses, the web app's prUrl() builds links back. Unique per account (not server-wide): always look names up within the caller's accounts.
  • Extension sign-in (/connect, app.ts): one-time code + PKCE, codes only to EXTENSION_IDS redirect addresses; the background (not the popup) runs launchWebAuthFlow. Content scripts: the newest copy on a page owns it (dnOwner), a copy that gets notes takes it back.
  • Bearer tokens authenticate only /api/v1/*, /files/* and /mcp (which takes nothing else, never the cookie); cookie writes need Sec-Fetch-Site: same-origin (or this site's Origin); only a Bearer header skips that check (a Basic auth proxy's doesn't). Invitations by email are listed only to a user whose email GitHub proved.
  • The web app's page gets PAGE_CSP and its __ORIGIN__ filled (app.ts page()); keep index.html's SEO tags there. A new web app address goes in both App (main.tsx) and PAGES (app.ts): any other path is a 404 that shows "not found". Prerendered pages (web/ssr.tsx) render with no browser globals but location: guard document/window at module level.
  • Diff DOM, one adapter per host in SITES (extension/content.js); if notes stop appearing, re-check these first: GitHub React view (h3[class*=file-name], td[data-diff-side][data-line-number]) and classic (.file-header[data-path], td.blob-num); GitLab rapid diffs (diff-file, rd-diff-file-copy-clipboard, td.rd-line-number[data-position] a[data-line-number]); Bitbucket ([data-qa=branch-diff-file], [data-qa=bk-filepath], [data-old-line-number]/[data-new-line-number], rows [role=row]).

Commands

npm test          # vitest, server/app.test.ts is one sequential story; edges.test.ts the limits and errors
npm run coverage  # server + extension/markdown.js, fails under 95% lines/statements/functions, 90% branches
npm run typecheck
npm run build
npm run image     # after a release: build and push ghcr.io/igorkasyanchuk/diffnotes:<version> and :latest (docker login ghcr.io first)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.