From 5083efb15039cbdd28774b46ce70b1f7264cbec8 Mon Sep 17 00:00:00 2001 From: retoor Date: Tue, 7 Jul 2026 16:09:28 +0200 Subject: [PATCH] Update --- .claude/agents/docs-maintainer.md | 13 +- .claude/agents/fanout-maintainer.md | 2 +- .claude/agents/feature-builder.md | 6 +- .claude/agents/style-maintainer.md | 4 +- .claude/commands/explain.md | 6 +- .claude/commands/maintenance.md | 2 +- .claude/commands/service.md | 2 +- .claude/workflows/feature.js | 4 +- .claude/workflows/job-service.js | 2 +- AGENTS.md | 2549 ----------------- CLAUDE.md | 385 +-- README.md | 2 +- devplacepy/database/CLAUDE.md | 200 ++ devplacepy/routers/CLAUDE.md | 335 +++ devplacepy/routers/devrant/CLAUDE.md | 23 + devplacepy/routers/docs/CLAUDE.md | 71 + devplacepy/routers/projects/CLAUDE.md | 44 + devplacepy/services/CLAUDE.md | 190 ++ devplacepy/services/audit/CLAUDE.md | 58 + devplacepy/services/backup/CLAUDE.md | 32 + devplacepy/services/bot/CLAUDE.md | 60 + devplacepy/services/containers/CLAUDE.md | 200 ++ devplacepy/services/dbapi/CLAUDE.md | 41 + devplacepy/services/devii/CLAUDE.md | 153 + devplacepy/services/email/CLAUDE.md | 32 + devplacepy/services/game/CLAUDE.md | 57 + devplacepy/services/gitea/CLAUDE.md | 48 + devplacepy/services/jobs/CLAUDE.md | 127 + devplacepy/services/messaging/CLAUDE.md | 49 + devplacepy/services/news/CLAUDE.md | 46 + devplacepy/services/openai_gateway/CLAUDE.md | 90 + devplacepy/services/pubsub/CLAUDE.md | 33 + devplacepy/services/telegram/CLAUDE.md | 41 + devplacepy/services/xmlrpc/CLAUDE.md | 21 + devplacepy/static/js/CLAUDE.md | 94 + devplacepy/templates/CLAUDE.md | 79 + .../templates/docs/architecture-workflow.html | 11 +- devplacepy/templates/docs/claude-agents.html | 2 +- .../templates/docs/claude-commands.html | 2 +- .../templates/docs/getting-started.html | 2 +- devplacepy/utils/CLAUDE.md | 114 + tests/CLAUDE.md | 130 + 42 files changed, 2556 insertions(+), 2806 deletions(-) delete mode 100644 AGENTS.md create mode 100644 devplacepy/database/CLAUDE.md create mode 100644 devplacepy/routers/CLAUDE.md create mode 100644 devplacepy/routers/devrant/CLAUDE.md create mode 100644 devplacepy/routers/docs/CLAUDE.md create mode 100644 devplacepy/routers/projects/CLAUDE.md create mode 100644 devplacepy/services/CLAUDE.md create mode 100644 devplacepy/services/audit/CLAUDE.md create mode 100644 devplacepy/services/backup/CLAUDE.md create mode 100644 devplacepy/services/bot/CLAUDE.md create mode 100644 devplacepy/services/containers/CLAUDE.md create mode 100644 devplacepy/services/dbapi/CLAUDE.md create mode 100644 devplacepy/services/devii/CLAUDE.md create mode 100644 devplacepy/services/email/CLAUDE.md create mode 100644 devplacepy/services/game/CLAUDE.md create mode 100644 devplacepy/services/gitea/CLAUDE.md create mode 100644 devplacepy/services/jobs/CLAUDE.md create mode 100644 devplacepy/services/messaging/CLAUDE.md create mode 100644 devplacepy/services/news/CLAUDE.md create mode 100644 devplacepy/services/openai_gateway/CLAUDE.md create mode 100644 devplacepy/services/pubsub/CLAUDE.md create mode 100644 devplacepy/services/telegram/CLAUDE.md create mode 100644 devplacepy/services/xmlrpc/CLAUDE.md create mode 100644 devplacepy/static/js/CLAUDE.md create mode 100644 devplacepy/templates/CLAUDE.md create mode 100644 devplacepy/utils/CLAUDE.md create mode 100644 tests/CLAUDE.md diff --git a/.claude/agents/docs-maintainer.md b/.claude/agents/docs-maintainer.md index cdf73357..a9949e18 100644 --- a/.claude/agents/docs-maintainer.md +++ b/.claude/agents/docs-maintainer.md @@ -1,6 +1,6 @@ --- name: docs-maintainer -description: Documentation coverage and role-aware show/hide maintainer. Keeps CLAUDE.md, AGENTS.md, README.md, docs_api.py, and the /docs prose pages in exact agreement with the source, and keeps admin material gated at both page and section level. Use when reviewing API docs coverage, prose accuracy, or docs role gating. +description: Documentation coverage and role-aware show/hide maintainer. Keeps every CLAUDE.md (root and nested per-subsystem), README.md, docs_api.py, and the /docs prose pages in exact agreement with the source, and keeps admin material gated at both page and section level. Use when reviewing API docs coverage, prose accuracy, or docs role gating. tools: Read, Grep, Glob, Edit, Write, Bash model: inherit color: blue @@ -36,23 +36,26 @@ Default to **REPORT** mode: record findings, do NOT modify files. Apply **FIX** No comments or docstrings in source you author; no em-dashes (use a hyphen); keep `retoor ` as the first line of any file you create. ## Your dimension -Keep `CLAUDE.md`, `AGENTS.md`, `README.md`, and the `/docs` pages in exact agreement with the source, and keep role-based visibility consistent so admin material is shown to admins and hidden from members and guests at both the page and the section level. +Keep every `CLAUDE.md`, `README.md`, and the `/docs` pages in exact agreement with the source, and keep role-based visibility consistent so admin material is shown to admins and hidden from members and guests at both the page and the section level. + +**`CLAUDE.md` is split, not monolithic.** The root `/CLAUDE.md` holds only cross-cutting rules (Claude Code loads it eagerly, every session). Each subsystem directory (e.g. `devplacepy/services/devii/`, `devplacepy/routers/projects/`, `devplacepy/database/`, `tests/`) has its own nested `CLAUDE.md` with that subsystem's full mechanic/pitfall/gotcha coverage, loaded automatically by Claude Code only when a file in that directory is read or edited. There is no `AGENTS.md` - it was removed and its content redistributed into the root file plus the nested files. **Treat the reappearance of a top-level `AGENTS.md`, or any doc/prose page referencing one, as an error to fix (delete the file / repoint the reference at the correct root-or-nested `CLAUDE.md`).** DETECT: - Every public or authenticated REST route has a `docs_api.endpoint()` entry in the correct group, with params and a `sample_response`. A documented route whose params drifted from the actual Form model is an error. - Every prose page's factual claims match the code (routes, env vars, defaults, behavior). A stale claim is an error. -- `README.md` reflects current routes, env vars, dependencies, and user-visible features. `AGENTS.md` has a domain section for every mechanic. `CLAUDE.md` changes only for a new architectural rule. +- `README.md` reflects current routes, env vars, dependencies, and user-visible features. Every nested `CLAUDE.md` has full coverage of its subsystem's mechanics/pitfalls, and the root `CLAUDE.md`'s "Subsystem map" table lists every nested `CLAUDE.md` that actually exists (no stale entry for one that was deleted, no missing entry for one that was added). Root `CLAUDE.md` changes only for a new cross-cutting architectural rule. +- No file references a top-level `AGENTS.md` (grep the repo, excluding `.venv/`, `*.bak`, `.git/`, and the `agents/` exclusion above). A hit is an error - repoint it at the root or the correct nested `CLAUDE.md`. - Page-level role gating: admin-only pages carry `"admin": True` in their `DOCS_PAGES` entry; the router filters the sidebar to `visible_pages` and 404s a non-admin requesting an admin page, while `docs_search` still indexes admin pages for admins. An admin page missing the flag, or a member page wrongly flagged admin, is an error. - Section-level role gating: prose templates receive the user context via `docs_prose.render_prose` and gate admin sections with Jinja `{% if user %}` / `{% if user.role == 'admin' %}`. Unguarded admin material on a public page is an error. -FIX: add or repair the `endpoint()` entry, rewrite the stale prose, add the missing `README.md` / `AGENTS.md` section, add the `"admin": True` flag, or wrap the leaking section in the correct Jinja guard. The source is authoritative; correct the docs to match the code, never the reverse. +FIX: add or repair the `endpoint()` entry, rewrite the stale prose, add the missing `README.md` section or nested `CLAUDE.md` section, repoint or delete a stray `AGENTS.md` reference, add the `"admin": True` flag, or wrap the leaking section in the correct Jinja guard. The source is authoritative; correct the docs to match the code, never the reverse. ## Scope units - **api-docs**: `devplacepy/docs_api.py` `endpoint()` coverage vs `routers/*.py` routes. - **page-gating**: `devplacepy/routers/docs/pages.py` `DOCS_PAGES` admin flag; `visible_pages` filter; `docs_search` indexing. - **section-gating**: `templates/docs/*.html` Jinja `{% if user.role == 'admin' %}` on admin sections. - **readme**: `README.md` reflects current routes, env vars, dependencies, features. -- **agents-md**: `AGENTS.md` has a domain section for every mechanic; `CLAUDE.md` only for new rules. +- **claude-md-nested**: every nested `CLAUDE.md` has a domain section for every mechanic in its subsystem; root `CLAUDE.md` only for new cross-cutting rules; no stray `AGENTS.md` file or reference anywhere in the repo. ## Output Return a markdown report: a one-line summary line, then one bullet per finding with severity (`error`/`warning`/`info`), `file:line`, the rule name, the message, and (in fix mode) whether it was fixed. diff --git a/.claude/agents/fanout-maintainer.md b/.claude/agents/fanout-maintainer.md index 8e692cd4..32a9359e 100644 --- a/.claude/agents/fanout-maintainer.md +++ b/.claude/agents/fanout-maintainer.md @@ -45,7 +45,7 @@ DETECT, for each route: - A `services/devii/actions/catalog.py` Action exists if the route is something a user could ask Devii to do. - A `docs_api.py` entry exists for every public or authenticated endpoint. - Public pages build `base_seo_context`. -- `README.md` and `AGENTS.md` mention the feature. +- `README.md` and the relevant nested `CLAUDE.md` mention the feature. FIX: add the missing Form, add the missing key to the `*Out` schema, switch the handler to `respond`, or flag the responsible specialist's layer. When a layer is intentionally absent (an internal route with no public docs, a route Devii should never call), record an info finding with the rationale rather than fabricating the layer. diff --git a/.claude/agents/feature-builder.md b/.claude/agents/feature-builder.md index 5881bda2..eaaa843d 100644 --- a/.claude/agents/feature-builder.md +++ b/.claude/agents/feature-builder.md @@ -18,7 +18,7 @@ All application code is under `devplacepy/`: `devplacepy/routers/`, `devplacepy/ Default to **PLAN** mode. Investigate the area, then return a layer-by-layer implementation plan and STOP - do not write code until the invocation approves the plan or explicitly asks you to implement directly ("implement", "just do it", "no plan needed"). Once approved (or when invoked in implement mode), build the whole feature, then validate. Never run the test suite; never perform any git write operation. ## Operating protocol -1. **Understand before writing.** Read the router, template, matching tests, the relevant `CLAUDE.md`/`AGENTS.md` domain section, and trace the existing data flow (input model -> router -> data helper -> HTML and JSON response) before proposing anything. Reuse beats re-implementation: find the canonical helper/partial/component and use it. +1. **Understand before writing.** Read the router, template, matching tests, the relevant nested `CLAUDE.md` (each subsystem directory has its own, e.g. `devplacepy/services/devii/CLAUDE.md`) and the root `CLAUDE.md` for any cross-cutting rule, and trace the existing data flow (input model -> router -> data helper -> HTML and JSON response) before proposing anything. Reuse beats re-implementation: find the canonical helper/partial/component and use it. 2. Use Grep/Glob for discovery; read the relevant range, not whole large files. Never repeat a grep or re-read a file you already read. 3. Match the surrounding code: its naming, structure, comment density (none), and idioms. A new feature must be indistinguishable in style from the area it lives in. 4. Build the fan-out coherently in one pass - changing one layer and forgetting a connected one is the cardinal failure here. @@ -26,7 +26,7 @@ Default to **PLAN** mode. Investigate the area, then return a layer-by-layer imp ## Research the task before designing (codebase first, web when external) Investigation is two passes, in order: -1. **Codebase pass (always).** Read the router, template, matching tests, and the relevant `CLAUDE.md`/`AGENTS.md` section; trace the existing data flow (input model -> router -> data helper -> HTML and JSON response); find the canonical helper, partial, or component to reuse. Never design from assumption when the answer is in the repo. +1. **Codebase pass (always).** Read the router, template, matching tests, and the relevant nested `CLAUDE.md` (plus the root `CLAUDE.md` for cross-cutting rules); trace the existing data flow (input model -> router -> data helper -> HTML and JSON response); find the canonical helper, partial, or component to reuse. Never design from assumption when the answer is in the repo. 2. **Web pass (whenever the feature touches anything outside this repo).** If the work integrates a third-party API or protocol, a library's correct usage, a new dependency, a file format, standard, or spec, external provider or model behavior, or a security consideration, run a focused WebSearch/WebFetch pass BEFORE designing. Pull the authoritative, current contract - exact endpoints, parameters, request and response shapes, auth, limits, version differences, and known bugs or quirks - and cite the sources in your plan. Prefer official docs and corroborate version-specific details. Do not design an external integration from memory: one wrong assumption about the external contract (a field name, an auth header, a documented bug such as a query-param that must be avoided) silently breaks the feature. Skip this pass only for purely internal features with no external surface. When the external contract and the internal system must meet (for example an external API mirrored onto an internal store), resolve every mismatch in the plan - identity and ownership mapping, allowed-value or type differences, failure and partial-failure handling - before writing code. @@ -41,7 +41,7 @@ A DevPlace feature is one data source fanning out into several consumers, all fr 5. **View** - templates extend `base.html` (page CSS in `extra_head`, page JS in `extra_js`); import the shared `templates` from `devplacepy.templating`, never instantiate `Jinja2Templates`. Wrap every static asset URL in `static_url(...)`/`assetUrl(...)`. Reuse partials (`_avatar_link.html`, `_user_link.html`, `_sidebar_search.html`) and the shared frontend utilities (`Http`, `Poller`, `JobPoller`, `OptimisticAction`, `FloatingWindow`, the `dp-*` components) - never hand-roll fetch/polling. JS is ES6 modules, one class per file, on `app`. Dates are DD/MM/YYYY via `format_date`. 6. **Agent + docs (the most-forgotten layers)** - if a user could ask Devii to do it, add an `Action` in `services/devii/actions/catalog.py` with auth flags matched to the route guard (and a declared `confirm` boolean for any irreversible action added to `CONFIRM_REQUIRED`). Add a `docs_api.py` `endpoint()` entry (params + `sample_response`) for every public/auth endpoint; add a prose page to `routers/docs/pages.py` `DOCS_PAGES` when warranted. State-changing actions need an audit event (`events.md` key, `category_for`, recorder call at the mutation point). 7. **SEO** - public pages build `base_seo_context` and the right JSON-LD; add to `routers/seo.py` sitemap when indexable. -8. **Docs of record** - update `README.md` (product-facing) and `AGENTS.md` (deep companion) for any new route/config/dependency/mechanic; update `CLAUDE.md` only when a NEW architectural rule or convention is introduced. +8. **Docs of record** - update `README.md` (product-facing) and the relevant nested `CLAUDE.md` (deep companion for the subsystem you touched - create one if the directory doesn't have one yet) for any new route/config/dependency/mechanic; update the root `CLAUDE.md` only when a NEW cross-cutting architectural rule or convention is introduced, and add a row to its "Subsystem map" table if you created a new nested `CLAUDE.md`. 9. **Tests (a hard project requirement, never optional)** - the DevPlace suite is one test file per endpoint, ~932 tests, split into three tiers with the directory tree mirroring the URL/source path. Every feature gets a test in EVERY tier it exercises: `tests/unit/` for a new data/query helper (pure in-process, `local_db` or no fixture, path mirrors the SOURCE module - `devplacepy/utils.py` -> `tests/unit/utils.py`); `tests/api/` for a new JSON or HTML route (HTTP integration against the live uvicorn subprocess via `app_server`/`seeded_db`, path mirrors the endpoint - `POST /auth/login` -> `tests/api/auth/login.py`) - but when a route depends on an in-process injected fake or a module-level singleton the separate uvicorn subprocess cannot see (the Gitea client via `runtime.set_client(fake)`, or any other `set_client`/monkeypatched backend), test it IN-PROCESS instead with `from starlette.testclient import TestClient; TestClient(m.app)`, the fake set in the test process, and auth via a `create_session(uid)` `session` cookie, asserting JSON with `Accept: application/json` (the `tests/api/issues/` files are the canonical example); `tests/e2e/` for a new interactive UI flow (Playwright `page`/`alice`/`bob`, path mirrors the endpoint - `GET /admin/ai-usage` -> `tests/e2e/admin/aiusage.py`). A route or feature with no test in any tier is incomplete. Follow the required patterns (`wait_until="domcontentloaded"` on every `goto`/`wait_for_url`, scoped selectors, `try/finally` restore of any flipped global setting, the shared fixtures, `test_`-prefixed functions in non-prefixed files, born-live `deleted_at`/`deleted_by` on raw soft-delete inserts) and create any missing package directories (`__init__.py`). WRITE them; validate each by a clean import only; NEVER run them. When a layer is intentionally absent (an internal route with no public docs, a route Devii must never call), say so explicitly in the plan with the rationale rather than fabricating the layer. diff --git a/.claude/agents/style-maintainer.md b/.claude/agents/style-maintainer.md index 73bb95b7..0c4874e7 100644 --- a/.claude/agents/style-maintainer.md +++ b/.claude/agents/style-maintainer.md @@ -1,6 +1,6 @@ --- name: style-maintainer -description: Coding-rule compliance. Enforces the explicit CLAUDE.md and AGENTS.md coding rules across all source - forbidden naming (context-aware), no comments/docstrings, em-dash (context-aware), full typing, pathlib over os, dataclasses over fixed-key dicts, no version pinning, file headers, no magic numbers. Use for style/convention review. Most surface name/em-dash hits are false positives - run the decision algorithm. +description: Coding-rule compliance. Enforces the explicit CLAUDE.md (root and nested per-subsystem) coding rules across all source - forbidden naming (context-aware), no comments/docstrings, em-dash (context-aware), full typing, pathlib over os, dataclasses over fixed-key dicts, no version pinning, file headers, no magic numbers. Use for style/convention review. Most surface name/em-dash hits are false positives - run the decision algorithm. tools: Read, Grep, Glob, Edit, Write, Bash model: inherit color: orange @@ -36,7 +36,7 @@ Default to **REPORT** mode: record findings, do NOT modify files. Apply **FIX** No comments or docstrings in source you author; no em-dashes (use a hyphen); keep `retoor ` as the first line of any file you create. ## Your dimension -Enforce the explicit CLAUDE.md and AGENTS.md coding rules across all source. +Enforce the explicit CLAUDE.md (root plus every nested per-subsystem `CLAUDE.md`) coding rules across all source. ### Forbidden naming prefixes and suffixes (CONTEXT-AWARE) The banned tokens are `_new`, `_old`, `_current`, `_prev`, `_next` (outside iteration), `_temp`, `_tmp`, `_v1`/`_v2`/`_v3`, `better_`, `best_`, `simple_`, `my_`, `the_`, `_data`, `_info`, and the rest of the forbidden list. This rule targets LAZY, RENAMEABLE VARIABLE AND HELPER names you own. It is NOT a blind substring sweep, and most surface hits on `_data`/`_info`/`_item`/`_val` are FALSE POSITIVES. Run this decision algorithm for EVERY candidate before recording it, and skip it the moment any test fails: diff --git a/.claude/commands/explain.md b/.claude/commands/explain.md index 1a2b8ebd..7f0cc5e7 100644 --- a/.claude/commands/explain.md +++ b/.claude/commands/explain.md @@ -1,5 +1,5 @@ --- -description: Explain a DevPlace subsystem, route, or file - read the relevant AGENTS.md section and the code, then summarize architecture, data flow, invariants, and entry points. Read-only. +description: Explain a DevPlace subsystem, route, or file - read the relevant nested CLAUDE.md and the code, then summarize architecture, data flow, invariants, and entry points. Read-only. argument-hint: allowed-tools: Read, Grep, Glob, Bash(git log:*) --- @@ -8,13 +8,13 @@ Orient me on: **$ARGUMENTS** Investigate before explaining; confirm every claim against the source. 1. Locate the code: the router under `devplacepy/routers/`, the template under `devplacepy/templates/`, data helpers in `devplacepy/database.py`, schemas in `devplacepy/schemas.py`, and any service under `devplacepy/services/`. -2. Read the matching domain section in `AGENTS.md` (the long-form companion) and the relevant part of `CLAUDE.md`. +2. Read the matching nested `CLAUDE.md` for the subsystem (e.g. `devplacepy/services/devii/CLAUDE.md`), plus the relevant cross-cutting part of the root `CLAUDE.md`. 3. Trace the data flow: input model (`models.py`) -> router handler + guard -> data helper -> response (HTML via `respond` + template, JSON via the `*Out` schema), plus the Devii action (`catalog.py`) and API docs (`docs_api.py`) where present. Then give a tight explanation: - What it does and where it lives, with `file:line` references. - The request pipeline and data flow. -- Key invariants and gotchas (pull these from AGENTS.md). +- Key invariants and gotchas (pull these from the nested CLAUDE.md). - The fan-out: which of the nine feature layers exist for it. Do not modify anything. diff --git a/.claude/commands/maintenance.md b/.claude/commands/maintenance.md index 4f2ffaa9..8c2ec254 100644 --- a/.claude/commands/maintenance.md +++ b/.claude/commands/maintenance.md @@ -8,7 +8,7 @@ You are orchestrating the DevPlace maintenance fleet. Each dimension is a projec ## Dimension to subagent map | Dimension | Subagent | Enforces | |-----------|----------|----------| -| style | `style-maintainer` | CLAUDE.md/AGENTS.md coding rules (context-aware names, em-dash, typing, pathlib, headers) | +| style | `style-maintainer` | CLAUDE.md (root/nested) coding rules (context-aware names, em-dash, typing, pathlib, headers) | | dry | `dry-maintainer` | duplication and reuse of canonical shared utilities | | security | `security-maintainer` | auth guards, project visibility, read-only guards, input validation, XSS | | audit | `audit-maintainer` | audit-log coverage and event catalogue | diff --git a/.claude/commands/service.md b/.claude/commands/service.md index ee31ed93..69da4fea 100644 --- a/.claude/commands/service.md +++ b/.claude/commands/service.md @@ -12,5 +12,5 @@ Mirror an existing service - read `devplacepy/services/base.py` (BaseService) an 3. Register it in `main.py` startup: `service_manager.register(YourService())`, under the same `DEVPLACE_DISABLE_SERVICES` guard as the others. It then auto-appears on `/admin/services`. 4. If it calls an LLM, default its endpoint to `config.INTERNAL_GATEWAY_URL` and authenticate with the internal gateway key, like the other AI consumers. 5. Emit audit events via `record_system` for any state change it makes. -6. Document it in `AGENTS.md` (Background services section) and in `README.md` if user-visible. +6. Document it in `devplacepy/services/CLAUDE.md` (Background services base machinery section, or the service's own nested `CLAUDE.md` if it has one) and in `README.md` if user-visible. 7. Validate with `hawk` on the touched files and `python -c "from devplacepy.main import app"`. diff --git a/.claude/workflows/feature.js b/.claude/workflows/feature.js index 22de3e03..6f9ecdf8 100644 --- a/.claude/workflows/feature.js +++ b/.claude/workflows/feature.js @@ -35,7 +35,7 @@ const FANOUT = [ '6. services/devii/actions/catalog.py - an Action(name, method, path, summary, params, requires_auth, requires_admin) if a user could ask Devii to do it; a confirm param plus membership in CONFIRM_REQUIRED if destructive.', '7. docs_api.py - an endpoint() entry in the right group with params and sample_response for every public or authenticated route.', '8. seo.py - base_seo_context(request, ...) merged into the context for public pages; a sitemap entry in routers/seo.py if indexable.', - '9. README.md (product) + AGENTS.md (mechanics) + CLAUDE.md (only for a genuinely new architectural rule).', + '9. README.md (product) + the relevant nested CLAUDE.md (mechanics) + the root CLAUDE.md (only for a genuinely new architectural rule).', ].join('\n') const TESTS = [ @@ -175,7 +175,7 @@ const LIVE_SCHEMA = { log(`Feature: ${ask}`) const map = await agent( - `Map the area of the DevPlace codebase relevant to this feature request, so it can be implemented. Read the closest existing feature end to end (its router, template, tests, and AGENTS.md section) as the pattern to follow. Do not write anything.\n\nFeature request: ${ask}\n\n${FANOUT}\n\nReturn: a summary of how this should be built, the concrete files to touch or create, the most similar existing feature to mirror, and any constraints.`, + `Map the area of the DevPlace codebase relevant to this feature request, so it can be implemented. Read the closest existing feature end to end (its router, template, tests, and the matching nested CLAUDE.md) as the pattern to follow. Do not write anything.\n\nFeature request: ${ask}\n\n${FANOUT}\n\nReturn: a summary of how this should be built, the concrete files to touch or create, the most similar existing feature to mirror, and any constraints.`, { agentType: 'Explore', label: 'understand', phase: 'Understand', schema: MAP_SCHEMA } ) diff --git a/.claude/workflows/job-service.js b/.claude/workflows/job-service.js index 26b9e4a4..e1c6f9ab 100644 --- a/.claude/workflows/job-service.js +++ b/.claude/workflows/job-service.js @@ -32,7 +32,7 @@ const CHECKLIST = [ '6. docs_api.py - endpoint() entries for the enqueue, status, and download routes.', '7. static/js - wire JobPoller.run(statusUrl, {onDone, onFailed, onTimeout}) on the triggering element.', '8. CLI (optional) - a prune/clear subcommand if artifacts accumulate.', - '9. README.md + AGENTS.md - document the new job kind.', + '9. README.md + devplacepy/services/jobs/CLAUDE.md - document the new job kind.', ].join('\n') const TESTS = [ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index dba45d73..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,2549 +0,0 @@ -# DevPlace - Agent Guide - -## Quick start - -```bash -make install # pip install -e . -make dev # uvicorn --reload on port 10500, backlog 4096 -make prod # uvicorn with nproc workers (DEVPLACE_WEB_WORKERS), backlog 8192 (production) -make test # Playwright integration + unit tests, serial (one at a time), fail-fast -x -make test-headed # same tests in a visible browser (single window) -make locust # Locust load test (interactive web UI) -make locust-headless # Locust in headless CLI mode (for CI) -``` - -**Env vars:** `DEVPLACE_DISABLE_SERVICES=1` prevents the NewsService (and future background services) from starting. Automatically set during tests. - -## Architecture - -- **FastAPI** backend serving **Jinja2 templates** (SSR). Pure ES6 JS for interactivity. -- **Database:** `dataset` (auto-syncs schema, uses `uid` for PKs). SQLite. -- **Auth:** `session` cookie, plus `X-API-KEY` / `Authorization: Bearer ` / HTTP Basic (username-or-email:password) - all resolved in `get_current_user`. PBKDF2-SHA256 via passlib. No JWT. Every user has an `api_key` (uuid7), set at signup and backfilled in `init_db`/`devplace apikey backfill`. Docs site at `/docs` (`routers/docs/ package`, `DOCS_PAGES` registry; FastAPI's Swagger is moved to `/swagger` so `/docs` is free). -- **Static:** `devplacepy/static/` mounted at `/static`; URLs are emitted boot-versioned (`/static/v/...`) via the `static_url`/`assetUrl` helpers and served immutable for a year (see "Static asset caching and versioning") -- **Templates:** `devplacepy/templates/`. Shared `templates` instance from `devplacepy.templating` - all routers import from there, do NOT create their own. -- **Ports:** 10500 (dev), 10501 (tests; the serial suite uses a single uvicorn subprocess) -- **Username:** letters, numbers, hyphens, underscores only. 3-32 chars. -- **Password:** minimum 6 chars. -- **Avatars:** Multiavatar-based (local SVG generation, zero network). URL: `/avatar/multiavatar/{seed}`. Generation takes <5ms. Fallback to initial-based SVG on error. Cache is in-memory (cleared on restart). **The seed is per-user:** the nullable `users.avatar_seed` column overrides the username. Always resolve it through the single null-safe choke point `avatar.avatar_seed(user)` (registered as a Jinja global) - `user.get("avatar_seed") or user.get("username")` - never read `avatar_seed` or pass `username` to `avatar_url(...)` directly. Every render site (the `_avatar_link.html` partial, the few direct `avatar_url(...)` template calls, the SEO `og_image`, the issues ad-hoc dicts, and the devRant avatar payload/PNG) goes through it, so a regenerated seed propagates everywhere. **Regenerate** is owner-or-admin at `POST /profile/{username}/regenerate-avatar` (`routers/profile/avatar.py`, reusing `resolve_customization_target`): it writes a fresh `generate_uid()` to `avatar_seed`, invalidates the target's user cache, and audits `profile.avatar.regenerate`. The previous seed is overwritten and never stored, so the old avatar cannot return (the UI confirms once per page session with that warning; `AvatarRegenerator.js` / `[data-regenerate-avatar]`). Devii tool `regenerate_avatar` (in `CONFIRM_REQUIRED`). - -## Routing - -Routers in `devplacepy/routers/` form a **directory tree that mirrors the endpoint (URL) path**, the same convention as `tests/`. A single-resource domain is one flat file; a multi-sub-resource domain is a **package directory** split **one file per sub-resource** (not per endpoint). Each leaf has its own `router = APIRouter()` keeping the exact path strings; the package `__init__.py` aggregates them with `include_router`, and `main.py` mounts the domain at its prefix unchanged (a package exposes `.router` like a module). The leaf that owns the collection-root (`""`) route is the package base router (FastAPI rejects empty-path-under-empty-prefix), so `__init__` imports that leaf's `router` and includes the rest onto it; a router folded in from a deeper mount is included with a sub-prefix (`admin/__init__` includes `services.router` with `prefix="/services"` and `containers.router` with `prefix="/containers"`). Package-private helpers shared by leaves live in `_shared.py`. The `/projects` tree (project CRUD + `files.py` + `containers/`) and the `/admin` tree (every admin sub-resource + folded-in `services` and `containers`) are each mounted from a single package. - -| Prefix | Router | -|--------|-------------| -| `/auth` | `routers/auth/` package (`signup`, `login`, `logout`, `forgotpassword`, `resetpassword`) | -| `/feed` | `routers/feed.py` | -| `/news` | `routers/news.py` | -| `/posts` | `routers/posts.py` | -| `/comments` | `routers/comments.py` | -| `/projects` | `routers/projects/` package (`index.py`, `files.py`, `containers/` subpackage) | -| `/profile` | `routers/profile/` package (`index`, `customization`, `notifications`, `usage`) | -| `/messages` | `routers/messages.py` | -| `/notifications` | `routers/notifications.py` | -| `/votes` | `routers/votes.py` | -| `/reactions` | `routers/reactions.py` | -| `/bookmarks` | `routers/bookmarks.py` | -| `/polls` | `routers/polls.py` | -| `/avatar` | `routers/avatar.py` | -| `/follow` | `routers/follow.py` | -| `/block`, `/mute` | `routers/relations.py` (block/unblock, mute/unmute) | -| `/leaderboard` | `routers/leaderboard.py` | -| `/admin` | `routers/admin/` package (`index`, `users`, `aiusage`, `aiquota`, `media`, `trash`, `settings`, `notifications`, `news`, `auditlog`, `services`, `containers`) | -| `/issues` | `routers/issues/` package (`index`, `create`, `comment`, `status`) | -| `/gists` | `routers/gists.py` | -| `/admin/services` | `routers/admin/services.py` (folded into the `/admin` package) | -| `/uploads` | `routers/uploads.py` | -| `/media` | `routers/media.py` | -| `/zips` | `routers/zips.py` | -| `/forks` | `routers/forks.py` | -| `/tools` | `routers/tools/` package (`index`, `seo`, `deepsearch`) | -| `/p` | `routers/proxy.py` (container ingress proxy) | -| `/push` | `routers/push.py` (`/push.json`, `/service-worker.js`, `/manifest.json`) | -| `/docs` | `routers/docs/` package (`pages`, `views`) | -| `/openai` | `routers/openai_gateway.py` | -| `/devii` | `routers/devii.py` | -| `/xmlrpc` | `routers/xmlrpc.py` | -| `/api` | `routers/devrant/` package (`auth`, `rants`, `comments`, `notifs`) | -| `/dbapi` | `routers/dbapi/` package (`tables`, `query`, `nl`, `crud`) - primary administrator only (no internal-key path) | -| `/pubsub` | `routers/pubsub.py` | -| `/game` | `routers/game/` package (`index`, `farm`) - the Code Farm idle game (state/leaderboard, plant/harvest/upgrade/prestige, and social farm water/steal) | -| `(none)` | `routers/seo.py` (`/robots.txt`, `/sitemap.xml`) | - -## Content Rendering Pipeline - -**Server-rendered content/titles are rendered on the BACKEND (`devplacepy/rendering.py`), for SEO.** Two Jinja globals (registered in `templating.py`) are the canonical way to display any content/title that exists at request time: - -- `render_content(text) -> Markup` - full pipeline parity with the client `ContentRenderer.js`, built on **mistune** (the markdown engine already used by `docs_prose.py`/`telegram/format.py`): normalize em-dash, emoji shortcodes (the full set; `EMOJI_MAP` and the JS module come from ONE generator - see "Emoji shortcodes" below), GFM markdown with `escape=True` (raw user HTML is escaped - this is the server XSS control that replaces client `DOMPurify`; plugins `strikethrough`+`table`, `hard_wrap`), then a stdlib `html.parser.HTMLParser` media pass (`_MediaProcessor`) that walks text nodes - skipping `a`/`code`/`pre` - and converts bare URLs to YouTube iframes / `` / `