diff --git a/.github/workflows/tests-backend.yml b/.github/workflows/tests-backend.yml index fb58ed7ea4..d7a3377772 100644 --- a/.github/workflows/tests-backend.yml +++ b/.github/workflows/tests-backend.yml @@ -62,14 +62,15 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Fmt + working-directory: ./backend + run: | + cljfmt check --parallel=true src/ test/ + - name: Lint working-directory: ./backend run: | - corepack enable; - corepack install; - pnpm install; - pnpm run check-fmt - pnpm run lint + clj-kondo --parallel --lint ../common/src/ src/ - name: Tests working-directory: ./backend @@ -81,4 +82,4 @@ jobs: run: | mkdir -p /tmp/penpot; - clojure -M:dev:test --reporter kaocha.report/documentation + clojure -M:dev:test diff --git a/.gitignore b/.gitignore index dfdb8bae03..a0fb7f7f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .clj-kondo .cpcache .lsp +.env .nrepl-port .nyc_output .rebel_readline_history @@ -73,8 +74,6 @@ opencode.json /frontend/target/ /frontend/test-results/ /frontend/.shadow-cljs -/other/ -/scripts/ /nexus/ /tmp/ /vendor/**/target diff --git a/.opencode/plugins/penpot.js b/.opencode/plugins/penpot.js new file mode 100644 index 0000000000..5f0fbba554 --- /dev/null +++ b/.opencode/plugins/penpot.js @@ -0,0 +1,299 @@ +import { tool } from "@opencode-ai/plugin" +import path from "path" +import { spawn } from "child_process" + +const penpotPsqlTool = tool({ + description: + "Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.", + + args: { + sql: tool.schema + .string() + .describe("SQL command to execute"), + + test: tool.schema + .boolean() + .describe("Use the penpot_test database") + .optional(), + }, + + async execute(args, context) { + const host = process.env.PENPOT_DB_HOST || "postgres" + const user = process.env.PENPOT_DB_USER || "penpot" + const db = args.test + ? "penpot_test" + : process.env.PENPOT_DB_NAME || "penpot" + const password = process.env.PENPOT_DB_PASSWORD || "penpot" + + const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", args.sql] + + return new Promise((resolve) => { + let stdout = "" + let stderr = "" + + const proc = spawn("psql", psqlArgs, { + cwd: context.worktree, + env: { ...process.env, PGPASSWORD: password }, + }) + + proc.stdout.on("data", (data) => { + stdout += data.toString() + }) + + proc.stderr.on("data", (data) => { + stderr += data.toString() + }) + + proc.on("error", (error) => { + resolve(`Error: ${error.message}`) + }) + + proc.on("close", (exitCode) => { + const output = + exitCode === 0 + ? stdout.trim() || "Query executed successfully" + : `Error (exit ${exitCode}): ${ + (stderr || stdout).trim() || "No error output" + }` + resolve(output) + }) + }) + }, +}) + +const parenRepairTool = tool({ + description: + "Fix mismatched parentheses/braces in Clojure files (.clj, .cljs, .cljc) then reformat with cljfmt.", + + args: { + // A string is used instead of an array so OpenCode displays it + // in the generic tool invocation. + files: tool.schema + .string() + .describe( + "Comma-separated file paths to fix, for example: frontend/src/app/config.cljs, backend/src/core.clj", + ) + .optional(), + + code: tool.schema + .string() + .describe("Code string to fix via stdin") + .optional(), + }, + + async execute(args, context) { + const script = path.join(context.worktree, "scripts/paren-repair") + + const files = args.files + ? args.files + .split(",") + .map((file) => file.trim()) + .filter(Boolean) + : [] + + const paramInfo = + files.length > 0 + ? `files=[${files.join(", ")}]` + : args.code !== undefined + ? `code=(${args.code.length} chars)` + : "none" + + return new Promise((resolve) => { + const childArgs = + files.length > 0 + ? [script, ...files] + : [script] + + const proc = spawn("bb", childArgs, { + cwd: context.worktree, + }) + + let stdout = "" + let stderr = "" + + proc.stdout.on("data", (data) => { + stdout += data.toString() + }) + + proc.stderr.on("data", (data) => { + stderr += data.toString() + }) + + proc.on("error", (error) => { + resolve(`Error: ${error.message}`) + }) + + proc.on("close", (exitCode) => { + const output = + exitCode === 0 + ? stdout.trim() || "No changes needed" + : `Error (exit ${exitCode}): ${ + (stderr || stdout).trim() || "No error output" + }` + + resolve(output) + }) + + // Close stdin in all cases so the process cannot wait indefinitely. + if (args.code !== undefined) { + proc.stdin.end(args.code) + } else { + proc.stdin.end() + } + }) + }, +}) + +export default async function plugin() { + return { + tool: { + "paren-repair": parenRepairTool, + "penpot-psql": penpotPsqlTool, + }, + } +} + + + + + + + + + + + +// import { tool } from "@opencode-ai/plugin" +// import path from "path" +// import { spawn } from "child_process" + +// function formatFiles(files) { +// if (files.length === 0) return "stdin" + +// // Keep the visible tool title reasonably short. +// if (files.length <= 3) return files.join(", ") + +// return `${files.slice(0, 3).join(", ")} (+${files.length - 3} more)` +// } + +// const parenRepairTool = tool({ +// description: +// "Fix mismatched parentheses/braces in Clojure files, then reformat with cljfmt.", + +// args: { +// files: tool.schema +// .array(tool.schema.string()) +// .describe("Array of file paths to fix") +// .optional(), + +// code: tool.schema +// .string() +// .describe("Code string to fix via stdin") +// .optional(), +// }, + +// async execute(args, context) { +// const script = path.join(context.worktree, "scripts/paren-repair") + +// const files = (args.files ?? []).map((file) => { +// const absolute = path.isAbsolute(file) +// ? file +// : path.resolve(context.worktree, file) + +// return path.relative(context.worktree, absolute) +// }) + +// const targetSummary = +// files.length > 0 +// ? formatFiles(files) +// : args.code !== undefined +// ? `stdin (${args.code.length} chars)` +// : "no input" + +// // This updates the tool-call title immediately, while it is running. +// await context.metadata({ +// title: `Paren repair: ${targetSummary}`, +// metadata: { +// files, +// codeChars: args.code?.length, +// }, +// }) + +// const childArgs = +// args.files && args.files.length > 0 +// ? [script, ...args.files] +// : [script] + +// return new Promise((resolve) => { +// const proc = spawn("bb", childArgs, { +// cwd: context.worktree, +// }) + +// let stdout = "" +// let stderr = "" + +// if (args.code !== undefined) { +// proc.stdin.end(args.code) +// } + +// proc.stdout.on("data", (data) => { +// stdout += data.toString() +// }) + +// proc.stderr.on("data", (data) => { +// stderr += data.toString() +// }) + +// proc.on("close", (exitCode) => { +// const successful = exitCode === 0 + +// const commandOutput = successful +// ? stdout.trim() || "No changes needed" +// : `Error (exit ${exitCode}): ${(stderr || stdout).trim()}` + +// const parameterOutput = +// files.length > 0 +// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}` +// : args.code !== undefined +// ? `Input passed through stdin: ${args.code.length} characters` +// : "No files or stdin input were passed" + +// resolve({ +// title: `Paren repair: ${targetSummary}`, +// output: `${parameterOutput}\n\n${commandOutput}`, +// metadata: { +// files, +// codeChars: args.code?.length, +// exitCode, +// successful, +// }, +// }) +// }) + +// proc.on("error", (error) => { +// resolve({ +// title: `Paren repair failed: ${targetSummary}`, +// output: [ +// files.length > 0 +// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}` +// : `Input: ${targetSummary}`, +// `Failed to start bb: ${error.message}`, +// ].join("\n\n"), +// metadata: { +// files, +// codeChars: args.code?.length, +// successful: false, +// }, +// }) +// }) +// }) +// }, +// }) + +// export default async function plugin() { +// return { +// tool: { +// "paren-repair": parenRepairTool, +// }, +// } +// } diff --git a/.opencode/skills/backport-commit/SKILL.md b/.opencode/skills/backport-commit/SKILL.md deleted file mode 100644 index 79494a3c5d..0000000000 --- a/.opencode/skills/backport-commit/SKILL.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: backport-commit -description: Port changes from a specific Git commit to the current branch by manually applying the diff, avoiding cherry-pick when it would introduce complex conflicts. ---- - -# Backport Commit - -Port changes from a specific Git commit to the current branch by manually -applying the diff, avoiding `git cherry-pick` when it would introduce -complex conflicts. - -## When to Use - -Use this skill whenever the user asks to backport a commit, especially when: - -- The commit touches multiple modules or files with significant divergence -- `git cherry-pick` is explicitly ruled out ("do not use cherry-pick") -- The target commit is old enough that conflicts are likely -- The commit introduces both source changes AND new files (tests, etc.) -- You need full control over how each hunk is applied - -## Workflow - -### 1. Identify the target commit - -```bash -# Verify the commit exists and understand what it does -git log --oneline -1 - -# Get the full diff (including new/deleted files) -git show - -# Capture the original commit message for later reuse -git log --format='%B' -1 -``` - -### 2. Identify affected modules - -From the file paths in the diff, determine which Penpot modules are affected -(frontend, backend, common, render-wasm, etc.) and read their `AGENTS.md` -files **before** making any changes. If a module has no `AGENTS.md`, skip -that step — verify with `ls /AGENTS.md` first. - -### 3. Read the current state of each affected file - -For every file the diff touches, read the current version on disk to understand -context and ensure correct placement before editing. - -### 4. Apply changes manually (the core of this approach) - -Process every hunk in the diff using the appropriate tool: - -| Diff action | Tool to use | -|-------------|-------------| -| Modify existing file | `edit` — use enough surrounding context in `oldString` to uniquely match the location | -| Add new file | `write` — include proper license header and namespace conventions matching project style | -| Delete file | `bash rm ` | -| Rename/move file | `bash mv `, then apply any content changes with `edit` | - -> **Tip:** Group nearby hunks from the same file into a single `edit` call. -> Use separate calls when hunks are far apart to keep `oldString` short and -> unambiguous. - -Repeat until **all** hunks in the diff are ported. - -### 5. Validate - -Run **lint**, **check-fmt**, and **tests** for every affected module (see each -module's `AGENTS.md` for the exact commands). If the formatter auto-fixes -indentation, verify the logic is still semantically correct. All checks must -pass before moving on. - -### 6. Commit - -Ask the `commiter` sub-agent to create a commit. Stage all relevant files -(exclude unrelated untracked files) and provide the original commit message as -a reference, adapting it as needed for the target branch context. - -## Key Principles - -- **Context matters** — always read files before editing; never guess - indentation or surrounding code -- **Lint + format + test** — never skip validation before committing -- **Preserve intent** — keep the original commit message meaning; the - `commiter` agent handles formatting diff --git a/.opencode/skills/nrepl-eval/SKILL.md b/.opencode/skills/nrepl-eval/SKILL.md index b4b3d76d7f..c84dc803c1 100644 --- a/.opencode/skills/nrepl-eval/SKILL.md +++ b/.opencode/skills/nrepl-eval/SKILL.md @@ -1,19 +1,19 @@ --- name: nrepl-eval -description: Evaluate Clojure code via nREPL using the standalone tools/nrepl-eval.mjs CLI tool. +description: Evaluate Clojure code via nREPL using the standalone scripts/nrepl-eval.mjs CLI tool. --- # nREPL Eval Evaluate Clojure (or ClojureScript) code via a running nREPL server using -`tools/nrepl-eval.mjs`. +`scripts/nrepl-eval.mjs`. -Full documentation: `mem:tools/nrepl-eval` (file: `.serena/memories/tools/nrepl-eval.md`) +Full documentation: `mem:scripts/nrepl-eval` (file: `.serena/memories/scripts/nrepl-eval.md`) ## Quick Reference ```bash -./tools/nrepl-eval.mjs [options] [] +./scripts/nrepl-eval.mjs [options] [] ``` | Flag | Description | Default | @@ -30,8 +30,8 @@ Full documentation: `mem:tools/nrepl-eval` (file: `.serena/memories/tools/nrepl- ## Examples ```bash -./tools/nrepl-eval.mjs '(+ 1 2 3)' -./tools/nrepl-eval.mjs --backend '(+ 1 2 3)' -./tools/nrepl-eval.mjs --frontend '(js/alert "hi")' -./tools/nrepl-eval.mjs -e +./scripts/nrepl-eval.mjs '(+ 1 2 3)' +./scripts/nrepl-eval.mjs --backend '(+ 1 2 3)' +./scripts/nrepl-eval.mjs --frontend '(js/alert "hi")' +./scripts/nrepl-eval.mjs -e ``` diff --git a/.opencode/skills/taiga/SKILL.md b/.opencode/skills/taiga/SKILL.md index 890c965c03..e63788698c 100644 --- a/.opencode/skills/taiga/SKILL.md +++ b/.opencode/skills/taiga/SKILL.md @@ -13,7 +13,7 @@ Fetch information from Taiga public API for the **Penpot** project ## Prerequisites -- `python3` — the `tools/taiga.py` CLI script is self-contained (stdlib only) +- `python3` — the `scripts/taiga.py` CLI script is self-contained (stdlib only) ## Quick Start @@ -21,17 +21,17 @@ The easiest way is to use the bundled Python script: ```bash # Pass a Taiga URL directly -python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714 +python3 scripts/taiga.py https://tree.taiga.io/project/penpot/issue/13714 # Or use " " syntax -python3 tools/taiga.py us 14128 -python3 tools/taiga.py task 13648 +python3 scripts/taiga.py us 14128 +python3 scripts/taiga.py task 13648 # Add --json for raw output -python3 tools/taiga.py --json issue 13714 +python3 scripts/taiga.py --json issue 13714 # See full usage -python3 tools/taiga.py --help +python3 scripts/taiga.py --help ``` ## URL Pattern Reference @@ -51,30 +51,30 @@ To extract the **type** and **ref** from a URL: ## Python Script Reference -The `tools/taiga.py` script wraps the Taiga API into a single convenient CLI +The `scripts/taiga.py` script wraps the Taiga API into a single convenient CLI with sensible defaults. ### Usage ``` -python3 tools/taiga.py -python3 tools/taiga.py -python3 tools/taiga.py [--json] -python3 tools/taiga.py [--json] +python3 scripts/taiga.py +python3 scripts/taiga.py +python3 scripts/taiga.py [--json] +python3 scripts/taiga.py [--json] ``` ### Examples ```bash # By URL (recommended — no need to think about type/ref) -python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714 +python3 scripts/taiga.py https://tree.taiga.io/project/penpot/issue/13714 # By type and ref -python3 tools/taiga.py us 14128 -python3 tools/taiga.py task 13648 +python3 scripts/taiga.py us 14128 +python3 scripts/taiga.py task 13648 # Raw JSON output -python3 tools/taiga.py --json issue 13714 +python3 scripts/taiga.py --json issue 13714 ``` ### Output diff --git a/.opencode/skills/update-changelog/SKILL.md b/.opencode/skills/update-changelog/SKILL.md index 0a241216df..cf15eede73 100644 --- a/.opencode/skills/update-changelog/SKILL.md +++ b/.opencode/skills/update-changelog/SKILL.md @@ -20,7 +20,7 @@ primary link, with the fix PR inline on the same line. - `gh` CLI authenticated (`gh auth status`) - Python 3.8+ -- `tools/gh.py` helper script available +- `scripts/gh.py` helper script available ## Workflow @@ -36,13 +36,13 @@ Use the helper script. It uses GraphQL for efficient single-pass fetching ```bash # All closed issues (default) -python3 tools/gh.py issues "2.16.0" +python3 scripts/gh.py issues "2.16.0" # Include open issues too -python3 tools/gh.py issues "2.16.0" --state all +python3 scripts/gh.py issues "2.16.0" --state all # Exclude entries that should not go in the changelog -python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" +python3 scripts/gh.py issues "2.16.0" --exclude "release blocker,no changelog" ``` **Exclusion rules (issue-level):** @@ -68,7 +68,7 @@ If updating from an existing `CHANGES.md`, find issues in the milestone that are NOT yet referenced in the changelog: ```bash -python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" --compare CHANGES.md +python3 scripts/gh.py issues "2.16.0" --exclude "release blocker,no changelog" --compare CHANGES.md ``` This returns a filtered JSON array with only the missing issues. @@ -85,23 +85,23 @@ community contribution attribution, or to read the PR body for ```bash # One or more PR numbers -python3 tools/gh.py prs 9179 9204 9311 +python3 scripts/gh.py prs 9179 9204 9311 # From a file -python3 tools/gh.py prs --file prs.txt +python3 scripts/gh.py prs --file prs.txt # From stdin -cat prs.txt | python3 tools/gh.py prs --stdin +cat prs.txt | python3 scripts/gh.py prs --stdin ``` The `prs` command also supports listing all PRs in a milestone in one call: ```bash # All merged PRs in a milestone (default) -python3 tools/gh.py prs --milestone "2.16.0" +python3 scripts/gh.py prs --milestone "2.16.0" # All states (merged, open, closed) -python3 tools/gh.py prs --milestone "2.16.0" --state all +python3 scripts/gh.py prs --milestone "2.16.0" --state all ``` The `prs` command returns JSON with `number`, `title`, `body`, `state`, @@ -113,13 +113,13 @@ You can also list all PRs in a milestone in a single call: ```bash # All merged PRs in a milestone (default) -python3 tools/gh.py prs --milestone "2.16.0" +python3 scripts/gh.py prs --milestone "2.16.0" # All states (merged, open, closed) -python3 tools/gh.py prs --milestone "2.16.0" --state all +python3 scripts/gh.py prs --milestone "2.16.0" --state all # Open PRs only -python3 tools/gh.py prs --milestone "2.16.0" --state open +python3 scripts/gh.py prs --milestone "2.16.0" --state open ``` The milestone path uses paginated GraphQL on the milestone's `pullRequests` @@ -155,7 +155,7 @@ The attribution should reference the **PR author**, not the issue author. The `prs` subcommand includes the `author` field — use that: ```bash -python3 tools/gh.py prs | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['author'])" +python3 scripts/gh.py prs | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['author'])" ``` Placement in the entry line: @@ -190,7 +190,7 @@ only reference **merged** PRs. Verify before writing: ```bash # Collect all PR numbers from the candidate entries and check them -python3 tools/gh.py prs | python3 -c " +python3 scripts/gh.py prs | python3 -c " import json, sys for pr in json.load(sys.stdin): if pr['state'] != 'MERGED': @@ -265,7 +265,7 @@ section) or in the candidate set for the current milestone, check: current milestone since the changelog was last updated (e.g., a fix arrived late and the issue was reassigned to a future milestone)? - Verify the issue is still in the current milestone via - `python3 tools/gh.py issues --state all`. If it's no + `python3 scripts/gh.py issues --state all`. If it's no longer there, remove the entry from the current section. (If the target section doesn't exist yet, the entry is simply dropped.) @@ -337,7 +337,7 @@ cross-reference to catch gaps: ```bash # List all merged PRs in the milestone -python3 tools/gh.py prs --milestone "" --state merged > /tmp/milestone-prs.json +python3 scripts/gh.py prs --milestone "" --state merged > /tmp/milestone-prs.json # Extract PR numbers from the changelog section python3 -c " @@ -378,7 +378,7 @@ changelog or is legitimately excluded (check its labels). Also verify that no closed-unmerged PRs remain in the changelog: ```bash -python3 tools/gh.py prs --milestone "" --state all | python3 -c " +python3 scripts/gh.py prs --milestone "" --state all | python3 -c " import json, sys data = json.load(sys.stdin) closed = [p for p in data if p['state'] == 'CLOSED'] @@ -483,13 +483,13 @@ def fmt_issue_list(nums): # --- Fetch milestone data --- result = subprocess.run( - ["python3", "tools/gh.py", "issues", MILESTONE, "--state", "all"], + ["python3", "scripts/gh.py", "issues", MILESTONE, "--state", "all"], capture_output=True, text=True) all_issues = json.loads(result.stdout) issue_by_num = {i['number']: i for i in all_issues} result = subprocess.run( - ["python3", "tools/gh.py", "prs", "--milestone", MILESTONE, "--state", "all"], + ["python3", "scripts/gh.py", "prs", "--milestone", MILESTONE, "--state", "all"], capture_output=True, text=True) all_prs = json.loads(result.stdout) pr_by_num = {p['number']: p for p in all_prs} @@ -728,7 +728,7 @@ self-contained and clickable in any Markdown viewer. reference if applicable. - **Re-fetch before editing.** Milestones can change — always re-fetch issues before making edits, don't rely on cached data. -- **Use `tools/gh.py`.** Prefer the helper script over raw `gh api` calls for +- **Use `scripts/gh.py`.** Prefer the helper script over raw `gh api` calls for milestone issue listing and PR detail fetching. It handles GraphQL pagination, batching, and label filtering automatically. - **Verify PR merge status.** Not all closing PRs are merged — community PRs @@ -739,7 +739,7 @@ self-contained and clickable in any Markdown viewer. labels. Check both. - **Cross-reference milestone PRs, not just issues.** The `--compare` flag on the `issues` command only compares issue numbers. Merged PRs not linked to - any milestone issue can be missed. Use `python3 tools/gh.py prs --milestone` + any milestone issue can be missed. Use `python3 scripts/gh.py prs --milestone` for a full PR cross-reference. - **False-positive PR-to-issue associations.** A PR may claim to close an issue from a different project or context. If the PR title and issue title diff --git a/.serena/memories/backend/core.md b/.serena/memories/backend/core.md index 15f0dd109d..6530f74308 100644 --- a/.serena/memories/backend/core.md +++ b/.serena/memories/backend/core.md @@ -39,8 +39,8 @@ Backend RPC command areas without focused memories include access tokens, binfil Database migrations live in `backend/src/app/migrations/`; pure SQL migrations are under `backend/src/app/migrations/sql/`. SQL filenames conventionally start with a sequence and verb/table description, e.g. `0026-mod-profile-table-add-is-active-field`. Applied migrations are tracked in the `migrations` table. -For interactive PostgreSQL access with correct dev defaults, use `tools/psql`; to dump -the current DDL schema, use `tools/db-schema` (see `mem:tools/psql`). +For interactive PostgreSQL access with correct dev defaults, use `scripts/psql`; to dump +the current DDL schema, use `scripts/db-schema` (see `mem:scripts/psql`). For deeper details on transaction semantics, advisory locks, Transit vs JSON helpers, and dev/test DB URLs: `mem:backend/rpc-db-worker-subtleties`. @@ -56,14 +56,14 @@ In devenv, backend nREPL is exposed on port 6064. ### Non-interactive eval (preferred for agents) -`./tools/nrepl-eval.mjs` connects to an already-running nREPL server and evaluates code. Session state (defs, `in-ns`) persists across invocations via a stored session ID in `/tmp/penpot-nrepl-session--`. +`./scripts/nrepl-eval.mjs` connects to an already-running nREPL server and evaluates code. Session state (defs, `in-ns`) persists across invocations via a stored session ID in `/tmp/penpot-nrepl-session--`. ```bash -./tools/nrepl-eval.mjs '(+ 1 2)' # single expression -./tools/nrepl-eval.mjs "(require '[my.ns :as ns] :reload)" # reload after edits -./tools/nrepl-eval.mjs -e # inspect last exception (*e) -./tools/nrepl-eval.mjs --reset-session '(def x 0)' # discard session, start fresh -./tools/nrepl-eval.mjs <<'EOF' # multi-expression heredoc +./scripts/nrepl-eval.mjs '(+ 1 2)' # single expression +./scripts/nrepl-eval.mjs "(require '[my.ns :as ns] :reload)" # reload after edits +./scripts/nrepl-eval.mjs -e # inspect last exception (*e) +./scripts/nrepl-eval.mjs --reset-session '(def x 0)' # discard session, start fresh +./scripts/nrepl-eval.mjs <<'EOF' # multi-expression heredoc (def x 10) (+ x 20) EOF @@ -92,12 +92,12 @@ Fixtures can populate local data for manual testing/perf work. From the backend IMPORTANT: all CLI commands must be executed from the `backend/` subdirectory. -* **Linting:** `pnpm run lint` from the repository root. -* **Formatting:** `pnpm run check-fmt`. Use `pnpm run fmt` to fix. Avoid unrelated whitespace diffs. +* **Linting:** `clj-kondo --lint ../common/src/ src/`. +* **Formatting:** `cljfmt check src/ test/` to check, `cljfmt fix src/ test/` to fix. Avoid unrelated whitespace diffs. **Before linting:** if delimiter errors are suspected (after LLM edits), run -`tools/paren-repair.bb` on the affected files first. Delimiter errors produce -misleading linter/compiler output. See `mem:tools/paren-repair`. +`scripts/paren-repair` on the affected files first. Delimiter errors produce +misleading linter/compiler output. See `mem:scripts/paren-repair`. ## Testing diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index d291c81bf9..a286887ac2 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -22,8 +22,8 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. - Align `let` binding values: when a `let` form has multiple bindings spanning several lines, align the value forms to the same column with spaces. - If you introduce delimiter errors (mismatched parens/brackets) in Clojure/CLJS files, - fix them with `tools/paren-repair.bb` BEFORE running lint/format checks. - See `mem:tools/paren-repair` for usage. + fix them with `scripts/paren-repair` BEFORE running lint/format checks. + See `mem:scripts/paren-repair` for usage. - Never run anything that destroys data without explicit permission, including `drop-devenv`, `docker compose down -v`, `docker volume rm ...`. The user's real work lives in the volumes of the shared infra. # Project modules @@ -54,18 +54,21 @@ module. You can read it from `mem:/core` # Dev tools -- `tools/nrepl-eval.mjs` — Evaluate Clojure/ClojureScript code via nREPL. +- `scripts/nrepl-eval.mjs` — Evaluate Clojure/ClojureScript code via nREPL. Supports `--backend` (port 6064) and `--frontend` (port 3447) aliases. - See `mem:tools/nrepl-eval`. -- `tools/paren-repair.bb` — Fix mismatched delimiters in Clojure/CLJS files + See `mem:scripts/nrepl-eval`. +- `scripts/paren-repair` — Fix mismatched delimiters in Clojure/CLJS files and reformat with cljfmt. Run before lint checks when LLM edits break parens. - See `mem:tools/paren-repair`. -- `tools/psql` — PostgreSQL client wrapper with devenv defaults. - Companion: `tools/db-schema` for DDL dumps. See `mem:tools/psql`. -- `tools/taiga.py` — Fetch public issues, user stories, and tasks from the - Penpot Taiga project without authentication. See `mem:tools/taiga`. -- `tools/gh.py` — GitHub operations helper: list milestone issues, fetch PR - details, compare against CHANGES.md. Requires `gh` CLI. See `mem:tools/gh`. + See `mem:scripts/paren-repair`. +- `scripts/psql` — PostgreSQL client wrapper with devenv defaults. + Companion: `scripts/db-schema` for DDL dumps. See `mem:scripts/psql`. +- `scripts/taiga.py` — Fetch public issues, user stories, and tasks from the + Penpot Taiga project without authentication. See `mem:scripts/taiga`. +- `scripts/gh.py` — GitHub operations helper: list milestone issues, fetch PR + details, compare against CHANGES.md. Requires `gh` CLI. See `mem:scripts/gh`. +- `scripts/error-reports.mjs` — Query error reports via RPC API with token + authentication. Supports list/get operations with filtering and pagination. + See `mem:scripts/error-reports`. # Dependency graph diff --git a/.serena/memories/frontend/core.md b/.serena/memories/frontend/core.md index 5f06f8cfb0..b229e2b124 100644 --- a/.serena/memories/frontend/core.md +++ b/.serena/memories/frontend/core.md @@ -27,9 +27,9 @@ From `frontend/`: - Translation formatting after i18n edits: `pnpm run translations`. **Before linting:** if delimiter errors are suspected (after LLM edits, or -lint/compiler reports syntax errors), run `tools/paren-repair.bb` on the +lint/compiler reports syntax errors), run `scripts/paren-repair` on the affected files first. Delimiter errors produce misleading linter output. -See `mem:tools/paren-repair`. +See `mem:scripts/paren-repair`. ## Focused memory routing diff --git a/.serena/memories/frontend/handling-errors-and-debugging.md b/.serena/memories/frontend/handling-errors-and-debugging.md index ede8c1581b..c44cb6b89e 100644 --- a/.serena/memories/frontend/handling-errors-and-debugging.md +++ b/.serena/memories/frontend/handling-errors-and-debugging.md @@ -11,10 +11,10 @@ The latter is needed because syntax errors in parentheses give an uninformative tool can often find the exact location of such errors. When delimiter errors are detected (typically from lint or compiler output), -fix the affected files with `tools/paren-repair.bb`. The `clj_check_parentheses` +fix the affected files with `scripts/paren-repair`. The `clj_check_parentheses` MCP tool can also pinpoint the error location when available, but it is not required — standard build errors are usually enough. -See `mem:tools/paren-repair`. +See `mem:scripts/paren-repair`. ## Runtime patching with `set!` diff --git a/.serena/memories/tools/gh.md b/.serena/memories/scripts/gh.md similarity index 67% rename from .serena/memories/tools/gh.md rename to .serena/memories/scripts/gh.md index 3f74b82b66..ed6adfa8f1 100644 --- a/.serena/memories/tools/gh.md +++ b/.serena/memories/scripts/gh.md @@ -1,6 +1,6 @@ # GitHub operations helper -`tools/gh.py` is a multi-purpose CLI for querying the penpot/penpot GitHub +`scripts/gh.py` is a multi-purpose CLI for querying the penpot/penpot GitHub repository via GraphQL and REST APIs through the authenticated `gh` CLI. ## When to use @@ -23,24 +23,24 @@ List issues in a milestone, with filtering by state, labels, and project status. ```bash # Closed issues in a milestone (default) -python3 tools/gh.py issues "2.16.0" +python3 scripts/gh.py issues "2.16.0" # All issues in a milestone -python3 tools/gh.py issues "2.16.0" --state all +python3 scripts/gh.py issues "2.16.0" --state all # Issues with no milestone -python3 tools/gh.py issues none -python3 tools/gh.py issues none --state open +python3 scripts/gh.py issues none +python3 scripts/gh.py issues none --state open # Filter by label (include only) -python3 tools/gh.py issues "2.16.0" --label "bug" -python3 tools/gh.py issues "2.16.0" --label "bug,regression" +python3 scripts/gh.py issues "2.16.0" --label "bug" +python3 scripts/gh.py issues "2.16.0" --label "bug,regression" # Exclude by label -python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" +python3 scripts/gh.py issues "2.16.0" --exclude "release blocker,no changelog" # Show only issues NOT yet in CHANGES.md -python3 tools/gh.py issues "2.16.0" --compare CHANGES.md +python3 scripts/gh.py issues "2.16.0" --compare CHANGES.md ``` **Default filters** (override with flags): @@ -55,19 +55,19 @@ Fetch PR details by number or by milestone. ```bash # Fetch specific PRs -python3 tools/gh.py prs 9179 9204 9311 +python3 scripts/gh.py prs 9179 9204 9311 # Read PR numbers from file -python3 tools/gh.py prs --file prs.txt +python3 scripts/gh.py prs --file prs.txt # Read PR numbers from stdin -cat prs.txt | python3 tools/gh.py prs --stdin +cat prs.txt | python3 scripts/gh.py prs --stdin # All PRs in a milestone (default: merged only) -python3 tools/gh.py prs --milestone "2.16.0" +python3 scripts/gh.py prs --milestone "2.16.0" # All PRs in a milestone (all states) -python3 tools/gh.py prs --milestone "2.16.0" --state all +python3 scripts/gh.py prs --milestone "2.16.0" --state all ``` **Output**: JSON array to stdout; progress to stderr. diff --git a/.serena/memories/tools/nrepl-eval.md b/.serena/memories/scripts/nrepl-eval.md similarity index 66% rename from .serena/memories/tools/nrepl-eval.md rename to .serena/memories/scripts/nrepl-eval.md index f5ec3ab994..96915492fb 100644 --- a/.serena/memories/tools/nrepl-eval.md +++ b/.serena/memories/scripts/nrepl-eval.md @@ -1,7 +1,7 @@ # nREPL Eval Evaluate Clojure (or ClojureScript) code via a running nREPL server using -`tools/nrepl-eval.mjs` — a standalone CLI application. +`scripts/nrepl-eval.mjs` — a standalone CLI application. Session state (defs, in-ns, etc.) persists across invocations via a stored session ID, so you can build up state incrementally. @@ -9,9 +9,9 @@ session ID, so you can build up state incrementally. ## Usage ```bash -node tools/nrepl-eval.mjs [options] [] +node scripts/nrepl-eval.mjs [options] [] # or -./tools/nrepl-eval.mjs [options] [] +./scripts/nrepl-eval.mjs [options] [] ``` ## Options @@ -47,37 +47,37 @@ Sessions are persisted to `/tmp/penpot-nrepl-session--`. State carries across calls automatically: ```bash -./tools/nrepl-eval.mjs '(def x 42)' -./tools/nrepl-eval.mjs 'x' +./scripts/nrepl-eval.mjs '(def x 42)' +./scripts/nrepl-eval.mjs 'x' # => 42 ``` Reset the session to start fresh: ```bash -./tools/nrepl-eval.mjs --reset-session '(def x 0)' +./scripts/nrepl-eval.mjs --reset-session '(def x 0)' ``` ### Evaluate code **Single expression (inline) — uses default port 6064:** ```bash -./tools/nrepl-eval.mjs '(+ 1 2 3)' +./scripts/nrepl-eval.mjs '(+ 1 2 3)' ``` **Backend nREPL (explicit):** ```bash -./tools/nrepl-eval.mjs --backend '(+ 1 2 3)' +./scripts/nrepl-eval.mjs --backend '(+ 1 2 3)' ``` **Frontend nREPL:** ```bash -./tools/nrepl-eval.mjs --frontend '(js/alert "hi")' +./scripts/nrepl-eval.mjs --frontend '(js/alert "hi")' ``` **Multiple expressions via heredoc (recommended — avoids escaping issues):** ```bash -./tools/nrepl-eval.mjs <<'EOF' +./scripts/nrepl-eval.mjs <<'EOF' (def x 10) (+ x 20) EOF @@ -85,7 +85,7 @@ EOF **Override with a different port:** ```bash -./tools/nrepl-eval.mjs -p 7888 '(+ 1 2 3)' +./scripts/nrepl-eval.mjs -p 7888 '(+ 1 2 3)' ``` ### Inspect last exception @@ -93,26 +93,48 @@ EOF After code throws an error, retrieve the full exception details: ```bash -./tools/nrepl-eval.mjs -e +./scripts/nrepl-eval.mjs -e ``` ## Common Patterns **Require a namespace with reload:** ```bash -./tools/nrepl-eval.mjs "(require '[my.namespace :as ns] :reload)" +./scripts/nrepl-eval.mjs "(require '[my.namespace :as ns] :reload)" ``` **Test a function:** ```bash -./tools/nrepl-eval.mjs "(ns/my-function arg1 arg2)" +./scripts/nrepl-eval.mjs "(ns/my-function arg1 arg2)" ``` **Long-running operation with custom timeout:** ```bash -./tools/nrepl-eval.mjs -t 300000 "(long-running-fn)" +./scripts/nrepl-eval.mjs -t 300000 "(long-running-fn)" ``` +### Accessing Private Functions + +Private functions (declared with `^:private` or `defn-`) cannot be called +directly from outside their namespace. Use the var quote syntax `#'` to +access the underlying var: + +**This fails:** +```bash +./scripts/nrepl-eval.mjs "(app.rpc.commands.error-reports/build-list-query {})" +# => Syntax error: app.rpc.commands.error-reports/build-list-query is not public +``` + +**This works:** +```bash +./scripts/nrepl-eval.mjs "(#'app.rpc.commands.error-reports/build-list-query {})" +# => Returns the result +``` + +The `#'` reader macro resolves to `(var ...)`, giving you direct access to +the var regardless of its visibility modifier. The syntax is `#'` followed +by the fully qualified symbol. + ## Key Principles - **Default port is 6064** — just pass code directly, no `-p` needed when diff --git a/.serena/memories/scripts/paren-repair.md b/.serena/memories/scripts/paren-repair.md new file mode 100644 index 0000000000..162e6d28d8 --- /dev/null +++ b/.serena/memories/scripts/paren-repair.md @@ -0,0 +1,43 @@ +# Paren-Repair + +`scripts/paren-repair` fixes mismatched parentheses, brackets, and braces in +Clojure/ClojureScript files, then reformats them with cljfmt. + +## When to use + +- After LLM edits introduce broken delimiters — proactively run it on files + you just touched. +- When lint (clj-kondo), the Clojure compiler, or shadow-cljs report syntax + errors mentioning mismatched/unclosed delimiters, reader errors, or + unexpected EOF. +- Before running lint/format checks — delimiter errors make linter output + misleading. Fix them first, then lint. + +## How to use (CLI) + +```bash +# File mode (in-place fix + format) +bb scripts/paren-repair path/to/file.clj + +# Pipe mode (stdin → fixed code to stdout) +echo '(def x 1' | bb scripts/paren-repair + +# Help +bb scripts/paren-repair --help +``` +`bb` must be invoked from the repo root so the path `scripts/paren-repair` resolves. + +## Native Tool Available (opencode) + +A native opencode tool `paren-repair` is available at `.opencode/tools/paren-repair.ts`. +The LLM can call it directly with: +- `files`: Array of file paths to fix +- `code`: Code string to fix via stdin + +Example usage by the LLM: +``` +paren-repair(files="src/foo.clj, src/bar.cljs") +paren-repair(code="(defn foo [x") +``` + + diff --git a/.serena/memories/scripts/psql.md b/.serena/memories/scripts/psql.md new file mode 100644 index 0000000000..c969fd6742 --- /dev/null +++ b/.serena/memories/scripts/psql.md @@ -0,0 +1,39 @@ +# Psql + +`scripts/psql` is a wrapper around `psql` that connects to the Penpot PostgreSQL +database using environment variables (`PENPOT_DB_HOST`, `PENPOT_DB_USER`, +`PENPOT_DB_PASSWORD`, `PENPOT_DB_NAME`) with sensible defaults for local +development. + +## When to use + +- Running ad-hoc SQL queries against the Penpot database. +- Inspecting schema, migrations, or data during development or debugging. + +## How to use (CLI) + +```bash +# Default connection (penpot db, localhost) +scripts/psql -c "SELECT version();" + +# Test database +scripts/psql --test -c "SELECT * FROM migrations;" + +# Custom host/user/database +scripts/psql --host myhost --user myuser --db mydb +``` + +`scripts/psql` must be invoked from the repo root so the path resolves. + +## Native Tool Available (opencode) + +A native opencode tool `penpot-psql` is available. The LLM can call it directly +with: +- `sql`: SQL command string to execute +- `test`: Boolean flag to use the `penpot_test` database + +Example usage by the LLM: +``` +penpot-psql(sql="SELECT version();") +penpot-psql(sql="SELECT * FROM migrations;", test=true) +``` diff --git a/.serena/memories/tools/taiga.md b/.serena/memories/scripts/taiga.md similarity index 69% rename from .serena/memories/tools/taiga.md rename to .serena/memories/scripts/taiga.md index b4e623e3e1..d68274fde5 100644 --- a/.serena/memories/tools/taiga.md +++ b/.serena/memories/scripts/taiga.md @@ -1,6 +1,6 @@ # Taiga API client -`tools/taiga.py` fetches public issues, user stories, and tasks from the +`scripts/taiga.py` fetches public issues, user stories, and tasks from the Penpot Taiga project (id 345963) without authentication. ## When to use @@ -13,16 +13,16 @@ Penpot Taiga project (id 345963) without authentication. ```bash # Fetch by full Taiga URL -python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714 +python3 scripts/taiga.py https://tree.taiga.io/project/penpot/issue/13714 # Fetch by type and ref number -python3 tools/taiga.py issue 13714 -python3 tools/taiga.py us 14128 -python3 tools/taiga.py task 13648 +python3 scripts/taiga.py issue 13714 +python3 scripts/taiga.py us 14128 +python3 scripts/taiga.py task 13648 # Output raw JSON instead of formatted summary -python3 tools/taiga.py --json issue 13714 -python3 tools/taiga.py --json https://tree.taiga.io/project/penpot/us/14128 +python3 scripts/taiga.py --json issue 13714 +python3 scripts/taiga.py --json https://tree.taiga.io/project/penpot/us/14128 ``` ## Supported types diff --git a/.serena/memories/tools/paren-repair.md b/.serena/memories/tools/paren-repair.md deleted file mode 100644 index e3817ca839..0000000000 --- a/.serena/memories/tools/paren-repair.md +++ /dev/null @@ -1,29 +0,0 @@ -# Paren-Repair - -`tools/paren-repair.bb` fixes mismatched parentheses, brackets, and braces in -Clojure/ClojureScript files, then reformats them with cljfmt. - -## When to use - -- After LLM edits introduce broken delimiters — proactively run it on files - you just touched. -- When lint (clj-kondo), the Clojure compiler, or shadow-cljs report syntax - errors mentioning mismatched/unclosed delimiters, reader errors, or - unexpected EOF. -- Before running lint/format checks — delimiter errors make linter output - misleading. Fix them first, then lint. - -## How to use - -```bash -# File mode (in-place fix + format) -bb tools/paren-repair.bb path/to/file.clj - -# Pipe mode (stdin → fixed code to stdout) -echo '(def x 1' | bb tools/paren-repair.bb - -# Help -bb tools/paren-repair.bb --help -``` - -`bb` must be invoked from the repo root so the path `tools/paren-repair.bb` resolves. diff --git a/.serena/memories/tools/psql.md b/.serena/memories/tools/psql.md deleted file mode 100644 index c502b4c1e9..0000000000 --- a/.serena/memories/tools/psql.md +++ /dev/null @@ -1,51 +0,0 @@ -# PostgreSQL client wrapper - -`tools/psql` is a wrapper around the `psql` command with defaults preconfigured -for the Penpot development environment. - -## When to use - -- Running SQL queries against the dev database (`penpot`) or test database - (`penpot_test`). -- Inspecting table structures, running migrations manually, or debugging - database state. -- Any time you need PostgreSQL access and want the correct host/user/password - without typing them each time. - -## How to use - -```bash -# Interactive session (penpot database) -tools/psql - -# Interactive session (penpot_test database) -tools/psql --test - -# Inline query (penpot) -tools/psql -c "SELECT 1" - -# Inline query (penpot_test) -tools/psql --test -c "SELECT 1" - -# Override defaults -tools/psql -h other-host -U other-user -d other-db - -# Pipe SQL from a file -tools/psql -f some-query.sql -``` - -All standard `psql` flags are passed through after the wrapper's own flags. - -## Defaults - -| Setting | Default | Env override | -|----------|-----------|---------------------| -| Host | `postgres` | `PENPOT_DB_HOST` | -| User | `penpot` | `PENPOT_DB_USER` | -| Password | `penpot` | `PENPOT_DB_PASSWORD` | -| Database | `penpot` | `PENPOT_DB_NAME` | - -## See also - -`tools/db-schema` — a companion script that dumps the current DDL schema -using `pg_dump --schema-only`, with the same defaults and `--test` flag. diff --git a/.serena/memories/workflow/creating-prs.md b/.serena/memories/workflow/creating-prs.md index c0f763f9e3..00252a7661 100644 --- a/.serena/memories/workflow/creating-prs.md +++ b/.serena/memories/workflow/creating-prs.md @@ -4,10 +4,10 @@ PR only on explicit request. Branch: issue/feature-specific; fallback `/ /tmp/pr-body.md << 'PR_BODY' PR_BODY -TARGET=$(tools/detect-target-branch) +TARGET=$(scripts/detect-target-branch) gh pr create \ --repo penpot/penpot \ diff --git a/AGENTS.md b/AGENTS.md index 8c3e6ba431..b6aae1f32d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AI AGENT GUIDE -## Hard rules (always apply — no exceptions) +## HARD RULES (always apply — no exceptions) - **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell. If a push is required to surface the @@ -92,3 +92,21 @@ precision while maintaining a strong focus on maintainability and performance. down into atomic steps. 2. Be concise and autonomous. 3. Do **not** touch unrelated modules unless the task explicitly requires it. + +--- + +# Available Scripts & Tools + +## Native opencode Tools (callable directly by the LLM) + +- `paren-repair` — Fix mismatched delimiters + reformat Clojure files. Example: `paren-repair(files="src/foo.clj, src/bar.cljs")` +- `penpot-psql` — Execute SQL against the Penpot database. Example: `penpot-psql(sql="SELECT version();")` + +## Scripts (from repo root via `scripts/`) + +- `scripts/paren-repair` — Fix mismatched delimiters in Clojure/CLJS files + reformat with cljfmt. See `mem:scripts/paren-repair`. +- `scripts/psql` — Connect to the Penpot PostgreSQL database (wraps `psql` with env-var defaults). See `mem:scripts/psql`. +- `scripts/nrepl-eval.mjs` — Evaluate Clojure code via nREPL (backend + frontend). +- `scripts/check-commit` — Validate commit messages against Penpot's commit guidelines. +- `scripts/check-fmt-clj` — Check Clojure formatting without modifying files. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12729395d4..63b931900f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,9 @@ Center](https://help.penpot.app/). - [Reporting Bugs](#reporting-bugs) - [Pull Requests](#pull-requests) - [Workflow](#workflow) - - [Title format](#title-format) - - [Description](#description) - - [Branch naming](#branch-naming) + - [Format](#format) + - [Title format](#title-format) + - [Description](#description) - [Review process](#review-process) - [What we won't accept](#what-we-wont-accept) - [Good first issues](#good-first-issues) @@ -73,35 +73,23 @@ Advisories](https://github.com/penpot/penpot/security/advisories) 4. **Format and lint** — run the checks described in [Formatting and Linting](#formatting-and-linting) before submitting. -### Title format +### Format + +#### Title + +> **IMPORTANT:** When a PR is squash-merged, the PR title becomes the +> commit message on the main branch. Getting the title right matters. Pull request titles **must** follow the same convention as commit subjects: ``` -:emoji: +:emoji: Subject line (imperative, capitalized, no period, <=70 chars) ``` -- Use the **imperative mood** (e.g. "Fix", not "Fixed"). -- Capitalize the first letter of the subject. -- Do not end the subject with a period. -- Keep the subject to **70 characters** or fewer. -- Use one of the [commit type emojis](#commit-types) listed below. +Read [Creating Commits](./.serena/memories/workflow/creating-commits.md) +for more concrete information. -When a PR contains multiple unrelated commits, choose the emoji that -best represents the dominant change. - -**Examples:** - -``` -:bug: Fix unexpected error on launching modal -:sparkles: Enable new modal for profile -:zap: Improve performance of dashboard navigation -``` - -> **Note:** When a PR is squash-merged, the PR title becomes the -> commit message on the main branch. Getting the title right matters. - -### Description +#### Description Every pull request should include a description that helps reviewers understand the change quickly: @@ -114,24 +102,8 @@ understand the change quickly: 5. **Breaking changes** — call out anything that affects existing users or requires migration steps. -### Branch naming - -Use a descriptive branch name that reflects the type and scope of the -change: - -``` -/ -``` - -Types: `fix`, `feat`, `refactor`, `docs`, `chore`, `perf`. - -Optionally include the issue number: - -``` -fix/9122-email-blacklisting -feat/export-webp -refactor/layout-sizing -``` +Read [Creating PRs](./.serena/memories/workflow/creating-prs.md) +for more concrete information. ### Review process @@ -151,10 +123,10 @@ refactor/layout-sizing To save time on both sides, please avoid submitting PRs that: - Introduce new dependencies without prior discussion. -- Change the build system or CI configuration without maintainer - approval. -- Mix unrelated changes in a single PR — keep PRs focused on one - concern. +- Change the build system or CI configuration without maintainer approval. +- Mix unrelated changes in a single PR — keep PRs focused on one concern. +- Submit AI-generated code without human review. +- Skip local syntax and formatting checks before submitting. - Skip the [discussion step](#workflow) for non-bug-fix changes. ### Good first issues @@ -217,36 +189,8 @@ Commit messages must follow this format: ## Formatting and Linting -We use [cljfmt](https://github.com/weavejester/cljfmt) for formatting and -[clj-kondo](https://github.com/clj-kondo/clj-kondo) for linting. - -```bash -# Check formatting (does not modify files) -./scripts/check-fmt - -# Fix formatting (modifies files in place) -./scripts/fmt - -# Lint -./scripts/lint -``` - -For frontend SCSS, we use `stylelint` for linting and -`Prettier` for formatting: - -```bash -cd frontend - -# Lint SCSS -pnpm run lint:scss (does not modify files) - -# Fix SCSS formatting (modifies files in place) -pnpm run fmt:scss -``` - -Ideally, run these as git pre-commit hooks. -[Husky](https://typicode.github.io/husky/#/) is a convenient option for -setting this up. +Each module has its own linting and formatting commands — see the relevant one on the +[Serena Memories](./.serena/memories/) ## Changelog diff --git a/package.json b/package.json index 60e85d0149..256ffc3436 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "penpot", - "version": "1.20.0", + "version": "2.17.0", "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, @@ -10,11 +10,6 @@ "url": "https://github.com/penpot/penpot" }, "type": "module", - "scripts": { - "lint": "./scripts/lint", - "check-fmt": "./scripts/check-fmt", - "fmt": "./scripts/fmt" - }, "devDependencies": { "@playwright/mcp": "^0.0.76", "@playwright/test": "^1.61.1", diff --git a/scripts/attach-opencode b/scripts/attach-opencode deleted file mode 100755 index ddf43bb828..0000000000 --- a/scripts/attach-opencode +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e; - -PORT=14180 -while [[ $# -gt 0 ]]; do - case "$1" in - --port|-p) PORT="$2"; shift 2 ;; - *) echo "Unknown option: $1"; exit 1 ;; - esac -done - -pnpm exec opencode attach "http://localhost:${PORT}"; diff --git a/scripts/check-fmt b/scripts/check-fmt-clj similarity index 100% rename from scripts/check-fmt rename to scripts/check-fmt-clj diff --git a/tools/db-schema b/scripts/db-schema similarity index 100% rename from tools/db-schema rename to scripts/db-schema diff --git a/tools/detect-target-branch b/scripts/detect-target-branch similarity index 93% rename from tools/detect-target-branch rename to scripts/detect-target-branch index 5d3d584552..65ff49c0ca 100755 --- a/tools/detect-target-branch +++ b/scripts/detect-target-branch @@ -12,8 +12,8 @@ set -euo pipefail # branch shares its tip commit with the target (not yet diverged). # # Usage: -# tools/detect-target-branch # print branch name -# tools/detect-target-branch --verbose # print "branch~N" +# scripts/detect-target-branch # print branch name +# scripts/detect-target-branch --verbose # print "branch~N" # # Exit status: # 0 — target found diff --git a/scripts/fmt b/scripts/fmt deleted file mode 100755 index 3de70a2709..0000000000 --- a/scripts/fmt +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -cljfmt --parallel=true fix \ - common/src/ \ - common/test/ \ - frontend/src/ \ - frontend/test/ \ - backend/src/ \ - backend/test/ \ - exporter/src/ \ - library/src; diff --git a/tools/gh.py b/scripts/gh.py similarity index 96% rename from tools/gh.py rename to scripts/gh.py index 51779cefb3..7017389d52 100755 --- a/tools/gh.py +++ b/scripts/gh.py @@ -9,20 +9,20 @@ Subcommands: prs Fetch details for one or more PRs (by number or milestone) Usage: - python3 tools/gh.py issues (default: state=closed) - python3 tools/gh.py issues "2.16.0" --state all - python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" - python3 tools/gh.py issues "2.16.0" --label "bug" (include only issues with label) - python3 tools/gh.py issues "2.16.0" --label "bug,regression" --exclude "no changelog" - python3 tools/gh.py issues "2.16.0" --compare CHANGES.md - python3 tools/gh.py issues none (issues with no milestone) - python3 tools/gh.py issues none --label "enhancement" - python3 tools/gh.py issues none --state open - python3 tools/gh.py prs 9179 9204 9311 - python3 tools/gh.py prs --file prs.txt - cat prs.txt | python3 tools/gh.py prs --stdin - python3 tools/gh.py prs --milestone "2.16.0" (default: state=merged) - python3 tools/gh.py prs --milestone "2.16.0" --state all + python3 scripts/gh.py issues (default: state=closed) + python3 scripts/gh.py issues "2.16.0" --state all + python3 scripts/gh.py issues "2.16.0" --exclude "release blocker,no changelog" + python3 scripts/gh.py issues "2.16.0" --label "bug" (include only issues with label) + python3 scripts/gh.py issues "2.16.0" --label "bug,regression" --exclude "no changelog" + python3 scripts/gh.py issues "2.16.0" --compare CHANGES.md + python3 scripts/gh.py issues none (issues with no milestone) + python3 scripts/gh.py issues none --label "enhancement" + python3 scripts/gh.py issues none --state open + python3 scripts/gh.py prs 9179 9204 9311 + python3 scripts/gh.py prs --file prs.txt + cat prs.txt | python3 scripts/gh.py prs --stdin + python3 scripts/gh.py prs --milestone "2.16.0" (default: state=merged) + python3 scripts/gh.py prs --milestone "2.16.0" --state all Prerequisites: - gh CLI authenticated (gh auth status) diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 4ab59aed13..0000000000 --- a/scripts/lint +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -clj-kondo --parallel=true --lint common/src; -clj-kondo --parallel=true --lint frontend/src; -clj-kondo --parallel=true --lint backend/src; -clj-kondo --parallel=true --lint exporter/src/; -clj-kondo --parallel=true --lint library/src; diff --git a/tools/nrepl-eval.mjs b/scripts/nrepl-eval.mjs similarity index 100% rename from tools/nrepl-eval.mjs rename to scripts/nrepl-eval.mjs diff --git a/tools/paren-repair.bb b/scripts/paren-repair similarity index 93% rename from tools/paren-repair.bb rename to scripts/paren-repair index aba0ee26ac..d00930dcc1 100755 --- a/tools/paren-repair.bb +++ b/scripts/paren-repair @@ -323,27 +323,14 @@ success-count (count successes) failure-count (count failures)] - ;; Print results - (println) - (println "paren-repair Results") - (println "========================") - (println) - - (doseq [{:keys [file-path message delimiter-fixed formatted]} results] - (let [tags (when (or delimiter-fixed formatted) - (str " [" - (string/join ", " - (filter some? - [(when delimiter-fixed "delimiter-fixed") - (when formatted "formatted")])) - "]"))] - (println (str " " file-path ": " message tags)))) - - (println) - (println "Summary:") - (println " Success:" success-count) - (println " Failed: " failure-count) - (println) + (doseq [{:keys [file-path success message delimiter-fixed formatted]} results] + (let [status (cond + (not success) (str "error: " message) + (and delimiter-fixed formatted) "delimiter-fixed, formatted" + delimiter-fixed "delimiter-fixed" + formatted "formatted" + :else "no-changes")] + (println (str file-path ": " status)))) (if (zero? failure-count) (System/exit 0) diff --git a/tools/psql b/scripts/psql similarity index 100% rename from tools/psql rename to scripts/psql diff --git a/scripts/start-opencode b/scripts/start-opencode deleted file mode 100755 index 5a7bb34f39..0000000000 --- a/scripts/start-opencode +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -set -e; -pnpm exec opencode; diff --git a/scripts/start-opencode-server b/scripts/start-opencode-server deleted file mode 100755 index b173f24cba..0000000000 --- a/scripts/start-opencode-server +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -set -e; -pnpm exec opencode serve --hostname 0.0.0.0 --port 14180; diff --git a/tools/taiga.py b/scripts/taiga.py similarity index 95% rename from tools/taiga.py rename to scripts/taiga.py index 1e96364a31..78bce5ccbd 100755 --- a/tools/taiga.py +++ b/scripts/taiga.py @@ -4,15 +4,15 @@ Taiga API client — fetch public issues, user stories, and tasks from the Penpot project (id 345963) without authentication. Usage: - python3 tools/taiga.py - python3 tools/taiga.py - python3 tools/taiga.py [--json] - python3 tools/taiga.py [--json] + python3 scripts/taiga.py + python3 scripts/taiga.py + python3 scripts/taiga.py [--json] + python3 scripts/taiga.py [--json] Examples: - python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714 - python3 tools/taiga.py --json https://tree.taiga.io/project/penpot/us/14128 - python3 tools/taiga.py task 13648 + python3 scripts/taiga.py https://tree.taiga.io/project/penpot/issue/13714 + python3 scripts/taiga.py --json https://tree.taiga.io/project/penpot/us/14128 + python3 scripts/taiga.py task 13648 """ import argparse