Skip to main content

skill-cite

Verify citation claims against Literature/ index and Zotero library. Invoke for /cite command.

Jump to install

Source facts

Repository
benbrastmckie/nvim
Last source activity
August 24, 2026 at 22:21
Detected SKILL.md language
English
Stars
444
Forks
459

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
skill-cite
description
Verify citation claims against Literature/ index and Zotero library. Invoke for /cite command.
allowed-tools
Bash, Read, Write, Edit, AskUserQuestion
# Cite Skill (Direct Execution) Direct execution skill for verifying citation claims in task artifacts against the Literature/ index and Zotero library. Extracts citations, searches for source matches, scores confidence, presents findings interactively, and creates tasks for unverified claims. **Key behavior**: Users always see citation findings BEFORE any tasks are created. Users select which unverified/gap claims to address via interactive prompts. ## Context References Reference (do not load eagerly): - Path: `@specs/state.json` - Machine state - Path: `@specs/TODO.md` - Current task list - Path: `@specs/literature/index.json` - Literature index for keyword matching --- ## Execution ### Step 1: Parse Arguments Extract task number, optional description text, and flags from command input: ```bash # Parse from command input args="$ARGUMENTS" # Extract task number (first numeric argument) task_num=$(echo "$args" | grep -oP '^\s*\K\d+' | head -1) # Check for --gaps flag (also show gap items separately) show_gaps=false if echo "$args" | grep -q -- '--gaps'; then show_gaps=true fi # Check for optional description text (everything after flags/number) description_override=$(echo "$args" | sed 's/--[a-z-]*//g' | sed "s/$task_num//" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') ``` If no task number is provided and no direct file path is given, report and exit: ``` ## Error: No Task Specified Usage: /cite N [--gaps] N Task number to verify citations for --gaps Also flag citations found but with no PDF source Example: /cite 42 /cite 42 --gaps ``` ### Step 2: Generate Session ID Generate session ID for tracking: ```bash source .claude/scripts/lib/common.sh session_id="$(common_session_id)" ``` ### Step 3: Locate Task Artifacts Read state.json to find the task slug and locate artifacts: ```bash project_root="$(pwd)" state_file="$project_root/specs/state.json" # Find task slug from state.json task_slug=$(jq -r --argjson num "$task_num" \ '.active_projects[] | select(.project_number == $num) | .project_name' \ "$state_file") # Also check archive if [ -z "$task_slug" ] || [ "$task_slug" = "null" ]; then task_slug=$(jq -r --argjson num "$task_num" \ '.archive[]? | select(.project_number == $num) | .project_name' \ "$state_file" 2>/dev/null) fi # Construct task directory path (zero-padded 3 digits) task_dir=$(printf "specs/%03d_%s" "$task_num" "$task_slug") if [ ! -d "$project_root/$task_dir" ]; then echo "Error: Task directory not found: $task_dir" exit 1 fi # Glob all artifact files (reports, plans, summaries) artifact_files=() while IFS= read -r -d '' f; do artifact_files+=("$f") done < <(find "$project_root/$task_dir" -name "*.md" -print0 2>/dev/null) if [ ${#artifact_files[@]} -eq 0 ]; then echo "No artifact files found in $task_dir" echo "Nothing to verify." exit 0 fi ``` ### Step 4: Extract Citations For each artifact file, run `cite-extract.sh` and aggregate results: ```bash script_dir="$project_root/.claude/extensions/literature/scripts" all_citations=() # JSON objects as strings total_found=0 for artifact_file in "${artifact_files[@]}"; do rel_path="${artifact_file#$project_root/}" # Run cite-extract.sh on the file raw_output=$("$script_dir/cite-extract.sh" --format=json "$artifact_file" 2>/dev/null) || { exit_code=$? # exit code 2 = no citations found (normal), anything else = error if [ $exit_code -ne 2 ]; then echo "Warning: cite-extract.sh failed for $rel_path (exit $exit_code)" >&2 fi continue } # Add source_file field to each result and accumulate # cite-extract.sh output schema: [{claim, source_text, line_number, confidence, pattern_type}] enriched=$(echo "$raw_output" | jq --arg file "$rel_path" \ '[.[] | . + {source_file: $file}]' 2>/dev/null) || continue count=$(echo "$enriched" | jq 'length' 2>/dev/null || echo 0) total_found=$((total_found + count)) # Append to all_citations (collect as JSON array strings to merge later) all_citations+=("$enriched") done # Merge all citation arrays into one if [ ${#all_citations[@]} -eq 0 ]; then combined_citations="[]" else combined_citations=$(printf '%s\n' "${all_citations[@]}" | jq -s 'add // []') fi ``` ### Step 5: Handle No Citations Found If no citations were extracted: ``` ## No Citations Found **Task**: #{N} — {task_slug} **Artifacts Scanned**: {count} files in {task_dir} No citation patterns detected across task artifacts. Patterns searched: author_year, parenthetical, phrase_attribution, theorem_attr, direct_quote, numeric_bracket, alpha_num_bracket, latex_cite ``` Exit gracefully without prompts. ### Step 6: Search Literature/ Index For each unique citation claim, extract query terms and search `specs/literature/index.json`: ```bash if [ -n "${LITERATURE_DIR:-}" ] && [ -d "$LITERATURE_DIR" ]; then lit_index="$LITERATURE_DIR/index.json" else lit_index="$project_root/specs/literature/index.json" fi index_available=false if [ -f "$lit_index" ]; then index_available=true fi # For each citation, extract key terms from source_text and match against index # index.json entry schema: {id, title, keywords[], file, summary} # Match strategy: count how many query terms appear in (title + keywords joined) score_against_index() { local source_text="$1" local query_terms # Extract significant words (strip common stop words, lowercase, split) query_terms=$(echo "$source_text" | tr '[:upper:]' '[:lower:]' \ | sed 's/[^a-z0-9 ]/ /g' \ | tr ' ' '\n' \ | grep -vE '^(a|an|the|in|on|at|of|to|for|and|or|by|as|is|are|was|were|be|that|this|from|with|it|its|et|al|pp|vol|no|doi)$' \ | grep -v '^[0-9]\{1,4\}$' \ | sort -u | tr '\n' ' ') if [ -z "$query_terms" ] || [ "$index_available" = "false" ]; then echo "0" return fi # Count term overlaps in title + keywords fields jq -r --arg terms "$query_terms" ' .entries // [] | map( (.title + " " + ((.keywords // []) | join(" "))) | ascii_downcase | . as $haystack | ($terms | split(" ") | map(select(length > 2)) | map(if (($haystack | test(.; "i")) // false) then 1 else 0 end) | add // 0) ) | max // 0 ' "$lit_index" 2>/dev/null || echo "0" } ``` ### Step 7: Search Zotero For each unique citation claim, search via `zotero-search.sh`: ```bash # Check if Zotero is configured (zotero-search.sh exits 1 if library not found) zotero_available=false if "$script_dir/zotero-search.sh" --limit=1 --format=json "test" &>/dev/null; then zotero_available=true elif [ $? -eq 2 ]; then # exit 2 = no results (library exists, query returned nothing) zotero_available=true fi search_zotero() { local source_text="$1" if [ "$zotero_available" = "false" ]; then echo "[]" return fi # zotero-search.sh accepts query terms as positional args # Output schema: [{citation_key, title, authors, year, score, pdf_paths, abstract_snippet}] local query_words query_words=$(echo "$source_text" | tr '[:upper:]' '[:lower:]' \ | sed 's/[^a-z0-9 ]/ /g' \ | tr ' ' '\n' \ | grep -vE '^(a|an|the|in|on|at|of|to|for|and|or|by|as|is|et|al|pp)$' \ | grep -v '^[0-9]\{1,4\}$' \ | sort -u | head -8 | tr '\n' ' ') if [ -z "$query_words" ]; then echo "[]" return fi # shellcheck disable=SC2086 "$script_dir/zotero-search.sh" --limit=5 --format=json $query_words 2>/dev/null \ || echo "[]" } ``` ### Step 8: Score Confidence Apply scoring to each extracted citation and classify it: **Scoring thresholds** (based on search results): - **confirmed**: Zotero top result score >= 3 OR index keyword overlap >= 2 - **partial**: Zotero top result score 1–2 OR index keyword overlap == 1 - **unconfirmed**: No match in either Zotero or Literature/ index - **gap**: Citation pattern found but source text suggests a specific work that exists in index/Zotero yet has no associated PDF ```bash score_citation() { local source_text="$1" local pattern_type="$2" local extract_confidence="$3" # from cite-extract.sh (0.5–0.9) # Get index overlap score local index_overlap index_overlap=$(score_against_index "$source_text") # Get Zotero results local zotero_results zotero_top_score zotero_results=$(search_zotero "$source_text") zotero_top_score=$(echo "$zotero_results" | jq '.[0].score // 0' 2>/dev/null || echo 0) # Classify local status best_match if [ "$zotero_top_score" -ge 3 ] 2>/dev/null || [ "$index_overlap" -ge 2 ] 2>/dev/null; then status="confirmed" best_match=$(echo "$zotero_results" | jq -r '.[0] | "\(.authors[0] // "Unknown") (\(.year // "n.d.")). \(.title)"' 2>/dev/null \ || echo "Literature index match (overlap: $index_overlap)") elif [ "$zotero_top_score" -ge 1 ] 2>/dev/null || [ "$index_overlap" -ge 1 ] 2>/dev/null; then status="partial" best_match=$(echo "$zotero_results" | jq -r '.[0] | "\(.authors[0] // "Unknown") (\(.year // "n.d.")). \(.title)"' 2>/dev/null \ || echo "Weak literature index match (overlap: $index_overlap)") else status="unconfirmed" best_match="No match found" fi # Check for gap: pattern found but PDF unavailable if [ "$status" != "unconfirmed" ] && [ "$show_gaps" = "true" ]; then local pdf_count pdf_count=$(echo "$zotero_results" | jq '.[0].pdf_paths // [] | length' 2>/dev/null || echo 0) if [ "$pdf_count" -eq 0 ] && [ "$zotero_top_score" -ge 1 ] 2>/dev/null; then status="gap" fi fi
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub