Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Validate References

ASecurity

Check BibTeX entries for completeness, DOI resolution, and broken links. Verify required fields per entry type (article, book, inproceedings), resolve and validate DOIs via the CrossRef API, check URL accessibility, and flag duplicate entries, missing abstracts, and inconsistent formatting. Use when preparing a manuscript bibliography for journal submission, auditing a shared .bib file before a project milestone, after merging bibliographies from multiple sources, when citations render incorr...

31 stars
0 votes
0 copies
6 views
Added 9/3/2026
ai-agentsgobashapidocumentation

Works with

claude codeapi

Security Analysis

A100/100

Scanned 9/3/2026

$npx -y skills add pjt222/agent-almanac --skill validate-references --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Validate References?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Validate References
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pjt222-validate-references-e6c289c4/badge)](https://www.skillsdirectory.com/skills/pjt222-validate-references-e6c289c4)

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

Download with Pro
Files
SKILL.md
---
name: validate-references
description: >
  Check BibTeX entries for completeness, DOI resolution, and broken links.
  Verify required fields per entry type (article, book, inproceedings), resolve
  and validate DOIs via the CrossRef API, check URL accessibility, and flag
  duplicate entries, missing abstracts, and inconsistent formatting. Use when
  preparing a manuscript bibliography for journal submission, auditing a shared
  .bib file before a project milestone, after merging bibliographies from
  multiple sources, when citations render incorrectly, or as a CI check on
  version-controlled .bib files; not for agent-memory or documentation
  cross-references — see repair-broken-references.
license: MIT
allowed-tools: Read Write Edit Bash Grep Glob
metadata:
  author: Philipp Thoss
  version: "1.1"
  domain: citations
  complexity: intermediate
  language: R
  tags: citations, validation, doi, bibtex, quality
---

# Validate References

Check BibTeX bibliography entries for completeness, accuracy, and consistency.
This skill covers verifying required fields per entry type, resolving DOIs via
the CrossRef API, checking URL accessibility, detecting duplicate entries, and
producing a structured validation report that flags issues by severity. It
ensures that .bib files are publication-ready before rendering.

## When to Use

- Preparing a manuscript bibliography for journal submission
- Auditing a shared .bib file for quality before a project milestone
- After merging bibliographies from multiple sources
- When citations render incorrectly and you need to diagnose .bib issues
- As a CI check on .bib files in version-controlled projects

**Do NOT use** for agent-memory or documentation cross-references — see
[`repair-broken-references`](../repair-broken-references/SKILL.md). This skill
validates bibliographic entries and DOI resolution; the name collision with
"references" in the link-checking sense is the only thing the two share. For the
reachability and budget of a Claude Code memory directory, see
[`verify-memory-integrity`](../verify-memory-integrity/SKILL.md).

## Inputs

- **Required**: Path to a .bib file
- **Optional**: Validation level (`basic`, `standard`, `strict`; default: `standard`)
- **Optional**: Whether to check DOI resolution online (default: `TRUE`)
- **Optional**: Whether to check URL accessibility (default: `TRUE`)
- **Optional**: Output report path (default: prints to console)
- **Optional**: CrossRef API email for polite pool (recommended for large files)

## Procedure

### Step 1: Install and Load Required Packages

```r
required_packages <- c("RefManageR", "httr2", "curl")
missing <- required_packages[!vapply(required_packages, requireNamespace,
                                     logical(1), quietly = TRUE)]
if (length(missing) > 0) install.packages(missing)

library(RefManageR)
```

**Expected:** All packages load without errors.

**On failure:** If httr2 is unavailable, install it with `install.packages("httr2")`.
For systems without curl headers: `sudo apt install libcurl4-openssl-dev`.

### Step 2: Parse and Inventory the Bibliography

```r
bib <- RefManageR::ReadBib("references.bib", check = FALSE)
message(sprintf("Loaded %d entries from references.bib", length(bib)))

# Inventory entry types
entry_types <- vapply(bib, function(x) tolower(attr(x, "bibtype")), character(1))
type_counts <- sort(table(entry_types), decreasing = TRUE)
message("Entry types:")
for (type in names(type_counts)) {
  message(sprintf("  %s: %d", type, type_counts[[type]]))
}
```

**Expected:** Summary of entry types (article, book, inproceedings, etc.) and total
count matching the number of `@type{` blocks in the file.

**On failure:** Parsing errors indicate malformed BibTeX. Check for unmatched braces,
missing commas between fields, or invalid UTF-8 characters.

### Step 3: Validate Required Fields per Entry Type

```r
# BibTeX required fields by entry type
required_fields <- list(
  article       = c("author", "title", "journal", "year"),
  book          = c("author", "title", "publisher", "year"),
  inproceedings = c("author", "title", "booktitle", "year"),
  incollection  = c("author", "title", "booktitle", "publisher", "year"),
  phdthesis     = c("author", "title", "school", "year"),
  mastersthesis = c("author", "title", "school", "year"),
  techreport    = c("author", "title", "institution", "year"),
  misc          = c("author", "title", "year"),
  unpublished   = c("author", "title", "note")
)

validate_fields <- function(bib) {
  issues <- list()
  for (i in seq_along(bib)) {
    key <- names(bib)[i]
    entry_type <- tolower(attr(bib[[i]], "bibtype"))
    req <- required_fields[[entry_type]]
    if (is.null(req)) {
      issues[[length(issues) + 1]] <- list(
        key = key, severity = "warning",
        message = sprintf("Unknown entry type: %s", entry_type)
      )
      next
    }
    for (field in req) {
      value <- bib[[i]][[field]]
      if (is.null(value) || !nzchar(trimws(as.character(value)))) {
        issues[[length(issues) + 1]] <- list(
          key = key, severity = "error",
          message = sprintf("Missing required field: %s (type: %s)", field, entry_type)
        )
      }
    }
  }
  issues
}

field_issues <- validate_fields(bib)
message(sprintf("Field validation: %d issues found", length(field_issues)))
```

**Expected:** A list of issues where required fields are missing. Zero issues for a
well-maintained bibliography.

**On failure:** This step runs locally and should not fail. If it does, check that the
.bib file parsed correctly in Step 2.

### Step 4: Resolve and Validate DOIs

```r
validate_dois <- function(bib, email = NULL) {
  issues <- list()

  # Set polite API headers
  headers <- list(`User-Agent` = "R-bibliography-validator/1.0")
  if (!is.null(email)) {
    headers[["mailto"]] <- email
  }

  for (i in seq_along(bib)) {
    key <- names(bib)[i]
    doi <- bib[[i]]$doi
    if (is.null(doi) || !nzchar(doi)) {
      issues[[length(issues) + 1]] <- list(
        key = key, severity = "info",
        message = "No DOI present"
      )
      next
    }

    # Normalize DOI
    doi <- gsub("^https?://doi\\.org/", "", doi)
    doi <- gsub("^doi:", "", doi, ignore.case = TRUE)
    doi <- trimws(doi)

    # Resolve via CrossRef
    tryCatch({
      resp <- httr2::request(sprintf("https://api.crossref.org/works/%s", doi)) |>
        httr2::req_headers(!!!headers) |>
        httr2::req_timeout(10) |>
        httr2::req_perform()

      if (httr2::resp_status(resp) != 200) {
        issues[[length(issues) + 1]] <- list(
          key = key, severity = "error",
          message = sprintf("DOI does not resolve: %s (HTTP %d)", doi,
                            httr2::resp_status(resp))
        )
      }
    }, error = function(e) {
      issues[[length(issues) + 1]] <<- list(
        key = key, severity = "warning",
        message = sprintf("DOI check failed for %s: %s", doi, e$message)
      )
    })

    Sys.sleep(0.5)  # Rate limiting
  }
  issues
}

# Only run online checks if requested
doi_issues <- validate_dois(bib, email = "your.email@example.com")
message(sprintf("DOI validation: %d issues found", length(doi_issues)))
```

**Expected:** Each DOI resolves successfully (HTTP 200 from CrossRef). Entries without
DOIs are flagged as informational.

**On failure:** Network errors or rate limiting produce warnings rather than hard
failures. Set the `email` parameter for higher rate limits from CrossRef's polite pool.

### Step 5: Check URL Accessibility

```r
validate_urls <- function(bib) {
  issues <- list()

  for (i in seq_along(bib)) {
    key <- names(bib)[i]
    url <- bib[[i]]$url

    if (is.null(url) || !nzchar(url)) next

    tryCatch({
      resp <- httr2::request(url) |>
        httr2::req_method("HEAD") |>
        httr2::req_timeout(10) |>
        httr2::req_error(is_error = function(resp) FALSE) |>
        httr2::req_perform()

      status <- httr2::resp_status(resp)
      if (status >= 400) {
        issues[[length(issues) + 1]] <- list(
          key = key, severity = "warning",
          message = sprintf("URL returned HTTP %d: %s", status, url)
        )
      }
    }, error = function(e) {
      issues[[length(issues) + 1]] <<- list(
        key = key, severity = "warning",
        message = sprintf("URL unreachable: %s (%s)", url, e$message)
      )
    })

    Sys.sleep(0.3)
  }
  issues
}

url_issues <- validate_urls(bib)
message(sprintf("URL validation: %d issues found", length(url_issues)))
```

**Expected:** All URLs return HTTP 200 (or 301/302 redirects). Broken links flagged.

**On failure:** Some servers block HEAD requests. Retry with GET for failed HEAD
checks. Timeout errors are common for slow academic servers.

### Step 6: Detect Duplicate Entries

```r
detect_duplicates <- function(bib) {
  issues <- list()

  # Check for duplicate DOIs
  dois <- vapply(bib, function(x) {
    d <- x$doi
    if (is.null(d)) NA_character_ else tolower(trimws(d))
  }, character(1))

  doi_table <- table(dois[!is.na(dois)])
  dup_dois <- names(doi_table[doi_table > 1])
  for (d in dup_dois) {
    keys <- names(bib)[which(dois == d)]
    issues[[length(issues) + 1]] <- list(
      key = paste(keys, collapse = ", "), severity = "error",
      message = sprintf("Duplicate DOI %s in entries: %s", d,
                        paste(keys, collapse = ", "))
    )
  }

  # Check for duplicate titles (fuzzy)
  titles <- vapply(bib, function(x) {
    t <- x$title
    if (is.null(t)) NA_character_ else tolower(gsub("[^a-z0-9 ]", "", tolower(t)))
  }, character(1))

  seen <- character(0)
  for (i in seq_along(titles)) {
    if (is.na(titles[i])) next
    for (j in seen) {
      if (identical(titles[i], titles[as.integer(j)])) {
        issues[[length(issues) + 1]] <- list(
          key = sprintf("%s, %s", names(bib)[as.integer(j)], names(bib)[i]),
          severity = "warning",
          message = sprintf("Possible duplicate titles: '%s'",
                            substr(bib[[i]]$title, 1, 60))
        )
      }
    }
    seen <- c(seen, as.character(i))
  }

  issues
}

dup_issues <- detect_duplicates(bib)
message(sprintf("Duplicate detection: %d issues found", length(dup_issues)))
```

**Expected:** Zero duplicates for a clean bibliography. Any detected duplicates are
flagged with the specific entry keys involved.

### Step 7: Generate Validation Report

```r
generate_report <- function(all_issues, bib, output_file = NULL) {
  errors   <- Filter(function(x) x$severity == "error", all_issues)
  warnings <- Filter(function(x) x$severity == "warning", all_issues)
  infos    <- Filter(function(x) x$severity == "info", all_issues)

  lines <- c(
    "# Bibliography Validation Report",
    "",
    sprintf("**File**: references.bib"),
    sprintf("**Entries**: %d", length(bib)),
    sprintf("**Date**: %s", Sys.Date()),
    "",
    sprintf("## Summary: %d errors, %d warnings, %d info",
            length(errors), length(warnings), length(infos)),
    ""
  )

  if (length(errors) > 0) {
    lines <- c(lines, "## Errors", "")
    for (issue in errors) {
      lines <- c(lines, sprintf("- **[%s]** %s", issue$key, issue$message))
    }
    lines <- c(lines, "")
  }

  if (length(warnings) > 0) {
    lines <- c(lines, "## Warnings", "")
    for (issue in warnings) {
      lines <- c(lines, sprintf("- **[%s]** %s", issue$key, issue$message))
    }
    lines <- c(lines, "")
  }

  report_text <- paste(lines, collapse = "\n")

  if (!is.null(output_file)) {
    writeLines(report_text, output_file)
    message(sprintf("Report written to %s", output_file))
  }

  cat(report_text)
  invisible(all_issues)
}

all_issues <- c(field_issues, doi_issues, url_issues, dup_issues)
generate_report(all_issues, bib, output_file = "validation-report.md")
```

**Expected:** A structured markdown report listing all issues grouped by severity.

## Validation

- [ ] All entries have required fields for their type (no errors in field check)
- [ ] All DOIs resolve to valid CrossRef records
- [ ] No duplicate DOIs exist in the bibliography
- [ ] All URLs are accessible (HTTP 200 or redirect)
- [ ] Validation report generated without R errors
- [ ] Zero errors in report for a publication-ready bibliography

## Common Pitfalls

- **DOI format inconsistency**: DOIs may appear as `10.1234/...`,
  `https://doi.org/10.1234/...`, or `doi:10.1234/...`. Normalize before comparing
- **CrossRef rate limiting**: Unauthenticated requests are limited to ~50/second.
  Always use the `email` parameter to join the polite pool for higher limits
- **Transient URL failures**: Academic servers occasionally timeout. Retry failed
  URLs once before flagging them as broken
- **Entry type variations**: BibLaTeX uses `@online` where BibTeX uses `@misc`.
  The validator should handle both
- **False positive duplicates**: Entries like "Introduction" or "Methods" as titles
  trigger fuzzy matching. Review flagged duplicates manually
- **Missing DOIs for older works**: Pre-2000 publications often lack DOIs. Flag as
  informational, not as errors
- **Author names are separated by `and`, not commas**: BibTeX splits an `author`
  or `editor` field on the word `and` surrounded by spaces and not enclosed in
  braces. A comma delimits parts inside a single name (`von Last, First` or
  `von Last, Jr, First`), so a comma-joined list collapses silently into one
  author rather than several, and a trailing comma makes BibTeX complain that a
  name ends with a comma
- **Organizations as authors need an extra pair of braces**: An institution left
  bare in an `author` field is dissected into First/von/Last like a personal
  name, and any internal `and` splits it into two people. Wrap it in its own
  braces: `author = {{National Aeronautics and Space Administration}}`
- **One person under two spellings**: `Donald E. Knuth` in one entry and `D. E. Knuth`
  in another alphabetize as two different authors. Settle on one form per person,
  or write `D[onald] E. Knuth`, which BibTeX alphabetizes as if the brackets were
  absent
- **Journal name inconsistency**: One entry writes `Journal of the American Chemical
  Society`, another writes `J. Am. Chem. Soc.`, and nothing above notices, because
  Step 6 compares DOIs and article titles and never looks at the `journal` field. The
  CrossRef record fetched in Step 4 carries the journal's `ISSN` and `container-title`,
  and often its `short-container-title` (`J. Am. Chem. Soc.` for this journal), so
  group entries by `ISSN` and flag any group whose `journal` values disagree. Flag
  rather than normalize: ISO 4 governs title-word abbreviation through the LTWA
  maintained by the ISSN International Centre, but NLM/MEDLINE strips the punctuation
  from that assignment, so PubMed writes `J Am Chem Soc` for the same journal. Pick the
  form the target journal's style requires
- **A resolving DOI is not a matching DOI**: HTTP 200 confirms the DOI is
  registered, not that it points at the entry's work. A wrong but registered DOI
  resolves exactly like a correct one, so compare the CrossRef record Step 4
  already fetched against the entry: `title` (an array), the `family` names in
  `author`, and `issued.date-parts`. Normalize both sides first, or LaTeX escapes
  and Unicode accents will manufacture mismatches
- **False mismatches from CrossRef's own shape**: a year off by one need not be an
  error, because `published-online` and `published-print` are separate fields that
  can fall in different years. `10.1093/bioinformatics/btz848` is online 2019 and
  print 2020, with `issued` following the online date. `short-container-title` is
  sometimes an empty array, so a missing journal abbreviation is not a mismatch,
  and a record paginated by article number can carry `article-number` with no
  `page` field at all

## Related Skills

- `manage-bibliography` - fix issues found by this validator (dedup, add fields)
- `format-citations` - format validated entries into styled citations
- `repair-broken-references` - the correct destination for broken internal links,
  dead URLs, stale imports, and orphaned files in documentation or agent memory
- `verify-memory-integrity` - read-only reachability and budget check for a Claude
  Code memory directory; not a bibliography tool despite the shared word
- `../reporting/format-apa-report` - APA reports require complete, validated references
- `../r-packages/write-vignette` - vignettes with citations need valid .bib entries

Attribution

pjt222pjt222
View sourceSee grades on GitHubMore from pjt222 →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

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 (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698461 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →