---
name: paper-contribute
version: 1.1.0
description: /paper-contribute, submit this to global, propose to global vault, open PR to global, edit a global note, send to global, contribute to paper global
allowed-tools: Read Glob Grep
---

# paper-contribute

Open, track, and rebase contributions (PRs) against the global vault at https://paper.farzai.com/global.

The global vault is the community knowledge base. It does not accept direct writes — every change is a `proposal` reviewed by an admin. This skill teaches Claude the PR workflow so a user can propose new notes, edit existing global notes, respond to admin feedback, and resolve conflicts — all from chat.

## When to invoke

- `/paper-contribute`
- "submit this to global"
- "propose to global vault"
- "open PR to global"
- "edit a global note"
- "send to global"
- "contribute to paper global"

## Prerequisite

The `paper-global` MCP server (URL contains `/api/mcp/global`) must be bound and exposed in the current session. Detect by matching `mcp__paper-[a-z0-9-]+__propose_global_note`.

If not bound:

> I don't see a `paper-global` MCP server. To contribute to the global vault, run `claude mcp add paper-global https://paper.farzai.com/api/mcp/global` — Claude Code will open the browser for OAuth approval. See https://paper.farzai.com/docs/mcp-setup for the full guide.
>
> (If you only have `paper-personal` bound, you can still open a *new-note* PR without adding a second server — see **Shortcut** below.)

## Shortcut: submit straight from the personal vault

If the note already lives in the user's personal vault and they just want to open a **new-note** PR, the personal server exposes `submit_to_global` — it snapshots the note and opens the global contribution in one call, with no `paper-global` binding required:

```
submit_to_global({
  noteId: <id or path of the personal note>,
  targetPath: "notes/<topic>/<slug>",   // optional; defaults to the note's own path
})
→ { contributionId, url: "/global/contributions/<id>", status: "open" }
```

Output `https://paper.farzai.com` + the returned `url`. Prefer this path for "submit my note X to global" when `paper-global` is NOT bound and the content is an unchanged personal note.

Use the full PR workflow below (which DOES need `paper-global`) when the user wants to: edit an existing global note, list/track/comment on PRs, rebase a conflict, or close a PR — `submit_to_global` only covers the initial new-note submission.

## Sub-commands

### 1. Propose a new note

Triggers: "propose", "submit this", "new global note about …"

Workflow:
1. Pick a title (ask the user if unclear).
2. Build a path slug from the title: `notes/<topic>/<slug>.md` — keep it scoped, not at the root.
3. Resolve wikilinks against the **global** vault first: `mcp__paper-global__search_notes({ query: "X", limit: 5 })`. Same rules as `paper-save` — keep on exact match, annotate `_(new)_` otherwise.
4. Build frontmatter (title, type, tags, source).
5. Show a preview: path + frontmatter + first 200 chars of body.
6. On confirm, call:
   ```
   propose_global_note({
     kind: "new_note",
     title: "…",
     path: "notes/…/slug.md",
     body: "…",
     frontmatter: { … },
     message: "<one-line PR description>"
   })
   ```
7. On success, output `https://paper.farzai.com/global/contributions/<id>` and explain the admin will review.

### 2. Propose an edit to an existing global note

Triggers: "edit global note", "fix the global note on X"

Workflow:
1. Find the target: `search_notes({ query: "X" })` or take the user-supplied path.
2. Read it: `read_note({ idOrPath })` — keep `id` AND `currentRevisionId` (you'll need both).
3. Show the user the current body and ask what to change.
4. Build the edited body. Compare against the source via diff in the chat.
5. On confirm, call:
   ```
   propose_global_note({
     kind: "edit_note",
     targetNoteId: <id from read>,
     baseRevisionId: <currentRevisionId from read>,
     title: <new or unchanged>,
     path: <new or unchanged>,
     body: <new body>,
     frontmatter: <merged frontmatter>,
     message: "<one-line summary of the change>"
   })
   ```

### 3. List my open PRs

Triggers: "my contributions", "my PRs", "what have I submitted"

Call `list_my_contributions({ status: "open" })`. Format as a table:

| PR | Status | Path | Updated |
|---|---|---|---|
| #42 | open | notes/research/mcp.md | 2 days ago |

Suggest follow-ups: "Want to check status on #42?" or "Want to comment on #42?"

### 4. Status check

Triggers: "status of PR #X", "what's happening with #X"

Call `get_contribution({ id: <X> })`. Show:
- Status (open / merged / rejected / conflict)
- Admin comments (most recent first)
- Diff preview if `kind: 'edit_note'`

### 5. Respond to a comment

Triggers: "reply to #X", "respond to the admin on #X"

Call `add_contribution_comment({ id: <X>, body: <user's reply> })`.

### 6. Rebase on conflict

When `get_contribution` returns `status: 'conflict'`, the response includes three-way merge data:
```json
{
  "baseRevisionId": "rev_…",
  "currentRevisionId": "rev_…",
  "currentBody": "…",
  "currentFrontmatter": { … }
}
```

Workflow:
1. Show the user the conflict clearly:
   ```
   This PR is based on revision <baseRevisionId>, but the global note has moved forward to <currentRevisionId>. Below are the three versions.

   YOUR PROPOSED CHANGES:
   <pr.proposedBody>

   CURRENT GLOBAL VAULT VERSION:
   <currentBody>
   ```
2. Help the user write a merged body that includes both their intent and the upstream change.
3. On confirm, call:
   ```
   update_proposal({
     id: <X>,
     rebaseOnto: <currentRevisionId>,
     body: <merged body>,
     frontmatter: <merged frontmatter>
   })
   ```
4. The PR status flips back to `open` for re-review.

### 7. Close own PR

Triggers: "close #X", "withdraw #X"

Call `close_contribution({ id: <X>, reason: <user-supplied or "withdrawn"> })`.

## Error matrix

| Error | Action |
|---|---|
| `401 unauthorized` | Check the `WWW-Authenticate` header. If `resource_metadata=...` is present, Claude Code handles the OAuth refresh — retry the call once. If the retry also 401s, direct the user to `/app/connections` to reconnect. |
| `429 rate_limited` | Wait `Retry-After`, retry once. |
| `contribution_already_open` (single-flight invariant — at most one open PR per (vault, path)) | Call `list_my_contributions` to find the existing PR. Offer to `update_proposal` it instead. |
| `revision_conflict` (PR's `baseRevisionId` is stale) | Trigger the rebase flow above. |
| `email_not_verified` | Tell the user to verify their email at /app/profile. |
| `target_not_found` (on edit_note) | The global note was deleted. Suggest a `new_note` PR instead. |

## Workflow from `paper-save` global-mode redirect

When invoked because `paper-save` was called against the global server:

1. Use the chat content `paper-save` had identified.
2. Apply the same wikilink resolution + duplicate-detection logic.
3. Skip directly to step 1 of this skill — propose a new note.

## Example session

User: "submit my MCP research note to global"

You:
1. Detect: `paper-global` is bound. ✓
2. Find the source note in personal vault: `mcp__paper-personal__read_note({ idOrPath: "notes/research/mcp.md" })`.
3. Resolve wikilinks against global: `mcp__paper-global__search_notes` for each `[[…]]`.
4. Build the proposal. Show preview.
5. User confirms.
6. Call `propose_global_note({ kind: "new_note", title: "Model Context Protocol overview", path: "notes/protocols/mcp.md", body, frontmatter, message: "Initial MCP overview note" })`.
7. Output: "PR #57 opened at https://paper.farzai.com/global/contributions/57. The admin will review."
