---
name: Sync
slug: sync
category: Automation
description: Sync re-synchronizes a vault from external sources linked in entity frontmatter. Use it to pull updated content from Confluence, Google Docs, GitHub, or Markdown and to scan people or GitHub activity when those flags are enabled.
github: "https://github.com/ccplugins/awesome-claude-code-plugins/tree/main/plugins/bedrock/skills/sync"
language: JavaScript
stars: 922
forks: 392
install: "npx degit https://github.com/ccplugins/awesome-claude-code-plugins/tree/main/plugins/bedrock/skills/sync ~/.claude/skills/sync"
installs_to: ~/.claude/skills/sync
source_path: plugins/bedrock/skills/sync/SKILL.md
collection_size: 25
category_size: 1523
collection_url: "https://dirskills.com/collections/ccplugins/awesome-claude-code-plugins"
added: 2026-08-22T05:20:47.917Z
last_synced: 2026-08-22T05:20:47.917Z
canonical_url: "https://dirskills.com/skills/sync"
---

# Sync

Sync re-synchronizes a vault from external sources linked in entity frontmatter. Use it to pull updated content from Confluence, Google Docs, GitHub, or Markdown and to scan people or GitHub activity when those flags are enabled.

**Install:**

```bash
npx degit https://github.com/ccplugins/awesome-claude-code-plugins/tree/main/plugins/bedrock/skills/sync ~/.claude/skills/sync
```

## README

# /bedrock:sync — Vault Synchronization

## Plugin Paths

Entity definitions and templates are in the plugin directory, not in the vault root.
Use the "Base directory for this skill" provided at invocation to resolve paths:

- Entity definitions: `<base_dir>/../../entities/`
- Templates: `<base_dir>/../../templates/{type}/_template.md`
- Plugin CLAUDE.md: `<base_dir>/../../CLAUDE.md` (already injected automatically into context)

Where `<base_dir>` is the path provided in "Base directory for this skill".

---

## Vault Resolution

Resolve which vault to sync. This skill can be invoked from any directory.

**Step 1 — Parse `--vault` flag:**
Check if the input arguments include `--vault <name>`. If found, extract the vault name and remove it from the arguments before parsing `--people` or `--github`.

**Step 2 — Resolve vault path:**

1. **If `--vault <name>` was provided:**
   Read the vault registry at `<base_dir>/../../vaults.json`. Find the entry matching the name.
   If not found: error — "Vault `<name>` is not registered. Run `/bedrock:vaults` to see available vaults."
   If found: set `VAULT_PATH` to the entry's `path` value. Store the resolved vault name as `VAULT_NAME`.

2. **If no `--vault` flag — CWD detection:**
   Read `<base_dir>/../../vaults.json`. Check if the current working directory is inside any registered vault path
   (CWD starts with a registered vault's absolute path). If multiple match, use the longest path (most specific).
   If found: set `VAULT_PATH` to the matching vault's `path`. Store its name as `VAULT_NAME`.

3. **If CWD detection fails — default vault:**
   From the registry, find the vault with `"default": true`.
   If found: set `VAULT_PATH` to the default vault's `path`. Store its name as `VAULT_NAME`.

4. **If no resolution:**
   Error — "No vault resolved. Available vaults:" followed by the registry listing.
   "Use `--vault <name>` to specify, or run `/bedrock:setup` to register a vault."

**Step 3 — Validate vault path:**
```bash
test -d "<VAULT_PATH>" && echo "exists" || echo "missing"
```
If missing: error — "Vault path `<VAULT_PATH>` does not exist on disk. Run `/bedrock:setup` to re-register."

**Step 4 — Read vault config:**
```bash
cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null
```
Extract `language`, `git.strategy`, and other relevant fields for use in later phases.

**From this point forward, ALL vault file operations use `<VAULT_PATH>` as the root.**
- Entity directories: `<VAULT_PATH>/actors/`, `<VAULT_PATH>/people/`, etc.
- Git operations: `git -C <VAULT_PATH> <command>`
- When delegating to `/bedrock:preserve`, pass `--vault <VAULT_NAME>`

---

## Overview

This skill synchronizes the vault with external sources. It operates in three modes:

| Mode | Flag | Description |
|---|---|---|
| **Sources (default)** | _(none)_ | Re-synchronizes entities with a populated `sources` field |
| **People** | `--people` | Scans actor repositories and identifies active contributors |
| **GitHub** | `--github` | Detects activity in repos and correlates PRs with topics/projects |

---

## Routing

Analyze the argument passed by the user:

1. If argument contains `--people` → go to **Mode: Sync People** (below)
2. If argument contains `--github` → go to **Mode: Sync GitHub** (below)
3. Otherwise → go to **Mode: Sync Sources (default)** (below)

> **Note:** If no argument is passed, or the argument does not contain recognized flags,
> execute the default mode (Sync Sources).

---
---


# Mode: Sync Sources (default)




## Overview

This skill scans the `sources` field of all vault entities, deduplicates by URL,
fetches updated content from each external source, compares with existing entities
in the vault (incremental diff), and delegates all changes to `/bedrock:preserve` for centralized writing.

`/bedrock:sync` **does NOT write entities directly** — all entity writing goes through `/bedrock:preserve`.
After re-sync, `/bedrock:preserve` updates `synced_at` in the `sources` field of affected entities.

`/bedrock:sync` **does NOT ingest new sources** — for that, use `/bedrock:teach`.

**You are an execution agent.** Follow the phases below in order, without skipping steps.

---

## Phase 0 — Synchronize the Vault

Execute:
```bash
git -C <VAULT_PATH> pull --rebase origin main
```

If the pull fails:
- No remote configured: warn "No remote configured. Working locally." and proceed.
- Pull conflict: `git -C <VAULT_PATH> rebase --abort` and warn the user. Do NOT proceed without resolving.
- Otherwise: proceed.

---

## Phase 1 — Collect Syncable Sources

Provenance is recorded in the `sources` field of each entity's frontmatter.
Scan all entities to collect unique URLs.

1. Use Grep to find entities with a non-empty `sources` field:
   ```
   Grep pattern "^sources:" in directories: actors/, people/, teams/, topics/, discussions/, projects/, fleeting/
   ```
2. For each file found, use Read to extract the `sources` field from the YAML frontmatter.
   Each entry has: `{url, type, synced_at}`
3. **Build URL → entities map:**
   Deduplicate by URL. For each unique URL, record all entities that reference it:
   ```
   {
     "https://mycompany.atlassian.net/...": {
       type: "confluence",
       synced_at: "2026-04-09",
       entities: ["actors/billing-api.md", "topics/2026-04-feature-x.md"]
     },
     "https://github.com/acme-corp/billing-api": {
       type: "github-repo",
       synced_at: "2026-04-10",
       entities: ["actors/billing-api.md"]
     }
   }
   ```
4. **Filter syncable sources:**
   - Keep only URLs with `type` in (`confluence`, `gdoc`, `github-repo`, `markdown`)
   - Ignore URLs with `type` = `csv` or `manual` (log: "URL X ignored — non-syncable type")
5. Store the list of syncable URLs with their entity maps

Report: "Phase 1: N entities with sources, M unique URLs found, K syncable, J ignored (non-syncable type)."

---

## Phase 2 — Re-read Sources

For each syncable source, fetch updated content:

### 2.1 Confluence

For sources with `source_type: confluence`:

1. Read the internal fetcher at `<base_dir>/../confluence-to-markdown/SKILL.md`
2. Follow its instructions to parse the URL, choose layer (MCP → API → browser), and extract content
3. The fetcher returns Markdown content and page title

### 2.2 Google Docs

For sources with `source_type: gdoc`:

1. Read the internal fetcher at `<base_dir>/../gdoc-to-markdown/SKILL.md`
2. Follow its instructions to parse the URL, detect document type, choose layer (MCP → API/public export → browser), and extract content
3. The fetcher returns Markdown content and document metadata

### 2.3 GitHub Repository

For sources with `source_type: github-repo`:

1. Extract `owner/repo` from the URL (path segments after `github.com/`)
2. Use GitHub MCP directly (NOT via subagent — MCP permissions are not inherited):
   - `mcp__plugin_github_github__get_file_contents` → read the repo's README.md
   - `mcp__plugin_github_github__list_commits` → last 10 commits
   - `mcp__plugin_github_github__list_pull_requests` → last 5 PRs (state=all, sort=updated)
3. Compile everything into a single markdown text

> **Best-effort:** If any MCP call fails, continue with what was obtained. Do NOT block the sync.

### 2.4 Local Markdown

For sources with `source_type: markdown`:

1. Extract the path from the `url` field
2. Use Read to read the file directly
3. If the file does not exist: log and skip

### 2.5 Error handling

- If reading a source fails (MCP unavailable, broken URL, missing file):
  - Log the error: "Source X failed — reason"
  - Continue with remaining sources
  - Do NOT abort the entire execution for one source

Report: "Phase 2: N sources read successfully, M failed (list)."

---

## Phase 3 — Incremental Diff + Entity Extraction

### 3.1 Load entity definitions

Use Read to read ALL entity definition files from the plugin (see "Plugin Paths" section):
`<base_dir>/../../entities/*.md`
These files define what each entity type is, when to create, and how to distinguish them.
Internalize these definitions — you will use them to classify content.

### 3.2 Catalog existing entities

Use Glob to list all files in each entity directory (excluding `_template.md`):
- `<VAULT_PATH>/actors/*.md`
- `<VAULT_PATH>/people/*.md`
- `<VAULT_PATH>/teams/*.md`
- `<VAULT_PATH>/topics/*.md`
- `<VAULT_PATH>/discussions/*.md`
- `<VAULT_PATH>/projects/*.md`
- `<VAULT_PATH>/fleeting/*.md`

For each file found:
- Extract the filename without extension (e.g.: `billing-api`)
- Use Read to extract the `name` (or `title`) and `aliases` fields from the YAML frontmatter
- Store: `{filename, name, aliases, type}` for matching

### 3.3 Analyze content and detect changes

For each source successfully read in Phase 2:

1. **Identify entities mentioned in the updated content:**
   - For each entity cataloged in Phase 3.2, check if the filename, name, or alias appears in the content
   - Match rules:
     - Normalize for comparison: lowercase, no accents, no hyphens
     - Partial match acceptable for compound names (e.g.: "billing api" matches "billing-api")
     - Do NOT match substrings of 3 letters or fewer (e.g.: "api" does NOT match "billing-api")
     - Do NOT match generic words (e.g.: "company", "service", "system")

2. **Compare with the source's `entities_generated`:**
   - Entity in content AND already in vault → candidate for `update` (if there is new info in the content)
   - Entity in content but NOT in vault → candidate for `create`
   - Entity in `entities_generated` but NOT in updated content → **keep** (do not delete)

3. **Classify new entities:**
   - For `create` candidates, consult the entity definitions:
     - "When to create" section → positive criteria
     - "When NOT to create" section → exclusion criteria
     - "How to distinguish from other types" section → disambiguation

   > **Projects:** `project` is a valid type in extraction. When classifying new entities,
   > pay special attention to signals of initiatives with closed scope (deadline, deliverables,
   > focal points). Consult `entities/project.md` for creation criteria. An excerpt that
   > mentions migration with a deadline and responsible person is probably a project, not a topic.

4. **Record** for each detected entity:
   - Type (actor, person, team, topic, discussion, project)
   - Canonical name (filename or suggested slug)
   - Action: `create` or `update`
   - Extracted info: excerpt of the content where it appears
   - Source of origin: source slug

Report: "Phase 3: N entities detected (P creates, Q updates) across M sources."

---

## Phase 4 — Consolidated Confirmation

**REQUIRED:** Before creating/updating any entity, present a SINGLE list
with all changes from ALL sources:

```
## Sync — Proposed Changes

| # | Source | Type | Name | Action | Info |
|---|---|---|---|---|---|
| 1 | roadmap-26q1 | topic | 2026-04-feature-x | create | New topic mentioned |
| 2 | eventos-cobranca | actor | webhook-receiver | update | Description updated |
| ... | ... | ... | ... | ... | ... |

Total: N creates, M updates across P sources.
Confirm? (yes/no/adjust)
```

- **yes**: proceed to Phase 5
- **no**: abort with "Sync cancelled. No entities modified."
- **adjust**: ask what to adjust, modify list, re-present

**If no changes detected in any source:**
- Report: "No changes detected in any source. Vault is already up to date."
- Skip to Phase 6 (update `last_synced` anyway)

**Do NOT proceed without explicit user confirmation.**

---

## Phase 5 — Delegate to /bedrock:preserve

### 5.1 Compile structured list

Build the entity list in the format accepted by `/bedrock:preserve`:

```yaml
entities:
  - type: topic
    name: "2026-04-feature-x"
    action: create
    content: "relevant excerpt from content extracted in Phase 3..."
    relations:
      actors: ["actor-slug-1"]
      people: ["person-slug-1"]
    source: "confluence"
  - type: actor
    name: "webhook-receiver"
    action: update
    content: "new context extracted in Phase 3..."
    source: "github-repo"
```

**Compilation rules:**
- `type` and `name`: extracted from Phase 3
- `action`: `create` or `update` as identified
- `content`: excerpt of the source content that justifies the entity
- `relations`: infer relationships between entities in the list (if A mentions B, include B in A's relations)
- `source`: use the `source_type` of the originating source

### 5.2 Invoke /bedrock:preserve

Use the Skill tool to invoke `/bedrock:preserve --vault <VAULT_NAME>` passing the structured list as argument.
The `--vault <VAULT_NAME>` flag ensures preserve writes to the same vault.

`/bedrock:preserve` handles:
- Textual matching with existing entities
- Creation of new entities following templates
- Updating existing entities (merge/append-only)
- Bidirectional linking (wikilinks)
- Git commit of entities

### 5.3 Await result

`/bedrock:preserve` returns:
- List of created/updated entities
- Commit hash (if there was a commit)
- Any errors or warnings

Record the result for use in the final report (Phase 7).

---

## Phase 6 — Update synced_at in Entities

After re-sync of each URL, `/bedrock:preserve` has already updated the entities with new content.
Additionally, for each URL processed successfully, pass `source_url` and `source_type`
to `/bedrock:preserve` so it updates `synced_at` in the `sources` field of each mapped entity.

The URL → entities map (built in Phase 1) indicates which entities need
`synced_at` updated for each re-synced URL.

> **Note:** `/bedrock:preserve` already handles the entity commit. `/bedrock:sync` does NOT make a separate commit.

---

## Phase 7 — Report

Present to the user:

```
## Report

| Metric | Value |
|---|---|
| Sources found | N |
| Sources synchronized | N |
| Sources ignored (type) | N |
| Sources with error | N |
| Entities created | N |
| Entities updated | N |

### Per source
| Source | Type | Entities | Status |
|---|---|---|---|
| roadmap-26q1 | confluence | 3 creates, 2 updates | ✅ |
| acme-corp-billing-api | github-repo | 0 creates, 1 update | ✅ |
| manual-notes | manual | — | ⏭️ ignored |
| broken-source | confluence | — | ❌ error (reason) |

### Git
- Commit (entities): <hash from /bedrock:preserve or "no entities">
- Commit (sources): vault: syncs N sources [source: sync]
- Push: ✅ success / ❌ failed (reason)

### Suggestions
- [sources with errors that can be fixed]
- [entities mentioned in content but not created, if any]
```

---

## Critical Rules

| # | Rule |
|---|---|
| 1 | **NEVER write entities directly** — all entity writing goes through `/bedrock:preserve` |
| 2 | **NEVER create sources** — `/bedrock:sync` only processes URLs already registered in entities' `sources` field |
| 3 | **NEVER delete entities** — entities absent from updated content are kept |
| 4 | **ALWAYS confirm** consolidated proposal with user before executing (Phase 4) |
| 5 | **Best-effort for external sources** — never block due to unavailable MCP or broken URL |
| 6 | **MCP in main context** — do NOT use subagents for GitHub/Atlassian MCP calls |
| 7 | **csv and manual sources are ignored** — static types with no URL to re-fetch |
| 8 | **Maximum 2 push attempts** — after that, abort and inform |
| 9 | **Sensitive data** — NEVER include credentials, tokens, passwords, PANs, CVVs |
| 10 | **Frontmatter keys in English**, values in the vault's configured language |
| 11 | **Bare wikilinks** — `[[name]]`, never `[[dir/name]]` |

---
---

# Mode: Sync People (--people)




Skill that populates `people/` from recent commits in repositories listed in `actors/`.

**You are an execution agent.** Follow the phases below in order, without skipping steps.
Do not make git commit/push. Do not update `topics/` or `actors/`. Do not read CLAUDE.md from repositories.

---

## Phase 1 — Actor collection

1. Use Glob to list all files `<VAULT_PATH>/actors/*.md`
2. Exclude `<VAULT_PATH>/actors/_template.md`
3. For each file, use Read to extract from the YAML frontmatter:
   - `repository` — GitHub URL (e.g.: `https://github.com/acme-corp/billing-api/`)
   - `team` — squad wikilink (e.g.: `[[squad-payments]]`)
   - `name` — canonical name of the actor (e.g.: `billing-api`)
4. Parse `owner/repo` from the URL: extract the two path segments after `github.com/`
5. **Skip** actors without a `repository` field, with an empty URL, or with a URL that does not contain `github.com`
6. Store the list of valid actors: `{name, owner, repo, team_wikilink, team_slug}`
   - `team_slug`: extracted from the wikilink, e.g.: `[[squad-payments]]` → `squad-payments`

At the end of this phase, report: "Phase 1: N actors found, M with valid repository, K skipped."

---

## Phase 2 — Commit collection

For each actor in the list (in parallel when possible):

1. Calculate the date 30 days ago in ISO 8601 format (e.g.: `2026-03-04T00:00:00Z`)
2. Execute via Bash:
   ```
   gh api "repos/{owner}/{repo}/commits?since={date_30_days}&per_page=100" 2>/dev/null
   ```
3. If the command fails (404, 403, network error): **log and skip** — do not fail the execution
4. For each commit in the JSON result, extract:
   - `author.login` — GitHub login (may be `null` if commit via email without linked account)
   - `commit.author.name` — author's display name
5. **Filter bots:** ignore commits where:
   - `author.login` is `null`
   - `author.login` contains `[bot]`
   - `author.login` (case-insensitive) is exactly: `dependabot`, `renovate`, `github-actions`, `snyk-bot`, `codecov`, `sonarcloud`, `renovate-bot`, `depfu`
6. Store the valid commits associated with the actor

At the end of this phase, report: "Phase 2: N repositories accessed, M with commits, K inaccessible (list). Total of L commits from P unique contributors."

---

## Phase 3 — Aggregation

1. Group all commits by `author.login` (lowercase)
2. For each unique person, build:
   - `github`: login in lowercase
   - `name`: `commit.author.name` from the most recent commit (fallback: login if name is empty)
   - `focal_points`: list of canonical actor names where the person has commits (no duplicates)
   - `team_counts`: commit count by squad (e.g.: `{squad-payments: 15, squad-notifications: 3}`)
   - `team`: squad with most commits; in case of tie, first alphabetically
   - `filename`: derived from `name` → lowercase, no accents (normalize NFD and remove combining marks), spaces→hyphens, special characters removed, kebab-case
     - E.g.: `Alice Smith` → `alice-smith.md`
     - E.g.: `José María` → `jose-maria.md`
     - Fallback: if name not available, use login as filename

3. **Duplicate detection by filename:**
   - If two contributors (different logins) generate the same filename: append `-2`, `-3`, etc. to the second
   - If a file `people/{filename}` already exists with a different `github`: treat as different person, append suffix

At the end of this phase, report: "Phase 3: N unique contributors identified. Distribution by squad: [list]."

---

## Phase 4 — Write people

For each person:

### If `people/{filename}` does NOT exist — CREATE:

Use Write to create the file with this exact content (replace the placeholders):

```markdown
---
type: person
name: "{display_name}"
role: ""
team: "[[{team_slug}]]"
focal_points: [{focal_points_yaml}]
github: "{github_login}"
jira: ""
updated_at: {today_date_YYYY-MM-DD}
updated_by: "sync-people"
tags: [type/person]
---

# {Display Name}

> Active contributor identified via commits in the last 30 days.

## Team

Member of [[{team_slug}]].

## Focal Points

{focal_points_list}

## Active Topics

_No topics linked yet._
```

Where:
- `{focal_points_yaml}` = YAML array of wikilinks, e.g.: `["[[billing-api]]", "[[notification-service]]"]`
- `{focal_points_list}` = markdown list, e.g.:
  ```
  - [[billing-api]] — recent commits
  - [[notification-service]] — recent commits
  ```
- `{today_date_YYYY-MM-DD}` = today's date in `YYYY-MM-DD` format

### If `people/{filename}` ALREADY exists — UPDATE:

1. Use Read to read the existing file
2. **Merge f
