Scan the vault (or a target directory) for tags used in page frontmatter and page bodies, diff against the vault's tag-index.md, prompt the user to approve new tags and clean stale ones, then update tag-index.md in place. Run after heavy ingest or other large content changes when new concepts may have introduced new tags. Triggers on: wiki-tags-refresh, refresh tags, update tag index, wiki tags, sync tags.
Installs into .claude/skills of the current project.
Are you the author of Wiki Tags Refresh?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ihudak-wiki-tags-refresh)
---
name: wiki-tags-refresh
description: >
Scan the vault (or a target directory) for tags used in page frontmatter and page
bodies, diff against the vault's tag-index.md, prompt the user to approve new tags
and clean stale ones, then update tag-index.md in place. Run after heavy ingest or
other large content changes when new concepts may have introduced new tags.
Triggers on: wiki-tags-refresh, refresh tags, update tag index, wiki tags, sync tags.
allowed-tools: Read Write Edit Glob Grep Bash
---
# wiki-tags-refresh
Read `skills/wiki-schema/SKILL.md` fully before proceeding.
Invoke with: `/wiki-tags-refresh [directory]`
If no directory argument is given, scan the entire vault (excludes `.obsidian/`).
---
## Step 1 — Resolve VAULT_PATH and scan scope
Vault path: `~/obsidian_vault` by default; set `VAULT_PATH` to override. WSL users with a Windows-side vault must set `VAULT_PATH` explicitly (e.g. `/mnt/c/Users/<name>/obsidian_vault`).
Scan scope: the vault root (default) or the given directory argument.
---
## Step 2 — Read the current tag index
Read `.obsidian/copilot/tag-index.md` fully. Extract every documented tag (lines matching
`` `#tagname` ``).
---
## Step 3 — Collect tags from scanned pages
Scan every `.md` file under the scan scope for frontmatter `tags:` entries only.
A broad grep would also match `related:` wikilinks — use the awk approach below
to stay strictly within the `tags:` block:
```bash
find "<scope>" -name "*.md" -not -path "*/.obsidian/*" -print0 | xargs -0 awk '
FNR==1{front=0; intags=0}
/^---/{if(front==0) front=1; else front=0; next}
!front{next}
/^tags:/{intags=1; next}
/^[a-zA-Z]/{intags=0; next}
intags && /^ - /{gsub(/^ - /, ""); print}
' | sort -u
```
> Note: tags must use the YAML multi-line block form (`tags:` followed by ` - tagname` lines); inline arrays (`tags: [a, b]`) are silently skipped by this script. `-print0`/`xargs -0` is required — vault filenames routinely contain spaces (e.g. `Daily/2026-07-13 Standup.md`), which would otherwise be word-split into separate, unopenable arguments.
Also collect inline tags from page bodies across the same scope:
```bash
grep -roh --exclude-dir=".obsidian" "#[a-zA-Z][a-zA-Z0-9/_-]*" "<scope>" | sort -u
```
Exclude false positives: markdown headings at line start (`^#`), code blocks, URLs.
Deduplicate the full collected set.
---
## Step 4 — Diff against tag-index.md
- **New tags**: found in scanned pages (default: entire vault) but NOT documented in `tag-index.md`
- **Stale candidates**: documented in `tag-index.md` but found in ZERO scanned pages
(flag only — never auto-remove; they may be used in other vault files)
---
## Step 5 — Report findings
```
wiki-tags-refresh results
─────────────────────────
New (undocumented in tag-index.md): #newtag #anothertag
Stale (0 uses found): #oldtag #unusedtag
Already in sync: N tags
```
---
## Step 6 — Process new tags
For each new tag, ask the user:
1. "What does `#newtag` mean?"
2. "Which section of tag-index.md should it go in?"
Present the existing section list as choices + "Other / new section…"
3. If the user skips a tag, leave it undocumented.
---
## Step 7 — Process stale candidates
Present the stale list and ask:
- `"Remove all stale tags from index"` — removes only confirmed zero-use tags
- `"Review one by one"` — step through each with keep/remove choice
- `"Keep all (ignore)"` — leave index unchanged for these
Always confirm zero vault-wide use before suggesting removal — the Step 3 scan may have
been narrower than the full vault. Exclude only `tag-index.md` itself (not all of
`.obsidian/`): every documented tag appears in `tag-index.md` by definition, so excluding
the whole `.obsidian/` directory would also hide genuine uses in other `.obsidian/` files
(e.g. Copilot custom prompts), silently dropping their removal protection.
```bash
grep -roh --include="*.md" --exclude="tag-index.md" "#[a-zA-Z][a-zA-Z0-9/_-]*" "<VAULT_PATH>" \
| grep -xF "#tagname" | wc -l
```
The exact-match filter (`-xF`) avoids `#tagname` false-matching a longer tag like
`#tagname-extended` or `#tagname/nested`.
If vault-wide count > 0, flag as "used elsewhere in the vault — keep?" rather than suggesting removal.
---
## Step 8 — Update tag-index.md
For approved new tags:
- Insert into the chosen section in alphabetical order within the section.
- Format: `` `#tagname` - <description> ``
- Before appending to `## Recently Approved Tags`, ensure the section exists:
```bash
grep -q "^## Recently Approved Tags" ".obsidian/copilot/tag-index.md" || \
printf "\n## Recently Approved Tags\n" >> ".obsidian/copilot/tag-index.md"
```
- Append to `## Recently Approved Tags`:
`` `#tagname` (YYYY-MM-DD) - <description> ``
For confirmed removals:
- Remove the entry from its section.
- Do NOT remove from `## Recently Approved Tags` (history record).
Do not touch any section not affected by the diff.
---
## Step 9 — Append to _log.md
```markdown
## [YYYY-MM-DD] tags-refresh | N new tags proposed, M approved, L removed
- New: #tag1, #tag2 (if any approved)
- Removed: #oldtag (if any removed)
```
---
## Finish
```
wiki-tags-refresh complete.
Tags added: N Tags removed: M Unchanged: K
tag-index.md updated.
```