Skip to content
Back to skills

Obsidian Pr Sync

ASecurity

Fetch open GitHub PRs where the user is an assignee or review-requested reviewer, today's Google Calendar events, and auto-create People stubs for new meeting attendees. Then write or refresh a "## PRs to review" section in today's Obsidian daily note. Use this skill whenever the user asks to sync PRs to Obsidian, update their daily note with GitHub reviews or calendar, check what needs attention, run a morning sync routine, or create people notes from calendar meetings. Also suitable for sch...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 14, 2026
ai-agentsgobashgitapi

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned October 14, 2026

npx -y skills add oleg-koval/agent-skills --skill obsidian-pr-sync --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Obsidian Pr Sync?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Obsidian Pr Sync
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/oleg-koval-obsidian-pr-sync-agent-skills/badge)](https://www.skillsdirectory.com/skills/oleg-koval-obsidian-pr-sync-agent-skills)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: obsidian-pr-sync
description: >
  Fetch open GitHub PRs where the user is an assignee or review-requested reviewer,
  today's Google Calendar events, and auto-create People stubs for new meeting attendees.
  Then write or refresh a "## PRs to review" section in today's Obsidian daily note.
  Use this skill whenever the user asks to sync PRs to Obsidian, update their daily note
  with GitHub reviews or calendar, check what needs attention, run a morning sync routine,
  or create people notes from calendar meetings. Also suitable for scheduled/automated runs:
  fully idempotent (re-running replaces the section, never appends; people stubs are only
  created once).
license: MIT
allowed-tools: Bash, Read, Write, Edit
compatibility: Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires the GitHub CLI (`gh`) authenticated and a daily note vault.
metadata:
  targets: [_source-only]
  author: Oleg Koval
  tags:
    - github
    - obsidian
    - daily-note
    - pull-requests
    - people
    - productivity
    - morning-routine
---

# Obsidian PR Sync

Fetch all open PRs where the user is assigned or requested as reviewer, filter out
noise (bots, drafts, self-authored), and write a clean grouped section into today's
daily note. The calendar pass also creates idempotent People stubs for attendees who
do not already have a matching note.

## Configuration

Before running, confirm:
- `vault_path`: absolute path to the Obsidian vault, from the plugin configuration
- `VAULT_DAILY`: derive as `${vault_path}/Lead/Daily`
- `VAULT_PEOPLE`: derive as `${vault_path}/Lead/People`
- `SELF_EMAIL`: the user's calendar email address, used to exclude the user from attendee processing
- `GH_USERNAME`: GitHub login to exclude self-authored PRs (e.g. `oleg-koval`)
- `ORG_PREFIX`: org name to group as "Work" (e.g. `Teifi-Digital`); everything else goes to "Personal"

If not specified by the user, infer from context (git config, existing vault files).

## Step 1: Fetch PRs and calendar events

Run two `gh` queries and merge results, deduplicating by URL:

```bash
# Review-requested
gh search prs --review-requested=@me --state=open \
  --json title,url,repository,author,createdAt,isDraft --limit 50

# Assigned
gh search prs --assignee=@me --state=open \
  --json title,url,repository,author,createdAt,isDraft --limit 50
```

Merge both lists, deduplicate by `url`. The union is what needs attention.

Fetch today's calendar events once with the available calendar integration. Retain each
event's stable ID, title, and attendees (`displayName`, `email`, and `responseStatus`) as
`CALENDAR_EVENTS`, and pass that same collection to Part C. Do not fetch the events again.

## Step 2: Filter

Discard entries where:
- `isDraft` is `true`
- `author.login` matches any bot pattern: `dependabot`, `copilot`, `renovate`, `github-actions`, or `author.is_bot` is `true`
- `author.login` equals `GH_USERNAME`: self-authored PRs are not reviews; they're your own work

## Step 3: Enrich authors

For each unique author login, resolve a display name for the Obsidian wiki-link:

```bash
gh api /users/<login> --jq '.name // .login'
```

If the API returns a name, use it (e.g. `Rutger Klaassen`). If it returns null, fall
back to the login with first letter uppercased. This gives `[[Rutger Klaassen]]` or
`[[Merlijnmacgillavry]]`.

Batch these lookups: run them in parallel if possible to keep the sync fast.

## Step 4: Calculate age

For each PR: `days_old = (today - createdAt).days`. Use today's date in local time.

## Step 5: Build the section

Format the section exactly as:

```markdown
## PRs to review

### Work (<ORG_PREFIX>)
- [ ] [TITLE](url) by [[Author Name]] (N days old)

### Personal
- [ ] [TITLE](url) by [[Author Name]] (N days old)
```

Rules:
- **Work**: `repository.nameWithOwner` starts with `<ORG_PREFIX>/`
- **Personal**: everything else
- Within each group, sort by `createdAt` ascending (oldest first: most overdue at top)
- If a group has no PRs, omit that subsection entirely (don't render an empty `### Work` header)
- If there are zero PRs total, write: `## PRs to review\n\n_No open PRs, clear queue! πŸŽ‰_`
- Add `⚠️` after the age if `days_old >= 7`

**Example line:**
```
- [ ] [RSL-108] Image sync from InRiver by [[Merlijn Mac Gillavry]] (12 days old ⚠️)
```

## Step 6: Write to today's daily note

**Find today's note:**
```bash
TODAY=$(date +%Y-%m-%d)
NOTE="${VAULT_DAILY}/${TODAY}.md"
```

If the file doesn't exist, create it with standard daily template frontmatter first,
then append the section. If the file exists:

**Replacement logic (idempotent):**
- If `## PRs to review` already exists: replace everything from that line up to (but not
  including) the next `## ` heading or end-of-file with the newly built section.
- If it doesn't exist: append the section at the end of the file (before the nav footer
  line `*← [[...` if present, otherwise at the very end).

This ensures re-running the skill produces the same result, not a growing list.

## Part C: People stubs for new meeting attendees

Run this with `CALENDAR_EVENTS` from Step 1. No additional calendar API calls are needed.

### Step C1 β€” Collect attendees

Collect attendees across all fetched events:

- Normalize `SELF_EMAIL` by trimming whitespace and lowercasing it.
- For each attendee, normalize `email` the same way. Use the normalized email as the
  persistent attendee identity. If an attendee has no email, a collection layer may use
  `event:<event-id>:attendee:<index>` as a transient collision-safe key, but skip that
  attendee before deduplication, note lookup, or stub creation.
- Skip attendees whose normalized email equals normalized `SELF_EMAIL` (self).
- Skip attendees whose `responseStatus` is `declined`.
- Deduplicate by normalized email across all events.
- Normalize the display name by trimming it and collapsing repeated whitespace. If it is
  empty, fall back to the part before `@` in the normalized email.

Keep the events each attendee appears in so every meeting can be written to the stub.

### Step C2 β€” Check for existing People notes

People notes live under `Lead/People/`, including `Peers/`, `Reports/`, and
`Stakeholders/`. Discover existing Markdown notes from the configured directory:

```bash
find "$VAULT_PEOPLE" -type f -name "*.md"
```

Do not match by filename containment. Treat a note as existing only when its title (the
first H1) exactly matches the attendee's normalized display name after case-folding and
whitespace normalization, and its recorded email exactly matches the normalized attendee
email. Create a stub when no note satisfies both checks.

### Step C3 β€” Create the stub

Create each new attendee at:

```text
${VAULT_PEOPLE}/<SafeDisplayName>--<IdentityHash>.md
```

Derive `IdentityHash` from the normalized email so attendees with the same display name
cannot collide. Before constructing the path, sanitize the display name to a filename-safe
basename: remove control characters, normalize whitespace, and replace reserved filename
characters with `-`. Reject the original display name if it contains `/`, `\\`, or `..`,
and reject an empty or dot-only sanitized basename. Resolve `VAULT_PEOPLE` and the candidate
path, then create the file only if the candidate's resolved parent is exactly the resolved
`VAULT_PEOPLE` directory. Never overwrite an existing path.

Use this template, replacing the placeholders and adding one meeting line per event:

```markdown
---
type: person
role: ""
team: ""
last-1-1:
next-1-1:
---

# <DisplayName>

## Context

- Email: <email>

## Strengths

## Growth areas

## Recent 1:1s

## Running notes

## Meetings

- [[Lead/Daily/<TODAY>]] - <Event title>

<!-- Classify: teifi.com email β†’ Peers or Reports Β· external email β†’ Stakeholders -->
```

Do not add `## Code Review Signals`; that section is for direct reports only.
Stubs are idempotent: existing matching notes are skipped and never overwritten.

## Invocation patterns

**Manual (user-triggered):**
The user says something like "sync my PRs to today's note" or "update obsidian with my reviews".
Run the full flow and confirm how many PRs were written.

**Scheduled morning routine:**
Same flow. No user prompt needed: just run, update the note, and report a one-line
summary: `"PR sync: 3 work PRs, 2 personal PRs written to 2026-05-11.md"`.

**In a remote CCR agent:**
Clone the vault repo, run the sync, commit and push. The local Obsidian Git plugin
pulls the changes automatically.

## Output to user

After writing the file, show a brief confirmation:

```
Synced N PRs to Lead/Daily/YYYY-MM-DD.md
  Work (<ORG_PREFIX>): X PRs
  Personal: Y PRs
  Skipped: Z bots/drafts
  People: N new stubs created (or "all known")
```

If any `gh` call fails (e.g. auth expired), surface the error clearly rather than
writing a partial/corrupt section.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.