agentsclimarketplace

Kb lint

Skill ernestolee13/para-knowledge-base/skills/kb-lint

Health check for the PARA Knowledge Base. Detects orphan documents, broken links, index drift, tag issues, and stale content. Run periodically or as part of weekly review.From its SKILL.md

Install
npx -y skills add ernestolee13/para-knowledge-base --skill kb-lint

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 20 stars20 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.2 KB, as published. Nobody here has run it

KB Lint — Knowledge Base Health Check

Audit the vault for structural issues, inconsistencies, and maintenance opportunities.

Token-efficient by design: prefer obsidian-cli calls (single command result) over reading full vault into context. CLI uses Obsidian's actual link resolution — much more accurate than naive grep matching.

When .para-kb/config.json exists, use its PARA roots, index names, telemetry paths, and exclusions. Remove .para-kb/, the Obsidian config directory, active/archive telemetry, runtime state, and generated reports from every document denominator before calculating orphan, frontmatter, stale-content, or index-drift metrics.

Usage

/kb-lint                       # Full report (read-only)
/kb-lint --fix                 # Auto-fix safe issues
/kb-lint --category Projects   # Lint only one category

Checks

1. Index Drift

Compare _index.md contents against actual files on disk.

IssueMeaning
MISSINGListed in index but file not found
UNLISTEDFile exists but not in index
STALE_COUNTCount in top-level index doesn't match
STALE_INDEXupdated date > 7 days old

CLI: obsidian files folder="1. Projects" for actual files; compare against _index.md lines. Auto-fix: Add unlisted files, remove missing entries, update counts and dates.

2. Orphan Documents

Files with no incoming wikilinks.

With CLI (preferred): obsidian orphans — uses Obsidian's actual link graph.

Without CLI: comm -13 <(grep wikilinks sorted) <(find files sorted).

Exclude (false positives):

  • 0. Common/daily/*, 0. Common/weekly/*, _index.md, CLAUDE.md, Dashboard.md
  • .para-kb/*, the configured Obsidian directory, active/archive telemetry, and generated report paths
  • 0. ... 프로젝트 개요.md — referenced by folder name in indexes, not by full filename
  • templates/*, _templates/* — Obsidian Templater templates
  • Any file with frontmatter tags containing project/topic tags (cross-folder reference is intentional)

Report: List orphans with suggested action (link, tag, or archive).

3. Tag Consistency

IssueRule
MISSING_PROJECT_TAGFile in 1. Projects/x/ lacks #proj/x
MISSING_TYPE_TAGPaper file lacks #type/paper
BAD_TAG_FORMATSpaces or unexpected casing in tag
CROSS_REF_TAG#proj/x tag on file outside Projects — note as intentional cross-reference

CLI: obsidian tags to list all, obsidian tag name=<tag> to find files for a tag. Auto-fix: Use obsidian property:set to add missing tags to frontmatter.

4. Broken Links

With CLI (preferred): obsidian unresolved — Obsidian's resolved link graph reports actual unresolvable wikilinks.

Without CLI: grep -rh -oE '\[\[[^]|#]+' . --include='*.md' | sed 's/^\[\[//' | sort -u then compare against find . -name '*.md' -exec basename {} .md \;.

False positive exclusion (apply to both CLI and grep results):

  • Code blocks (``` ... ```)
  • Inline code ( ... ) — e.g., when `[[wikilink]]` is shown as documentation
  • Templater variables: <% ... %> content
  • Frontmatter blocks (between ---)
  • External paths starting with ./, /, http*:
  • Folder wikilinks ([[1. Projects/foo/]]) — Obsidian resolves these to folders

Pre-filter command for grep fallback (mask false positives before extraction):

sed -E '/^---$/,/^---$/d; /^```/,/^```/d; s/`[^`]*`//g; s/<%[^%]*%>//g'

Severity tiers:

  • Real broken (after FP filter, no matching file or folder): error
  • Folder wikilink: skip (Obsidian-native)
  • Templater/code/external: skip silently

5. Frontmatter Quality

Check documents in PARA directories for required fields:

FieldRequired?
titleYes
createdYes
tagsRecommended
typeRecommended
summaryRecommended — kb-ingest writes this on every new document; older documents fall back to their first paragraph in kb-query

CLI: obsidian property:read name=title file=<name> to check; bulk via obsidian properties. Report: Count of documents missing each field, list worst offenders.

6. Stale Content

IssueCondition
POSSIBLY_STALEProject file not modified >30 days
SHOULD_ARCHIVECompleted project still in 1. Projects/
MISPLACEDActive project found in 4. Archive/

CLI: obsidian file path=<file> for mtime; find -mtime +30 as fallback.

Output Format

# KB Lint Report — {date}

## Summary
- Passed: N checks
- Warnings: N items
- Errors: N items
- (CLI used: yes|no)

## Details
### Index Drift
...
### Orphan Documents
...
### Tag Issues
...
### Broken Links (real, after FP filter)
...
### Frontmatter Quality
...
### Stale Content
...

## Recommended Actions
1. {highest priority}
2. ...

Severity

LevelExamplesAuto-fixable
ErrorBroken links, missing filesSome (--fix)
WarningMissing tags, orphans, stale contentMost (--fix)
InfoOptimization suggestionsNo

Token Efficiency

Always prefer CLI. A single obsidian unresolved call returns a few hundred bytes; running grep across hundreds of files into context costs 7K–10K tokens unnecessarily.

When CLI is unavailable:

  1. Use find ... -size and find ... -mtime for stat-based checks (no read).
  2. Use grep -l first to locate files; only grep -h content from those.
  3. Apply false-positive sed mask before extracting wikilinks.
  4. Use comm -23 / -13 on sorted unique sets (linear time, no nested loops).

Avoid: reading every .md file into model context. The vault has 700+ files — that exceeds budget by an order of magnitude.

Log Entry

Append to 0. Common/log.md:

[date] lint | passed N | warnings N | errors N | fixed N | cli: yes|no

Arguments

ArgumentEffect
--fixApply safe-category fixes (see Auto-fix Philosophy below)
--category <name>Limit to one category: Projects, Areas, Resources, Archive
--no-cliForce grep fallback even if obsidian-cli is available (for debugging)

Obsidian-Native Exclusion Filters

Obsidian vaults can declare exclusion paths in .obsidian/app.json under userIgnoreFilters (used by Obsidian's search to hide certain folders — common examples: personal journals, templates, draft directories). The path list is vault-specific — no two vaults share the same set, so the plugin reads whatever the user has already configured rather than hardcoding any path.

Whether obsidian-cli orphans applies these filters to its output depends on the installed CLI version — do not assume either way. Verify with a quick smoke test (check whether a file inside a known userIgnoreFilters path is still listed in obsidian orphans) before deciding whether post-filtering is needed. If the CLI does not apply the filters, post-filter to respect the user's intent:

# Read whatever exclusion paths the user has set in Obsidian
USER_IGNORES=$(python3 -c "
import json
try:
    d = json.load(open('.obsidian/app.json'))
    for p in d.get('userIgnoreFilters', []):
        print(p.rstrip('/'))
except: pass
")

# Apply to path-based results (orphan check)
obsidian orphans | grep -vFf <(echo "$USER_IGNORES") > orphans_filtered.txt

obsidian unresolved is target-based, not path-based, so userIgnoreFilters does not apply directly to its output. If a broken target string happens to overlap with an excluded prefix, you may filter as well, but typically unresolved is left raw.

Reporting: The plugin should output both raw orphan count and filtered orphan count (after userIgnoreFilters) so the user sees the effect. Effect varies per vault — typically 20-40% reduction when journals/templates are excluded, but a vault that doesn't use userIgnoreFilters shows zero change.

Why this matters: vault-specific exclusion intent honored without per-vault hardcoding inside the plugin. Each user's vault automatically benefits from whatever filters they've already set in Obsidian's GUI.

Auto-fix Philosophy

This plugin does not bundle vault-specific batch-fix subcommands (e.g. --batch-weekly-wikilink, --fix-moved-projects). Adding one option per pattern explodes the surface and forces every vault to inherit assumptions that may not match.

Instead, the plugin gives Claude:

  1. Pattern categories (the tables above) to classify broken/orphan into safe vs human-judgment buckets
  2. CLI + sed primitives (Section "Token Efficiency", "Broken Links") to compose fixes
  3. Safety rules (vault CLAUDE.md: preserve links, update affected indexes, append one structural log entry)

When /kb-lint --fix is invoked, Claude:

  1. Runs obsidian unresolved / orphans (or grep+sed fallback)
  2. Classifies into safe categories (e.g. old weekly headers, index drift, missing project tags) vs human-judgment (e.g. orphan archive vs link decision, moved-project alias matching)
  3. Proposes a dry-run summary of safe-category batch fixes with vault-specific sed/Edit calls Claude composes from primitives
  4. Applies after user confirmation, appends a single [date] lint --fix | summary | N files | counts before→after line to log.md

Why this matters: each vault has unique conventions (Templater patterns, project naming, archive structure). Hardcoding a --batch-X for every pattern Claude could detect would freeze the plugin to one vault style. Letting Claude compose fixes from primitives keeps the plugin general while still automating the boring mechanical work.

Counting caveat

obsidian unresolved counts distinct broken targets, not instances that reference them. If 300 daily files all reference [[2021-W44]], that's 1 unresolved count but 300 instances. After fixing the 300 instances, count drops by only 1 (sometimes 0 if other files still reference the same target). For meaningful "fix progress" measurement, also report instance count via grep -rln '\[\[<target>\]\]' . | wc -l.

When applying batch fixes, surface both numbers in the dry-run summary — count alone misleads.

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.