mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
feat: add paperclip-dev skill with optional bundled skill support
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
b8725c52ef
commit
d5afd4eacb
3 files changed
+460
-7
No files matched your search
@@ -708,6 +708,20 @@ export async function resolvePaperclipSkillsDir(
|
||||
return null;
|
||||
}
|
||||
|
||||
async function readSkillRequired(skillDir: string): Promise<boolean> {
|
||||
try {
|
||||
const content = await fs.readFile(path.join(skillDir, "SKILL.md"), "utf8");
|
||||
const normalized = content.replace(/\r\n/g, "\n");
|
||||
if (!normalized.startsWith("---\n")) return true;
|
||||
const closing = normalized.indexOf("\n---\n", 4);
|
||||
if (closing < 0) return true;
|
||||
const frontmatter = normalized.slice(4, closing);
|
||||
return !/^\s*required\s*:\s*false\s*$/m.test(frontmatter);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function listPaperclipSkillEntries(
|
||||
moduleDir: string,
|
||||
additionalCandidates: string[] = [],
|
||||
@@ -717,15 +731,20 @@ export async function listPaperclipSkillEntries(
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(root, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => ({
|
||||
const dirs = entries.filter((entry) => entry.isDirectory());
|
||||
return Promise.all(dirs.map(async (entry) => {
|
||||
const skillDir = path.join(root, entry.name);
|
||||
const required = await readSkillRequired(skillDir);
|
||||
return {
|
||||
key: `paperclipai/paperclip/${entry.name}`,
|
||||
runtimeName: entry.name,
|
||||
source: path.join(root, entry.name),
|
||||
required: true,
|
||||
requiredReason: "Bundled Paperclip skills are always available for local adapters.",
|
||||
}));
|
||||
source: skillDir,
|
||||
required,
|
||||
requiredReason: required
|
||||
? "Bundled Paperclip skills are always available for local adapters."
|
||||
: null,
|
||||
};
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -35,6 +35,34 @@ describe("paperclip skill utils", () => {
|
||||
expect(entries[0]?.source).toBe(path.join(root, "skills", "paperclip"));
|
||||
});
|
||||
|
||||
it("marks skills with required: false in SKILL.md frontmatter as optional", async () => {
|
||||
const root = await makeTempDir("paperclip-skill-optional-");
|
||||
cleanupDirs.add(root);
|
||||
|
||||
const moduleDir = path.join(root, "a", "b", "c", "d", "e");
|
||||
await fs.mkdir(moduleDir, { recursive: true });
|
||||
|
||||
// Required skill (no frontmatter flag)
|
||||
const requiredDir = path.join(root, "skills", "paperclip");
|
||||
await fs.mkdir(requiredDir, { recursive: true });
|
||||
await fs.writeFile(path.join(requiredDir, "SKILL.md"), "---\nname: paperclip\n---\n\n# Paperclip\n");
|
||||
|
||||
// Optional skill (required: false)
|
||||
const optionalDir = path.join(root, "skills", "paperclip-dev");
|
||||
await fs.mkdir(optionalDir, { recursive: true });
|
||||
await fs.writeFile(path.join(optionalDir, "SKILL.md"), "---\nname: paperclip-dev\nrequired: false\n---\n\n# Dev\n");
|
||||
|
||||
const entries = await listPaperclipSkillEntries(moduleDir);
|
||||
entries.sort((a, b) => a.runtimeName.localeCompare(b.runtimeName));
|
||||
|
||||
expect(entries).toHaveLength(2);
|
||||
expect(entries[0]?.runtimeName).toBe("paperclip");
|
||||
expect(entries[0]?.required).toBe(true);
|
||||
expect(entries[1]?.runtimeName).toBe("paperclip-dev");
|
||||
expect(entries[1]?.required).toBe(false);
|
||||
expect(entries[1]?.requiredReason).toBeNull();
|
||||
});
|
||||
|
||||
it("removes stale maintainer-only symlinks from a shared skills home", async () => {
|
||||
const root = await makeTempDir("paperclip-skill-cleanup-");
|
||||
cleanupDirs.add(root);
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
---
|
||||
name: paperclip-dev
|
||||
required: false
|
||||
description: >
|
||||
Develop and operate a local Paperclip instance — start and stop servers,
|
||||
pull updates from master, run builds and tests, manage worktrees, back up
|
||||
databases, and diagnose problems. Use whenever you need to work on the
|
||||
Paperclip codebase itself or keep a running instance healthy.
|
||||
---
|
||||
|
||||
# Paperclip Dev
|
||||
|
||||
This skill covers the day-to-day workflows for developing and operating a local Paperclip instance. It assumes the repo lives at `~/workspace/paperclip` (or the current checkout) with `origin` pointing to `git@github.com:paperclipai/paperclip.git`.
|
||||
|
||||
## Starting and Stopping the Server
|
||||
|
||||
### Quick start (recommended)
|
||||
|
||||
```bash
|
||||
npx paperclipai run
|
||||
```
|
||||
|
||||
Runs onboarding (if first time), doctor checks, and then starts the Paperclip server. This is the single command that gets everything running.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-c, --config <path>` | Path to config file |
|
||||
| `-d, --data-dir <path>` | Paperclip data directory root (isolates state from `~/.paperclip`) |
|
||||
| `-i, --instance <id>` | Local instance id (default: `default`) |
|
||||
| `--bind <mode>` | Reachability preset: `loopback`, `lan`, or `tailnet` |
|
||||
| `--repair` / `--no-repair` | Attempt automatic repairs during doctor (default: true) |
|
||||
|
||||
### Development mode (hot reload)
|
||||
|
||||
```bash
|
||||
pnpm dev # watches for changes and restarts
|
||||
pnpm dev:once # single dev run without watch
|
||||
pnpm dev:server # server only (no agent processes)
|
||||
pnpm dev:ui # UI dev server only
|
||||
```
|
||||
|
||||
### Stopping
|
||||
|
||||
```bash
|
||||
pnpm dev:stop # stop the dev service
|
||||
pnpm dev:list # list running dev services
|
||||
```
|
||||
|
||||
## Pulling from Master
|
||||
|
||||
```bash
|
||||
cd ~/workspace/paperclip
|
||||
git fetch origin
|
||||
git pull origin master # or merge/rebase as appropriate
|
||||
pnpm install # pick up dependency changes
|
||||
pnpm build # rebuild all packages
|
||||
```
|
||||
|
||||
After pulling, if schema changes landed:
|
||||
|
||||
```bash
|
||||
pnpm db:generate # regenerate Drizzle client from schema
|
||||
pnpm db:migrate # run pending migrations
|
||||
```
|
||||
|
||||
## Building and Testing
|
||||
|
||||
```bash
|
||||
pnpm build # full monorepo build
|
||||
pnpm typecheck # type-check all packages
|
||||
pnpm test # run all tests (vitest)
|
||||
pnpm test:watch # run tests in watch mode
|
||||
```
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Migrations
|
||||
|
||||
```bash
|
||||
pnpm db:generate # generate Drizzle client from schema changes
|
||||
pnpm db:migrate # apply pending migrations
|
||||
```
|
||||
|
||||
### Backups
|
||||
|
||||
```bash
|
||||
npx paperclipai db:backup
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-c, --config <path>` | Path to config file |
|
||||
| `-d, --data-dir <path>` | Paperclip data directory root |
|
||||
| `--dir <path>` | Backup output directory (overrides config) |
|
||||
| `--retention-days <days>` | Retention window for pruning old backups |
|
||||
| `--filename-prefix <prefix>` | Backup filename prefix (default: `paperclip`) |
|
||||
| `--json` | Print backup metadata as JSON |
|
||||
|
||||
## Diagnostics
|
||||
|
||||
```bash
|
||||
npx paperclipai doctor # check setup health
|
||||
npx paperclipai doctor --repair # check and auto-fix issues
|
||||
npx paperclipai env # print current environment variables
|
||||
```
|
||||
|
||||
## Agent Operations (Local)
|
||||
|
||||
### Trigger a heartbeat
|
||||
|
||||
```bash
|
||||
npx paperclipai heartbeat run --agent-id <agent-id>
|
||||
```
|
||||
|
||||
### Install skills and get agent env vars
|
||||
|
||||
```bash
|
||||
npx paperclipai agent local-cli <agent-id-or-shortname> --company-id <company-id>
|
||||
```
|
||||
|
||||
Installs Paperclip skills for Claude/Codex and prints shell exports for that agent identity.
|
||||
|
||||
### List agents
|
||||
|
||||
```bash
|
||||
npx paperclipai agent list --company-id <company-id>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Worktrees
|
||||
|
||||
Paperclip worktrees combine git worktrees with isolated Paperclip instances. Each worktree gets its own database, server port, and full Paperclip environment seeded from the primary instance.
|
||||
|
||||
### When to Use Worktrees
|
||||
|
||||
- Starting a feature branch that needs its own Paperclip environment
|
||||
- Running parallel agent work without cross-contaminating the primary instance
|
||||
- Testing Paperclip changes in isolation before merging
|
||||
- Cleaning up after a completed branch
|
||||
|
||||
### Commands
|
||||
|
||||
All commands use `npx paperclipai`. The CLI has two tiers:
|
||||
|
||||
1. **Top-level shortcuts** (`worktree:make`, `worktree:list`, `worktree:cleanup`, `worktree:merge-history`) -- end-to-end lifecycle commands
|
||||
2. **Subcommands** (`worktree init`, `worktree env`, `worktree reseed`, `worktree repair`) -- lower-level instance management within an existing worktree
|
||||
|
||||
---
|
||||
|
||||
### Create a Worktree
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree:make <name>
|
||||
```
|
||||
|
||||
Creates `~/paperclip-<name>` as a git worktree with a new branch, then bootstraps an isolated Paperclip instance inside it. The name is auto-prefixed with `paperclip-` if needed.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--start-point <ref>` | Remote ref to base the new branch on (env: `PAPERCLIP_WORKTREE_START_POINT`) |
|
||||
| `--instance <id>` | Explicit isolated instance id |
|
||||
| `--home <path>` | Home root for worktree instances (env: `PAPERCLIP_WORKTREES_DIR`, default: `~/.paperclip-worktrees`) |
|
||||
| `--from-config <path>` | Source config.json to seed from |
|
||||
| `--from-data-dir <path>` | Source `PAPERCLIP_HOME` used when deriving the source config |
|
||||
| `--from-instance <id>` | Source instance id (default: `default`) |
|
||||
| `--server-port <port>` | Preferred server port |
|
||||
| `--db-port <port>` | Preferred embedded Postgres port |
|
||||
| `--seed-mode <mode>` | `minimal` or `full` (default: `minimal`) |
|
||||
| `--no-seed` | Skip database seeding from the source instance |
|
||||
| `--force` | Replace existing repo-local config and isolated instance data |
|
||||
|
||||
**Example -- start a feature worktree based on main:**
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree:make auth-refactor --start-point origin/main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### List Worktrees
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree:list
|
||||
```
|
||||
|
||||
Lists all git worktrees visible from the current repo and indicates which ones have a Paperclip instance configured.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--json` | Print JSON output instead of text |
|
||||
|
||||
---
|
||||
|
||||
### Initialize a Worktree Instance (Low-Level)
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree init
|
||||
```
|
||||
|
||||
Run this inside an existing git worktree to create repo-local config/env and bootstrap an isolated Paperclip instance. This is the lower-level equivalent of what `worktree:make` does automatically.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--name <name>` | Display name used to derive the instance id |
|
||||
| `--instance <id>` | Explicit isolated instance id |
|
||||
| `--home <path>` | Home root (env: `PAPERCLIP_WORKTREES_DIR`, default: `~/.paperclip-worktrees`) |
|
||||
| `--from-config <path>` | Source config.json to seed from |
|
||||
| `--from-data-dir <path>` | Source `PAPERCLIP_HOME` for deriving config |
|
||||
| `--from-instance <id>` | Source instance id (default: `default`) |
|
||||
| `--server-port <port>` | Preferred server port |
|
||||
| `--db-port <port>` | Preferred embedded Postgres port |
|
||||
| `--seed-mode <mode>` | `minimal` or `full` (default: `minimal`) |
|
||||
| `--no-seed` | Skip database seeding |
|
||||
| `--force` | Replace existing config and instance data |
|
||||
|
||||
---
|
||||
|
||||
### Print Worktree Environment
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree env
|
||||
```
|
||||
|
||||
Prints shell exports (`PAPERCLIP_API_URL`, `PAPERCLIP_HOME`, etc.) for the current worktree-local instance.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-c, --config <path>` | Path to config file |
|
||||
| `--json` | Print JSON instead of shell exports |
|
||||
|
||||
**Example -- source the environment in your shell:**
|
||||
|
||||
```bash
|
||||
eval "$(npx paperclipai worktree env)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reseed a Worktree
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree reseed
|
||||
```
|
||||
|
||||
Re-seeds an existing worktree-local instance from another Paperclip instance. Use this to refresh a stale worktree with current data.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--from <worktree>` | Source worktree path, directory name, branch name, or `current` |
|
||||
| `--to <worktree>` | Target worktree (defaults to `current`) |
|
||||
| `--from-config <path>` | Source config.json |
|
||||
| `--from-data-dir <path>` | Source `PAPERCLIP_HOME` |
|
||||
| `--from-instance <id>` | Source instance id |
|
||||
| `--seed-mode <mode>` | `minimal` or `full` (default: `full`) |
|
||||
| `--yes` | Skip the destructive confirmation prompt |
|
||||
| `--allow-live-target` | Override the guard requiring the target DB to be stopped |
|
||||
|
||||
**Important:** Stop the target worktree's database before reseeding unless using `--allow-live-target`.
|
||||
|
||||
---
|
||||
|
||||
### Repair a Worktree Instance
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree repair
|
||||
```
|
||||
|
||||
Creates or repairs a linked worktree-local Paperclip instance without touching the primary checkout.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--branch <name>` | Branch/worktree selector to repair |
|
||||
| `--home <path>` | Home root (env: `PAPERCLIP_WORKTREES_DIR`, default: `~/.paperclip-worktrees`) |
|
||||
| `--from-config <path>` | Source config.json |
|
||||
| `--from-data-dir <path>` | Source `PAPERCLIP_HOME` |
|
||||
| `--from-instance <id>` | Source instance id (default: `default`) |
|
||||
| `--seed-mode <mode>` | `minimal` or `full` (default: `minimal`) |
|
||||
| `--no-seed` | Repair metadata only, skip reseeding |
|
||||
| `--allow-live-target` | Override the stopped-DB guard |
|
||||
|
||||
---
|
||||
|
||||
### Merge Issue History Between Worktrees
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree:merge-history [source]
|
||||
```
|
||||
|
||||
Previews or imports issue and comment history from one worktree's Paperclip instance into another.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--from <worktree>` | Source worktree path, directory name, branch name, or `current` |
|
||||
| `--to <worktree>` | Target worktree (defaults to `current`) |
|
||||
| `--company <id-or-prefix>` | Shared company id or issue prefix |
|
||||
| `--scope <items>` | Comma-separated: `issues`, `comments` (default: `issues,comments`) |
|
||||
| `--apply` | Apply the import after preview |
|
||||
| `--dry` | Preview only |
|
||||
| `--yes` | Skip interactive confirmation |
|
||||
|
||||
---
|
||||
|
||||
### Clean Up a Worktree
|
||||
|
||||
```bash
|
||||
npx paperclipai worktree:cleanup <name>
|
||||
```
|
||||
|
||||
Removes a worktree, its git branch, and its isolated Paperclip instance data. The name is auto-prefixed with `paperclip-` if needed.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--instance <id>` | Explicit instance id (if different from the worktree name) |
|
||||
| `--home <path>` | Home root (env: `PAPERCLIP_WORKTREES_DIR`, default: `~/.paperclip-worktrees`) |
|
||||
| `--force` | Bypass safety checks (uncommitted changes, unique commits) |
|
||||
|
||||
**Safety:** Without `--force`, the command checks for uncommitted changes and unique (unmerged) commits before removing. Always merge or push important work before cleanup.
|
||||
|
||||
---
|
||||
|
||||
### Typical Worktree Workflow
|
||||
|
||||
```bash
|
||||
# 1. Create a worktree for a feature
|
||||
npx paperclipai worktree:make my-feature --start-point origin/main
|
||||
|
||||
# 2. Move into it
|
||||
cd ~/paperclip-my-feature
|
||||
|
||||
# 3. Source the Paperclip environment
|
||||
eval "$(npx paperclipai worktree env)"
|
||||
|
||||
# 4. Start the isolated Paperclip server
|
||||
npx paperclipai run
|
||||
|
||||
# 5. Do your work -- agents use the isolated instance
|
||||
|
||||
# 6. When done, merge history back if needed
|
||||
npx paperclipai worktree:merge-history --from paperclip-my-feature --to current --apply
|
||||
|
||||
# 7. Clean up
|
||||
npx paperclipai worktree:cleanup my-feature
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Instance isolation:** Each worktree gets its own database, server port, and config. Agents in one worktree don't interfere with another.
|
||||
- **Seeding:** New worktrees are seeded from an existing instance. `minimal` copies config (company, agents, skills). `full` copies everything including issues.
|
||||
- **Home directory:** Worktree instance data lives under `~/.paperclip-worktrees/` by default (configurable via `PAPERCLIP_WORKTREES_DIR`).
|
||||
- **History merging:** Issue and comment history can be selectively merged between worktrees before cleanup.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
> **MANDATORY PRE-FLIGHT:** Before creating ANY pull request, you MUST read the canonical source files listed below. Do NOT run `gh pr create` until you have read these files and verified your PR body matches every required section.
|
||||
|
||||
### Step 1 — Read the canonical files
|
||||
|
||||
You MUST read all three of these files before creating a PR:
|
||||
|
||||
1. **`.github/PULL_REQUEST_TEMPLATE.md`** — the required PR body structure
|
||||
2. **`CONTRIBUTING.md`** — contribution conventions, PR requirements, and thinking-path examples
|
||||
3. **`.github/workflows/pr.yml`** — CI checks that gate merge
|
||||
|
||||
### Step 2 — Validate your PR body against this checklist
|
||||
|
||||
After reading the template, verify your `--body` includes every one of these sections (names must match exactly):
|
||||
|
||||
- [ ] `## Thinking Path` — blockquote style, 5-8 reasoning steps
|
||||
- [ ] `## What Changed` — bullet list of concrete changes
|
||||
- [ ] `## Verification` — how a reviewer confirms this works
|
||||
- [ ] `## Risks` — what could go wrong
|
||||
- [ ] `## Model Used` — provider, model ID, version, capabilities
|
||||
- [ ] `## Checklist` — copied from the template, items checked off
|
||||
|
||||
If any section is missing or empty, do NOT submit the PR. Go back and fill it in.
|
||||
|
||||
### Step 3 — Create the PR
|
||||
|
||||
Only after completing Steps 1 and 2, run `gh pr create`. Use the template contents as the structure for `--body` — do not write a freeform summary.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Fix |
|
||||
|---------|-----|
|
||||
| Server won't start | Run `npx paperclipai doctor --repair` to diagnose and auto-fix |
|
||||
| Forgetting to source worktree env | Run `eval "$(npx paperclipai worktree env)"` after cd-ing into the worktree |
|
||||
| Stale dependencies after pull | Run `pnpm install && pnpm build` after pulling |
|
||||
| Schema out of date after pull | Run `pnpm db:generate && pnpm db:migrate` |
|
||||
| Reseeding while target DB is running | Stop the target server first, or use `--allow-live-target` |
|
||||
| Cleaning up with unmerged commits | Merge or push first, or use `--force` if intentionally discarding |
|
||||
| Running agents against wrong instance | Verify `PAPERCLIP_API_URL` points to the correct port |
|
||||
Reference in new issue
Block a user