📚 Add minor improvements to ai agents documentation

This commit is contained in:
Andrey Antukh
2026-03-24 17:49:24 +01:00
parent 750e8a9d51
commit cc03f3f884
6 changed files with 114 additions and 191 deletions

168
AGENTS.md
View File

@@ -1,139 +1,63 @@
# IA Agent guide for Penpot monorepo
# AI Agent Guide
This document provides comprehensive context and guidelines for AI
agents working on this repository.
This document provides the core context and operating guidelines for AI agents
working in this repository.
CRITICAL: When you encounter a file reference (e.g.,
@rules/general.md), use your Read tool to load it on a need-to-know
basis. They're relevant to the SPECIFIC task at hand.
## Before You Start
Before responding to any user request, you must:
## STOP - DO NOT PROCEED WITHOUT COMPLETING THESE STEPS
1. Read this file completely.
2. Identify which modules are affected by the task.
3. Load the `AGENTS.md` file **only** for each affected module (see the
architecture table below). Not all modules have an `AGENTS.md` — verify the
file exists before attempting to read it.
4. Do **not** load `AGENTS.md` files for unrelated modules.
Before responding to ANY user request, you MUST:
## Role: Senior Software Engineer
1. **READ** the CONTRIBUTING.md file
2. **READ** this file and has special focus on your ROLE.
You are a high-autonomy Senior Full-Stack Software Engineer. You have full
permission to navigate the codebase, modify files, and execute commands to
fulfill your tasks. Your goal is to solve complex technical tasks with high
precision while maintaining a strong focus on maintainability and performance.
### Operational Guidelines
## ROLE: SENIOR SOFTWARE ENGINEER
1. Before writing code, describe your plan. If the task is complex, break it
down into atomic steps.
2. Be concise and autonomous.
3. Do **not** touch unrelated modules unless the task explicitly requires it.
4. Commit only when explicitly asked. Follow the commit format rules in
`CONTRIBUTING.md`.
5. When searching code, prefer `ripgrep` (`rg`) over `grep` — it respects
`.gitignore` by default.
You are a high-autonomy Senior Software Engineer. You have full
permission to navigate the codebase, modify files, and execute
commands to fulfill your tasks. Your goal is to solve complex
technical tasks with high precision, focusing on maintainability and
performance.
## Architecture Overview
Penpot is an open-source design tool composed of several modules:
### OPERATIONAL GUIDELINES
| Directory | Language | Purpose | Has `AGENTS.md` |
|-----------|----------|---------|:----------------:|
| `frontend/` | ClojureScript + SCSS | Single-page React app (design editor) | Yes |
| `backend/` | Clojure (JVM) | HTTP/RPC server, PostgreSQL, Redis | Yes |
| `common/` | Cljc (shared Clojure/ClojureScript) | Data types, geometry, schemas, utilities | Yes |
| `render-wasm/` | Rust -> WebAssembly | High-performance canvas renderer (Skia) | Yes |
| `exporter/` | ClojureScript (Node.js) | Headless Playwright-based export (SVG/PDF) | No |
| `mcp/` | TypeScript | Model Context Protocol integration | No |
| `plugins/` | TypeScript | Plugin runtime and example plugins | No |
1. Always begin by analyzing this document and understand the
architecture and read the additional context from AGENTS.md of the
affected modules.
2. Before writing code, describe your plan. If the task is complex,
break it down into atomic steps.
3. Be concise and autonomous as possible in your task.
4. Commit only if it explicitly asked, and use the CONTRIBUTING.md
document to understand the commit format guidelines.
5. Do not touch unrelated modules if not proceed or not explicitly
asked (per example you probably do not need to touch and read
docker/ directory unless the task explicitly requires it)
6. When searching code, always use `ripgrep` (rg) instead of grep if
available, as it respects `.gitignore` by default.
Some submodules use `pnpm` workspaces. The root `package.json` and
`pnpm-lock.yaml` manage shared dependencies. Helper scripts live in `scripts/`.
## ARCHITECTURE OVERVIEW
Penpot is a full-stack design tool composed of several distinct
components separated in modules and subdirectories:
| Component | Language | Role | IA Agent CONTEXT |
|-----------|----------|------|----------------
| `frontend/` | ClojureScript + SCSS | Single-page React app (design editor) | @frontend/AGENTS.md |
| `backend/` | Clojure (JVM) | HTTP/RPC server, PostgreSQL, Redis | @backend/AGENTS.md |
| `common/` | Cljc (shared Clojure/ClojureScript) | Data types, geometry, schemas, utilities | @common/AGENTS.md |
| `exporter/` | ClojureScript (Node.js) | Headless Playwright-based export (SVG/PDF) | @exporter/AGENTS.md |
| `render-wasm/` | Rust → WebAssembly | High-performance canvas renderer using Skia | @render-wasm/AGENTS.md |
| `mcp/` | TypeScript | Model Context Protocol integration | @mcp/AGENTS.md |
| `plugins/` | TypeScript | Plugin runtime and example plugins | @plugins/AGENTS.md |
Several of the mentionend submodules are internall managed with `pnpm` workspaces.
## COMMIT FORMAT
We have very precise rules on how our git commit messages must be
formatted.
The commit message format is:
### Module Dependency Graph
```
<type> <subject>
[body]
[footer]
frontend ──> common
backend ──> common
exporter ──> common
frontend ──> render-wasm (loads compiled WASM)
```
Where type is:
- :bug: `:bug:` a commit that fixes a bug
- :sparkles: `:sparkles:` a commit that adds an improvement
- :tada: `:tada:` a commit with a new feature
- :recycle: `:recycle:` a commit that introduces a refactor
- :lipstick: `:lipstick:` a commit with cosmetic changes
- :ambulance: `:ambulance:` a commit that fixes a critical bug
- :books: `:books:` a commit that improves or adds documentation
- :construction: `:construction:` a WIP commit
- :boom: `:boom:` a commit with breaking changes
- :wrench: `:wrench:` a commit for config updates
- :zap: `:zap:` a commit with performance improvements
- :whale: `:whale:` a commit for Docker-related stuff
- :paperclip: `:paperclip:` a commit with other non-relevant changes
- :arrow_up: `:arrow_up:` a commit with dependency updates
- :arrow_down: `:arrow_down:` a commit with dependency downgrades
- :fire: `:fire:` a commit that removes files or code
- :globe_with_meridians: `:globe_with_meridians:` a commit that adds or updates
translations
The commit should contain a sign-off at the end of the patch/commit
description body. It can be automatically added by adding the `-s`
parameter to `git commit`.
This is an example of what the line should look like:
```
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
```
Please, use your real name (sorry, no pseudonyms or anonymous
contributions are allowed).
CRITICAL: The commit Signed-off-by is mandatory and should match the commit author.
Each commit should have:
- A concise subject using the imperative mood.
- The subject should capitalize the first letter, omit the period
at the end, and be no longer than 65 characters.
- A blank line between the subject line and the body.
- An entry in the CHANGES.md file if applicable, referencing the
GitHub or Taiga issue/user story using these same rules.
Examples of good commit messages:
- `:bug: Fix unexpected error on launching modal`
- `:bug: Set proper error message on generic error`
- `:sparkles: Enable new modal for profile`
- `:zap: Improve performance of dashboard navigation`
- `:wrench: Update default backend configuration`
- `:books: Add more documentation for authentication process`
- `:ambulance: Fix critical bug on user registration process`
- `:tada: Add new approach for user registration`
More info:
- https://gist.github.com/parmentf/035de27d6ed1dce0b36a
- https://gist.github.com/rxaviers/7360908
`common` is referenced as a local dependency (`{:local/root "../common"}`) by
both `frontend` and `backend`. Changes to `common` can therefore affect multiple
modules — test across consumers when modifying shared code.

View File

@@ -7,8 +7,8 @@ Redis for messaging/caching.
## General Guidelines
This is a golden rule for backend development standards. To ensure consistency
across the Penpot JVM stack, all contributions must adhere to these criteria:
To ensure consistency across the Penpot JVM stack, all contributions must adhere
to these criteria:
### 1. Testing & Validation
@@ -16,14 +16,14 @@ across the Penpot JVM stack, all contributions must adhere to these criteria:
tests in `test/backend_tests/` must be added or updated.
* **Execution:**
* **Isolated:** Run `clojure -M:dev:test --focus backend-tests.my-ns-test` for the specific task.
* **Regression:** Run `clojure -M:dev:test` for ensure the suite passes without regressions in related functional areas.
* **Isolated:** Run `clojure -M:dev:test --focus backend-tests.my-ns-test` for the specific test namespace.
* **Regression:** Run `clojure -M:dev:test` to ensure the suite passes without regressions in related functional areas.
### 2. Code Quality & Formatting
* **Linting:** All code must pass `clj-kondo` checks (run `pnpm run lint:clj`)
* **Formatting:** All the code must pass the formatting check (run `pnpm run
check-fmt`). Use the `pnpm run fmt` fix the formatting issues. Avoid "dirty"
check-fmt`). Use `pnpm run fmt` to fix formatting issues. Avoid "dirty"
diffs caused by unrelated whitespace changes.
* **Type Hinting:** Use explicit JVM type hints (e.g., `^String`, `^long`) in
performance-critical paths to avoid reflection overhead.
@@ -40,18 +40,18 @@ namespaces structure:
- `app.db.*` Database layer
- `app.tasks.*` Background job tasks
- `app.main` Integrant system setup and entrypoint
- `app.loggers` Internal loggers (auditlog, mattermost, etc) (do not be confused with `app.common.loggin`)
- `app.loggers` Internal loggers (auditlog, mattermost, etc.) (not to be confused with `app.common.logging`)
### RPC
The PRC methods are implement in a some kind of multimethod structure using
`app.util.serivices` namespace. The main RPC methods are collected under
The RPC methods are implemented using a multimethod-like structure via the
`app.util.services` namespace. The main RPC methods are collected under
`app.rpc.commands` namespace and exposed under `/api/rpc/command/<cmd-name>`.
The RPC method accepts POST and GET requests indistinctly and uses `Accept`
header for negotiate the response encoding (which can be transit, the defaut or
plain json). It also accepts transit (defaut) or json as input, which should be
indicated using `Content-Type` header.
The RPC method accepts POST and GET requests indistinctly and uses the `Accept`
header to negotiate the response encoding (which can be Transit the default
or plain JSON). It also accepts Transit (default) or JSON as input, which should
be indicated using the `Content-Type` header.
The main convention is: use `get-` prefix on RPC name when we want READ
operation.
@@ -107,7 +107,7 @@ are config maps with `::ig/ref` for dependencies. Components implement
(db/insert! conn :table row)))
```
Almost all methods on `app.db` namespace accepts `pool`, `conn` or
Almost all methods in the `app.db` namespace accept `pool`, `conn`, or
`cfg` as params.
Migrations live in `src/app/migrations/` as numbered SQL files. They run automatically on startup.
@@ -116,7 +116,7 @@ Migrations live in `src/app/migrations/` as numbered SQL files. They run automat
### Error Handling
The exception helpers are defined on Common module, and are available under
`app.commin.exceptions` namespace.
`app.common.exceptions` namespace.
Example of raising an exception:
@@ -132,10 +132,11 @@ Common types: `:not-found`, `:validation`, `:authorization`, `:conflict`, `:inte
### Performance Macros (`app.common.data.macros`)
Always prefer these macros over their `clojure.core` equivalents — they compile to faster JavaScript:
Always prefer these macros over their `clojure.core` equivalents — they provide
optimized implementations:
```clojure
(dm/select-keys m [:a :b]) ;; ~6x faster than core/select-keys
(dm/select-keys m [:a :b]) ;; faster than core/select-keys
(dm/get-in obj [:a :b :c]) ;; faster than core/get-in
(dm/str "a" "b" "c") ;; string concatenation
```

View File

@@ -1,14 +1,13 @@
# Penpot Common Agent Instructions
A shared module with code written in Clojure, ClojureScript and
JavaScript. Contains multplatform code that can be used and executed
from frontend, backend or exporter modules. It uses clojure reader
conditionals for specify platform specific implementation.
A shared module with code written in Clojure, ClojureScript, and
JavaScript. Contains multiplatform code that can be used and executed
from the frontend, backend, or exporter modules. It uses Clojure reader
conditionals to specify platform-specific implementations.
## General Guidelines
This is a golden rule for common module development. To ensure
consistency across the penpot stack, all contributions must adhere to
To ensure consistency across the Penpot stack, all contributions must adhere to
these criteria:
### 1. Testing & Validation
@@ -16,11 +15,11 @@ these criteria:
If code is added or modified in `src/`, corresponding tests in
`test/common_tests/` must be added or updated.
* **Environment:** Tests should run in a JS (nodejs) and JVM
* **Environment:** Tests should run in both JS (Node.js) and JVM environments.
* **Location:** Place tests in the `test/common_tests/` directory, following the
namespace structure of the source code (e.g., `app.common.colors` ->
`common-tests.colors-test`).
* **Execution:** The tests should be executed on both: JS (nodejs) and JVM environments
* **Execution:** Tests should be executed on both JS (Node.js) and JVM environments:
* **Isolated:**
* JS: To run a focused ClojureScript unit test: edit the
`test/common_tests/runner.cljs` to narrow the test suite, then
@@ -37,8 +36,8 @@ If code is added or modified in `src/`, corresponding tests in
* **Formatting:** All code changes must pass the formatting check
* Run `pnpm run check-fmt:clj` for CLJ/CLJS/CLJC
* Run `pnpm run check-fmt:js` for JS
* Use the `pnpm run fmt` fix all the formatting issues (`pnpm run
fmt:clj` or `pnpm run fmt:js` for isolated formatting fix)
* Use `pnpm run fmt` to fix all formatting issues (`pnpm run
fmt:clj` or `pnpm run fmt:js` for isolated formatting fix).
## Code Conventions
@@ -50,16 +49,16 @@ namespaces structure:
- `app.common.types.*` Shared data types for shapes, files, pages using Malli schemas
- `app.common.schema` Malli abstraction layer, exposes the most used functions from malli
- `app.common.geom.*` Geometry and shape transformation helpers
- `app.common.data` Generic helpers used around all application
- `app.common.math` Generic math helpers used around all aplication
- `app.common.data` Generic helpers used across the entire application
- `app.common.math` Generic math helpers used across the entire application
- `app.common.json` Generic JSON encoding/decoding helpers
- `app.common.data.macros` Performance macros used everywhere
### Reader Conditionals
We use reader conditionals to target for differentiate an
implementation depending on the target platform where code should run:
We use reader conditionals to differentiate implementations depending on the
target platform where the code runs:
```clojure
#?(:clj (import java.util.UUID)

View File

@@ -1,13 +1,12 @@
# Penpot Frontend Agent Instructions
ClojureScript based frontend application that uses React, RxJS as main
ClojureScript-based frontend application that uses React and RxJS as its main
architectural pieces.
## General Guidelines
This is a golden rule for frontend development standards. To ensure consistency
across the penpot stack, all contributions must adhere to these criteria:
To ensure consistency across the Penpot stack, all contributions must adhere to
these criteria:
### 1. Testing & Validation
@@ -22,7 +21,7 @@ If code is added or modified in `src/`, corresponding tests in
running backend. Test are developed using cljs.test.
* **Mocks & Stubs:** * Use proper mocks for any side-effecting
functions (e.g., API calls, storage access).
* Avoid testing through the UI (DOM), we have e2e tests for that/
* Avoid testing through the UI (DOM); we have e2e tests for that.
* Use `with-redefs` or similar ClojureScript mocking utilities to isolate the logic under test.
* **No Flakiness:** Tests must be deterministic. Do not use `setTimeout` or real
network calls. Use synchronous mocks for asynchronous workflows where
@@ -34,15 +33,15 @@ If code is added or modified in `src/`, corresponding tests in
* **Isolated:** To run a focused ClojureScript unit test: edit the
`test/frontend_tests/runner.cljs` to narrow the test suite, then `pnpm run
test`.
* **Regression:** Run `pnpm run test` without modifications on the runner (preferred)
* **Regression:** To run `pnpm run test` without modifications on the runner (preferred)
#### Integration Tests (Playwright)
Integration tests are developed under `frontend/playwright` directory, we use
mocks for remove communication with backend.
mocks for remote communication with the backend.
You should not add, modify or run the integration tests unless it exlicitly asked for.
You should not add, modify or run the integration tests unless explicitly asked.
```
@@ -50,7 +49,7 @@ pnpm run test:e2e # Playwright e2e tests
pnpm run test:e2e --grep "pattern" # Single e2e test by pattern
```
Ensure everything installed before executing tests with `./scripts/setup` script.
Ensure everything is installed before executing tests with the `./scripts/setup` script.
### 2. Code Quality & Formatting
@@ -68,8 +67,8 @@ Ensure everything installed before executing tests with `./scripts/setup` script
### 3. Implementation Rules
* **Logic vs. View:** If logic is embedded in an UI component, extract it into a
function in the same namespace if is only used locally or look for a helper
* **Logic vs. View:** If logic is embedded in a UI component, extract it into a
function in the same namespace if it is only used locally, or look for a helper
namespace to make it unit-testable.
@@ -113,7 +112,7 @@ State is a single atom managed by a Potok store. Events implement protocols
```
The state is located under `app.main.store` namespace where we have
the `emit!` function responsible of emiting events.
the `emit!` function responsible for emitting events.
Example:
@@ -128,15 +127,14 @@ Example:
(st/emit! (my-event)))
```
On `app.main.refs` we have reactive references which lookup into the main state
for just inner data or precalculated data. That references are very usefull but
should be used with care because, per example if we have complex operation, this
operation will be executed on each state change, and sometimes is better to have
simple references and use react `use-memo` for more granular memoization.
On `app.main.refs` we have reactive references which look up the main state
for inner data or precalculated data. These references are very useful but
should be used with care because, for example, if we have a complex operation,
this operation will be executed on each state change. Sometimes it is better to
have simple references and use React `use-memo` for more granular memoization.
Prefer helpers from `app.util.dom` instead of using direct dom calls, if no helper is
available, prefer adding a new helper for handling it and the use the
new helper.
Prefer helpers from `app.util.dom` instead of using direct DOM calls. If no
helper is available, prefer adding a new helper and then using it.
### UI Components (React & Rumext: mf/defc)
@@ -175,19 +173,20 @@ lifecycle management. These are analogous to standard React hooks:
```
The `mf/use-state` in difference with React.useState, returns an atom-like
object, where you can use `swap!` or `reset!` for to perform an update and
`deref` for get the current value.
object, where you can use `swap!` or `reset!` to perform an update and
`deref` to get the current value.
You also has `mf/deref` hook (which does not follow the `use-` naming pattern)
and it's purpose is watch (subscribe to changes) on atom or derived atom (from
okulary) and get the current value. Is mainly used for subscribe to lenses
defined in `app.main.refs` or (private lenses defined in namespaces).
You also have the `mf/deref` hook (which does not follow the `use-` naming
pattern) and its purpose is to watch (subscribe to changes on) an atom or
derived atom (from okulary) and get the current value. It is mainly used to
subscribe to lenses defined in `app.main.refs` or private lenses defined in
namespaces.
Rumext also comes with improved syntax macros as alternative to `mf/use-effect`
and `mf/use-memo` functions. Examples:
Example for `mf/with-memo` macro:
Example for `mf/with-effect` macro:
```clj
;; Using functions
@@ -221,7 +220,7 @@ Example for `mf/with-memo` macro:
(filterv #(= team-id (:team-id %)))))
```
Prefer using the macros for it syntax simplicity.
Prefer using the macros for their syntax simplicity.
#### 4. Component Usage (Hiccup Syntax)
@@ -282,22 +281,22 @@ CSS modules pattern):
- If a value isn't in the DS, use the `px2rem(n)` mixin: `@use "ds/_utils.scss"
as *; padding: px2rem(23);`.
- Do **not** create new SCSS variables for one-off values.
- Use physical directions with logical ones to support RTL/LTR naturally.
- `margin-left`, `padding-right`, `left`, `right`.
- `margin-inline-start`, `padding-inline-end`, `inset-inline-start`.
- Always use the `use-typography` mixin from `ds/typography.scss`.
- `@include t.use-typography("title-small");`
- Use physical directions with logical ones to support RTL/LTR naturally:
- Avoid: `margin-left`, `padding-right`, `left`, `right`.
- Prefer: `margin-inline-start`, `padding-inline-end`, `inset-inline-start`.
- Always use the `use-typography` mixin from `ds/typography.scss`:
- Example: `@include t.use-typography("title-small");`
- Use `$br-*` for radius and `$b-*` for thickness from `ds/_borders.scss`.
- Use only tokens from `ds/colors.scss`. Do **NOT** use `design-tokens.scss` or
legacy color variables.
- Use mixins only those defined in`ds/mixins.scss`. Avoid legacy mixins like
- Use mixins only from `ds/mixins.scss`. Avoid legacy mixins like
`@include flexCenter;`. Write standard CSS (flex/grid) instead.
- Use the `@use` instead of `@import`. If you go to refactor existing SCSS file,
try to replace all `@import` with `@use`. Example: `@use "ds/_sizes.scss" as
*;` (Use `as *` to expose variables directly).
- Avoid deep selector nesting or high-specificity (IDs). Flatten selectors:
- `.card { .title { ... } }`
- `.card-title { ... }`
- Avoid: `.card { .title { ... } }`
- Prefer: `.card-title { ... }`
- Leverage component-level CSS variables for state changes (hover/focus) instead
of rewriting properties.
@@ -324,5 +323,5 @@ Always prefer these macros over their `clojure.core` equivalents — they compil
### Configuration
`src/app/config.clj` reads globally defined variables and exposes precomputed
configuration vars ready to be used from other parts of the application
configuration values ready to be used from other parts of the application.

View File

@@ -1,4 +1,4 @@
{
"$schema": "https://opencode.ai/config.json",
"instructions": ["CONTRIBUTING.md", "AGENTS.md"]
"instructions": ["AGENTS.md"]
}

View File

@@ -59,4 +59,4 @@ parent/child relationships are tracked separately.
The WASM module is loaded by `app.render-wasm.*` namespaces in the
frontend. ClojureScript calls exported Rust functions to push shape
data, then calls `render_frame`. Do not change export function
signatures without updating the ClojureScript bridge.
signatures without updating the corresponding ClojureScript bridge.