Milan Jovanovic Blog Skill
Overview
This skill provides access to Milan Jovanovic's curated .NET blog content, filtered to November 2025+ (aligned with .NET 10 GA). It helps developers discover and apply proven patterns for Clean Architecture, Domain-Driven Design, CQRS, Entity Framework Core, and ASP.NET Core.
Content scope: Articles published November 2025 and later, with promotional content stripped.
When to Use This Skill
Use this skill when:
-
Working with .NET projects that could benefit from architectural patterns
-
Implementing Clean Architecture, DDD, CQRS, or Vertical Slice patterns
-
Configuring Entity Framework Core or ASP.NET Core
-
Looking for proven .NET code patterns and examples
-
Researching .NET 10/Aspire features
Quick Start
Search by Keywords
python scripts/core/find_articles.py search clean architecture cqrs
Filter by Tag
python scripts/core/find_articles.py tag ef-core
Resolve doc_id to Path
python scripts/core/find_articles.py resolve milanjovanovic-tech-blog-{slug}
Natural Language Query
python scripts/core/find_articles.py query "how to implement CQRS"
List Recent Articles
python scripts/core/find_articles.py list --sort date --limit 10
Tag Taxonomy
Tag Description
clean-architecture
Clean/Onion/Hexagonal Architecture patterns
ddd
Domain-Driven Design (aggregates, value objects, domain events)
cqrs
Command Query Responsibility Segregation
mediatr
MediatR library usage patterns
ef-core
Entity Framework Core optimization and patterns
aspnet-core
ASP.NET Core patterns and Minimal APIs
modular-monolith
Modular Monolith architecture
vertical-slice
Vertical Slice Architecture
dotnet-10
.NET 10 features and patterns
aspire
.NET Aspire patterns
result-pattern
Result pattern for error handling
outbox-pattern
Transactional outbox pattern
specification-pattern
Specification pattern
repository-pattern
Repository pattern
validation
FluentValidation patterns
testing
Unit/integration testing patterns
Path Resolution
Windows PowerShell users: Avoid cd && python chains - use absolute paths or run from repo root to prevent path doubling issues.
Script location: All scripts are in scripts/ relative to skill root.
Canonical content: Scraped articles are stored in canonical/milanjovanovic-tech/blog/ .
Index Management
Check Index Stats
python scripts/management/manage_index.py stats
Verify Index Integrity
python scripts/management/manage_index.py verify
Refresh Index (After Scraping)
python scripts/management/refresh_index.py
Scraping (Use /scrape-posts Command)
For scraping operations, use the /milan-jovanovic:scrape-posts command which handles:
-
Pre-filtering optimization - Parses dates from listing page before scraping individual articles
-
Date filtering (November 2025+)
-
Content cleanup (removes promotional sections)
-
Idempotent updates (skip unchanged articles via content hash comparison)
Efficiency Optimizations
The scraping workflow uses pre-filtering to minimize firecrawl API calls:
Scenario Without Optimization With Optimization Savings
No new articles 10+ requests 1-2 requests 80-90%
1 new article 10+ requests 2-3 requests 70-80%
Force (unchanged) 10+ requests 10+ requests (skips writes) I/O savings
How it works:
-
Scrape listing page only (1-2 requests)
-
Parse dates from listing markdown (no network)
-
Compare against index to identify new articles
-
Scrape ONLY articles not already indexed
Check for New Articles (Pre-Filter)
Before scraping, check what needs updating:
Check for new articles since November 2025
python scripts/core/check_new_articles.py .claude/temp/listing.md --json --since 2025-11-01
Force mode - include existing articles for re-check
python scripts/core/check_new_articles.py .claude/temp/listing.md --json --force
URLs only output
python scripts/core/check_new_articles.py .claude/temp/listing.md --urls-only
Output includes to_scrape list with in_index and content_hash for smart handling.
Python API
For programmatic access, use the public API:
from milan_jovanovic_api import ( search_articles, get_by_tag, resolve_doc_id, get_article_content, )
Search by keywords
results = search_articles(['clean-architecture', 'cqrs'])
Get articles by tag
ef_articles = get_by_tag('ef-core')
Resolve doc_id to path
path = resolve_doc_id('milanjovanovic-tech-blog-some-slug')
Get article content
content = get_article_content('milanjovanovic-tech-blog-some-slug')
Content Cleanup
Scraped articles have promotional content removed:
-
Sponsor sections (between title and first H2)
-
Promotional footer ("Whenever you're ready, there are X ways...")
-
Newsletter signup sections
-
Course CTAs
-
Reading time metadata
All educational content, code blocks, and internal links are preserved.
Related Components
-
Command: /milan-jovanovic:scrape-posts
-
Scrape new articles
-
Agent: blog-advisor
-
Proactive project analysis and recommendations
Troubleshooting
No Results Found
-
Check if index exists: python scripts/management/manage_index.py count
-
If count is 0, run scrape: /milan-jovanovic:scrape-posts
-
Verify tags with: python scripts/core/find_articles.py tag --list
Path Issues on Windows
Use PowerShell or prefix Git Bash with MSYS_NO_PATHCONV=1 :
MSYS_NO_PATHCONV=1 python scripts/core/find_articles.py search cqrs
Version History
-
v1.1.0 (2025-12-27): Pre-filtering optimization - 80-90% reduction in firecrawl API calls
-
v1.0.0 (2025-12-22): Initial release - November 2025+ articles, full tag taxonomy
Last Updated: 2025-12-27