mirror of
https://github.com/penpot/penpot.git
synced 2026-07-22 22:17:58 +00:00
♻️ Consolidate dev tooling into scripts/ and reorganize docs
Move all development tools from tools/ to scripts/ for consistency. Rename lint/fmt/check-fmt to lint-clj/fmt-clj/check-fmt-clj to clarify they target Clojure specifically. Remove unused scripts (attach-opencode, start-opencode, start-opencode-server) and the backport-commit skill. Update all internal references across .serena/, AGENTS.md, and CONTRIBUTING.md to point to the new script locations. Simplify CONTRIBUTING.md by delegating module-specific fmt/lint instructions to the respective serena memories. AI-assisted-by: deepseek-v4-flash
This commit is contained in:
parent
73bfc0dc15
commit
f3bf24b4f6
13
.github/workflows/tests-backend.yml
vendored
13
.github/workflows/tests-backend.yml
vendored
@ -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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
||||
299
.opencode/plugins/penpot.js
Normal file
299
.opencode/plugins/penpot.js
Normal file
@ -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,
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
@ -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 <commit-sha>
|
||||
|
||||
# Get the full diff (including new/deleted files)
|
||||
git show <commit-sha>
|
||||
|
||||
# Capture the original commit message for later reuse
|
||||
git log --format='%B' -1 <commit-sha>
|
||||
```
|
||||
|
||||
### 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 <module>/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 <path>` |
|
||||
| Rename/move file | `bash mv <old> <new>`, 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
|
||||
@ -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] [<code>]
|
||||
./scripts/nrepl-eval.mjs [options] [<code>]
|
||||
```
|
||||
|
||||
| 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
|
||||
```
|
||||
|
||||
@ -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 "<type> <ref>" 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 <taiga-url>
|
||||
python3 tools/taiga.py <type> <ref>
|
||||
python3 tools/taiga.py [--json] <taiga-url>
|
||||
python3 tools/taiga.py [--json] <type> <ref>
|
||||
python3 scripts/taiga.py <taiga-url>
|
||||
python3 scripts/taiga.py <type> <ref>
|
||||
python3 scripts/taiga.py [--json] <taiga-url>
|
||||
python3 scripts/taiga.py [--json] <type> <ref>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
@ -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 <PR_NUMBER> | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['author'])"
|
||||
python3 scripts/gh.py prs <PR_NUMBER> | 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 <ALL_PR_NUMBERS> | python3 -c "
|
||||
python3 scripts/gh.py prs <ALL_PR_NUMBERS> | 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 <MILESTONE> --state all`. If it's no
|
||||
`python3 scripts/gh.py issues <MILESTONE> --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 "<MILESTONE>" --state merged > /tmp/milestone-prs.json
|
||||
python3 scripts/gh.py prs --milestone "<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 "<MILESTONE>" --state all | python3 -c "
|
||||
python3 scripts/gh.py prs --milestone "<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
|
||||
|
||||
@ -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-<host>-<port>`.
|
||||
`./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-<host>-<port>`.
|
||||
|
||||
```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
|
||||
|
||||
|
||||
@ -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:<MODULE>/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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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!`
|
||||
|
||||
|
||||
@ -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.
|
||||
@ -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] [<code>]
|
||||
node scripts/nrepl-eval.mjs [options] [<code>]
|
||||
# or
|
||||
./tools/nrepl-eval.mjs [options] [<code>]
|
||||
./scripts/nrepl-eval.mjs [options] [<code>]
|
||||
```
|
||||
|
||||
## Options
|
||||
@ -47,37 +47,37 @@ Sessions are persisted to `/tmp/penpot-nrepl-session-<host>-<port>`. 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
|
||||
43
.serena/memories/scripts/paren-repair.md
Normal file
43
.serena/memories/scripts/paren-repair.md
Normal file
@ -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")
|
||||
```
|
||||
|
||||
|
||||
39
.serena/memories/scripts/psql.md
Normal file
39
.serena/memories/scripts/psql.md
Normal file
@ -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)
|
||||
```
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -4,10 +4,10 @@ PR only on explicit request. Branch: issue/feature-specific; fallback `<type>/<s
|
||||
|
||||
## Target Branch
|
||||
|
||||
Auto-detect the base branch with `tools/detect-target-branch`:
|
||||
Auto-detect the base branch with `scripts/detect-target-branch`:
|
||||
|
||||
```bash
|
||||
TARGET=$(tools/detect-target-branch)
|
||||
TARGET=$(scripts/detect-target-branch)
|
||||
```
|
||||
|
||||
This outputs `staging` or `develop` by walking the local commit graph (pure local, no remote/network). Do not ask the user for the target branch unless the tool fails.
|
||||
@ -83,7 +83,7 @@ cat > /tmp/pr-body.md << 'PR_BODY'
|
||||
<body content here>
|
||||
PR_BODY
|
||||
|
||||
TARGET=$(tools/detect-target-branch)
|
||||
TARGET=$(scripts/detect-target-branch)
|
||||
|
||||
gh pr create \
|
||||
--repo penpot/penpot \
|
||||
|
||||
20
AGENTS.md
20
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/<name>`)
|
||||
|
||||
- `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.
|
||||
|
||||
|
||||
@ -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: <subject>
|
||||
: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:
|
||||
|
||||
```
|
||||
<type>/<short-description>
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}";
|
||||
@ -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
|
||||
13
scripts/fmt
13
scripts/fmt
@ -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;
|
||||
@ -9,20 +9,20 @@ Subcommands:
|
||||
prs Fetch details for one or more PRs (by number or milestone)
|
||||
|
||||
Usage:
|
||||
python3 tools/gh.py issues <milestone-title> (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 <milestone-title> (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)
|
||||
@ -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;
|
||||
@ -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)
|
||||
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e;
|
||||
pnpm exec opencode;
|
||||
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e;
|
||||
pnpm exec opencode serve --hostname 0.0.0.0 --port 14180;
|
||||
@ -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 <taiga-url>
|
||||
python3 tools/taiga.py <type> <ref>
|
||||
python3 tools/taiga.py [--json] <taiga-url>
|
||||
python3 tools/taiga.py [--json] <type> <ref>
|
||||
python3 scripts/taiga.py <taiga-url>
|
||||
python3 scripts/taiga.py <type> <ref>
|
||||
python3 scripts/taiga.py [--json] <taiga-url>
|
||||
python3 scripts/taiga.py [--json] <type> <ref>
|
||||
|
||||
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
|
||||
Loading…
x
Reference in New Issue
Block a user