From 3ea96e49b221de67d0cd58ca500b6f3ba6cf1773 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Sat, 8 Aug 2026 03:38:25 +0700 Subject: [PATCH 1/3] docs: record agent skill config, symlink CLAUDE.md to AGENTS.md Adds docs/agents/{issue-tracker,triage-labels,domain}.md so the engineering skills know where issues live (Gitea via tea, not gh), which triage labels to apply, and that domain docs are single-context. Every CLAUDE.md was a stale subset of the AGENTS.md beside it, so each is now a symlink and AGENTS.md is the single source of truth. --- AGENTS.md | 16 ++++++ CLAUDE.md | 107 +---------------------------------- backend/CLAUDE.md | 82 +-------------------------- docs/agents/domain.md | 47 +++++++++++++++ docs/agents/issue-tracker.md | 60 ++++++++++++++++++++ docs/agents/triage-labels.md | 20 +++++++ userscript/CLAUDE.md | 65 +-------------------- 7 files changed, 146 insertions(+), 251 deletions(-) mode change 100644 => 120000 CLAUDE.md mode change 100644 => 120000 backend/CLAUDE.md create mode 100644 docs/agents/domain.md create mode 100644 docs/agents/issue-tracker.md create mode 100644 docs/agents/triage-labels.md mode change 100644 => 120000 userscript/CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 5779425..1730d07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,6 +133,22 @@ Test: "competent reader get this from code in few sec?" Yes → skip. Needs deto `golang-code-style`, `golang-error-handling`, `golang-performance`, `golang-testing` for backend Go work. +## Agent skills + +`AGENTS.md` is the single source of truth for agent guidance; every `CLAUDE.md` in this repo is a symlink to the `AGENTS.md` beside it. Edit `AGENTS.md`. + +### Issue tracker + +Issues live as Gitea issues on `gitea.violetcrown.my.id` (`sulthan/mangaBookmark`), driven by the `tea` CLI — not `gh`. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Default five-role vocabulary, label strings unchanged (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context: one root `CONTEXT.md` plus `docs/adr/`, both created lazily. See `docs/agents/domain.md`. + ## graphify Project has knowledge graph at graphify-out/ with god nodes, community structure, cross-file relationships. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index c3809e1..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,106 +0,0 @@ -# CLAUDE.md - -Guidance for Claude Code (claude.ai/code) working in this repo. - -## What this is - -Manga read-progress tracker, user read on **asurascans.com** (current domain; asuracomic.net 301s here) and **demonicscans.org** via **Violentmonkey**. Userscript inject on-page UI (floating button + slide-in panel), sync progress to self-hosted Go backend so bookmarks unify across both sites and devices. - -## Hard constraints (drive design — don't violate) - -Userscript targets **Violentmonkey**, so `GM_*` APIs available, but stay GM-free where plain web APIs suffice — keeps portability across engines: -- **Avoid `GM_*` unless needed.** Prefer page `localStorage` over `GM_setValue`/`GM_getValue`, on-page UI over `GM_registerMenuCommand`, plain `fetch()` over `GM_xmlhttpRequest` for cross-origin. -- Cross-origin `fetch()` work **only** against CORS-enabled backend. Manga sites `https://`, so backend **must be HTTPS** (else mixed-content block). -- Asura and Demonic are **separate origins with separate `localStorage`** — shared remote store only way to unify bookmarks. Cloud sync required, not optional. -- Userscript run in **isolated world**, so embedded API token safe from site's JS. -- Cloudflare's block on manga sites **IP-reputation-based, not universal — and not reliably reproducible.** Verified 2026-07-26: plain `curl` from both CGNAT dev machine *and* deployed VPS got clean 200s with real HTML on both asurascans.com and demonicscans.org (homepage, series, chapter pages) — no interactive Turnstile challenge from either IP at test time. Contradicts earlier untested assumption CGNAT dev IP blocked; wasn't, at least this date. Treat "does curl work right now" as live, time-varying fact to re-check, not fixed property of machine — Cloudflare's bot scoring can flip previously-clean IP without notice. Backend fetcher still needs graceful-degrade path for when challenged, and adapters should be **verified against live pages** (Playwright MCP, on-device devtools, or direct probe) before finalize, not assumed from single earlier test. - -## Architecture - -``` -Violentmonkey userscript (isolated world, per-site adapters, localStorage cache) - -- fetch() HTTPS --> reverse proxy (TLS + CORS) --> Go net/http --> SQLite (volume) -``` - -Backend-specific architecture (packages, endpoints, poller, config env vars) lives in `backend/CLAUDE.md`. Userscript-specific structure (adapters, retry queue, UI, live URL shapes) lives in `userscript/CLAUDE.md`. - -## Commands - -Backend (`cd backend`): -- Test all: `go test ./...` -- Single test: `go test -run TestName ./...` -- Build static binary: `CGO_ENABLED=0 go build` - -Local stack: `docker compose up` (named volume mounted at `/data`, `restart: unless-stopped`). - -Smoke test: `curl` endpoints with `Authorization: Bearer `; confirm `OPTIONS` preflight return CORS headers and `/healthz` return 200. - -## Forge: Gitea, not GitHub - -`origin` is self-hosted Gitea instance (`gitea.violetcrown.my.id`), so **`gh` don't work here — use `tea` (Gitea CLI) for anything past plain git.** Common ones: - -- Open PR: `tea pr create --head --base main --title "..." --description "..."` -- List / view / check out: `tea pr list`, `tea pr `, `tea pr checkout ` -- Issues: `tea issue create`, `tea issue list` -- Auth lives in `tea login`, not `GH_TOKEN` env var. - -`tea` print output as rendered boxes rather than plain text; PR URL lands on last line. - -## Design system - -Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md` -— source of truth Claude Design project `BookmarkManager Web UI` -(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching -`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript -`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other -state (busy, error, destruction) may use `--ember`; destruction gets -`--danger`. No cards/corners/shadows, one `--measure: 760px` column, tokens -only (never hardcode hex outside `:root`), both colour branches touched -together. Any move that pulls series out of list (archive/finish/remove) -must be confirm-gated via its own `.confirm-row`; only restore fires -instantly. - -## Security invariants - -- Auth on `/bookmarks*`: require `Authorization: Bearer `, **constant-time compare**, 401 otherwise. -- CORS: reflect `Origin` only when in `ALLOWED_ORIGINS`; allow `GET,PUT,DELETE,OPTIONS` + headers `Authorization,Content-Type`; answer preflight `OPTIONS` with `204`. - -## Comments - -Comment only if code alone can't carry info. Cost per read — must earn spot. - -Write for: -- Why not what. Tradeoffs, non-obvious decisions. -- Load-bearing detail looking incidental — say so if "simplify" breaks it. -- Non-local consequence, invisible from function alone. -- Wire format / encoding / interface contract — save callers re-deriving. -- Gotcha/workaround, with ref if exists. -- Domain/business rule not derivable from code. - -Skip: -- Restating code (no `// increment i` above `i++`). -- Trivial getter/setter/pass-through. -- Banners, dividers, `// helpers`. -- Change narration (`// fix bug`, `// as requested`, `// new impl`) — git's job. -- Commented-out code — delete. -- TODO without concrete action. - -Style: one dense comment over function beats one per line inside. Tight, no worked example unless bug subtle. Wrong comment worse than none — update/delete on change. Default fewer — sparse+high-signal beats comprehensive. - -Test: "competent reader get this from code in few sec?" Yes → skip. Needs detour through another file/spec/git-blame → write it. - -## Relevant skills - -`multi-stage-dockerfile` and `docker-compose-orchestration` for container work (referenced in plan). - -`golang-code-style`, `golang-error-handling`, `golang-performance`, `golang-testing` for backend Go work. - -## graphify - -Project has knowledge graph at graphify-out/ with god nodes, community structure, cross-file relationships. - -Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. Return scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. -- If graphify-out/wiki/index.md exists, use for broad navigation instead of raw source browsing. -- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain don't surface enough context. -- After modifying code, run `graphify update .` to keep graph current (AST-only, no API cost). \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md deleted file mode 100644 index 83d17e8..0000000 --- a/backend/CLAUDE.md +++ /dev/null @@ -1,81 +0,0 @@ -Guidance for Claude Code working under `backend/`. See root `CLAUDE.md` for the project-wide architecture diagram, hard constraints, and design system. - -- **Backend** (`backend/`): stdlib `net/http` (handful routes, no framework) + `modernc.org/sqlite` (pure Go, `CGO_ENABLED=0` -> static binary -> distroless/scratch image). Reverse proxy terminates TLS; Go service listens plain `:8080`. - Single binary, split into packages under `backend/internal/`: `store` - (Bookmark type, SQLite persistence, migrations), `latest` (background - poller, site parsers, TLS fetcher), `session` (cookie signing, login - rate limiter), `httpmw` (Auth/Gzip/CORS middleware), `api` (JSON - bookmark handlers), `userscript` (userscript-serving handler), `web` - (browser UI handler + `templates/` + `static/`, `go:embed`-ed). - `backend/main.go` is the composition root — the only place that wires - packages together into `newRouter`. Root-level `*_test.go` hold - integration tests that exercise the full router; unit tests for a - package live beside it under `internal/`. -- **Single-user store.** One `bookmarks` table keyed `:` (`asura`|`demonic`|`comix`|`kagane`). Sync **last-write-wins**. Schema and endpoint list in plan. -- **Endpoints:** `GET /bookmarks`, `PUT /bookmarks/{key}` (upsert; see `updated_at` rule below), `DELETE /bookmarks/{key}`, `GET /healthz` (no auth). -- **Web UI:** same binary serve password-gated browser UI on second - hostname — `GET /` (list, or login page when no session), - `POST /login`, `POST /logout`, `GET /static/*`, htmx fragment endpoints - under `/ui/*`. Templates + assets `go:embed`-ed under - `backend/internal/web/`, so `backend/Dockerfile` must copy the whole - `internal/` tree, not just `*.go`. Sessions stateless - HMAC cookies keyed off `API_TOKEN`; `WEB_PASSWORD` gates them, and when empty, - web routes not registered at all. UI mutations read-modify-write - through `Store.Get` + `Store.Upsert` so `updated_at` rule stays one - place. See `docs/superpowers/specs/2026-07-25-web-ui-design.md`. - **Design-tool caveat:** templates link `/static/style.css` root-absolutely - (correct — served from `/`), but impeccable detector resolves - stylesheet href with `path.resolve(fileDir, href)`, drops directory - on leading `/` and silently skip file. Relative href don't help - either: template's directory isn't its served path. So - `detect.mjs backend/internal/web/templates` reports **false clean** — - always pass `backend/internal/web/static` too. One finding there, - `overused-font` on "Instrument Serif", deliberate identity choice, not debt. -- **Every action that moves series out of list is confirm-gated.** - Archive, finish, remove each open own `.confirm-row` disclosure - (`toggleConfirmRow(key, kind)` in `filter.js`, `kind` ∈ - `archive|finish|remove`); restore fire instantly since it's the reversal. - Remove's row wear ember wash, two reversible ones wear `.calm` grey. - `--ember` stay reserved for new-chapter signal: busy bar and inline - error use `--mute`. -- **Latest-chapter poller:** ticker goroutine in same binary re-check - each bookmarked series' newest published chapter from backend's own - network access, so `latest_chapter` stay fresh when user not - browsing. Second, parallel signal — userscript keep own - `maybeCaptureLatestOnSeriesPage`/`backgroundRefreshLatest` logic unchanged. - Two independent clocks: per-bookmark cooldown (`latest_checked_at` column, - enforced by `Store.DueForLatestCheck`'s WHERE clause) and wake interval. - Row stamped *before* fetch so broken series wait out full - cooldown instead of retrying every tick, and writes go through - `Store.Get` + `Store.Upsert` so new chapter never reorders list. - Fetches use `bogdanfinn/tls-client` with Chrome profile as defence in depth - against fingerprint-based blocking; any failure log and skip. kagane sits - behind a Cloudflare JavaScript challenge the TLS client can't clear, so it is - browser-only: fetched over CDP via `BROWSER_WS_URL`, and simply not polled - when that's unset. See - `docs/superpowers/specs/2026-07-26-server-latest-chapter-polling-design.md`. - Poller's `Store.Get` + `Store.Upsert` not wrapped in transaction, so - userscript `PUT` that commits between the two can get overwritten by - poller's stale re-read — reverting that read progress and, since stored - value now differs, moving `updated_at` and reordering list. Known, - accepted limitation for single-user deployment, not bug to fix. -- **`updated_at` drives list order, so moves only on real reading progress:** server apply its timestamp when row new or `last_chapter_num` changes, else keep stored value — favouriting series or recording newly published chapter must not reorder list. `PUT` therefore returns row **as stored**, clients must adopt that response rather than own payload. See `plans/2026-07-25-bookmark-list-favorites-design.md` §4. -- **Lifecycle buckets:** `status` on each bookmark is `reading` | `archived` | - `finished`, orthogonal to `favorite`. Archived and finished appear only in - own tab — not in All, Updated, Favourites, or recent strip. Poller keeps - checking archived series and skip finished ones. `finished` settable - only from web UI; `PUT /bookmarks/{key}` reject it with 400. - **Empty incoming status means "keep stored one"** — resolved on the - `VALUES` side of `Store.Upsert`, not conflict clause, since - `excluded.*` is post-evaluation row and default applied there would - wipe bucket on every PUT from client that predates column. See - `docs/superpowers/specs/2026-07-27-status-buckets-design.md`. -- **Config via env:** `API_TOKEN`, `ALLOWED_ORIGINS` (comma list), `DB_PATH` - (default `/data/bookmarks.db`), `PORT` (default `8080`), `WEB_PASSWORD` - (gates browser UI; unset disable it), - `LATEST_CHAPTER_POLL_ENABLED`/`_COOLDOWN`/`_INTERVAL`/`_BATCH`/`_STAGGER` - (background latest-chapter poller; defaults on, `1h`/`10m`/`14`/`20s`). - `USERSCRIPT_PATH` (file served at `/u/{token}/manga-bookmark.user.js`, - default `/userscript/manga-bookmark.user.js`, supplied by bindmount). - `BROWSER_WS_URL` (headless-shell CDP endpoint for kagane; unset disables - browser polling and leaves that site to the userscript alone). diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..ca179ff --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,47 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the +codebase. Layout: **single-context** — one `CONTEXT.md` plus `docs/adr/` at the repo root. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root — the glossary / ubiquitous language. +- **`docs/adr/`** — read ADRs that touch the area you're about to work in. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest +creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and +`/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +Neither exists yet in this repo. The existing `AGENTS.md` / `CLAUDE.md` and `docs/design-system.md` +carry the current architecture and design law — read those regardless. + +## File structure + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-....md +│ └── 0002-....md +├── backend/ +└── userscript/ +``` + +If this repo ever splits into genuinely separate contexts, add a root `CONTEXT-MAP.md` pointing at +one `CONTEXT.md` per context and update this file. + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a +test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary +explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal — either you're inventing +language the project doesn't use (reconsider) or there's a real gap (note it for +`/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0002 (…) — but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..d0ba10a --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,60 @@ +# Issue tracker: Gitea (`tea` CLI) + +Issues and specs for this repo live as issues on the self-hosted Gitea instance +`gitea.violetcrown.my.id` (repo `sulthan/mangaBookmark`). **`gh` does not work here** — use +[`tea`](https://gitea.com/gitea/tea) for everything past plain git. Auth lives in `tea login`, +not a `GH_TOKEN` env var. `tea` infers the repo from the local clone's `origin`. + +`tea` prints rendered boxes rather than plain text; pass `--output json` (or `-o json`) when a +skill needs to parse the result. + +## Conventions + +- **Create an issue**: `tea issue create --title "..." --description "..."` (`--labels`, + `--assignees` optional). Multi-line bodies: pass the body through a shell variable or heredoc. +- **Read an issue**: `tea issue --comments` (add `-o json` for machine-readable output). +- **List issues**: `tea issue list --state open -o json --fields index,title,body,labels,state,author`; + filter with `--labels "..."`, `--state open|closed|all`, `--assignee`, `--keyword`. +- **Comment**: `tea comment "..."` (alias of `tea comments add`). +- **Apply / remove labels**: `tea issue edit --add-labels "..."` / `--remove-labels "..."`. + Labels must exist first — see `tea labels list` / `tea labels create --name "..." --color "#rrggbb"`. +- **Close**: `tea issue close ` (comment separately with `tea comment`; `close` takes no + `--comment` flag). + +## Pull requests as a triage surface + +**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature +requests; `/triage` reads this flag.)_ + +When set to `yes`, PRs run through the same labels and states as issues, using the `tea pr` +equivalents: `tea pr --comments`, `tea pr list --state open -o json`, +`tea pr create --head --base main --title "..." --description "..."`, `tea comment`, +`tea pr close`. Gitea shares one index space across issues and PRs, so a bare `#42` may be either +— resolve with `tea pr 42` and fall back to `tea issue 42`. + +## When a skill says "publish to the issue tracker" + +Create a Gitea issue with `tea issue create`. + +## When a skill says "fetch the relevant ticket" + +Run `tea issue --comments`. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a single issue; **tickets** are child issues. + +- **Map**: one issue labelled `wayfinder:map` holding the Notes / Decisions-so-far / Fog body. + `tea issue create --labels wayfinder:map --title "..." --description "..."`. +- **Child ticket**: an issue labelled `wayfinder:` (`research`/`prototype`/`grilling`/`task`) + with `Part of #` as the first body line, and a task-list entry in the map body. `tea` has no + sub-issue command, so the task list plus the `Part of` line is the canonical link. +- **Blocking**: a `Blocked by: #, #` line at the top of the child body. Gitea's native issue + dependencies exist in the API but `tea` does not expose them; the body line is the source of + truth. A ticket is unblocked when every listed blocker is closed. +- **Frontier query**: `tea issue list --state open -o json` scoped to the map's task list; drop any + ticket with an open blocker or an assignee; first in map order wins. +- **Claim**: `tea issue edit --add-assignees ` — the session's first write. + (`tea` has no `@me` shorthand; use the Gitea username from `tea login list`.) +- **Resolve**: `tea comment ""`, then `tea issue close `, then append a context + pointer to the map's Decisions-so-far via `tea issue edit --description "..."`. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 0000000..d068ec1 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,20 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual +label strings used in this repo's issue tracker (Gitea — see `docs/agents/issue-tracker.md`). + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label +string from this table. + +Gitea will not auto-create labels on `tea issue edit --add-labels`; create a missing one first with +`tea labels create --name "