A citation validation and management tool for markdown files that enforces cross-document links and proper anchor patterns. Features three-tier validation with warning detection and (limited) automated citation correction capabilities.
# Install dependencies
npm install
# Build TypeScript and link CLI globally
npm run build
npm link
# Verify CLI is available
jact --helpNote: Source is TypeScript (
src/*.ts), compiled todist/*.js. After any TS changes, re-runnpm run buildto update the CLI.
The jact implements a comprehensive validation approach with three distinct status levels:
- File Not Found: Target file does not exist in filesystem
- Invalid Path: Malformed or inaccessible file paths
- Anchor Not Found: Referenced header/anchor missing in target file
- Invalid Caret Syntax: Malformed requirement/criteria patterns
- Short Filename Citations: Citations using only filename without directory context (
@file.md) - Cross-Directory References: Links spanning multiple directory levels (
@../../other/file.md) - Ambiguous Paths: Multiple files with same name in different directories
- Relative Path Usage: Citations using relative paths without full context (
@./local.md)
- Valid Citations: Successfully resolved and validated links
- Path Conversions: Automatic path transformations applied during fix operations
- Cache Hits: Files found via intelligent scope-based resolution
- Backup Operations: File backup confirmations during fix operations
- Automatic Path Conversion: Transforms short filenames and relative paths to absolute paths
- Warning Resolution: Converts warning-level issues to validated citations
- Backup Creation: Automatic backup files before making changes
- Dry Run Mode: Preview changes without applying modifications
- JSON Output: Detailed reporting of all path conversions and fixes applied
# Validate citations in a markdown file (CLI output)
npm run jact:validate path/to/file.md
# Get JSON output for programmatic use
npm run jact:validate path/to/file.md -- --format json
# Validate specific line range
npm run jact:validate path/to/file.md -- --lines 150-160
# Validate single line
npm run jact:validate path/to/file.md -- --lines 157
# Combine line filtering with JSON output
npm run jact:validate path/to/file.md -- --lines 157 --format json
# Validate with folder scope (smart filename resolution)
npm run jact:validate path/to/file.md -- --scope /path/to/project/docs
# Combine scope with line filtering
npm run jact:validate path/to/file.md -- --lines 148-160 --scope /path/to/project/docs
# Auto-fix kebab-case anchors to raw header format for better Obsidian compatibility
npm run jact:validate path/to/file.md -- --fix --scope /path/to/project/docs
# Direct CLI usage
# node utility-scripts/citation-links/jact.js validate path/to/file.md --lines 157 --scope /path/to/docsThe fix command provides comprehensive citation correction with warning detection and path conversion capabilities:
# Basic fix with automatic backup creation
npm run jact:validate path/to/file.md -- --fix --scope /path/to/project/docs
# Fix with dry-run mode to preview changes
npm run jact:validate path/to/file.md -- --fix --dry-run --scope /path/to/project/docs
# Fix with JSON output for detailed reporting
npm run jact:validate path/to/file.md -- --fix --format json --scope /path/to/project/docs
# Direct CLI usage with enhanced options
node utility-scripts/citation-links/jact.js validate path/to/file.md --fix --backup --scope /path/to/docs# Show parsed AST and extracted citation data
npm run jact:ast path/to/file.md
# Direct CLI usage
node utility-scripts/citation-links/jact.js ast path/to/file.mdThe base-paths command is implemented as an npm script facade that wraps the validate command.
# Extract distinct base paths from citations
npm run jact:base-paths path/to/file.mdThis is equivalent to: npm run jact:validate path/to/file.md -- --format json | jq -r '.links[] | select(.target.path.absolute) | .target.path.absolute' | sort -u
Rationale: With the Validation Enrichment Pattern (US1.8), LinkObjects include target.path.absolute directly. The facade preserves the convenient interface while eliminating redundant code.
Extract entire file content directly without requiring a source document containing links:
# Extract complete file content
node tools/jact/src/jact.js extract file docs/architecture.md
# Extract with scope restriction for filename resolution
node tools/jact/src/jact.js extract file architecture.md --scope ./docs
# Pipe output to jq for content filtering
node tools/jact/src/jact.js extract file file.md | jq '.extractedContentBlocks'Output Format: JSON OutgoingLinksExtractedContent structure with deduplicated content blocks
Exit Codes:
0: File extracted successfully1: File not found or validation failed2: System error (permission denied, parse error)
Use Cases:
- Direct content extraction without creating source documents
- Building LLM context packages from specific files
- Automated content aggregation pipelines
- File content validation and inspection
# Run the test suite
npm run test:citationTest Coverage:
- β Basic citation validation (happy path)
- β Broken link detection and error reporting
- β JSON output format validation
- β AST generation and parsing
- β Non-existent file error handling
- β Line range filtering (new)
- β Folder scope with smart file resolution (new)
- β Combined line range + scope functionality (new)
- β Single line filtering (new)
- β URL-encoded path handling (new)
- β Symlink-aware path resolution (new)
- β Obsidian absolute path format support (new)
Enhanced Test Features:
- Line Filtering Tests: Validate
--lines 13-14and--lines 7options work correctly - Scope Resolution Tests: Verify
--scopeoption builds file cache and resolves broken paths - JSON Integration Tests: Ensure scope messages don't interfere with JSON output format
- Edge Case Coverage: Test single line filtering and combined option usage
- Symlink Resolution Tests: Verify proper handling of symlinked directories and files
- URL Encoding Tests: Validate paths with spaces and special characters (
%20, etc.) - Obsidian Path Tests: Test Obsidian absolute path format (
0_SoftwareDevelopment/...)
[Link Text](relative/path/to/file.md#anchor-name)
[Component Details](../architecture/components.md#auth-service)Markdown Headers Handling:
- Headers with markdown formatting (backticks, bold, italic, highlights, links) use raw text as anchors
- Plain text headers generate both kebab-case and raw header anchors
- URL encoding required for spaces and special characters in markdown headers
- Obsidian Compatibility: Raw header format is preferred for better navigation experience
# Plain text header β #plain-text-header (kebab-case) OR #Plain%20text%20header (raw, preferred)
# Header with `backticks` β #Header%20with%20%60backticks%60 (raw only)
# Header with **bold** text β #Header%20with%20**bold**%20text (raw only)The citation validator can automatically fix kebab-case anchors to use raw header format for better Obsidian compatibility.
- Only validated citations: Auto-fix only converts kebab-case anchors that point to existing headers
- Safe conversions: Only converts when a raw header equivalent exists and is validated
- Obsidian optimization: Raw header format provides more reliable navigation in Obsidian
# Before auto-fix (kebab-case)
[Architecture](design.md#code-and-file-structure)
[Testing Guide](guide.md#test-implementation)
# After auto-fix (raw header format)
[Architecture](design.md#Code%20and%20File%20Structure)
[Testing Guide](guide.md#Test%20Implementation)# Auto-fix kebab-case citations in a file
npm run jact:validate path/to/file.md -- --fix --scope /path/to/docs
# Check what would be fixed without making changes
npm run jact:validate path/to/file.md -- --scope /path/to/docsBenefits of Raw Header Format:
- β Reliable Obsidian navigation - clicking links consistently jumps to the correct header
- β Future-proof - works consistently across different markdown renderers
- β Explicit anchoring - uses the exact header text as it appears
- FR1: Functional requirement. ^FR1
- NFR2: Non-functional requirement. ^NFR2
- AC1: Acceptance criteria. ^US1-1AC1
- Task: Implementation task. ^US1-1T1
- MVP Priority 1. ^MVP-P1[[#^FR1|FR1]]
[[#user-authentication|User Authentication]]### ==**Component Name**== {#component-name}
[Reference](file.md#==**Component%20Name**==)Citation Validation Report
==========================
File: path/to/file.md
Processed: 8 citations found
β
VALID CITATIONS (3)
ββ Line 5: [Component](file.md#component) β
ββ Line 8: ^FR1 β
ββ Line 12: [[#anchor|Reference]] β
β οΈ WARNINGS (3)
ββ Line 15: @shortname.md
β ββ Short filename citation without directory context
β ββ Suggestion: Use full path @/full/path/to/shortname.md
ββ Line 18: @../other/file.md
β ββ Cross-directory reference spans multiple levels
β ββ Suggestion: Consider absolute path for clarity
ββ Line 22: @./local.md
β ββ Relative path without full context
β ββ Suggestion: Use absolute path @/current/directory/local.md
β CRITICAL ERRORS (2)
ββ Line 3: [Missing](nonexistent.md#anchor)
β ββ File not found: nonexistent.md
β ββ Suggestion: Check if file exists or fix path
ββ Line 7: ^invalid
β ββ Invalid caret pattern: ^invalid
β ββ Suggestion: Use format: ^FR1, ^US1-1AC1, ^NFR2, ^MVP-P1
SUMMARY:
- Total citations: 8
- Valid: 3
- Warnings: 3 (potential issues requiring review)
- Critical errors: 2 (must fix)
- Validation time: 0.1s
β οΈ VALIDATION COMPLETED WITH WARNINGS - Review 3 warnings, fix 2 critical errors
Citation Fix Report
===================
File: path/to/file.md
Citations processed: 6
PATH CONVERSIONS (4):
π Line 15: @shortname.md β @/full/path/to/shortname.md
π Line 18: @../other/file.md β @/full/path/to/other.md
π Line 22: @./local.md β @/current/directory/local.md
π Line 25: @relative.md β @/resolved/path/to/relative.md
β οΈ WARNINGS RESOLVED (4):
ββ Short filename citations: 2 converted to absolute paths
ββ Cross-directory references: 1 standardized
ββ Relative path citations: 1 converted to absolute
πΎ BACKUP CREATED:
ββ path/to/file.md β path/to/file.md.backup.20240919-143022
β
VALIDATION AFTER FIX:
- Total citations: 6
- Valid: 6
- Warnings: 0
- Errors: 0
β
FIX COMPLETED SUCCESSFULLY - All warning-level issues resolved
{
"file": "path/to/file.md",
"summary": {
"total": 8,
"valid": 3,
"warnings": 3,
"errors": 2,
"timestamp": "2024-09-19T21:30:22.123Z"
},
"results": [
{
"line": 5,
"citation": "[Component](file.md#component)",
"status": "valid",
"type": "cross-document"
},
{
"line": 15,
"citation": "@shortname.md",
"status": "warning",
"type": "short_filename",
"message": "Citation uses only filename without directory context",
"targetPath": null,
"suggestion": "Use full path: @/full/path/to/shortname.md"
},
{
"line": 18,
"citation": "@../other/file.md",
"status": "warning",
"type": "cross_directory",
"message": "Cross-directory reference spans multiple levels",
"targetPath": "/resolved/path/to/other/file.md",
"suggestion": "Consider absolute path for clarity"
},
{
"line": 3,
"citation": "[Missing](nonexistent.md#anchor)",
"status": "error",
"type": "cross-document",
"error": "File not found: nonexistent.md",
"suggestion": "Check if file exists or fix path"
}
],
"warnings": [
{
"line": 15,
"citation": "@shortname.md",
"type": "short_filename",
"message": "Citation uses only filename without directory context"
},
{
"line": 18,
"citation": "@../other/file.md",
"type": "cross_directory",
"message": "Cross-directory reference spans multiple levels"
},
{
"line": 22,
"citation": "@./local.md",
"type": "relative_path",
"message": "Relative path without full context"
}
],
"errors": [
{
"line": 3,
"citation": "[Missing](nonexistent.md#anchor)",
"type": "file_not_found",
"message": "Target file does not exist"
},
{
"line": 7,
"citation": "^invalid",
"type": "invalid_caret_syntax",
"message": "Malformed requirement/criteria pattern"
}
],
"validationTime": "0.1s"
}{
"file": "path/to/file.md",
"summary": {
"citationsProcessed": 6,
"pathConversions": 4,
"warningsResolved": 4,
"backupsCreated": 1,
"validationAfterFix": {
"total": 6,
"valid": 6,
"warnings": 0,
"errors": 0
},
"timestamp": "2024-09-19T21:30:22.123Z"
},
"pathConversions": [
{
"line": 15,
"original": "@shortname.md",
"converted": "@/full/path/to/shortname.md",
"type": "short_filename_expansion",
"resolvedPath": "/full/path/to/shortname.md"
},
{
"line": 18,
"original": "@../other/file.md",
"converted": "@/full/path/to/other.md",
"type": "relative_to_absolute",
"resolvedPath": "/full/path/to/other.md"
},
{
"line": 22,
"original": "@./local.md",
"converted": "@/current/directory/local.md",
"type": "relative_to_absolute",
"resolvedPath": "/current/directory/local.md"
},
{
"line": 25,
"original": "@relative.md",
"converted": "@/resolved/path/to/relative.md",
"type": "short_filename_expansion",
"resolvedPath": "/resolved/path/to/relative.md"
}
],
"warningsResolved": {
"shortFilename": 2,
"crossDirectory": 1,
"relativePaths": 1,
"total": 4
},
"backups": [
{
"original": "path/to/file.md",
"backup": "path/to/file.md.backup.20240919-143022",
"timestamp": "2024-09-19T21:30:22.123Z"
}
],
"validationTime": "0.2s"
}When using --scope <folder>, the tool builds a cache of all markdown files in the specified folder and enables smart filename matching:
# Enable smart resolution for design-docs folder
npm run jact:validate story.md -- --scope /path/to/design-docsBenefits:
- Resolves short filenames:
file.mdβ finds actual file anywhere in scope folder - Handles broken relative paths:
../missing/path/file.mdβ findsfile.mdin scope - Detects duplicate filenames: Warns when multiple files have the same name
- Performance: Caches file locations for fast lookup
Example Output with Warning Detection:
π Scanned 34 files in /path/to/design-docs
β οΈ Found 2 duplicate filenames: config.md, guide.md
Citation Validation Report
==========================
β
VALID CITATIONS (2)
ββ Line 146: [Component](version-analysis.md#anchor) β // Found via cache
ββ Line 147: [Guide](implementation-guide.md#section) β // Found via cache
β οΈ WARNINGS (2)
ββ Line 148: @config.md
β ββ Short filename citation - Multiple files found: /docs/setup/config.md, /docs/api/config.md
β ββ Suggestion: Specify directory context: @setup/config.md or @api/config.md
ββ Line 152: @../external/guide.md
β ββ Cross-directory reference with potential ambiguity
β ββ Suggestion: Use absolute path: @/full/path/to/external/guide.md
SUMMARY:
- Total citations: 4
- Valid: 2
- Warnings: 2 (review for clarity)
- Validation time: 0.2s
Use Cases:
- Large documentation projects with complex folder structures
- Obsidian vaults where relative paths may be inconsistent
- Refactored projects where files moved but citations weren't updated
- Symlinked directories where documentation spans multiple filesystem locations
The tool automatically detects and resolves symlinked directories:
# Works with symlinked documentation folders
npm run jact:validate /path/to/symlinked/docs/story.mdResolution Strategy:
- Standard Path: Try relative path from current location
- Obsidian Absolute: Handle
0_SoftwareDevelopment/...format paths - Symlink Resolution: Resolve symlinks and try relative path from real location
- Cache Fallback: Use filename matching in scope folder
Debug Information: When validation fails, the tool shows all attempted resolution paths:
Source via symlink: /link/path β /real/path
Tried: /link/path/resolved/file.md
Symlink-resolved: /real/path/resolved/file.md
Handles URL-encoded paths automatically:
[Design Principles](../../../Design%20Principles.md) // Spaces as %20
[Component Details](`setupOrchestrator.js`) // Backticks preservedSupported Encodings:
%20for spaces%60for backticks- Other standard URL encodings
Automatic Decoding:
- Tries both encoded and decoded versions
- Maintains backward compatibility
- Works with all path resolution strategies
Supports Obsidian's absolute path format:
[Design Principles](0_SoftwareDevelopment/claude-code-knowledgebase/design-docs/Design%20Principles.md)Path Resolution:
- Walks up directory tree from source file
- Finds project root automatically
- Resolves to filesystem absolute path
- Works with symlinked project structures
0: All citations valid (success)1: Broken citations found (validation failure)2: File not found or permission error
The tool consists of:
- JactCli: Main orchestrator with CLI interface
- MarkdownParser: AST generation using
markedlibrary - CitationValidator: Pattern validation and file existence checking
Scenario: Multi-directory documentation project with ambiguous citation patterns.
# Validate project documentation
npm run jact:validate ./project-docs/user-guide.md -- --scope ./project-docsSample Output:
π Scanned 127 files in ./project-docs
β οΈ Found 3 duplicate filenames: setup.md, api.md, troubleshooting.md
Citation Validation Report
==========================
β οΈ WARNINGS (5)
ββ Line 23: @setup.md
β ββ Short filename citation - Multiple files: ./admin/setup.md, ./user/setup.md
β ββ Suggestion: Use @admin/setup.md or @user/setup.md for clarity
ββ Line 45: @../../legacy/api.md
β ββ Cross-directory reference spans multiple parent levels
β ββ Suggestion: Use absolute path @/project-docs/legacy/api.md
ββ Line 67: @./local-config.md
β ββ Relative path citation without full context
β ββ Suggestion: Use @/project-docs/guides/local-config.md
RECOMMENDATIONS:
- Review 5 warning-level citations for improved clarity
- Consider running --fix to automatically resolve path issues
Scenario: Legacy documentation requiring standardized citation paths.
# Fix citations with automatic path conversion and backup
npm run jact:validate ./legacy-docs/migration-guide.md -- --fix --scope ./legacy-docs --format jsonSample JSON Output:
{
"summary": {
"citationsProcessed": 12,
"pathConversions": 8,
"warningsResolved": 8,
"backupsCreated": 1
},
"pathConversions": [
{
"line": 34,
"original": "@old-system.md",
"converted": "@/legacy-docs/architecture/old-system.md",
"type": "short_filename_expansion"
},
{
"line": 67,
"original": "@../config/settings.md",
"converted": "@/legacy-docs/config/settings.md",
"type": "relative_to_absolute"
}
],
"warningsResolved": {
"shortFilename": 5,
"crossDirectory": 2,
"relativePaths": 1
}
}Scenario: Automated validation in build pipeline with warning awareness.
# Validate with structured output for CI processing
npm run jact:validate ./docs --format json > citation-report.json
# Process results programmatically
node -e "
const report = JSON.parse(require('fs').readFileSync('citation-report.json'));
const { errors, warnings } = report.summary;
if (errors > 0) {
console.log(\`β ${errors} critical citation errors found\`);
process.exit(1);
}
if (warnings > 0) {
console.log(\`β οΈ ${warnings} citation warnings found (review recommended)\`);
console.log('Consider running: npm run jact:validate ./docs -- --fix');
}
console.log('β
Citation validation passed');
"Scenario: Regular maintenance of large Obsidian knowledge base.
# Weekly citation health check
npm run jact:validate ./ObsidianVault -- --scope ./ObsidianVault --format json > weekly-report.json
# Auto-fix common issues while preserving backups
npm run jact:validate ./ObsidianVault/daily-notes -- --fix --backup --scope ./ObsidianVaultExpected Benefits:
- Standardized Citations: All short filename citations converted to unambiguous paths
- Cross-Vault Compatibility: Citations work consistently across different vault structures
- Backup Safety: Original files preserved before any automated changes
- Warning Resolution: Proactive identification and correction of potential link issues
The validator detects:
- β Broken cross-document links (missing files)
- β Missing anchors in target documents
- β Invalid caret syntax patterns
- β Malformed emphasis-marked anchors
- β File path resolution errors
- β Short filename ambiguity (new)
- β Cross-directory path complexity (new)
- β Relative path context issues (new)
Enhanced Error Reporting:
- Shows actual available headers when anchors are not found
- Displays both header text and corresponding anchor format
- Provides URL-encoded anchor suggestions for markdown headers
- Identifies warning-level issues that may cause future problems
- Suggests specific path corrections for ambiguous citations
β Anchor not found: #wrong-anchor
ββ Available headers: "Header with `backticks`" β #Header%20with%20%60backticks%60,
"Plain Header" β #plain-header
β οΈ Short filename detected: @config.md
ββ Multiple matches found: ./admin/config.md, ./user/config.md
ββ Suggestion: Use @admin/config.md or @user/config.md
- Validates typical story files in <5 seconds
- Efficient pattern matching with regex optimization
- Graceful error handling with detailed reporting