diff --git a/.claude/hooks/build-reminder.ps1 b/.claude/hooks/build-reminder.ps1
new file mode 100644
index 0000000..52d4a29
--- /dev/null
+++ b/.claude/hooks/build-reminder.ps1
@@ -0,0 +1,14 @@
+# PostToolUse hook: Remind to verify build after source file changes
+$jsonInput = [Console]::In.ReadToEnd()
+if (-not $jsonInput) { exit 0 }
+
+$data = $jsonInput | ConvertFrom-Json
+
+$filePath = $data.tool_input.file_path
+if (-not $filePath) { exit 0 }
+
+if ($filePath -match '\.(cs|csproj|razor)$') {
+ @{ additionalContext = [char]0x1F3D7 + [char]0xFE0F + " Source file modified. Remember to verify the build compiles (dotnet build)." } | ConvertTo-Json -Compress | Write-Output
+}
+
+exit 0
diff --git a/.claude/hooks/context-optimizer.ps1 b/.claude/hooks/context-optimizer.ps1
new file mode 100644
index 0000000..bc487a5
--- /dev/null
+++ b/.claude/hooks/context-optimizer.ps1
@@ -0,0 +1,6 @@
+# SessionStart hook: Provide project context to Claude
+$context = "NexTruzt.io EscrowApp: .NET 10 Blazor Server fintech escrow. Clean Architecture + CQRS/MediatR. Layers: Components/ (UI) -> Features/ (handlers) -> Models/Events (domain) <- Data/ (EF Core/PostgreSQL). Payment strategies: IFundHoldable/IFundReleasable/IFundCancellable. Always: code-behind, scoped CSS, docs sync, OWASP security-first, idempotency keys."
+
+@{ additionalContext = $context } | ConvertTo-Json -Compress | Write-Output
+
+exit 0
diff --git a/.claude/hooks/doc-sync-reminder.ps1 b/.claude/hooks/doc-sync-reminder.ps1
new file mode 100644
index 0000000..04b6b56
--- /dev/null
+++ b/.claude/hooks/doc-sync-reminder.ps1
@@ -0,0 +1,31 @@
+# PostToolUse hook: Remind to update documentation when key source files change
+$jsonInput = [Console]::In.ReadToEnd()
+if (-not $jsonInput) { exit 0 }
+
+$data = $jsonInput | ConvertFrom-Json
+
+$filePath = $data.tool_input.file_path
+if (-not $filePath) { exit 0 }
+
+$docTriggerPaths = @(
+ 'Components/', 'Components\\'
+ 'Features/', 'Features\\'
+ 'Services/', 'Services\\'
+ 'Models/', 'Models\\'
+ 'Events/', 'Events\\'
+ 'Infrastructure/', 'Infrastructure\\'
+)
+
+$needsDocSync = $false
+foreach ($trigger in $docTriggerPaths) {
+ if ($filePath -like "*$trigger*") {
+ $needsDocSync = $true
+ break
+ }
+}
+
+if ($needsDocSync) {
+ @{ additionalContext = [char]0x1F4DD + " Remember: update corresponding docs/ README.md to reflect these changes." } | ConvertTo-Json -Compress | Write-Output
+}
+
+exit 0
diff --git a/.claude/hooks/dotnet-conventions.ps1 b/.claude/hooks/dotnet-conventions.ps1
new file mode 100644
index 0000000..e15e4ac
--- /dev/null
+++ b/.claude/hooks/dotnet-conventions.ps1
@@ -0,0 +1,58 @@
+# PostToolUse hook: Check .NET/Blazor coding conventions
+$jsonInput = [Console]::In.ReadToEnd()
+if (-not $jsonInput) { exit 0 }
+
+$data = $jsonInput | ConvertFrom-Json
+
+$filePath = $data.tool_input.file_path
+if (-not $filePath) { exit 0 }
+
+$issues = @()
+
+if ($filePath -match '\.cs$') {
+ $content = $data.tool_input.content
+ if (-not $content) { $content = $data.tool_input.new_str }
+ if (-not $content) { $content = $data.tool_input.file_text }
+ if (-not $content) { exit 0 }
+
+ # Check for block-scoped namespaces (should use file-scoped)
+ if ($content -match 'namespace\s+\S+\s*\{') {
+ $issues += "Use file-scoped namespace (no braces) instead of block-scoped namespace"
+ }
+
+ # Check code-behind files missing partial keyword
+ if ($filePath -match '\.razor\.cs$' -and $content -match 'class\s+' -and $content -notmatch 'partial\s+class') {
+ $issues += "Code-behind class must be declared as 'partial'"
+ }
+
+ # Check for missing nullable enable
+ if ($content -match 'namespace\s+' -and $content -notmatch '#nullable\s+enable' -and $content -notmatch 'enable') {
+ $issues += "Consider adding '#nullable enable' or verify it is set in .csproj"
+ }
+}
+elseif ($filePath -match '\.razor$') {
+ $content = $data.tool_input.content
+ if (-not $content) { $content = $data.tool_input.new_str }
+ if (-not $content) { $content = $data.tool_input.file_text }
+ if (-not $content) { exit 0 }
+
+ # Check for inline @code blocks (should use code-behind)
+ if ($content -match '@code\s*\{') {
+ $issues += "Use code-behind (.razor.cs) instead of inline @code blocks"
+ }
+
+ # Check for inline style attributes
+ if ($content -match 'style\s*=\s*"') {
+ $issues += "Use scoped CSS (.razor.css) instead of inline style attributes"
+ }
+}
+else {
+ exit 0
+}
+
+if ($issues.Count -gt 0) {
+ $message = "Convention issues: " + ($issues -join "; ") + ". Fix before continuing."
+ @{ additionalContext = $message } | ConvertTo-Json -Compress | Write-Output
+}
+
+exit 0
diff --git a/.claude/hooks/notification.ps1 b/.claude/hooks/notification.ps1
new file mode 100644
index 0000000..78c3551
--- /dev/null
+++ b/.claude/hooks/notification.ps1
@@ -0,0 +1,291 @@
+# -------------------------------------------------
+# Notification Hook – Multi-Channel Support
+# -------------------------------------------------
+# Features:
+# - Console & file logging always on
+# - Slack, Teams webhooks
+# - Multiple fallback email accounts with credential prompts
+# - HTML email templates (configurable)
+# - Rate limiting to avoid spam
+# - All channels fail independently
+#
+# Usage:
+# .\notification.ps1 # Normal run (prompts for email credentials if needed)
+# .\notification.ps1 -SkipEmail # Skip email notifications entirely
+# .\notification.ps1 -NoPrompt # Don't prompt for credentials (skip email if not stored)
+
+[CmdletBinding()]
+param(
+ [switch]$SkipEmail,
+ [switch]$NoPrompt
+)
+
+$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+$message = "Notification hook triggered at $timestamp"
+
+# Load configuration
+$configPath = ".claude/hooks/notification-config.json"
+$config = $null
+
+if (Test-Path $configPath) {
+ try {
+ $config = Get-Content $configPath -Raw | ConvertFrom-Json
+ } catch {
+ Write-Warning "Failed to parse config file. Using defaults."
+ }
+} else {
+ Write-Warning "Config file not found. Creating default."
+ $defaultConfig = @{
+ slack = @{ webhookUrl = ""; channel = "#claude-hooks"; enabled = $false }
+ email = @{
+ accounts = @(
+ @{
+ smtpServer = "smtp.gmail.com"
+ smtpPort = 587
+ from = ""
+ to = ""
+ subjectPrefix = "[Claude Hook]"
+ useHtml = $true
+ enabled = $false
+ }
+ )
+ }
+ teams = @{ webhookUrl = ""; enabled = $false }
+ console = @{ enabled = $true }
+ fileLog = @{ enabled = $true; path = ".claude/hooks/notifications.log" }
+ rateLimit = @{ enabled = $true; intervalSeconds = 30; lastNotificationFile = ".claude/hooks/.rate-limit-timestamp" }
+ }
+ $defaultConfig | ConvertTo-Json -Depth 3 | Set-Content $configPath
+ $config = $defaultConfig
+}
+
+# -------------------------------------------------
+# Rate Limiting Check
+# -------------------------------------------------
+$rateLimited = $false
+if ($config.rateLimit.enabled) {
+ $lastTimePath = $config.rateLimit.lastNotificationFile
+ if (-not $lastTimePath) { $lastTimePath = ".claude/hooks/.rate-limit-timestamp" }
+
+ if (Test-Path $lastTimePath) {
+ try {
+ $lastSent = Get-Content $lastTimePath -Raw | Get-Date
+ $interval = $config.rateLimit.intervalSeconds
+ if ($interval -lt 1) { $interval = 30 }
+
+ if (((Get-Date) - $lastSent).TotalSeconds -lt $interval) {
+ $remaining = [math]::Ceiling($interval - ((Get-Date) - $lastSent).TotalSeconds)
+ Write-Host "Rate limit active. Wait $remaining seconds." -ForegroundColor Yellow
+ $rateLimited = $true
+ }
+ } catch {
+ # If timestamp exists but invalid, ignore and proceed
+ }
+ }
+}
+
+# If rate-limited, skip all external notifications but still log
+if ($rateLimited) {
+ if ($config.console.enabled) {
+ Write-Host "$message (rate limited)" -ForegroundColor Yellow
+ }
+ if ($config.fileLog.enabled) {
+ $logPath = $config.fileLog.path
+ if (-not $logPath) { $logPath = ".claude/hooks/notifications.log" }
+ "$timestamp`t$message (rate-limited)" | Add-Content -Path $logPath -Encoding UTF8
+ }
+ return
+}
+
+# Record successful notification time
+if ($config.rateLimit.enabled) {
+ $lastTimePath = $config.rateLimit.lastNotificationFile
+ if (-not $lastTimePath) { $lastTimePath = ".claude/hooks/.rate-limit-timestamp" }
+ $timestamp | Set-Content -Path $lastTimePath -Encoding UTF8
+}
+
+# -------------------------------------------------
+# 1. Console notification
+# -------------------------------------------------
+if ($config.console.enabled) {
+ Write-Host "[BELL] $message" -ForegroundColor Cyan
+}
+
+# -------------------------------------------------
+# 2. File logging
+# -------------------------------------------------
+if ($config.fileLog.enabled) {
+ $logPath = $config.fileLog.path
+ if (-not $logPath) { $logPath = ".claude/hooks/notifications.log" }
+ "$timestamp`t$message" | Add-Content -Path $logPath -Encoding UTF8
+}
+
+# -------------------------------------------------
+# 3. Slack notification
+# -------------------------------------------------
+if ($config.slack.enabled -and $config.slack.webhookUrl) {
+ try {
+ $payload = @{
+ text = "$message"
+ channel = $config.slack.channel
+ username = "Claude Hooks"
+ icon_emoji = ":robot_face:"
+ } | ConvertTo-Json -Depth 10
+
+ Invoke-RestMethod -Uri $config.slack.webhookUrl -Method Post -Body $payload -ContentType "application/json" -ErrorAction Stop | Out-Null
+ Write-Host "[OK] Slack notification sent" -ForegroundColor Green
+ } catch {
+ Write-Warning "Slack notification failed: $($_.Exception.Message)"
+ }
+}
+
+# -------------------------------------------------
+# 4. Email notification - Multiple accounts with fallback
+# -------------------------------------------------
+if ($SkipEmail) {
+ Write-Host "[INFO] Email notification skipped (-SkipEmail)" -ForegroundColor DarkGray
+} elseif ($config.email -and $config.email.accounts) {
+ $accounts = @($config.email.accounts | Where-Object { $_.enabled -and $_.smtpServer -and $_.from -and $_.to })
+ if ($accounts.Count -eq 0) {
+ Write-Host "[INFO] Email notification: no enabled accounts with valid config" -ForegroundColor DarkGray
+ } else {
+ foreach ($account in $accounts) {
+ try {
+ $subject = "$($account.subjectPrefix) $message"
+
+ # Build HTML or plain text body
+ if ($account.useHtml) {
+ $fromAddr = $account.from
+ $toAddr = $account.to
+ $envName = "Development"
+ $body = @"
+
+
+
+
+
+
+
+
+
+
+
Timestamp: $timestamp
+
Source: Claude Code Hook System
+
From: $fromAddr
+
To: $toAddr
+
Environment: $envName
+
+
+
+
+
+"@
+ $bodyAsHtml = $true
+ } else {
+ $body = "Notification hook triggered at $timestamp. Event: Hook Triggered. Source: Claude Code."
+ $bodyAsHtml = $false
+ }
+
+ $safeFrom = $account.from.Replace("@", "_")
+ $credPath = ".claude/hooks/smtp-cred-$safeFrom.xml"
+
+ # Credential handling
+ $credential = $null
+ if (Test-Path $credPath) {
+ try {
+ $credential = Import-CliXml -Path $credPath
+ Write-Host "[INFO] Using stored credentials for $($account.from)" -ForegroundColor DarkGray
+ } catch {
+ Write-Warning "Failed to load stored credentials for $($account.from): $($_.Exception.Message)"
+ $credential = $null
+ }
+ }
+
+ if ((-not (Test-Path $credPath)) -or $null -eq $credential) {
+ if ($NoPrompt) {
+ Write-Host "[INFO] No stored credentials for $($account.from) and -NoPrompt specified. Skipping email." -ForegroundColor DarkGray
+ continue
+ }
+
+ Write-Host "[INFO] Enter SMTP credentials for $($account.from) on $($account.smtpServer)" -ForegroundColor Yellow
+ try {
+ $credential = Get-Credential -UserName $account.from -Message "Enter password for $($account.from)"
+ if ($credential) {
+ $credential | Export-CliXml -Path $credPath
+ Write-Host "[OK] Credentials saved (encrypted)" -ForegroundColor Green
+ } else {
+ Write-Warning "No credentials provided for $($account.from). Skipping..."
+ continue
+ }
+ } catch {
+ if ($_.Exception.Message -like "*Get-Credential*") {
+ Write-Warning "Get-Credential failed (non-interactive environment?). Skipping email."
+ } else {
+ Write-Warning "Credential prompt failed: $($_.Exception.Message)"
+ }
+ continue
+ }
+ }
+
+ $smtpParams = @{
+ SmtpServer = $account.smtpServer
+ Port = $account.smtpPort
+ From = $account.from
+ To = $account.to
+ Subject = $subject
+ Body = $body
+ UseSsl = $true
+ Credential = $credential
+ }
+
+ if ($bodyAsHtml) {
+ $smtpParams.BodyAsHtml = $true
+ }
+
+ Send-MailMessage @smtpParams -ErrorAction Stop
+ Write-Host "[OK] Email sent via $($account.from) to $($account.to)" -ForegroundColor Green
+ break
+ } catch {
+ Write-Warning "Email via $($account.from) failed: $($_.Exception.Message)"
+ continue
+ }
+ }
+ }
+}
+
+# -------------------------------------------------
+# 5. Teams notification
+# -------------------------------------------------
+if ($config.teams.enabled -and $config.teams.webhookUrl) {
+ try {
+ $card = @{
+ title = "Claude Hook Notification"
+ text = $message
+ themeColor = "0076D7"
+ sections = @(
+ @{
+ activityTitle = "Hook Triggered"
+ activitySubtitle = $timestamp
+ facts = @(
+ @{ name = "Source"; value = "Claude Code" },
+ @{ name = "Environment"; value = "Development" }
+ )
+ }
+ )
+ } | ConvertTo-Json -Depth 10
+
+ Invoke-RestMethod -Uri $config.teams.webhookUrl -Method Post -Body $card -ContentType "application/json" -ErrorAction Stop | Out-Null
+ Write-Host "[OK] Teams notification sent" -ForegroundColor Green
+ } catch {
+ Write-Warning "Teams notification failed: $($_.Exception.Message)"
+ }
+}
diff --git a/.claude/hooks/research-first.ps1 b/.claude/hooks/research-first.ps1
new file mode 100644
index 0000000..9f22f63
--- /dev/null
+++ b/.claude/hooks/research-first.ps1
@@ -0,0 +1,35 @@
+# UserPromptSubmit hook: Encourage research-first approach before implementation
+$jsonInput = [Console]::In.ReadToEnd()
+if (-not $jsonInput) { exit 0 }
+
+$data = $jsonInput | ConvertFrom-Json
+
+$prompt = $data.user_prompt
+if (-not $prompt) { exit 0 }
+
+$implKeywords = @('create', 'implement', 'build', 'add', 'write', 'refactor', 'fix', 'update', 'modify', 'change', 'delete', 'remove', 'replace', 'migrate')
+$researchKeywords = @('explain', 'analyze', 'review', 'understand', 'explore', 'investigate', 'describe', 'show', 'list', 'what is', 'how does', 'why')
+
+$promptLower = $prompt.ToLower()
+
+$hasImpl = $false
+foreach ($kw in $implKeywords) {
+ if ($promptLower -match "\b$kw\b") {
+ $hasImpl = $true
+ break
+ }
+}
+
+$hasResearch = $false
+foreach ($kw in $researchKeywords) {
+ if ($promptLower -match "\b$kw\b") {
+ $hasResearch = $true
+ break
+ }
+}
+
+if ($hasImpl -and -not $hasResearch) {
+ @{ additionalContext = [char]0x1F4DA + " Research-First: Before implementing, check docs/ for existing documentation and understand the affected architecture layer." } | ConvertTo-Json -Compress | Write-Output
+}
+
+exit 0
diff --git a/.claude/hooks/security-scanner.ps1 b/.claude/hooks/security-scanner.ps1
new file mode 100644
index 0000000..3a878a6
--- /dev/null
+++ b/.claude/hooks/security-scanner.ps1
@@ -0,0 +1,42 @@
+# PreToolUse hook: Scan for hardcoded secrets in file content
+$jsonInput = [Console]::In.ReadToEnd()
+if (-not $jsonInput) { exit 0 }
+
+$data = $jsonInput | ConvertFrom-Json
+
+$toolName = $data.tool_name
+if ($toolName -notmatch 'Edit|Write|MultiEdit|Create') { exit 0 }
+
+$content = $null
+if ($data.tool_input.content) { $content = $data.tool_input.content }
+elseif ($data.tool_input.new_str) { $content = $data.tool_input.new_str }
+elseif ($data.tool_input.file_text) { $content = $data.tool_input.file_text }
+
+if (-not $content) { exit 0 }
+
+$secretPatterns = @(
+ @{ Name = "Connection string with password"; Pattern = '(?i)(connection\s*string|Server=|Data Source=).*(?:Password|Pwd)\s*=' }
+ @{ Name = "AWS access key"; Pattern = 'AKIA[0-9A-Z]{16}' }
+ @{ Name = "API key (sk- prefix)"; Pattern = 'sk-[a-zA-Z0-9]{20,}' }
+ @{ Name = "API key (pk_ prefix)"; Pattern = 'pk_[a-zA-Z0-9]{20,}' }
+ @{ Name = "Bearer token"; Pattern = '(?i)bearer\s+[a-zA-Z0-9\-._~+/]+=*' }
+ @{ Name = "Private key block"; Pattern = '-----BEGIN\s+(RSA\s+)?PRIVATE KEY-----' }
+ @{ Name = "Hardcoded password literal"; Pattern = '(?i)(password|passwd|pwd)\s*=\s*"[^"]{4,}"' }
+ @{ Name = "Generic secret assignment"; Pattern = '(?i)(secret|api_key|apikey)\s*=\s*"[^"]{8,}"' }
+)
+
+foreach ($sp in $secretPatterns) {
+ if ($content -match $sp.Pattern) {
+ $result = @{
+ hookSpecificOutput = @{
+ hookEventName = "PreToolUse"
+ permissionDecision = "deny"
+ permissionDecisionReason = "Security: hardcoded secret detected ($($sp.Name)). Use user-secrets, environment variables, or Azure Key Vault instead."
+ }
+ }
+ $result | ConvertTo-Json -Depth 3 -Compress | Write-Output
+ exit 0
+ }
+}
+
+exit 0
diff --git a/.claude/hooks/test-runner.ps1 b/.claude/hooks/test-runner.ps1
new file mode 100644
index 0000000..e9ee0b2
--- /dev/null
+++ b/.claude/hooks/test-runner.ps1
@@ -0,0 +1,51 @@
+# -------------------------------------------------
+# Test Runner for Notification Hook (Multi-Channel)
+# -------------------------------------------------
+# Tests the notification hook and verifies config exists.
+# Usage: powershell -File .claude/hooks/test-runner.ps1
+
+Write-Host "=== Claude Hook Notification Test ===" -ForegroundColor Cyan
+
+# 1. Verify config file exists
+$configPath = ".claude/hooks/notification-config.json"
+if (Test-Path $configPath) {
+ Write-Host "[✓] Config file found" -ForegroundColor Green
+ $config = Get-Content $configPath -Raw | ConvertFrom-Json
+ Write-Host " Enabled channels:" -NoNewline
+ $enabled = @()
+ if ($config.console.enabled) { $enabled += "Console" }
+ if ($config.fileLog.enabled) { $enabled += "File" }
+ if ($config.slack.enabled -and $config.slack.webhookUrl) { $enabled += "Slack" }
+ if ($config.email.enabled -and $config.email.from -and $config.email.to) { $enabled += "Email" }
+ if ($config.teams.enabled -and $config.teams.webhookUrl) { $enabled += "Teams" }
+ Write-Host ($enabled -join ", ")
+} else {
+ Write-Host "[✗] Config file missing. Creating default..." -ForegroundColor Red
+ Write-Host " Run the notification script to auto-create." -ForegroundColor Yellow
+}
+
+# 2. Run the notification script directly
+Write-Host "`n[→] Triggering notification hook directly..." -ForegroundColor Cyan
+powershell -ExecutionPolicy Bypass -File ".claude/hooks/notification.ps1"
+
+# 3. Verify log file created
+$logPath = ".claude/hooks/notifications.log"
+if (Test-Path $logPath) {
+ $lastEntry = Get-Content $logPath -Tail 1
+ Write-Host "`n[✓] Log entry created:" -ForegroundColor Green
+ Write-Host " $lastEntry" -ForegroundColor Gray
+} else {
+ Write-Host "`n[✗] Log file not found" -ForegroundColor Red
+}
+
+# 4. Instructions for enabling external channels
+Write-Host "`n=== Next Steps ===" -ForegroundColor Cyan
+Write-Host "To enable Slack, Teams, or Email:" -ForegroundColor White
+Write-Host "1. Edit: .claude/hooks/notification-config.json" -ForegroundColor Yellow
+Write-Host "2. Set 'enabled' to true and fill in credentials" -ForegroundColor Yellow
+Write-Host "3. Re-run this test" -ForegroundColor Yellow
+
+Write-Host "`nExample Slack config:" -ForegroundColor Gray
+Write-Host '{ "slack": { "webhookUrl": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL", "enabled": true } }' -ForegroundColor Gray
+
+Write-Host "`nTest complete! 🎉" -ForegroundColor Green
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..24ce890
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,83 @@
+{
+ "env": {},
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"C:\\DATA\\MYSTUFFS\\SIDE PROJECTS\\CloudZen\\.claude\\hooks\\context-optimizer.ps1\"",
+ "statusMessage": "Loading project context..."
+ }
+ ]
+ }
+ ],
+ "UserPromptSubmit": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/research-first.ps1\"",
+ "statusMessage": "Checking research-first..."
+ }
+ ]
+ }
+ ],
+ "PreToolUse": [
+ {
+ "matcher": "Edit|Write|MultiEdit",
+ "hooks": [
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/security-scanner.ps1\"",
+ "statusMessage": "🔒 Security scanning..."
+ }
+ ]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "Edit|Write|MultiEdit",
+ "hooks": [
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/dotnet-conventions.ps1\"",
+ "statusMessage": "Checking conventions..."
+ },
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/doc-sync-reminder.ps1\""
+ },
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/build-reminder.ps1\""
+ },
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/notification.ps1\"",
+ "statusMessage": "Sending notification..."
+ }
+ ]
+ }
+ ],
+ "Notification": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "shell": "powershell",
+ "command": "powershell -ExecutionPolicy Bypass -File \"$PSScriptRoot/hooks/notification.ps1\"",
+ "statusMessage": "Sending notification..."
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.claude/skills/README.md b/.claude/skills/README.md
new file mode 100644
index 0000000..beced0c
--- /dev/null
+++ b/.claude/skills/README.md
@@ -0,0 +1,70 @@
+# Claude Code Skills
+
+Bridge files that register the project's universal AI skills with Claude Code's `/skills` discovery system. Each subfolder contains a lightweight `SKILL.md` that redirects Claude to the full skill definition in `.github/skills/`.
+
+## At a Glance
+
+| Aspect | Detail |
+|--------|--------|
+| **Count** | 42 skills across 11 categories |
+| **Architecture** | Bridge pattern — `.claude/skills/` → `.github/skills/` (single source of truth) |
+| **Invocation** | `/skill-name` in Claude Code (e.g., `/owasp-audit`, `/code-reviewer`) |
+| **Relationship** | Same universal skills work in Copilot CLI, Gemini, and any file-reading AI agent |
+
+## How It Works
+
+```
+1. User types: /owasp-audit
+2. Claude loads: .claude/skills/owasp-audit/SKILL.md (bridge, ~15 lines)
+3. Bridge says: "Read .github/skills/security/owasp-audit/SKILL.md"
+4. Claude follows: Full Core Workflow from the universal skill file
+5. On-demand: Load references/*.md only when the current step needs them
+```
+
+## Why Bridges?
+
+- **Single source of truth** — all skill content lives in `.github/skills/`, shared across AI tools.
+- **Claude-specific discovery** — bridges register skills with Claude Code's `/skills` list.
+- **Minimal overhead** — bridge files are < 20 lines each; no duplicated content.
+
+## Skill Categories (11)
+
+| Category | Skills | Path |
+|----------|--------|------|
+| `code-quality` | code-reviewer, refactor-planner, code-documenter, debugging-wizard | `.github/skills/code-quality/` |
+| `security` | owasp-audit, secret-scanner, threat-modeler, authentication, authorization | `.github/skills/security/` |
+| `architecture` | architecture-reviewer, design-pattern-advisor, dependency-analyzer, legacy-modernizer | `.github/skills/architecture/` |
+| `testing` | test-generator, tdd-coach, test-coverage-analyzer | `.github/skills/testing/` |
+| `database` | schema-reviewer, query-optimizer | `.github/skills/database/` |
+| `devops` | ci-cd-builder, deployment-preflight, monitoring-expert, chaos-engineer | `.github/skills/devops/` |
+| `documentation` | readme-generator, adr-creator, api-documenter | `.github/skills/documentation/` |
+| `research` | codebase-explorer, tech-spike-planner, spec-miner | `.github/skills/research/` |
+| `project-management` | spec-writer, issue-creator, feature-forge | `.github/skills/project-management/` |
+| `ai` | mcp-developer, prompt-engineer, agent-orchestrator | `.github/skills/ai/` |
+| `language` | dotnet-core-expert, csharp-developer | `.github/skills/language/` |
+
+## Creating a New Skill
+
+1. **Create the universal skill** in `.github/skills/{category}/{skill-name}/SKILL.md` with Core Workflow and optional `references/` folder.
+2. **Create the bridge** in `.claude/skills/{skill-name}/SKILL.md`:
+ ```markdown
+ # {Skill Name}
+ > Claude Code bridge — read the universal skill for full instructions.
+ Read: `.github/skills/{category}/{skill-name}/SKILL.md`
+ Follow the Core Workflow steps inside.
+ ```
+3. The skill appears in Claude Code's `/skills` list automatically.
+
+## Key Rules
+
+- Bridge files must be **minimal** (< 20 lines) — all real content lives in `.github/skills/`.
+- **Never duplicate** skill content in the bridge. If the bridge grows, the content belongs upstream.
+- Follow **progressive disclosure** — load `references/*.md` only when the current workflow step requires it.
+- See `.github/skills/CATALOG.md` for the full skill inventory with descriptions.
+
+## See Also
+
+- `.github/skills/` — Universal skill definitions (source of truth)
+- `.github/skills/CATALOG.md` — Full skill catalog with descriptions and categories
+- `.claude/rules/` — Always-on behavioral rules for Claude Code
+- `.claude/hooks/` — Event-triggered PowerShell scripts for Claude Code
diff --git a/.claude/skills/adr-creator/SKILL.md b/.claude/skills/adr-creator/SKILL.md
new file mode 100644
index 0000000..b315670
--- /dev/null
+++ b/.claude/skills/adr-creator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: adr-creator
+description: Create Architecture Decision Records following the ADR standard
+---
+
+# Adr Creator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/adr-creator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/adr-creator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/adr-creator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/adr-creator/SKILL.md
+```
diff --git a/.claude/skills/agent-orchestrator/SKILL.md b/.claude/skills/agent-orchestrator/SKILL.md
new file mode 100644
index 0000000..1b29059
--- /dev/null
+++ b/.claude/skills/agent-orchestrator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: agent-orchestrator
+description: Orchestrate parallel sub-agent fleets with token-aware delegation and approval gates
+---
+
+# Agent Orchestrator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/agent-orchestrator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/agent-orchestrator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/agent-orchestrator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/agent-orchestrator/SKILL.md
+```
diff --git a/.claude/skills/api-documenter/SKILL.md b/.claude/skills/api-documenter/SKILL.md
new file mode 100644
index 0000000..ceba0ac
--- /dev/null
+++ b/.claude/skills/api-documenter/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: api-documenter
+description: Generate API documentation from code with examples and schemas
+---
+
+# Api Documenter
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/api-documenter/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/api-documenter/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/api-documenter/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/api-documenter/SKILL.md
+```
diff --git a/.claude/skills/architecture-reviewer/SKILL.md b/.claude/skills/architecture-reviewer/SKILL.md
new file mode 100644
index 0000000..68bf225
--- /dev/null
+++ b/.claude/skills/architecture-reviewer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: architecture-reviewer
+description: Review system architecture for quality attributes and anti-patterns
+---
+
+# Architecture Reviewer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/architecture-reviewer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/architecture-reviewer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/architecture-reviewer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/architecture-reviewer/SKILL.md
+```
diff --git a/.claude/skills/authentication/SKILL.md b/.claude/skills/authentication/SKILL.md
new file mode 100644
index 0000000..198fad9
--- /dev/null
+++ b/.claude/skills/authentication/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: authentication
+description: Implement authentication flows with Entra ID, OIDC, JWT, and ASP.NET Core Identity
+---
+
+# Authentication
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/authentication/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/authentication/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/authentication/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/authentication/SKILL.md
+```
diff --git a/.claude/skills/authorization/SKILL.md b/.claude/skills/authorization/SKILL.md
new file mode 100644
index 0000000..ced2acc
--- /dev/null
+++ b/.claude/skills/authorization/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: authorization
+description: Implement policy-based authorization, RBAC, resource-based access control
+---
+
+# Authorization
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/authorization/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/authorization/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/authorization/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/authorization/SKILL.md
+```
diff --git a/.claude/skills/chaos-engineer/SKILL.md b/.claude/skills/chaos-engineer/SKILL.md
new file mode 100644
index 0000000..d6e3d99
--- /dev/null
+++ b/.claude/skills/chaos-engineer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: chaos-engineer
+description: Design and execute chaos experiments to verify system resilience
+---
+
+# Chaos Engineer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/chaos-engineer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/chaos-engineer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/chaos-engineer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/chaos-engineer/SKILL.md
+```
diff --git a/.claude/skills/ci-cd-builder/SKILL.md b/.claude/skills/ci-cd-builder/SKILL.md
new file mode 100644
index 0000000..9a699a9
--- /dev/null
+++ b/.claude/skills/ci-cd-builder/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: ci-cd-builder
+description: Create or improve CI/CD pipeline configurations
+---
+
+# Ci Cd Builder
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/ci-cd-builder/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/ci-cd-builder/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/ci-cd-builder/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/ci-cd-builder/SKILL.md
+```
diff --git a/.claude/skills/code-documenter/SKILL.md b/.claude/skills/code-documenter/SKILL.md
new file mode 100644
index 0000000..6813366
--- /dev/null
+++ b/.claude/skills/code-documenter/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: code-documenter
+description: Generate inline documentation, XML doc comments, and usage examples
+---
+
+# Code Documenter
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/code-documenter/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/code-documenter/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/code-documenter/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/code-documenter/SKILL.md
+```
diff --git a/.claude/skills/code-reviewer/SKILL.md b/.claude/skills/code-reviewer/SKILL.md
new file mode 100644
index 0000000..62c66e9
--- /dev/null
+++ b/.claude/skills/code-reviewer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: code-reviewer
+description: Review code changes for correctness, style, security, and maintainability
+---
+
+# Code Reviewer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/code-reviewer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/code-reviewer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/code-reviewer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/code-reviewer/SKILL.md
+```
diff --git a/.claude/skills/codebase-explorer/SKILL.md b/.claude/skills/codebase-explorer/SKILL.md
new file mode 100644
index 0000000..60befd1
--- /dev/null
+++ b/.claude/skills/codebase-explorer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: codebase-explorer
+description: Explore and map unfamiliar codebases to build understanding
+---
+
+# Codebase Explorer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/codebase-explorer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/codebase-explorer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/codebase-explorer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/codebase-explorer/SKILL.md
+```
diff --git a/.claude/skills/csharp-developer/SKILL.md b/.claude/skills/csharp-developer/SKILL.md
new file mode 100644
index 0000000..7ed15b4
--- /dev/null
+++ b/.claude/skills/csharp-developer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: csharp-developer
+description: Senior C# 13 developer — records, pattern matching, Blazor, performance
+---
+
+# Csharp Developer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/csharp-developer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/csharp-developer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/csharp-developer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/csharp-developer/SKILL.md
+```
diff --git a/.claude/skills/debugging-wizard/SKILL.md b/.claude/skills/debugging-wizard/SKILL.md
new file mode 100644
index 0000000..ec0b70c
--- /dev/null
+++ b/.claude/skills/debugging-wizard/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: debugging-wizard
+description: Systematic debugging with root cause analysis and fix verification
+---
+
+# Debugging Wizard
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/debugging-wizard/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/debugging-wizard/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/debugging-wizard/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/debugging-wizard/SKILL.md
+```
diff --git a/.claude/skills/deep-context-generator/SKILL.md b/.claude/skills/deep-context-generator/SKILL.md
new file mode 100644
index 0000000..eda77bd
--- /dev/null
+++ b/.claude/skills/deep-context-generator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: deep-context-generator
+description: Generate LLM-optimized codebase context for onboarding and architecture understanding
+---
+
+# Deep Context Generator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/deep-context-generator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/deep-context-generator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/deep-context-generator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/deep-context-generator/SKILL.md
+```
diff --git a/.claude/skills/dependency-analyzer/SKILL.md b/.claude/skills/dependency-analyzer/SKILL.md
new file mode 100644
index 0000000..ee91b71
--- /dev/null
+++ b/.claude/skills/dependency-analyzer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: dependency-analyzer
+description: Analyze project dependencies for risks, updates, and license compliance
+---
+
+# Dependency Analyzer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/dependency-analyzer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/dependency-analyzer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/dependency-analyzer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/dependency-analyzer/SKILL.md
+```
diff --git a/.claude/skills/deployment-preflight/SKILL.md b/.claude/skills/deployment-preflight/SKILL.md
new file mode 100644
index 0000000..2f2d470
--- /dev/null
+++ b/.claude/skills/deployment-preflight/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: deployment-preflight
+description: Run pre-deployment checks and generate go/no-go reports
+---
+
+# Deployment Preflight
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/deployment-preflight/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/deployment-preflight/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/deployment-preflight/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/deployment-preflight/SKILL.md
+```
diff --git a/.claude/skills/design-pattern-advisor/SKILL.md b/.claude/skills/design-pattern-advisor/SKILL.md
new file mode 100644
index 0000000..090c86c
--- /dev/null
+++ b/.claude/skills/design-pattern-advisor/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: design-pattern-advisor
+description: Recommend and apply appropriate design patterns to solve structural problems
+---
+
+# Design Pattern Advisor
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/design-pattern-advisor/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/design-pattern-advisor/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/design-pattern-advisor/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/design-pattern-advisor/SKILL.md
+```
diff --git a/.claude/skills/dotnet-core-expert/SKILL.md b/.claude/skills/dotnet-core-expert/SKILL.md
new file mode 100644
index 0000000..904baba
--- /dev/null
+++ b/.claude/skills/dotnet-core-expert/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: dotnet-core-expert
+description: Deep .NET 10 expertise — Clean Architecture, EF Core, CQRS/MediatR, JWT auth
+---
+
+# Dotnet Core Expert
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/dotnet-core-expert/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/dotnet-core-expert/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/dotnet-core-expert/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/dotnet-core-expert/SKILL.md
+```
diff --git a/.claude/skills/feature-forge/SKILL.md b/.claude/skills/feature-forge/SKILL.md
new file mode 100644
index 0000000..b4e5553
--- /dev/null
+++ b/.claude/skills/feature-forge/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: feature-forge
+description: Generate complete feature breakdowns with stories, tasks, and acceptance criteria
+---
+
+# Feature Forge
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/feature-forge/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/feature-forge/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/feature-forge/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/feature-forge/SKILL.md
+```
diff --git a/.claude/skills/issue-creator/SKILL.md b/.claude/skills/issue-creator/SKILL.md
new file mode 100644
index 0000000..271cddc
--- /dev/null
+++ b/.claude/skills/issue-creator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: issue-creator
+description: Create structured GitHub issues with acceptance criteria and sub-task decomposition
+---
+
+# Issue Creator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/issue-creator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/issue-creator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/issue-creator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/issue-creator/SKILL.md
+```
diff --git a/.claude/skills/legacy-modernizer/SKILL.md b/.claude/skills/legacy-modernizer/SKILL.md
new file mode 100644
index 0000000..a141bf9
--- /dev/null
+++ b/.claude/skills/legacy-modernizer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: legacy-modernizer
+description: Plan and execute modernization of legacy codebases to modern architectures
+---
+
+# Legacy Modernizer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/legacy-modernizer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/legacy-modernizer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/legacy-modernizer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/legacy-modernizer/SKILL.md
+```
diff --git a/.claude/skills/mcp-developer/SKILL.md b/.claude/skills/mcp-developer/SKILL.md
new file mode 100644
index 0000000..71527ae
--- /dev/null
+++ b/.claude/skills/mcp-developer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: mcp-developer
+description: Build, debug, and extend MCP servers and clients with JSON-RPC transport
+---
+
+# Mcp Developer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/mcp-developer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/mcp-developer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/mcp-developer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/mcp-developer/SKILL.md
+```
diff --git a/.claude/skills/memory-optimization/SKILL.md b/.claude/skills/memory-optimization/SKILL.md
new file mode 100644
index 0000000..ea3e894
--- /dev/null
+++ b/.claude/skills/memory-optimization/SKILL.md
@@ -0,0 +1,30 @@
+---
+name: memory-optimization
+description: Context window and token optimization rules — load less, achieve more. Apply to every session.
+---
+
+# Memory & Context Optimization
+
+> **Bridge to universal instruction.** The full rules live in
+> `~/.claude/skills/memory-optimization/SKILL.md`.
+
+This skill teaches token-efficient AI behavior: progressive disclosure, context budgeting,
+selective loading, and output compression. Apply these rules to **every session**.
+
+## Instructions
+
+1. **Read the full rules:** Open `~/.claude/skills/memory-optimization/SKILL.md`
+2. **Internalize the 7 sections** — they apply to all tasks, not just specific workflows
+3. **Key principles to always follow:**
+ - Load only what you need (grep first, then read matched files)
+ - Use `view_range` instead of reading entire files
+ - Suppress verbose output (`--quiet`, pipe to `head`)
+ - Batch parallel reads in a single turn
+ - Progressive disclosure: SKILL.md first, references only when needed
+ - Token budget awareness: <30% normal, 30-60% selective, 60-80% delegate, >80% compact
+
+## Quick Start
+
+```
+Read ~/.claude/skills/memory-optimization/SKILL.md
+```
diff --git a/.claude/skills/monitoring-expert/SKILL.md b/.claude/skills/monitoring-expert/SKILL.md
new file mode 100644
index 0000000..9cca9a0
--- /dev/null
+++ b/.claude/skills/monitoring-expert/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: monitoring-expert
+description: Design observability stacks with metrics, logs, traces, and alerting
+---
+
+# Monitoring Expert
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/monitoring-expert/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/monitoring-expert/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/monitoring-expert/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/monitoring-expert/SKILL.md
+```
diff --git a/.claude/skills/owasp-audit/SKILL.md b/.claude/skills/owasp-audit/SKILL.md
new file mode 100644
index 0000000..6621891
--- /dev/null
+++ b/.claude/skills/owasp-audit/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: owasp-audit
+description: Audit code against OWASP Top 10 vulnerabilities
+---
+
+# Owasp Audit
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/owasp-audit/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/owasp-audit/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/owasp-audit/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/owasp-audit/SKILL.md
+```
diff --git a/.claude/skills/polyglot-analyzer/SKILL.md b/.claude/skills/polyglot-analyzer/SKILL.md
new file mode 100644
index 0000000..e1d6612
--- /dev/null
+++ b/.claude/skills/polyglot-analyzer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: polyglot-analyzer
+description: Multi-language quality comparison with cross-language boundary analysis and unified quality gates
+---
+
+# Polyglot Analyzer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/polyglot-analyzer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/polyglot-analyzer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/polyglot-analyzer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/polyglot-analyzer/SKILL.md
+```
diff --git a/.claude/skills/prompt-engineer/SKILL.md b/.claude/skills/prompt-engineer/SKILL.md
new file mode 100644
index 0000000..78b55ab
--- /dev/null
+++ b/.claude/skills/prompt-engineer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: prompt-engineer
+description: Write, refactor, and evaluate LLM prompts with structured outputs
+---
+
+# Prompt Engineer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/prompt-engineer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/prompt-engineer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/prompt-engineer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/prompt-engineer/SKILL.md
+```
diff --git a/.claude/skills/quality-analyzer/SKILL.md b/.claude/skills/quality-analyzer/SKILL.md
new file mode 100644
index 0000000..9239ae8
--- /dev/null
+++ b/.claude/skills/quality-analyzer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: quality-analyzer
+description: Analyze code quality metrics — complexity, maintainability, SATD, and style conformance
+---
+
+# Quality Analyzer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/quality-analyzer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/quality-analyzer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/quality-analyzer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/quality-analyzer/SKILL.md
+```
diff --git a/.claude/skills/query-optimizer/SKILL.md b/.claude/skills/query-optimizer/SKILL.md
new file mode 100644
index 0000000..15213dd
--- /dev/null
+++ b/.claude/skills/query-optimizer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: query-optimizer
+description: Analyze and optimize SQL queries for performance
+---
+
+# Query Optimizer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/query-optimizer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/query-optimizer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/query-optimizer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/query-optimizer/SKILL.md
+```
diff --git a/.claude/skills/readme-generator/SKILL.md b/.claude/skills/readme-generator/SKILL.md
new file mode 100644
index 0000000..60f64e4
--- /dev/null
+++ b/.claude/skills/readme-generator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: readme-generator
+description: Generate comprehensive README files from project analysis
+---
+
+# Readme Generator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/readme-generator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/readme-generator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/readme-generator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/readme-generator/SKILL.md
+```
diff --git a/.claude/skills/refactor-planner/SKILL.md b/.claude/skills/refactor-planner/SKILL.md
new file mode 100644
index 0000000..64ad3cb
--- /dev/null
+++ b/.claude/skills/refactor-planner/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: refactor-planner
+description: Analyze code and produce a prioritized refactoring plan
+---
+
+# Refactor Planner
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/refactor-planner/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/refactor-planner/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/refactor-planner/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/refactor-planner/SKILL.md
+```
diff --git a/.claude/skills/schema-reviewer/SKILL.md b/.claude/skills/schema-reviewer/SKILL.md
new file mode 100644
index 0000000..7640c4d
--- /dev/null
+++ b/.claude/skills/schema-reviewer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: schema-reviewer
+description: Review database schema design for normalization, indexing, and integrity
+---
+
+# Schema Reviewer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/schema-reviewer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/schema-reviewer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/schema-reviewer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/schema-reviewer/SKILL.md
+```
diff --git a/.claude/skills/secret-scanner/SKILL.md b/.claude/skills/secret-scanner/SKILL.md
new file mode 100644
index 0000000..24d133b
--- /dev/null
+++ b/.claude/skills/secret-scanner/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: secret-scanner
+description: Detect hardcoded secrets, API keys, and credentials in source code
+---
+
+# Secret Scanner
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/secret-scanner/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/secret-scanner/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/secret-scanner/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/secret-scanner/SKILL.md
+```
diff --git a/.claude/skills/smart-refactor/SKILL.md b/.claude/skills/smart-refactor/SKILL.md
new file mode 100644
index 0000000..4da0770
--- /dev/null
+++ b/.claude/skills/smart-refactor/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: smart-refactor
+description: Metrics-driven refactoring with baseline/after comparison and scientific measurement
+---
+
+# Smart Refactor
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/smart-refactor/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/smart-refactor/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/smart-refactor/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/smart-refactor/SKILL.md
+```
diff --git a/.claude/skills/spec-miner/SKILL.md b/.claude/skills/spec-miner/SKILL.md
new file mode 100644
index 0000000..1de25b4
--- /dev/null
+++ b/.claude/skills/spec-miner/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: spec-miner
+description: Extract implicit specifications from code, tests, and documentation
+---
+
+# Spec Miner
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/spec-miner/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/spec-miner/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/spec-miner/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/spec-miner/SKILL.md
+```
diff --git a/.claude/skills/spec-writer/SKILL.md b/.claude/skills/spec-writer/SKILL.md
new file mode 100644
index 0000000..923b1be
--- /dev/null
+++ b/.claude/skills/spec-writer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: spec-writer
+description: Write comprehensive technical specifications from feature requests
+---
+
+# Spec Writer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/spec-writer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/spec-writer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/spec-writer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/spec-writer/SKILL.md
+```
diff --git a/.claude/skills/tdd-coach/SKILL.md b/.claude/skills/tdd-coach/SKILL.md
new file mode 100644
index 0000000..a3982d0
--- /dev/null
+++ b/.claude/skills/tdd-coach/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: tdd-coach
+description: Guide test-driven development with red-green-refactor cycle
+---
+
+# Tdd Coach
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/tdd-coach/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/tdd-coach/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/tdd-coach/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/tdd-coach/SKILL.md
+```
diff --git a/.claude/skills/tech-debt-tracker/SKILL.md b/.claude/skills/tech-debt-tracker/SKILL.md
new file mode 100644
index 0000000..67d4c2d
--- /dev/null
+++ b/.claude/skills/tech-debt-tracker/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: tech-debt-tracker
+description: Detect, quantify, and prioritize technical debt with SATD detection and sprint planning
+---
+
+# Tech Debt Tracker
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/tech-debt-tracker/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/tech-debt-tracker/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/tech-debt-tracker/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/tech-debt-tracker/SKILL.md
+```
diff --git a/.claude/skills/tech-spike-planner/SKILL.md b/.claude/skills/tech-spike-planner/SKILL.md
new file mode 100644
index 0000000..e37fecb
--- /dev/null
+++ b/.claude/skills/tech-spike-planner/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: tech-spike-planner
+description: Plan time-boxed technical investigations with clear success criteria
+---
+
+# Tech Spike Planner
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/tech-spike-planner/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/tech-spike-planner/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/tech-spike-planner/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/tech-spike-planner/SKILL.md
+```
diff --git a/.claude/skills/test-coverage-analyzer/SKILL.md b/.claude/skills/test-coverage-analyzer/SKILL.md
new file mode 100644
index 0000000..2642bbb
--- /dev/null
+++ b/.claude/skills/test-coverage-analyzer/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: test-coverage-analyzer
+description: Analyze test coverage gaps and recommend high-value tests to add
+---
+
+# Test Coverage Analyzer
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/test-coverage-analyzer/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/test-coverage-analyzer/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/test-coverage-analyzer/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/test-coverage-analyzer/SKILL.md
+```
diff --git a/.claude/skills/test-generator/SKILL.md b/.claude/skills/test-generator/SKILL.md
new file mode 100644
index 0000000..3bdfa12
--- /dev/null
+++ b/.claude/skills/test-generator/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: test-generator
+description: Generate unit and integration tests with Arrange-Act-Assert structure
+---
+
+# Test Generator
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/test-generator/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/test-generator/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/test-generator/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/test-generator/SKILL.md
+```
diff --git a/.claude/skills/threat-modeler/SKILL.md b/.claude/skills/threat-modeler/SKILL.md
new file mode 100644
index 0000000..49692a4
--- /dev/null
+++ b/.claude/skills/threat-modeler/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: threat-modeler
+description: Create STRIDE-based threat models for system components
+---
+
+# Threat Modeler
+
+> **Bridge to universal skill catalog.** This file registers the skill with Claude's `/skills` system.
+> The full skill definition lives in `~/.claude/skills/threat-modeler/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `~/.claude/skills/threat-modeler/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `~/.claude/skills/threat-modeler/references/` — only when the Reference Guide table says to
+4. **Never load all references at once** — progressive disclosure saves tokens
+
+## Quick Start
+
+```
+Read ~/.claude/skills/threat-modeler/SKILL.md
+```
diff --git a/.github/SETUP-GUIDE.md b/.github/SETUP-GUIDE.md
new file mode 100644
index 0000000..cc7e545
--- /dev/null
+++ b/.github/SETUP-GUIDE.md
@@ -0,0 +1,837 @@
+# Copilot CLI Project Configuration — Setup Guide
+
+> How to replicate this AI development infrastructure in any .NET project.
+> This guide walks through every component, explains its purpose, and provides
+> templates you can adapt.
+
+## Overview
+
+This configuration system gives AI assistants (Copilot, Claude, Gemini) deep
+project knowledge through layered instruction files, custom extensions with
+hooks and tools, MCP server integrations, and LSP configuration.
+
+### What You Get
+
+| Layer | Purpose | Files |
+|-------|---------|-------|
+| **Model Instructions** | Project identity, per-model optimization | `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` |
+| **Master Rules** | Technology stack, architecture, conventions | `.github/copilot-instructions.md` |
+| **Scoped Instructions** | Domain-specific rules activated by file glob | `.github/instructions/**/*.instructions.md` |
+| **Skills Catalog** | Reusable AI skills with lazy-loaded references | `.github/skills/{category}/{skill}/SKILL.md` |
+| **Extensions** | Custom tools, hooks, and real-time behaviors | `.github/extensions/*/extension.mjs` |
+| **MCP Servers** | External tool integrations (DB, APIs) | `.github/copilot-mcp.json` |
+| **LSP Config** | Language server for code intelligence | `.github/lsp.json` |
+| **Cloud Agent** | CI environment for Copilot coding agent | `.github/copilot-setup-steps.yml` |
+
+---
+
+## Step 1: Create the Directory Structure
+
+```bash
+# From your solution/repo root:
+mkdir -p .github/instructions/{architecture,security,testing,resilience,memory,development}
+mkdir -p .github/instructions/{blazor,cqrs,database,domain}
+mkdir -p .github/extensions/{security-scanner,build-guardian,context-optimizer}
+mkdir -p .github/extensions/{research-first,doc-sync,dotnet-conventions}
+mkdir -p .github/skills/{code-quality,security,architecture,testing}
+mkdir -p .github/skills/{database,devops,documentation,research}
+mkdir -p .github/skills/{project-management,ai,language}
+# Claude Code bridge (for /skills discovery)
+mkdir -p .claude/skills
+```
+
+On Windows (PowerShell):
+
+```powershell
+$dirs = @(
+ # Instructions (scoped rules)
+ ".github\instructions\architecture",
+ ".github\instructions\security",
+ ".github\instructions\testing",
+ ".github\instructions\resilience",
+ ".github\instructions\memory",
+ ".github\instructions\development",
+ ".github\instructions\blazor",
+ ".github\instructions\cqrs",
+ ".github\instructions\database",
+ ".github\instructions\domain",
+ # Extensions (hooks + tools)
+ ".github\extensions\security-scanner",
+ ".github\extensions\build-guardian",
+ ".github\extensions\context-optimizer",
+ ".github\extensions\research-first",
+ ".github\extensions\doc-sync",
+ ".github\extensions\dotnet-conventions",
+ # Skills catalog (11 categories)
+ ".github\skills\code-quality",
+ ".github\skills\security",
+ ".github\skills\architecture",
+ ".github\skills\testing",
+ ".github\skills\database",
+ ".github\skills\devops",
+ ".github\skills\documentation",
+ ".github\skills\research",
+ ".github\skills\project-management",
+ ".github\skills\ai",
+ ".github\skills\language"
+)
+# Add Claude Code bridge directory
+$dirs += ".claude\skills"
+foreach ($d in $dirs) {
+ New-Item -ItemType Directory -Path $d -Force | Out-Null
+}
+```
+
+Add domain-specific instruction folders as needed (e.g., `blazor/`, `cqrs/`,
+`database/`, `domain/`).
+
+### Final Structure
+
+```
+your-project/
+├── AGENTS.md
+├── CLAUDE.md
+├── GEMINI.md
+├── .github/
+│ ├── copilot-instructions.md
+│ ├── copilot-mcp.json
+│ ├── copilot-setup-steps.yml
+│ ├── lsp.json
+│ ├── SETUP-GUIDE.md ← This file
+│ ├── instructions/
+│ │ ├── architecture/
+│ │ │ └── clean-architecture.instructions.md
+│ │ ├── security/
+│ │ │ └── owasp-top10.instructions.md
+│ │ ├── testing/
+│ │ │ └── testing-standards.instructions.md
+│ │ ├── resilience/
+│ │ │ └── polly-patterns.instructions.md
+│ │ ├── memory/
+│ │ │ └── memory-optimization.instructions.md
+│ │ ├── development/
+│ │ │ └── mvp-first.instructions.md ← MVP anti-over-engineering rules
+│ │ ├── blazor/
+│ │ │ └── component-patterns.instructions.md
+│ │ ├── cqrs/
+│ │ │ └── mediatr-patterns.instructions.md
+│ │ ├── database/
+│ │ │ └── ef-core-patterns.instructions.md
+│ │ ├── domain/
+│ │ │ └── ddd-guidelines.instructions.md
+│ │ └── {your-domain}/
+│ │ └── {your-rules}.instructions.md
+│ ├── skills/ ← Reusable AI skills catalog
+│ │ ├── CATALOG.md ← Master index of all skills
+│ │ ├── code-quality/
+│ │ │ ├── code-reviewer/
+│ │ │ │ ├── SKILL.md ← Lean core (4-6 KB)
+│ │ │ │ └── references/ ← Deep-dive files (2-4 KB each)
+│ │ │ │ ├── review-checklist.md
+│ │ │ │ ├── common-issues.md
+│ │ │ │ └── ...
+│ │ │ ├── refactor-planner/
+│ │ │ ├── code-documenter/
+│ │ │ └── debugging-wizard/
+│ │ ├── security/
+│ │ │ ├── owasp-audit/
+│ │ │ ├── secret-scanner/
+│ │ │ ├── threat-modeler/
+│ │ │ ├── authentication/ ← Auth patterns (Entra ID, JWT, OIDC)
+│ │ │ └── authorization/ ← AuthZ patterns (policies, claims, Blazor)
+│ │ ├── architecture/
+│ │ ├── testing/
+│ │ ├── database/
+│ │ ├── devops/
+│ │ ├── documentation/
+│ │ ├── research/
+│ │ ├── project-management/
+│ │ ├── ai/ ← Agent orchestration, MCP, prompts
+│ │ └── language/ ← .NET Core, C# deep expertise
+│ └── extensions/
+│ ├── security-scanner/
+│ │ └── extension.mjs
+│ ├── build-guardian/
+│ │ └── extension.mjs
+│ ├── context-optimizer/
+│ │ └── extension.mjs
+│ ├── research-first/
+│ │ └── extension.mjs
+│ ├── doc-sync/
+│ │ └── extension.mjs
+│ └── dotnet-conventions/
+│ └── extension.mjs
+├── .claude/ ← Claude Code specific
+│ ├── settings.json
+│ ├── skills/ ← Bridge files for /skills discovery
+│ │ ├── code-reviewer/SKILL.md ← Bridges to .github/skills/code-reviewer/
+│ │ ├── owasp-audit/SKILL.md ← Bridges to .github/skills/owasp-audit/
+│ │ ├── agent-orchestrator/SKILL.md ← Bridges to .github/skills/agent-orchestrator/
+│ │ └── ... (37 total — one per universal skill)
+│ └── rules/ ← Scoped rules (auto-loaded by file path)
+│ ├── clean-architecture.md ← paths: ["**/*.cs"]
+│ ├── blazor-components.md ← paths: ["**/*.razor", "**/*.razor.cs"]
+│ ├── cqrs-mediatr.md ← paths: ["**/Commands/**", "**/Queries/**"]
+│ ├── ef-core.md ← paths: ["**/Infrastructure/**"]
+│ ├── owasp-security.md ← paths: ["**/*.cs", "**/*.razor"]
+│ ├── memory-optimization.md ← paths: ["**/*"] (always active)
+│ ├── mvp-first.md ← paths: ["**/*"] (always active)
+│ ├── ddd-domain.md ← paths: ["**/Domain/**"]
+│ ├── polly-resilience.md ← paths: ["**/Services/**"]
+│ └── testing-standards.md ← paths: ["**/*Test*"]
+└── YourProject.sln
+```
+
+---
+
+## Step 2: Model Instruction Files (Root)
+
+These files sit at the repo root and are auto-discovered by Copilot CLI.
+
+### `AGENTS.md` — Universal Agent Instructions
+
+This is the **primary identity file** all AI models read. Include:
+
+```markdown
+# Project Name — Agent Instructions
+
+## Project Identity
+- What the project does (1-2 sentences)
+- Target users / domain
+
+## Architecture
+- Pattern (Clean Architecture, Vertical Slice, etc.)
+- Layer map with directory names
+- Dependency flow diagram (ASCII)
+
+## Mandatory Rules
+- [ ] List non-negotiable rules (e.g., "always use code-behind")
+- [ ] Security requirements
+- [ ] Documentation sync requirements
+
+## Key Design Patterns
+- List patterns in use with where they're applied
+
+## Anti-Patterns
+- What NOT to do (with brief justification)
+```
+
+### `CLAUDE.md` / `GEMINI.md` — Model-Specific Optimization
+
+Tailor prompting patterns per model's strengths:
+
+| Model | Emphasize |
+|-------|-----------|
+| **Claude** | Structured reasoning, chain-of-thought, systematic OWASP enumeration |
+| **Gemini** | Code search, dependency graph mapping, cross-reference analysis |
+
+---
+
+## Step 3: Master Copilot Instructions
+
+### `.github/copilot-instructions.md`
+
+This is the **single most important file** — Copilot reads it for every session.
+
+Template structure:
+
+```markdown
+# {Project Name} — Copilot Instructions
+
+## Project Overview
+[1-paragraph description]
+
+## Technology Stack
+| Technology | Version | Purpose |
+|------------|---------|---------|
+| .NET | 10 | Runtime |
+| ... | ... | ... |
+
+## Architecture
+[Layer diagram, dependency rules]
+
+## File Organization
+| Directory | Layer | Contents |
+|-----------|-------|----------|
+| ... | ... | ... |
+
+## Conventions
+- Naming, formatting, patterns
+
+## Domain Rules
+- Business-specific rules the AI must follow
+```
+
+**Keep it under 10 KB** — this loads into every conversation.
+
+---
+
+## Step 4: Scoped Instruction Files
+
+These activate **only when the AI touches matching files**, keeping context lean.
+
+### Format
+
+Each file needs a glob at the top:
+
+```markdown
+---
+applyTo: "**/*.cs"
+---
+
+# Rule Title
+
+## Rules
+...
+```
+
+### Recommended Scopes for .NET Projects
+
+| File | Glob | Purpose |
+|------|------|---------|
+| `architecture/*.instructions.md` | `**/*.cs` | Layer dependency rules |
+| `blazor/*.instructions.md` | `**/*.razor*` | Component patterns |
+| `security/*.instructions.md` | `**/*.cs, **/*.razor` | OWASP rules |
+| `testing/*.instructions.md` | `**/*Test*/**` | Test conventions |
+| `database/*.instructions.md` | `**/Data/**, **/Migrations/**` | EF Core patterns |
+| `resilience/*.instructions.md` | `**/Services/**, **/Infrastructure/**` | Polly patterns |
+| `memory/*.instructions.md` | `**/*` | Context window optimization |
+
+### Adapting for Non-.NET Projects
+
+| Stack | Suggested Scopes |
+|-------|-----------------|
+| **React/TypeScript** | `components/`, `hooks/`, `api/`, `store/`, `**/*.test.ts` |
+| **Python/Django** | `models/`, `views/`, `serializers/`, `tests/`, `migrations/` |
+| **Go** | `cmd/`, `internal/`, `pkg/`, `**/*_test.go` |
+| **Java/Spring** | `controller/`, `service/`, `repository/`, `**/test/**` |
+
+---
+
+## Step 5: Extensions (Skills, Hooks, Agents)
+
+Extensions are Node.js ES modules (`.mjs`) that run as child processes.
+
+### Anatomy of an Extension
+
+```javascript
+import { joinSession } from "@github/copilot-sdk/extension";
+
+const session = await joinSession({
+ hooks: {
+ // Intercept and modify behavior at lifecycle points
+ onUserPromptSubmitted: async (input) => { /* ... */ },
+ onPreToolUse: async (input) => { /* ... */ },
+ onPostToolUse: async (input) => { /* ... */ },
+ onSessionStart: async (input) => { /* ... */ },
+ },
+ tools: [
+ // Custom tools the AI can invoke
+ {
+ name: "my_tool",
+ description: "What it does",
+ parameters: { type: "object", properties: { /* ... */ } },
+ handler: async (args) => "result string",
+ },
+ ],
+});
+```
+
+### Extension Catalog — What to Include
+
+| Extension | Transferable? | Adapt For |
+|-----------|--------------|-----------|
+| **security-scanner** | ✅ Universal | Adjust secret patterns per stack |
+| **build-guardian** | ✅ Change build command | `npm run build`, `go build`, `mvn package` |
+| **context-optimizer** | ✅ Update project summary | Change the hardcoded summary text |
+| **research-first** | ✅ Universal | Adjust docs/ path if different |
+| **doc-sync** | ⚠️ Project-specific | Rewrite feature→docs mapping |
+| **dotnet-conventions** | ❌ .NET only | Replace with eslint/pylint/golint hooks |
+
+### Adapting `build-guardian` for Other Stacks
+
+```javascript
+// Node.js/TypeScript
+const buildCmd = isWindows ? "npm.cmd" : "npm";
+const buildArgs = ["run", "build"];
+const testCmd = isWindows ? "npm.cmd" : "npm";
+const testArgs = ["run", "test"];
+
+// Go
+const buildCmd = "go";
+const buildArgs = ["build", "./..."];
+const testCmd = "go";
+const testArgs = ["test", "./..."];
+
+// Python
+const buildCmd = "python";
+const buildArgs = ["-m", "pytest"];
+```
+
+### Critical Rules for Extensions
+
+1. **Tool names must be globally unique** across all extensions
+2. **Never use `console.log()`** — stdout is JSON-RPC. Use `session.log()`
+3. **Only `.mjs` files** — TypeScript not supported
+4. **`@github/copilot-sdk` auto-resolves** — don't `npm install` it
+5. **Reload after changes**: use `/clear` or the `extensions_reload` command
+
+---
+
+## Step 6: Skills Catalog (Reusable AI Skills)
+
+The skills catalog provides **reusable, cross-platform AI skills** that work with Copilot CLI,
+Claude, and Gemini. Each skill follows the **Jeffallan `references/` pattern** for memory optimization.
+
+### What is a Skill?
+
+A skill is a structured markdown file (`SKILL.md`) that tells AI assistants *how* to perform
+a specific task — code review, security audit, test generation, etc. Skills include:
+
+- **YAML frontmatter** — metadata, triggers, platform targeting
+- **Core Workflow** — numbered steps with validation checkpoints
+- **Reference Guide** — lazy-loaded deep-dive files (memory optimization)
+- **Constraints** — MUST DO / MUST NOT DO rules
+- **Output Template** — expected deliverable format
+
+### The `references/` Pattern (Memory Optimization)
+
+This is the key innovation for token savings. Instead of one large SKILL.md file:
+
+```
+# WITHOUT references/ (old pattern — 15-18 KB loaded every time)
+skills/owasp-audit/SKILL.md ← 17 KB monolithic file
+
+# WITH references/ (new pattern — 5 KB base + surgical deep-dives)
+skills/owasp-audit/
+ SKILL.md ← 5 KB core (always loaded)
+ references/
+ injection-prevention.md ← 3 KB (loaded ONLY when doing SQL injection work)
+ broken-auth.md ← 3 KB (loaded ONLY when doing auth review)
+ access-control.md ← 3 KB (loaded ONLY when doing access control)
+ crypto-failures.md ← 3 KB (loaded ONLY when doing crypto review)
+```
+
+**Result: ~60-70% token savings per skill invocation.**
+
+The SKILL.md includes a **Reference Guide table** that tells the AI *when* to load each file:
+
+```markdown
+## Reference Guide
+
+| Topic | Reference | Load When |
+|-------|-----------|-----------|
+| Injection Prevention | `references/injection-prevention.md` | SQL injection, XSS, command injection |
+| Authentication | `references/broken-auth.md` | Auth failures, session management |
+| Access Control | `references/access-control.md` | Broken access control (A01) |
+| Crypto Failures | `references/crypto-failures.md` | Data exposure, weak encryption |
+```
+
+### SKILL.md Format
+
+```yaml
+---
+name: skill-name
+description: "What this skill does and when to invoke it"
+license: MIT
+allowed-tools: Read, Grep, Glob, Bash
+metadata:
+ author: YourOrg
+ version: "2.0.0"
+ domain: code-quality # category
+ triggers: review code, PR review # activation phrases
+ role: specialist # specialist | reviewer | expert
+ scope: review # review | implementation | analysis | design
+ platforms: copilot-cli, claude, gemini
+ output-format: report # report | code | document | analysis
+ related-skills: refactor-planner, test-generator
+---
+
+# Skill Name
+
+One-sentence role definition.
+
+## When to Use This Skill
+- Trigger scenario 1
+- Trigger scenario 2
+
+## Core Workflow
+1. **Step** — Description. _Checkpoint: verify X before proceeding._
+2. **Step** — Description.
+
+## Reference Guide
+| Topic | Reference | Load When |
+|-------|-----------|-----------|
+| Topic 1 | `references/topic-1.md` | When doing X |
+
+## Quick Reference
+(1-2 inline code examples)
+
+## Constraints
+### MUST DO
+### MUST NOT DO
+
+## Output Template
+(Expected deliverable structure)
+```
+
+### Skill Categories (11)
+
+| # | Category | Skills | Description |
+|---|----------|--------|-------------|
+| 1 | `code-quality` | code-reviewer, refactor-planner, code-documenter, debugging-wizard | Code review, refactoring, documentation, debugging |
+| 2 | `security` | owasp-audit, secret-scanner, threat-modeler, authentication, authorization | Security audit, secrets, threats, auth |
+| 3 | `architecture` | architecture-reviewer, design-pattern-advisor, dependency-analyzer, legacy-modernizer | Architecture review, patterns, dependencies, migration |
+| 4 | `testing` | test-generator, tdd-coach, test-coverage-analyzer | Test generation, TDD, coverage |
+| 5 | `database` | schema-reviewer, query-optimizer | Schema review, query optimization |
+| 6 | `devops` | ci-cd-builder, deployment-preflight, monitoring-expert, chaos-engineer | CI/CD, deployment, monitoring, resilience |
+| 7 | `documentation` | readme-generator, adr-creator, api-documenter | README, ADR, API docs |
+| 8 | `research` | codebase-explorer, tech-spike-planner, spec-miner | Exploration, spikes, reverse engineering |
+| 9 | `project-management` | spec-writer, issue-creator, feature-forge | Specs, issues, requirements |
+| 10 | `ai` | mcp-developer, prompt-engineer, agent-orchestrator | MCP, prompts, agent coordination |
+| 11 | `language` | dotnet-core-expert, csharp-developer | .NET Core, C# deep expertise |
+
+### Creating a New Skill
+
+```bash
+# 1. Create skill directory with references
+mkdir -p .github/skills/{category}/{skill-name}/references
+
+# 2. Create SKILL.md with frontmatter + sections
+# 3. Create 3-5 reference files for deep-dive content
+# 4. Update CATALOG.md master index
+```
+
+### Adapting Skills for Your Stack
+
+Skills are stack-agnostic by design. To adapt for a different stack:
+
+1. **Keep the workflow and constraints** — they're universal
+2. **Replace code examples** — swap C# for Python/Go/Java in Quick Reference
+3. **Update reference files** — replace `.NET` patterns with your framework's equivalents
+4. **Update triggers** — match your team's vocabulary
+
+| Original (.NET) | React/TypeScript | Python/Django | Go |
+|-----------------|------------------|---------------|----|
+| `xUnit + Moq` | `Jest + React Testing Library` | `pytest + unittest.mock` | `testing + testify` |
+| `EF Core` | `Prisma / Drizzle` | `Django ORM` | `GORM / sqlc` |
+| `FluentValidation` | `Zod / Yup` | `Pydantic / marshmallow` | `go-playground/validator` |
+| `MediatR` | `tRPC` | `Django signals` | `Go channels` |
+| `Blazor AuthorizeView` | `Next-Auth + middleware` | `Django permissions` | `casbin` |
+
+---
+
+## Step 6B: Claude Code Bridge Skills (`.claude/skills/`)
+
+Claude Code discovers skills from `.claude/skills/`, not `.github/skills/`. To make all
+universal skills appear in Claude's `/skills` menu, create **bridge files** that redirect
+to the universal definitions.
+
+### Why a Bridge?
+
+```
+.github/skills/ ← Universal source of truth (all models)
+.claude/skills/ ← Claude Code discovery layer (bridge files only)
+```
+
+- **Single source of truth** stays in `.github/skills/`
+- Bridge files are thin (~20 lines) — just YAML frontmatter + read instruction
+- `/skills` in Claude Code shows all 36 skills
+- Users invoke via `/skill-name` (e.g., `/owasp-audit`, `/code-reviewer`)
+
+### Bridge File Template
+
+Create `.claude/skills/{skill-name}/SKILL.md` for each skill:
+
+```markdown
+---
+name: {skill-name}
+description: {Brief description — shown in /skills listing}
+---
+
+# {Skill Display Name}
+
+> **Bridge to universal skill catalog.** The full skill definition lives in
+> `.github/skills/{category}/{skill-name}/SKILL.md`.
+
+## Instructions
+
+1. **Read the full skill:** Open `.github/skills/{category}/{skill-name}/SKILL.md`
+2. **Follow the Core Workflow** steps defined in that file
+3. **Load references on demand** from `.github/skills/{category}/{skill-name}/references/`
+4. **Never load all references at once** — progressive disclosure saves tokens
+```
+
+### Automation Script (PowerShell)
+
+Generate all bridge files from your skills catalog:
+
+```powershell
+# Define skills: @{ name="skill-name"; cat="category"; desc="description" }
+$skills = @(
+ @{ name="code-reviewer"; cat="code-quality"; desc="Review code for correctness and style" },
+ @{ name="owasp-audit"; cat="security"; desc="Audit code against OWASP Top 10" },
+ # ... add all your skills
+)
+
+foreach ($s in $skills) {
+ $dir = ".claude\skills\$($s.name)"
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
+ @"
+---
+name: $($s.name)
+description: $($s.desc)
+---
+# $($s.name)
+> Bridge to universal skill catalog.
+> Full definition: ``.github/skills/$($s.cat)/$($s.name)/SKILL.md``
+## Instructions
+1. Read ``.github/skills/$($s.cat)/$($s.name)/SKILL.md``
+2. Follow the Core Workflow steps
+3. Load references on demand from ``references/``
+"@ | Set-Content "$dir\SKILL.md" -Encoding UTF8
+}
+```
+
+### Bash equivalent:
+
+```bash
+# For each skill, create the bridge:
+for skill_name in code-reviewer owasp-audit agent-orchestrator; do
+ mkdir -p ".claude/skills/${skill_name}"
+ cat > ".claude/skills/${skill_name}/SKILL.md" << 'EOF'
+---
+name: SKILL_NAME
+description: SKILL_DESC
+---
+# Bridge — read .github/skills/{category}/{skill}/SKILL.md
+EOF
+done
+```
+
+### Verification
+
+After creating bridge files, run `/skills` in Claude Code — all 37 skills should appear.
+
+---
+
+## Step 6C: Claude Code Scoped Rules (`.claude/rules/`)
+
+Claude Code's equivalent of Copilot's `.github/instructions/` scoped instructions.
+Rules in `.claude/rules/` are **auto-loaded** when Claude touches files matching the `paths:` globs.
+
+### How It Maps
+
+| Copilot (`.github/instructions/`) | Claude Code (`.claude/rules/`) | Targeting |
+|---|---|---|
+| `applyTo: "**/*.cs"` | `paths: ["**/*.cs"]` | Same glob syntax |
+| Auto-loaded per file | Auto-loaded per file | Same behavior |
+| Full detail (120-340 lines) | Condensed (40-80 lines) + reference link | Claude = lean |
+
+### Rule File Template
+
+```markdown
+---
+paths:
+ - "**/*.cs"
+ - "**/*.razor"
+description: One-line description of what this rule covers
+---
+
+# Rule Name
+
+> Auto-loaded by Claude Code when working with matching files.
+> Full reference: `.github/instructions/{category}/{filename}`
+
+## Key Rules
+
+- Rule 1...
+- Rule 2...
+
+---
+
+*Deep-dive: Read `.github/instructions/{category}/{filename}` for complete patterns.*
+```
+
+### Rules to Create
+
+| Rule File | Scoped To | Source |
+|-----------|-----------|--------|
+| `clean-architecture.md` | `**/*.cs` | `architecture/clean-architecture.instructions.md` |
+| `blazor-components.md` | `**/*.razor`, `**/*.razor.cs`, `**/*.razor.css` | `blazor/component-patterns.instructions.md` |
+| `cqrs-mediatr.md` | `**/Commands/**`, `**/Queries/**`, `**/Handlers/**` | `cqrs/mediatr-patterns.instructions.md` |
+| `ef-core.md` | `**/Infrastructure/**`, `**/*DbContext*`, `**/*Repository*` | `database/ef-core-patterns.instructions.md` |
+| `mvp-first.md` | `**/*` (always) | `development/mvp-first.instructions.md` |
+| `ddd-domain.md` | `**/Domain/**`, `**/Entities/**`, `**/ValueObjects/**` | `domain/ddd-guidelines.instructions.md` |
+| `memory-optimization.md` | `**/*` (always) | `memory/memory-optimization.instructions.md` |
+| `polly-resilience.md` | `**/Infrastructure/**`, `**/Services/**` | `resilience/polly-patterns.instructions.md` |
+| `owasp-security.md` | `**/*.cs`, `**/*.razor` | `security/owasp-top10.instructions.md` |
+| `testing-standards.md` | `**/*Test*`, `**/*.Tests/**` | `testing/testing-standards.instructions.md` |
+
+### Maintenance
+
+When you update a `.github/instructions/` file, also update the matching `.claude/rules/` file.
+The `.claude/rules/` files are condensed summaries — keep them under 80 lines. Point to the
+full instruction file for deep-dive reference.
+
+---
+
+## Step 7: MCP Server Configuration
+
+### `.github/copilot-mcp.json`
+
+```json
+{
+ "mcpServers": {
+ "your-db": {
+ "command": "npx",
+ "args": ["-y", "@anthropic/mcp-sqlserver"],
+ "env": {
+ "CONNECTION_STRING": "${env:YOUR_DB_CONNECTION_STRING}"
+ }
+ }
+ }
+}
+```
+
+**Never hardcode connection strings** — always use `${env:VAR_NAME}`.
+
+### Common MCP Servers
+
+| Server | Package | Use Case |
+|--------|---------|----------|
+| SQL Server | `@anthropic/mcp-sqlserver` | Database exploration |
+| PostgreSQL | `@anthropic/mcp-postgres` | Database exploration |
+| Filesystem | `@anthropic/mcp-filesystem` | Sandboxed file access |
+| GitHub | Built-in | Repo, issues, PRs |
+
+---
+
+## Step 8: LSP Configuration
+
+### `.github/lsp.json`
+
+```json
+{
+ "lspServers": {
+ "csharp": {
+ "command": "dotnet",
+ "args": ["tool", "run", "csharp-ls", "--solution", "YourProject.sln"],
+ "fileExtensions": {
+ ".cs": "csharp"
+ }
+ }
+ }
+}
+```
+
+### Common Language Servers
+
+| Language | Command | Install |
+|----------|---------|---------|
+| C# | `csharp-ls` | `dotnet tool install csharp-ls` |
+| TypeScript | `typescript-language-server` | `npm i -g typescript-language-server` |
+| Python | `pylsp` | `pip install python-lsp-server` |
+| Go | `gopls` | `go install golang.org/x/tools/gopls@latest` |
+| Rust | `rust-analyzer` | Via rustup |
+
+---
+
+## Step 9: Cloud Agent Setup
+
+### `.github/copilot-setup-steps.yml`
+
+This configures the GitHub Copilot coding agent's CI environment:
+
+```yaml
+steps:
+ - name: Setup runtime
+ uses: actions/setup-dotnet@v4 # or setup-node, setup-go, etc.
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Install dependencies
+ run: dotnet restore YourProject.sln # or npm ci, go mod download, etc.
+
+ - name: Build
+ run: dotnet build YourProject.sln --no-restore
+
+ - name: Setup database
+ uses: ikalnytskyi/action-setup-postgres@v7
+ with:
+ username: app_user
+ password: ${{ secrets.DB_PASSWORD }}
+ database: app_db
+```
+
+**All secrets via `${{ secrets.* }}`** — never inline credentials.
+
+---
+
+## Checklist for New Projects
+
+### Phase 1: Foundation
+- [ ] Create directory structure (Step 1)
+- [ ] Write `AGENTS.md` with project identity and rules
+- [ ] Write `CLAUDE.md` and `GEMINI.md` with model-specific guidance
+- [ ] Write `.github/copilot-instructions.md` (most important file)
+
+### Phase 2: Scoped Instructions
+- [ ] Add scoped instructions for your stack's key concerns
+- [ ] Add `memory-optimization.instructions.md` (copy as-is — it's universal)
+- [ ] Add `mvp-first.instructions.md` (copy as-is — it's universal)
+- [ ] Add domain-specific instructions (architecture, testing, security, etc.)
+
+### Phase 3: Skills Catalog
+- [ ] Copy `.github/skills/` directory (all categories)
+- [ ] Adapt code examples in SKILL.md files for your stack
+- [ ] Adapt reference files for your framework/language
+- [ ] Update `CATALOG.md` with any added/removed skills
+- [ ] Add authentication/authorization skills matching your auth provider
+- [ ] Generate `.claude/skills/` bridge files (see Step 6B automation script)
+- [ ] Verify `/skills` shows all skills in Claude Code
+
+### Phase 3B: Claude Code Rules (`.claude/rules/`)
+- [ ] Create `.claude/rules/` directory
+- [ ] Create condensed rule files for each `.github/instructions/` file (see Step 6C)
+- [ ] Verify `paths:` globs match your project structure
+- [ ] Test: Edit a `.cs` file in Claude Code → rules should auto-load
+
+### Phase 4: Extensions & Hooks
+- [ ] Copy and adapt Copilot CLI extensions:
+ - [ ] `security-scanner` — update secret patterns
+ - [ ] `build-guardian` — update build/test commands
+ - [ ] `context-optimizer` — update project summary
+ - [ ] `research-first` — update docs path
+ - [ ] `doc-sync` — rewrite feature→docs mapping
+ - [ ] Stack-specific conventions extension
+- [ ] Validate extensions: `node --check .github/extensions/*/extension.mjs`
+- [ ] Create Claude Code hooks (`.claude/hooks/*.ps1` or `.sh`):
+ - [ ] `security-scanner` — PreToolUse blocker for secrets
+ - [ ] `dotnet-conventions` — PostToolUse convention checker
+ - [ ] `doc-sync-reminder` — PostToolUse docs reminder
+ - [ ] `build-reminder` — PostToolUse build reminder
+ - [ ] `research-first` — UserPromptSubmit guidance
+ - [ ] `context-optimizer` — SessionStart project context
+- [ ] Add `hooks` section to `.claude/settings.json`
+- [ ] Review hooks reference: `.github/docs/hooks-reference.md`
+
+### Phase 5: Infrastructure
+- [ ] Configure MCP servers if using databases
+- [ ] Configure LSP for your language
+- [ ] Configure cloud agent setup steps
+- [ ] Test: start Copilot CLI in the repo and verify extensions load
+
+---
+
+## Maintenance
+
+- **Update instructions** when architecture or conventions change
+- **Update extension hooks** when adding new directories or features
+- **Update `context-optimizer` summary** when the tech stack evolves
+- **Update `doc-sync` mappings** when adding new feature documentation
+- **Update skills** when adding new frameworks or patterns
+- **Update `CATALOG.md`** when adding or removing skills
+- **Update this SETUP-GUIDE.md** when any structural changes are made
+- **Run `/instructions`** in Copilot CLI to verify which files are loaded
+- **Run `extensions_manage({ operation: "list" })`** to check extension health
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..6feac5a
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,251 @@
+# Copilot Instructions — Project Conventions
+
+> Master project-level instructions for GitHub Copilot and all AI coding assistants.
+> This file defines coding conventions, architecture patterns, and best practices
+> for .NET / Blazor projects. Customize the examples to match your domain.
+
+## Project
+
+This file defines the **coding conventions and architecture standards** for this .NET / Blazor project. All AI assistants working on this codebase should follow these patterns.
+
+**Tech Stack:**
+- .NET 10, Blazor Server (interactive SSR)
+- PostgreSQL with EF Core (Npgsql)
+- MediatR (CQRS vertical slices)
+- Bootstrap 5 (enterprise LOB UI)
+- IStringLocalizer with .resx files (en-US, es-MX)
+
+---
+
+## Architecture
+
+**Clean Architecture** with **CQRS** organized as vertical slices.
+
+### Layer Map
+
+```
+Presentation Components/ Blazor pages, layouts, scoped CSS
+Application Features/{Domain}/ MediatR command/query handlers
+Domain Models/ Entities, value objects, enums
+ Events/ DomainEvent, IEventBus, domain event classes
+ Services/Strategies/ Strategy interfaces (IChargeable, IRefundable, etc.)
+Infrastructure Data/ AppDbContext, repository implementations
+ Services/ External service integrations
+ Infrastructure/Auth/ Authentication handlers
+ Infrastructure/Middleware/ Exception handling, logging middleware
+```
+
+### Dependency Direction — MANDATORY
+
+```
+Components/ ──→ Features/ ──→ Models/ ←── Data/
+ Events/ ←── Services/
+ Strategies/ ←── Infrastructure/
+```
+
+Inner layers (Models, Events, Strategies) **never** reference outer layers. Infrastructure implements domain interfaces.
+
+---
+
+## Design Patterns
+
+| Pattern | Where | Purpose |
+|---|---|---|
+| **Strategy** | `Services/Strategies/` | External provider abstraction (e.g., payment, notification) |
+| **Repository** | `Data/Repositories/` | Data access abstraction — EF Core hidden from business logic |
+| **Factory** | `IStrategyFactory` | Runtime resolution of strategy implementation by provider name |
+| **Event Bus** | `Events/IEventBus` | Decouple side effects from business operations |
+| **MediatR/CQRS** | `Features/{Domain}/` | Separate command (write) and query (read) paths |
+| **Vertical Slice** | `Features/{Domain}/*/` | Each feature is a self-contained slice: command + handler + (optional validator) |
+
+### Strategy Interfaces (ISP-Compliant)
+
+```csharp
+IPaymentProcessor // Marker — every provider implements this
+├── IChargeable // ChargeAsync(amount, paymentMethodId, idempotencyKey)
+├── IRefundable // RefundAsync(transactionReference, idempotencyKey)
+└── ICancellable // CancelAsync(transactionReference, idempotencyKey)
+```
+
+Providers implement only the capabilities they support. One provider may implement all three; another might only implement `IChargeable`.
+
+---
+
+## Blazor Rules — MANDATORY
+
+### Code-Behind Pattern (Always)
+
+Every component produces **three files**:
+
+```
+ComponentName.razor ← Markup only. No @code {} blocks. Ever.
+ComponentName.razor.cs ← sealed partial class. All logic here.
+ComponentName.razor.css ← Scoped CSS. Bootstrap 5 + custom overrides.
+```
+
+### Component Conventions
+
+- Inject services via `[Inject]` in code-behind — not `@inject` in markup (markup `@inject` is acceptable for `IStringLocalizer` only).
+- Use `IStringLocalizer` for all user-facing text.
+- Use `IMediator` for all data operations — never call repositories or services directly from components.
+- Use `[CascadingParameter] Task` for auth state.
+- Implement `IDisposable` / `IAsyncDisposable` when using event handlers or JS interop.
+- Override `OnInitializedAsync` for data loading — not the constructor.
+
+---
+
+## Security — OWASP Top 10
+
+| Category | Requirement |
+|---|---|
+| **Broken Access Control** | `[Authorize]` on every endpoint. Policy-based auth (`"ApiAccess"`). Default deny. |
+| **Cryptographic Failures** | Secrets via env vars or Key Vault. Never in source or `appsettings.json`. |
+| **Injection** | Parameterized queries only (EF Core). No raw SQL string concatenation. |
+| **Insecure Design** | Strategy Pattern enforces external provider boundaries. |
+| **Security Misconfiguration** | HTTPS + HSTS enforced. Antiforgery tokens. Swagger only in Development. |
+| **Vulnerable Components** | Keep NuGet packages updated. Monitor for CVEs. |
+| **Auth Failures** | Validate credentials on every request. Use policy-based auth. |
+| **Logging Failures** | Structured logging. Correlation IDs. **Never log PII, tokens, or secrets.** |
+
+---
+
+## Business Operation Rules — MANDATORY
+
+1. **Idempotency keys** on every write operation that calls external services. All strategy methods require an `idempotencyKey` parameter.
+2. **Domain events after persistence** — publish domain events (e.g., `OrderCreatedEvent`, `OrderCompletedEvent`) only after `SaveChangesAsync`.
+3. **State machine integrity** — enforce valid status transitions in the domain model. Invalid transitions throw domain exceptions.
+4. **External references** — store provider-specific IDs (e.g., payment intent ID, tracking number) on the entity for reconciliation.
+5. **Never modify monetary amounts** after initial creation. Amounts flow from the domain model to external providers — no manual arithmetic.
+6. **Audit trail** — every state transition must be traceable via domain events.
+
+---
+
+## CQRS Flow
+
+All business operations go through MediatR:
+
+```
+UI/API ──→ IMediator.Send(Command/Query)
+ │
+ ▼
+ Handler (Features/{Domain}/*/Handler.cs)
+ │
+ ├──→ Validate input
+ ├──→ Resolve strategy (IStrategyFactory)
+ ├──→ Execute operation (IChargeable, etc.)
+ ├──→ Persist via repository interface
+ ├──→ Publish domain event (IEventBus)
+ └──→ Return result
+```
+
+### Example Slices
+
+| Slice | Type | Purpose |
+|---|---|---|
+| `CreateOrder/` | Command | Create a new order with initial validation |
+| `CompleteOrder/` | Command | Mark order as completed and trigger side effects |
+| `CancelOrder/` | Command | Cancel an order and initiate reversal if needed |
+| `GetOrder/` | Query | Read single order by ID |
+| `ListOrders/` | Query | List orders with filtering and pagination |
+
+---
+
+## Code Conventions
+
+| Convention | Rule |
+|---|---|
+| Namespaces | File-scoped (`namespace ProjectName.X;`) |
+| Nullability | Enabled — use `string?` for nullable |
+| Inheritance | `sealed` by default on concrete classes |
+| DTOs | `record` types with `init` properties |
+| Async | `async Task` / `async Task` with `CancellationToken` |
+| Naming | Intention-revealing. No abbreviations except DTO, ID, HTTP. |
+| Guard clauses | Fail fast at method entry — no deep nesting |
+| Constants | No magic strings or numbers — use `const` or `enum` |
+
+---
+
+## Localization
+
+- **Resource files:** `Resources/SharedResource.resx` (en-US default), `SharedResource.es.resx` (es-MX)
+- **Component resources:** `Resources/Components/` for component-specific strings
+- **Injection:** `IStringLocalizer` in code-behind files
+- **Markup:** `@L["KeyName"]` for localized strings
+- **Culture switch:** `GET /culture/set?culture={code}&redirectUri={path}` — cookie-based
+- **All user-facing strings must be localized** — no hardcoded text in `.razor` or `.razor.cs` files
+
+---
+
+## Data Model (Example)
+
+```
+Order
+├── Id int (PK, auto-increment)
+├── CustomerId string (required)
+├── Amount decimal (required)
+├── Description string (required)
+├── Status string — "Pending" | "Processing" | "Completed" | "Cancelled"
+├── ExternalReference string? — external provider transaction ID
+├── ExternalProvider string? — e.g., "Stripe", "PayPal"
+└── CreatedAt DateTime (UTC)
+```
+
+Replace `Order` with your domain's aggregate root entity. Add fields as needed for your domain.
+
+---
+
+## Documentation — MANDATORY
+
+Update `docs/` when features change. Maintain numbered documentation files:
+
+```
+00-Architecture-Overview Cross-cutting architecture
+01-Feature-Name Feature-specific workflow docs
+02-Feature-Name ...
+```
+
+Each feature should have a corresponding doc. New features without a matching doc → create the next numbered file (e.g., `03-Feature-Name`).
+
+---
+
+## DI Registration (Program.cs)
+
+When adding new services, register them in `Program.cs` following existing patterns:
+
+```csharp
+// Repository
+builder.Services.AddScoped();
+
+// Strategy (new provider implementation)
+builder.Services.AddScoped();
+
+// Event handler
+// (auto-discovered by MediatR if implementing INotificationHandler)
+
+// New service
+builder.Services.AddScoped();
+```
+
+MediatR handlers are auto-discovered — no manual registration needed.
+
+---
+
+## Agent Orchestration — MANDATORY
+
+When delegating work to sub-agents (parallel or serial):
+
+1. **ALWAYS present the delegation plan to the user** before spawning any agent.
+2. **Use `ask_user`** to show: agent count, agent types, task descriptions, blast radius, estimated tokens.
+3. **Wait for explicit approval** — do not assume approval from silence or prior permissions.
+4. **Never spawn agents without the user seeing and approving the plan first.**
+
+See `.github/skills/agent-orchestrator/SKILL.md` (Step 3) for the full approval gate workflow.
+
+---
+
+## Skills Catalog
+
+See **AGENTS.md → Skills Catalog** for the complete skill loading instructions, categories,
+and usage examples. Skills are universal across all models.
+
+**Quick start:** Read `.github/skills/CATALOG.md` to browse all 36 skills across 11 categories.
diff --git a/.github/copilot-mcp.json b/.github/copilot-mcp.json
new file mode 100644
index 0000000..ae8a288
--- /dev/null
+++ b/.github/copilot-mcp.json
@@ -0,0 +1,20 @@
+{
+ "mcpServers": {
+ "sqlserver": {
+ "command": "npx",
+ "args": ["-y", "@anthropic/mcp-sqlserver"],
+ "env": {
+ "SQLSERVER_CONNECTION_STRING": "${env:DB_CONNECTION_STRING}"
+ },
+ "description": "SQL Server MCP for database exploration. Connection string must be set via DB_CONNECTION_STRING environment variable — never hardcode credentials."
+ },
+ "postgres": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-postgres"],
+ "env": {
+ "POSTGRES_CONNECTION_STRING": "${env:POSTGRES_CONNECTION_STRING}"
+ },
+ "description": "PostgreSQL MCP server. Set POSTGRES_CONNECTION_STRING env var to connect."
+ }
+ }
+}
diff --git a/.github/copilot-setup-steps.yml b/.github/copilot-setup-steps.yml
new file mode 100644
index 0000000..c0870fe
--- /dev/null
+++ b/.github/copilot-setup-steps.yml
@@ -0,0 +1,37 @@
+# copilot-setup-steps.yml
+# Configures the environment for GitHub Copilot coding agent (cloud agent).
+# These steps run before Copilot begins working on pull requests or issues.
+# See: https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent
+
+steps:
+ # Install the .NET 10 SDK (net10.0 target framework)
+ - name: Setup .NET 10 SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "10.0.x"
+
+ # Node.js is needed for MCP server extensions (npx-based tools)
+ - name: Setup Node.js for MCP extensions
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+
+ # Restore all NuGet packages
+ - name: Restore NuGet packages
+ run: dotnet restore
+
+ # Build the solution to ensure the codebase compiles before Copilot makes changes.
+ # This gives the agent a known-good baseline to diff against.
+ - name: Build solution
+ run: dotnet build --no-restore
+
+ # Set environment variables for the development/CI context.
+ - name: Configure environment variables
+ run: |
+ echo "ASPNETCORE_ENVIRONMENT=Development" >> $GITHUB_ENV
+
+ # Install the csharp-ls language server as a global dotnet tool.
+ # This enables rich code intelligence (go-to-definition, references, diagnostics)
+ # for the Copilot agent when navigating the codebase.
+ - name: Install C# language server
+ run: dotnet tool install --global csharp-ls
diff --git a/.github/docs/README.md b/.github/docs/README.md
new file mode 100644
index 0000000..05e0d98
--- /dev/null
+++ b/.github/docs/README.md
@@ -0,0 +1,27 @@
+# AI Context Documentation Index
+
+This folder contains AI-assistant support docs. Use this file as the quick map for where to load context from.
+
+## Start Here
+
+1. Project overview: `README.md`
+2. Architecture and APIs: `docs/01-architecture/`
+3. Feature behavior: `docs/03-features/`
+4. Security guidance: `docs/04-security/`
+5. Troubleshooting and fixes: `docs/05-troubleshooting/`
+6. Reusable implementation patterns: `docs/06-patterns/`
+7. Deployment runbooks: `docs/02-deployment/`
+
+## AI/Agent-Specific Docs
+
+- Hook lifecycle reference: `.github/docs/hooks-reference.md`
+- Project-wide AI instructions: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`
+- Skill catalog and workflows: `.github/skills/CATALOG.md` and `.github/skills/*/SKILL.md`
+
+## Context-Saving Guidance
+
+- For architecture changes, read `docs/01-architecture/*` first.
+- For feature work, read the matching file in `docs/03-features/*`.
+- For security/compliance-sensitive work, check `docs/04-security/*` before edits.
+- For known incidents, load `docs/05-troubleshooting/*`.
+- When adding new behavior, update the relevant file under `docs/` and keep this index accurate.
diff --git a/.github/docs/hooks-reference.md b/.github/docs/hooks-reference.md
new file mode 100644
index 0000000..69122a6
--- /dev/null
+++ b/.github/docs/hooks-reference.md
@@ -0,0 +1,154 @@
+# Hooks Reference — Copilot CLI vs Claude Code
+
+> Cross-platform hooks comparison for the the project AI development framework.
+> Both platforms fire hooks at lifecycle events. This doc maps events, capabilities, and our implementations.
+
+---
+
+## Hook Events Comparison
+
+### Copilot CLI Hook Events
+
+| Event | When It Fires | Can Block | Available In |
+|-------|--------------|-----------|-------------|
+| `onSessionStart` | Session begins | No | `joinSession()` |
+| `onSessionEnd` | Session terminates | No | `joinSession()` |
+| `onUserPromptSubmitted` | User submits a prompt, before processing | No* | `joinSession()` |
+| `onPreToolUse` | Before a tool call executes | Yes (return `"reject"`) | `joinSession()` |
+| `onPostToolUse` | After a tool call succeeds | No | `joinSession()` |
+| `onErrorOccurred` | When an error occurs during tool execution | No | `joinSession()` |
+
+*\* Can inject `additionalContext` to influence behavior but cannot block the prompt.*
+
+**Implementation:** Node.js ES modules (`.mjs`) in `.github/extensions/*/extension.mjs`
+
+---
+
+### Claude Code Hook Events
+
+| Event | When It Fires | Can Block | Matcher |
+|-------|--------------|-----------|---------|
+| `SessionStart` | Session begins or resumes | No | `startup`, `resume`, `clear`, `compact` |
+| `SessionEnd` | Session terminates | No | `clear`, `resume`, `logout`, etc. |
+| `UserPromptSubmit` | User submits a prompt, before processing | Yes | *(no matcher)* |
+| `PreToolUse` | Before a tool call executes | Yes (`permissionDecision: "deny"`) | Tool name regex: `Bash`, `Edit\|Write` |
+| `PostToolUse` | After a tool call succeeds | No | Tool name regex |
+| `PostToolUseFailure` | After a tool call fails | No | Tool name regex |
+| `PermissionRequest` | Permission dialog appears | Yes | Tool name regex |
+| `PermissionDenied` | Tool call denied by classifier | No (but can `retry: true`) | Tool name regex |
+| `Notification` | Claude sends notification | No | `permission_prompt`, `idle_prompt` |
+| `SubagentStart` | Subagent spawned | No | Agent type: `Bash`, `Explore`, `Plan` |
+| `SubagentStop` | Subagent finishes | Yes | Agent type |
+| `TaskCreated` | Task created via TaskCreate | No | *(no matcher)* |
+| `TaskCompleted` | Task marked complete | No | *(no matcher)* |
+| `Stop` | Claude finishes responding | Yes | *(no matcher)* |
+| `StopFailure` | Turn ends due to API error | No | `rate_limit`, `server_error`, etc. |
+| `TeammateIdle` | Agent team member going idle | No | *(no matcher)* |
+| `InstructionsLoaded` | CLAUDE.md or rules file loads | No | `session_start`, `path_glob_match` |
+| `ConfigChange` | Config file changes mid-session | No | `user_settings`, `project_settings` |
+| `CwdChanged` | Working directory changes (`cd`) | No | *(always fires)* |
+| `FileChanged` | Watched file changes on disk | Yes | Filename (e.g., `.envrc`) |
+| `WorktreeCreate` | Git worktree being created | No | *(no matcher)* |
+| `WorktreeRemove` | Git worktree being removed | No | *(no matcher)* |
+| `PreCompact` | Before context compaction | No | `manual`, `auto` |
+| `PostCompact` | After compaction completes | No | `manual`, `auto` |
+| `Elicitation` | MCP server requests user input | No | MCP server name |
+| `ElicitationResult` | User responds to MCP elicitation | No | MCP server name |
+
+**Implementation:** Shell scripts, HTTP endpoints, LLM prompts, or agent hooks in `.claude/settings.json`
+
+---
+
+## Event Mapping: Copilot CLI ↔ Claude Code
+
+| Copilot CLI Event | Claude Code Equivalent | Notes |
+|---|---|---|
+| `onSessionStart` | `SessionStart` | Direct equivalent |
+| `onSessionEnd` | `SessionEnd` | Direct equivalent |
+| `onUserPromptSubmitted` | `UserPromptSubmit` | Claude can also block prompts |
+| `onPreToolUse` | `PreToolUse` | Both can block; Claude has richer matcher syntax |
+| `onPostToolUse` | `PostToolUse` | Direct equivalent |
+| `onErrorOccurred` | `PostToolUseFailure` / `StopFailure` | Claude splits into tool vs API errors |
+| *(none)* | `PermissionRequest` | Claude-only: intercept permission dialogs |
+| *(none)* | `SubagentStart` / `SubagentStop` | Claude-only: subagent lifecycle |
+| *(none)* | `InstructionsLoaded` | Claude-only: react to config loading |
+| *(none)* | `PreCompact` / `PostCompact` | Claude-only: context compaction hooks |
+| *(none)* | `FileChanged` | Claude-only: file watcher hooks |
+| *(none)* | `CwdChanged` | Claude-only: directory change hooks |
+| *(none)* | `Notification` | Claude-only: notification interception |
+| *(none)* | `TaskCreated` / `TaskCompleted` | Claude-only: task lifecycle |
+| *(none)* | `Stop` | Claude-only: validate before turn ends |
+
+---
+
+## Our Implementations
+
+### Copilot CLI Extensions (`.github/extensions/`)
+
+| Extension | Hooks Used | Purpose |
+|-----------|-----------|---------|
+| **security-scanner** | `onPreToolUse`, `onPostToolUse`, `onUserPromptSubmitted` | Blocks secrets in writes, OWASP reminders, payment/auth context |
+| **build-guardian** | `onPostToolUse` | Tracks modified `.cs` files, reminds to validate build |
+| **context-optimizer** | `onSessionStart`, `onUserPromptSubmitted` | Injects project summary, warns on long prompts |
+| **doc-sync** | `onSessionStart`, `onPostToolUse` | Reminds to update docs when source changes |
+| **dotnet-conventions** | `onSessionStart`, `onPostToolUse` | Checks `.cs`/`.razor` conventions after edits |
+| **research-first** | `onSessionStart`, `onUserPromptSubmitted` | Injects "read docs first" before implementation |
+
+### Claude Code Hooks (`.claude/settings.json` + `.claude/hooks/`)
+
+| Hook Script | Event | Matcher | Purpose |
+|-------------|-------|---------|---------|
+| **security-scanner.ps1** | `PreToolUse` | `Edit\|Write\|MultiEdit` | Blocks hardcoded secrets, API keys, connection strings |
+| **dotnet-conventions.ps1** | `PostToolUse` | `Edit\|Write\|MultiEdit` | Checks code-behind, namespaces, scoped CSS |
+| **doc-sync-reminder.ps1** | `PostToolUse` | `Edit\|Write\|MultiEdit` | Reminds to update docs for source changes |
+| **build-reminder.ps1** | `PostToolUse` | `Edit\|Write\|MultiEdit` | Reminds to verify build after `.cs` changes |
+| **research-first.ps1** | `UserPromptSubmit` | *(all)* | Injects research-first guidance |
+| **context-optimizer.ps1** | `SessionStart` | *(all)* | Injects project architecture context |
+
+---
+
+## Configuration Locations
+
+| Platform | Config File | Hook Scripts |
+|----------|------------|-------------|
+| **Copilot CLI** | `.github/extensions/*/extension.mjs` | Inline (Node.js ES modules) |
+| **Claude Code** | `.claude/settings.json` → `hooks` | `.claude/hooks/*.ps1` (Windows) or `.sh` (Linux/Mac) |
+
+---
+
+## Key Differences
+
+| Feature | Copilot CLI | Claude Code |
+|---------|------------|-------------|
+| **Language** | JavaScript (ES modules, `.mjs`) | Any (shell, PowerShell, HTTP, LLM prompt) |
+| **Hook types** | Code callbacks only | `command`, `http`, `prompt`, `agent` |
+| **Blocking** | `onPreToolUse` returns `"reject"` | `PreToolUse` outputs `permissionDecision: "deny"` |
+| **Context injection** | Return `{ additionalContext: "..." }` | Output `{ "additionalContext": "..." }` JSON |
+| **Custom tools** | `registerTool()` in extension | Not in hooks (use MCP servers instead) |
+| **Matcher syntax** | Programmatic (`if` statements in code) | Regex on tool name + `if` field for arguments |
+| **Total events** | 6 | 27 |
+| **Async hooks** | All async by nature (Node.js) | `async: true` flag for background execution |
+| **Discovery** | `.github/extensions/*/extension.mjs` | `.claude/settings.json` + script paths |
+| **SDK** | `@github/copilot-sdk/extension` | stdin/stdout JSON protocol |
+
+---
+
+## Adding New Hooks
+
+### Copilot CLI
+
+1. Create `.github/extensions/{name}/extension.mjs`
+2. Import `joinSession` from `@github/copilot-sdk/extension`
+3. Register hooks in the `joinSession()` call
+4. Optionally register tools with `registerTool()`
+
+### Claude Code
+
+1. Create script in `.claude/hooks/{name}.ps1` (Windows) or `.sh` (Linux/Mac)
+2. Add hook entry to `.claude/settings.json` under the appropriate event
+3. Script reads JSON from stdin, outputs JSON to stdout
+4. Use `exit 0` for no action, output JSON for context/decisions
+
+---
+
+*Maintained as part of the the project AI Development Framework*
diff --git a/.github/extensions/README.md b/.github/extensions/README.md
new file mode 100644
index 0000000..bb26e06
--- /dev/null
+++ b/.github/extensions/README.md
@@ -0,0 +1,71 @@
+# Copilot CLI Extensions
+
+Custom JavaScript modules that extend GitHub Copilot CLI with hooks and tools.
+
+## At a Glance
+
+| Aspect | Detail |
+|--------|--------|
+| **SDK** | `@github/copilot-sdk/extension` — `joinSession()` API |
+| **Runtime** | Node.js, ES modules (`.mjs`) |
+| **Entry point** | `extension.mjs` per folder |
+| **Capabilities** | `hooks` (session/tool lifecycle) and `tools` (custom tool definitions) |
+| **Auto-load** | Extensions load on session start; run `extensions_reload` to pick up changes |
+
+## Current Extensions
+
+| Extension | Purpose |
+|-----------|---------|
+| `build-guardian` | Enforces build verification before commits |
+| `context-optimizer` | Provides `project_summary` and `check_docs` tools for efficient context loading |
+| `doc-sync` | Config-driven reminders to keep `docs/` and planning docs in sync with code changes |
+| `dotnet-conventions` | `check_conventions` tool for .NET coding standards enforcement |
+| `research-first` | Encourages codebase exploration before making changes |
+| `security-scanner` | OWASP security scanning tools (`owasp_security_scan`, `check_secrets`) |
+| `superpowers` | Workflow skill catalog and loader (`superpowers_catalog`, `superpowers_skill`) |
+
+## File Structure
+
+```
+extensions/
+└── my-extension/
+ ├── extension.mjs ← Entry point (required)
+ └── config.json ← Optional configuration
+```
+
+## How to Create a New Extension
+
+1. **Scaffold:** Use `extensions_manage scaffold` with a name and description, or create the folder manually.
+2. **Implement:** Export a default function that calls `joinSession()`, registering `hooks` and/or `tools`.
+3. **Reload:** Run `extensions_reload` to activate without restarting the CLI.
+
+```js
+// extension.mjs — minimal example
+import { joinSession } from "@github/copilot-sdk/extension";
+
+joinSession({
+ hooks: {
+ onSessionStart: async (ctx) => { /* ... */ },
+ },
+ tools: {
+ my_tool: {
+ description: "Does something useful",
+ parameters: { /* JSON Schema */ },
+ execute: async (params) => { /* ... */ },
+ },
+ },
+});
+```
+
+## Key Rules
+
+- Extensions run in a **Node.js context** — they have access to `process.cwd()` and the filesystem.
+- Extensions do **not** have access to the agent's conversation or chat context directly.
+- Use ES module syntax (`import`/`export`) — CommonJS (`require`) is not supported.
+- Keep extensions focused — one concern per extension.
+
+## See Also
+
+- [`.github/skills/`](../skills/) — Reusable AI workflow methodologies (markdown, not code)
+- [`.github/instructions/`](../instructions/) — Pattern-matched context injection for AI agents
+- [`.github/hooks/`](../hooks/) — Git hooks (trigger on git events, not AI events)
diff --git a/.github/extensions/build-guardian/extension.mjs b/.github/extensions/build-guardian/extension.mjs
new file mode 100644
index 0000000..316d611
--- /dev/null
+++ b/.github/extensions/build-guardian/extension.mjs
@@ -0,0 +1,218 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { execFile } from "node:child_process";
+import { readdirSync } from "node:fs";
+import { readdir } from "node:fs/promises";
+import { resolve, extname } from "node:path";
+
+const SOLUTION_NAME = "EscrowApp.sln";
+const BUILD_TIMEOUT = 120_000;
+const TEST_TIMEOUT = 120_000;
+const MAX_BUFFER = 1024 * 1024 * 5; // 5MB
+
+const modifiedFiles = new Set();
+let lastReminderTime = 0;
+const REMINDER_COOLDOWN = 30_000; // Only remind every 30 seconds
+
+function findSolutionRoot() {
+ // Walk up from cwd to find the .sln file
+ let dir = process.cwd();
+ const root = resolve(dir, "/");
+ while (dir !== root) {
+ try {
+ const files = readdirSync(dir);
+ if (files.includes(SOLUTION_NAME)) return dir;
+ } catch {
+ // Skip
+ }
+ dir = resolve(dir, "..");
+ }
+ return process.cwd();
+}
+
+function runDotnetCommand(args, timeoutMs) {
+ const solutionRoot = findSolutionRoot();
+ const solutionPath = resolve(solutionRoot, SOLUTION_NAME);
+
+ return new Promise((res) => {
+ const child = execFile("dotnet", [...args, solutionPath], {
+ cwd: solutionRoot,
+ timeout: timeoutMs,
+ maxBuffer: MAX_BUFFER,
+ windowsHide: true,
+ }, (err, stdout, stderr) => {
+ const output = (stdout || "") + (stderr || "");
+ if (err) {
+ if (err.killed) {
+ res({ success: false, output: `Command timed out after ${timeoutMs / 1000}s.\n${output}` });
+ } else {
+ res({ success: false, output });
+ }
+ } else {
+ res({ success: true, output });
+ }
+ });
+ });
+}
+
+function isWatchedFile(filePath) {
+ if (!filePath || typeof filePath !== "string") return false;
+ const ext = extname(filePath).toLowerCase();
+ return ext === ".cs" || ext === ".csproj" || ext === ".razor";
+}
+
+async function hasTestProjects(solutionRoot) {
+ try {
+ const entries = await readdir(solutionRoot, { withFileTypes: true });
+ for (const entry of entries) {
+ if (entry.isDirectory() && entry.name.toLowerCase().includes("test")) {
+ return true;
+ }
+ }
+ // Also check if dotnet test would find anything by looking for test csproj references
+ return false;
+ } catch {
+ return false;
+ }
+}
+
+const session = await joinSession({
+ hooks: {
+ onPostToolUse: async (input) => {
+ const toolName = input.toolName;
+ if (toolName !== "create" && toolName !== "edit") return;
+
+ const args = input.toolArgs;
+ if (!args || typeof args !== "object") return;
+
+ const filePath = args.path;
+ if (!isWatchedFile(filePath)) return;
+
+ modifiedFiles.add(filePath);
+
+ const now = Date.now();
+ if (now - lastReminderTime < REMINDER_COOLDOWN) return;
+ lastReminderTime = now;
+
+ const fileList = [...modifiedFiles].map(f => ` - ${f}`).join("\n");
+ return {
+ additionalContext: `🏗️ Build Guardian: ${modifiedFiles.size} file(s) modified since last build check:\n${fileList}\nRemember to verify the build compiles after these changes. Use the project_dotnet_build_check tool to validate.`,
+ };
+ },
+ },
+
+ tools: [
+ {
+ name: "project_dotnet_build_check",
+ description: "Runs 'dotnet build' on the EscrowApp.sln solution and returns a structured result. Returns 'Build succeeded' on success or detailed error messages on failure.",
+ parameters: {
+ type: "object",
+ properties: {},
+ additionalProperties: false,
+ },
+ handler: async () => {
+ await session.log("🏗️ Running dotnet build...", { ephemeral: true });
+
+ const result = await runDotnetCommand(["build", "--no-restore", "--verbosity", "minimal"], BUILD_TIMEOUT);
+
+ // Clear tracked files on successful build
+ if (result.success) {
+ const count = modifiedFiles.size;
+ modifiedFiles.clear();
+ await session.log("✅ Build succeeded", { ephemeral: true });
+ return `Build succeeded. ${count > 0 ? `(${count} pending file change(s) verified)` : ""}`;
+ }
+
+ await session.log("❌ Build failed", { level: "warning", ephemeral: true });
+
+ // Extract error lines for concise output
+ const lines = result.output.split("\n");
+ const errors = lines.filter(l => /:\s*error\s+\w+/i.test(l));
+ const warnings = lines.filter(l => /:\s*warning\s+\w+/i.test(l));
+
+ let output = "Build FAILED.\n\n";
+ if (errors.length > 0) {
+ output += `### Errors (${errors.length})\n`;
+ output += errors.slice(0, 20).join("\n");
+ if (errors.length > 20) output += `\n... and ${errors.length - 20} more errors`;
+ output += "\n\n";
+ }
+ if (warnings.length > 0) {
+ output += `### Warnings (${warnings.length})\n`;
+ output += warnings.slice(0, 10).join("\n");
+ if (warnings.length > 10) output += `\n... and ${warnings.length - 10} more warnings`;
+ }
+ if (errors.length === 0 && warnings.length === 0) {
+ output += result.output.substring(0, 2000);
+ }
+
+ return { textResultForLlm: output, resultType: "failure" };
+ },
+ },
+ {
+ name: "project_dotnet_test_check",
+ description: "Runs 'dotnet test' on the EscrowApp.sln solution. Returns test results summary on success or failing test details on failure. Reports if no test projects exist.",
+ parameters: {
+ type: "object",
+ properties: {},
+ additionalProperties: false,
+ },
+ handler: async () => {
+ const solutionRoot = findSolutionRoot();
+
+ // Check for test projects first
+ const hasTests = await hasTestProjects(solutionRoot);
+ if (!hasTests) {
+ await session.log("ℹ️ No test projects detected", { ephemeral: true });
+ }
+
+ await session.log("🧪 Running dotnet test...", { ephemeral: true });
+
+ const result = await runDotnetCommand(["test", "--no-build", "--verbosity", "minimal"], TEST_TIMEOUT);
+
+ if (result.success) {
+ // Extract test count from output
+ const totalMatch = result.output.match(/Passed!\s*-\s*Failed:\s*(\d+),\s*Passed:\s*(\d+)/i)
+ || result.output.match(/Total tests:\s*(\d+)/i);
+
+ let summary = "All tests passed";
+ if (totalMatch) {
+ summary += ` (${totalMatch[0].trim()})`;
+ }
+
+ // Check for "no test" scenarios
+ if (/No test is available/i.test(result.output) || /No test matches/i.test(result.output)) {
+ await session.log("ℹ️ No tests found in solution", { ephemeral: true });
+ return "No test projects or test methods found in the solution. Consider adding a test project (e.g., EscrowApp.Tests) with xUnit or NUnit.";
+ }
+
+ await session.log("✅ Tests passed", { ephemeral: true });
+ return summary;
+ }
+
+ await session.log("❌ Tests failed", { level: "warning", ephemeral: true });
+
+ const lines = result.output.split("\n");
+ const failedTests = lines.filter(l => /Failed\s+\w+/i.test(l) || /✗|×/.test(l));
+ const errorLines = lines.filter(l => /:\s*error\s+/i.test(l));
+
+ let output = "Tests FAILED.\n\n";
+ if (failedTests.length > 0) {
+ output += `### Failed Tests (${failedTests.length})\n`;
+ output += failedTests.slice(0, 20).join("\n");
+ output += "\n\n";
+ }
+ if (errorLines.length > 0) {
+ output += `### Errors\n`;
+ output += errorLines.slice(0, 10).join("\n");
+ }
+ if (failedTests.length === 0 && errorLines.length === 0) {
+ output += result.output.substring(0, 2000);
+ }
+
+ return { textResultForLlm: output, resultType: "failure" };
+ },
+ },
+ ],
+});
+
+await session.log("🏗️ Build Guardian loaded");
diff --git a/.github/extensions/context-optimizer/extension.mjs b/.github/extensions/context-optimizer/extension.mjs
new file mode 100644
index 0000000..84b19a7
--- /dev/null
+++ b/.github/extensions/context-optimizer/extension.mjs
@@ -0,0 +1,81 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+
+const PROJECT_SUMMARY = `## NexTruzt.io EscrowApp — Project Summary
+
+**Stack:** .NET 10 · Blazor Server · EF Core · PostgreSQL · MediatR · FluentValidation
+
+### Architecture (Clean Architecture + CQRS)
+\`\`\`
+┌─────────────────────────────────────────────────┐
+│ Components/ (Blazor UI — code-behind pattern) │
+│ Pages/, Layout/, Shared/ │
+├─────────────────────────────────────────────────┤
+│ Features/ (Application — CQRS handlers) │
+│ Commands/, Queries/, Validators/ │
+├─────────────────────────────────────────────────┤
+│ Models/ + Events/ (Domain layer) │
+│ Entities, Value Objects, Domain Events │
+├─────────────────────────────────────────────────┤
+│ Data/ (Infrastructure — EF Core + PostgreSQL) │
+│ DbContext, Repositories, Migrations/ │
+├─────────────────────────────────────────────────┤
+│ Services/ + Infrastructure/ │
+│ External integrations, Payment gateways │
+└─────────────────────────────────────────────────┘
+\`\`\`
+
+### Key Patterns
+- **Payment Strategy:** IFundHoldable / IFundReleasable / IFundCancellable interfaces
+- **CQRS:** MediatR command/query separation
+- **Code-behind:** All Blazor components use .razor + .razor.cs (never inline @code)
+- **Scoped CSS:** Every component has .razor.css
+- **Validation:** FluentValidation on all commands
+- **Resilience:** Polly retry + circuit breaker on external calls
+- **Security:** OWASP-first, idempotency keys on payments, [Authorize] everywhere
+
+### Key Files
+- \`EscrowApp.sln\` — Solution root
+- \`EscrowApp/Program.cs\` — App bootstrap + DI
+- \`EscrowApp/Data/\` — EF Core DbContext + repositories
+- \`EscrowApp/Models/\` — Domain entities + value objects
+- \`EscrowApp/Features/\` — CQRS handlers (commands + queries)
+- \`EscrowApp/Components/\` — Blazor pages + shared components
+- \`EscrowApp/Services/\` — Business services + payment integration
+- \`EscrowApp/docs/\` — Architecture + API documentation (keep in sync)`;
+
+const session = await joinSession({
+ hooks: {
+ onSessionStart: async () => {
+ await session.log("📋 Context Optimizer loaded", { ephemeral: true });
+ return {
+ additionalContext: "NexTruzt.io EscrowApp: .NET 10 Blazor Server fintech escrow. Clean Architecture + CQRS/MediatR. Layers: Components/ (UI) → Features/ (handlers) → Models/Events (domain) ← Data/ (EF Core/PostgreSQL). Payment strategies: IFundHoldable/IFundReleasable/IFundCancellable. Key: code-behind required, docs/ must stay in sync, OWASP security-first, idempotency keys on payments.",
+ };
+ },
+
+ onUserPromptSubmitted: async (input) => {
+ const prompt = input.prompt;
+ if (!prompt || typeof prompt !== "string") return;
+
+ if (prompt.length > 2000) {
+ return {
+ additionalContext: "Note: The user's prompt is quite long. Be efficient with context usage — prefer concise responses and avoid repeating the prompt back. If the conversation is getting long, suggest the user use /compact to optimize the context window.",
+ };
+ }
+ },
+ },
+
+ tools: [
+ {
+ name: "cloudzen_project_summary",
+ description: "Returns a concise, structured summary of the NexTruzt.io EscrowApp project including architecture diagram, key files, design patterns, and technology stack. Use this to quickly orient yourself without reading multiple files.",
+ parameters: {
+ type: "object",
+ properties: {},
+ additionalProperties: false,
+ },
+ handler: async () => {
+ return PROJECT_SUMMARY;
+ },
+ },
+ ],
+});
diff --git a/.github/extensions/doc-sync/extension.mjs b/.github/extensions/doc-sync/extension.mjs
new file mode 100644
index 0000000..1de34e2
--- /dev/null
+++ b/.github/extensions/doc-sync/extension.mjs
@@ -0,0 +1,201 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { existsSync, statSync, readdirSync } from "node:fs";
+import { join, resolve, relative } from "node:path";
+
+// Maps source path segments to their corresponding docs/ folder
+const FEATURE_MAP = [
+ { pattern: /Features[/\\]Escrow[/\\]HoldFunds/i, doc: "01-Escrow-Hold-Funds" },
+ { pattern: /Features[/\\]Escrow[/\\]CreateAndHoldFunds/i, doc: "01-Escrow-Hold-Funds" },
+ { pattern: /Features[/\\]Escrow[/\\]ReleaseFunds/i, doc: "02-Escrow-Release-Funds" },
+ { pattern: /Features[/\\]Escrow[/\\]DisputeFunds/i, doc: "03-Escrow-Dispute-Funds" },
+ { pattern: /Services[/\\]Strategies/i, doc: "04-Payment-Strategies" },
+ { pattern: /Services[/\\]/i, doc: "04-Payment-Strategies" },
+ { pattern: /Infrastructure[/\\]Auth/i, doc: "05-Hybrid-Identity" },
+ { pattern: /Events[/\\]/i, doc: "06-Event-Bus" },
+ { pattern: /Resources[/\\]/i, doc: "07-Localization" },
+ { pattern: /Components[/\\]Pages[/\\]/i, doc: "08-Landing-Page-UI" },
+ { pattern: /Features[/\\]Escrow[/\\]Api/i, doc: "09-API-Integration" },
+ { pattern: /Features[/\\]Escrow[/\\]GetTransaction/i, doc: "09-API-Integration" },
+ { pattern: /Features[/\\]Escrow[/\\]ListTransactions/i, doc: "09-API-Integration" },
+ { pattern: /Infrastructure[/\\]Middleware/i, doc: "09-API-Integration" },
+];
+
+const WATCHED_DIRS =
+ /[/\\](Features|Services|Models|Events|Components|Infrastructure|Resources)[/\\]/i;
+
+// Deduplication: track last reminder time per doc target
+const lastReminder = new Map();
+const REMINDER_COOLDOWN_MS = 60_000;
+
+function findAppRoot(cwd) {
+ const candidates = [
+ join(cwd, "EscrowApp"),
+ cwd,
+ ];
+ for (const candidate of candidates) {
+ if (existsSync(join(candidate, "docs")) && existsSync(join(candidate, "EscrowApp.csproj"))) {
+ return candidate;
+ }
+ }
+ // Fallback: check if docs/ exists at cwd/EscrowApp
+ if (existsSync(join(cwd, "EscrowApp", "docs"))) {
+ return join(cwd, "EscrowApp");
+ }
+ return undefined;
+}
+
+function mapFileToDoc(filePath) {
+ const normalized = filePath.replace(/\\/g, "/");
+ for (const entry of FEATURE_MAP) {
+ if (entry.pattern.test(normalized)) {
+ return entry.doc;
+ }
+ }
+ return "00-Architecture-Overview";
+}
+
+function getLatestMtime(dirPath) {
+ let latest = 0;
+ if (!existsSync(dirPath)) return latest;
+
+ try {
+ const entries = readdirSync(dirPath, { withFileTypes: true });
+ for (const entry of entries) {
+ const fullPath = join(dirPath, entry.name);
+ try {
+ if (entry.isDirectory()) {
+ const childMtime = getLatestMtime(fullPath);
+ if (childMtime > latest) latest = childMtime;
+ } else if (entry.isFile()) {
+ const mtime = statSync(fullPath).mtimeMs;
+ if (mtime > latest) latest = mtime;
+ }
+ } catch {
+ // Skip inaccessible entries
+ }
+ }
+ } catch {
+ // Skip inaccessible directories
+ }
+ return latest;
+}
+
+// Source directories and their doc mappings for the docs_status tool
+const STATUS_MAP = [
+ { label: "Escrow Hold Funds", srcDir: "Features/Escrow/HoldFunds", doc: "01-Escrow-Hold-Funds" },
+ { label: "Escrow Release Funds", srcDir: "Features/Escrow/ReleaseFunds", doc: "02-Escrow-Release-Funds" },
+ { label: "Escrow Dispute Funds", srcDir: "Features/Escrow/DisputeFunds", doc: "03-Escrow-Dispute-Funds" },
+ { label: "Payment Strategies", srcDir: "Services/Strategies", doc: "04-Payment-Strategies" },
+ { label: "Hybrid Identity", srcDir: "Infrastructure/Auth", doc: "05-Hybrid-Identity" },
+ { label: "Event Bus", srcDir: "Events", doc: "06-Event-Bus" },
+ { label: "Localization", srcDir: "Resources", doc: "07-Localization" },
+ { label: "Landing Page UI", srcDir: "Components/Pages", doc: "08-Landing-Page-UI" },
+ { label: "API Integration", srcDir: "Features/Escrow/Api", doc: "09-API-Integration" },
+];
+
+function formatTimestamp(ms) {
+ if (ms === 0) return "N/A";
+ return new Date(ms).toISOString().replace("T", " ").substring(0, 19);
+}
+
+const session = await joinSession({
+ hooks: {
+ onSessionStart: async () => {
+ await session.log("Doc-Sync extension loaded");
+ },
+
+ onPostToolUse: async (input) => {
+ if (input.toolName !== "edit" && input.toolName !== "create") {
+ return undefined;
+ }
+
+ const filePath = typeof input.toolArgs?.path === "string"
+ ? input.toolArgs.path
+ : undefined;
+ if (!filePath) return undefined;
+
+ // Only watch relevant directories
+ if (!WATCHED_DIRS.test(filePath)) return undefined;
+
+ const docFolder = mapFileToDoc(filePath);
+
+ // Deduplicate reminders
+ const now = Date.now();
+ const lastTime = lastReminder.get(docFolder) || 0;
+ if (now - lastTime < REMINDER_COOLDOWN_MS) return undefined;
+ lastReminder.set(docFolder, now);
+
+ return {
+ additionalContext: [
+ `DOCS SYNC REQUIRED: You modified code related to "${docFolder}".`,
+ `Per project rules, the corresponding docs/${docFolder}/README.md must be updated to reflect these changes.`,
+ "Check if documentation needs updating before moving on.",
+ ].join(" "),
+ };
+ },
+ },
+
+ tools: [
+ {
+ name: "cloudzen_docs_status",
+ description:
+ "Compares last-modified timestamps of source code directories vs their corresponding docs/ README.md files. Reports which docs are potentially stale.",
+ parameters: {
+ type: "object",
+ properties: {},
+ },
+ handler: async () => {
+ const cwd = process.cwd();
+ const appRoot = findAppRoot(cwd);
+
+ if (!appRoot) {
+ return "Could not locate EscrowApp directory. Searched from: " + cwd;
+ }
+
+ const docsRoot = join(appRoot, "docs");
+ const lines = [
+ "# Documentation Freshness Report",
+ "",
+ `App root: ${appRoot}`,
+ "",
+ "Feature Area | Source Last Modified | Docs Last Modified | Status",
+ "-----------------------|------------------------|------------------------|------------------",
+ ];
+
+ for (const entry of STATUS_MAP) {
+ const srcPath = join(appRoot, ...entry.srcDir.split("/"));
+ const docReadme = join(docsRoot, entry.doc, "README.md");
+
+ const srcMtime = getLatestMtime(srcPath);
+
+ let docMtime = 0;
+ try {
+ if (existsSync(docReadme)) {
+ docMtime = statSync(docReadme).mtimeMs;
+ }
+ } catch {
+ // Not accessible
+ }
+
+ let status;
+ if (srcMtime === 0) {
+ status = "no source";
+ } else if (docMtime === 0) {
+ status = "MISSING DOCS";
+ } else if (srcMtime > docMtime) {
+ status = "potentially-stale";
+ } else {
+ status = "up-to-date";
+ }
+
+ const label = entry.label.padEnd(23);
+ const srcTs = formatTimestamp(srcMtime).padEnd(24);
+ const docTs = formatTimestamp(docMtime).padEnd(24);
+ lines.push(`${label}| ${srcTs}| ${docTs}| ${status}`);
+ }
+
+ return lines.join("\n");
+ },
+ },
+ ],
+});
diff --git a/.github/extensions/dotnet-conventions/extension.mjs b/.github/extensions/dotnet-conventions/extension.mjs
new file mode 100644
index 0000000..7b97885
--- /dev/null
+++ b/.github/extensions/dotnet-conventions/extension.mjs
@@ -0,0 +1,213 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
+import { join, basename, extname, resolve } from "node:path";
+
+const EXCLUDED_DIRS = new Set(["bin", "obj", "node_modules", ".git", "Migrations"]);
+const EXCLUDED_FILES = /\.(g|Designer|AssemblyInfo)\.cs$/i;
+const TOPLEVEL_EXCEPTIONS = new Set(["Program.cs"]);
+
+function checkCsConventions(filePath, content) {
+ const findings = [];
+ const fileName = basename(filePath);
+
+ // Skip known exceptions
+ if (TOPLEVEL_EXCEPTIONS.has(fileName)) return findings;
+
+ // 1. File-scoped namespace check: detect block-scoped `namespace X\n{` or `namespace X {`
+ const blockNamespace = /^namespace\s+[\w.]+\s*\r?\n?\s*\{/m;
+ if (blockNamespace.test(content)) {
+ findings.push("Uses block-scoped namespace. Convert to file-scoped namespace (namespace X;).");
+ }
+
+ // 2. .razor.cs must declare partial class
+ if (filePath.endsWith(".razor.cs")) {
+ const hasPartial = /\bpartial\s+class\b/i.test(content);
+ if (!hasPartial) {
+ findings.push("Code-behind file (.razor.cs) must declare a partial class.");
+ }
+ }
+
+ // 3. Nullable reference types (check for #nullable enable or nullable annotation)
+ // Only flag if there's a namespace (real source file, not top-level Program.cs)
+ if (/\bnamespace\b/.test(content) && !/#nullable\s+enable/.test(content)) {
+ // Not necessarily a violation if enabled in .csproj, note as advisory
+ findings.push("Advisory: No #nullable enable directive found. Ensure enable is set in .csproj.");
+ }
+
+ // 4. Class name should match file name (for non-razor.cs files)
+ if (!filePath.endsWith(".razor.cs")) {
+ const expectedName = fileName.replace(/\.cs$/, "");
+ const classDecl = /\bclass\s+(\w+)/.exec(content);
+ if (classDecl && classDecl[1] !== expectedName) {
+ findings.push(`Class name "${classDecl[1]}" does not match file name "${fileName}".`);
+ }
+ }
+
+ return findings;
+}
+
+function checkRazorConventions(filePath, content) {
+ const findings = [];
+
+ // 1. @code blocks — should use code-behind pattern
+ if (/@code\s*\{/i.test(content)) {
+ findings.push("Contains @code block. Use code-behind pattern (.razor + .razor.cs) instead.");
+ }
+
+ // 2. Inline styles
+ if (/\bstyle\s*=\s*"/i.test(content)) {
+ findings.push("Contains inline style attribute. Use scoped CSS (.razor.css) instead.");
+ }
+
+ return findings;
+}
+
+function checkFile(filePath) {
+ try {
+ const content = readFileSync(filePath, "utf-8");
+ const ext = extname(filePath).toLowerCase();
+
+ if (ext === ".cs") {
+ return checkCsConventions(filePath, content);
+ }
+ if (ext === ".razor") {
+ return checkRazorConventions(filePath, content);
+ }
+ } catch {
+ return [`Could not read file: ${filePath}`];
+ }
+ return [];
+}
+
+function walkDirectory(dirPath, results) {
+ if (!existsSync(dirPath)) return;
+
+ try {
+ const entries = readdirSync(dirPath, { withFileTypes: true });
+ for (const entry of entries) {
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
+
+ const fullPath = join(dirPath, entry.name);
+ if (entry.isDirectory()) {
+ walkDirectory(fullPath, results);
+ } else if (entry.isFile()) {
+ if (EXCLUDED_FILES.test(entry.name)) continue;
+ const ext = extname(entry.name).toLowerCase();
+ if (ext === ".cs" || ext === ".razor") {
+ const findings = checkFile(fullPath);
+ if (findings.length > 0) {
+ results.push({ file: fullPath, findings });
+ }
+ }
+ }
+ }
+ } catch {
+ // Skip inaccessible directories
+ }
+}
+
+const session = await joinSession({
+ hooks: {
+ onSessionStart: async () => {
+ await session.log("DotNet Conventions extension loaded");
+ },
+
+ onPostToolUse: async (input) => {
+ if (input.toolName !== "edit" && input.toolName !== "create") {
+ return undefined;
+ }
+
+ const filePath = typeof input.toolArgs?.path === "string"
+ ? input.toolArgs.path
+ : undefined;
+ if (!filePath) return undefined;
+
+ const ext = extname(filePath).toLowerCase();
+ if (ext !== ".cs" && ext !== ".razor") return undefined;
+
+ // Skip excluded files
+ const fileName = basename(filePath);
+ if (TOPLEVEL_EXCEPTIONS.has(fileName)) return undefined;
+ if (EXCLUDED_FILES.test(fileName)) return undefined;
+
+ try {
+ const findings = checkFile(filePath);
+ if (findings.length === 0) return undefined;
+
+ return {
+ additionalContext: [
+ `CONVENTION VIOLATIONS in ${fileName}:`,
+ ...findings.map((f, i) => ` ${i + 1}. ${f}`),
+ "",
+ "Please fix these violations to comply with project .NET conventions.",
+ ].join("\n"),
+ };
+ } catch {
+ // If file can't be read, skip silently
+ return undefined;
+ }
+ },
+ },
+
+ tools: [
+ {
+ name: "cloudzen_check_conventions",
+ description:
+ "Checks .NET coding conventions on a file or directory. Scans .cs and .razor files for: file-scoped namespaces, code-behind pattern, partial class declarations, inline styles, and class naming.",
+ parameters: {
+ type: "object",
+ properties: {
+ path: {
+ type: "string",
+ description: "Absolute path to a file or directory to check.",
+ },
+ },
+ required: ["path"],
+ },
+ handler: async (args) => {
+ const targetPath = args.path;
+
+ if (!existsSync(targetPath)) {
+ return `Path does not exist: ${targetPath}`;
+ }
+
+ const stat = statSync(targetPath);
+ const results = [];
+
+ if (stat.isFile()) {
+ const ext = extname(targetPath).toLowerCase();
+ if (ext !== ".cs" && ext !== ".razor") {
+ return `Not a .cs or .razor file: ${targetPath}`;
+ }
+ const findings = checkFile(targetPath);
+ if (findings.length > 0) {
+ results.push({ file: targetPath, findings });
+ }
+ } else if (stat.isDirectory()) {
+ walkDirectory(targetPath, results);
+ }
+
+ if (results.length === 0) {
+ return "✓ No convention violations found.";
+ }
+
+ const lines = [
+ "# Convention Check Results",
+ "",
+ `Files with violations: ${results.length}`,
+ "",
+ ];
+
+ for (const r of results) {
+ lines.push(`## ${r.file}`);
+ for (const f of r.findings) {
+ lines.push(` - ${f}`);
+ }
+ lines.push("");
+ }
+
+ return lines.join("\n");
+ },
+ },
+ ],
+});
diff --git a/.github/extensions/research-first/extension.mjs b/.github/extensions/research-first/extension.mjs
new file mode 100644
index 0000000..7b9bd72
--- /dev/null
+++ b/.github/extensions/research-first/extension.mjs
@@ -0,0 +1,139 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
+import { join, resolve } from "node:path";
+
+const IMPLEMENTATION_KEYWORDS =
+ /\b(create|implement|add|build|write|refactor|change|modify|update)\b/i;
+
+const RESEARCH_KEYWORDS =
+ /\b(explore|search|find|understand|analyze|review|read|explain)\b|what is|how does/i;
+
+function findDocsRoot(cwd) {
+ const candidates = [
+ join(cwd, "EscrowApp", "docs"),
+ join(cwd, "docs"),
+ ];
+ for (const candidate of candidates) {
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
+ return candidate;
+ }
+ }
+ return undefined;
+}
+
+function listDocFolders(docsRoot) {
+ const entries = readdirSync(docsRoot, { withFileTypes: true });
+ return entries
+ .filter((e) => e.isDirectory() && /^\d{2}-/.test(e.name))
+ .map((e) => {
+ const readmePath = join(docsRoot, e.name, "README.md");
+ const hasReadme = existsSync(readmePath);
+ return { folder: e.name, readmePath, hasReadme };
+ })
+ .sort((a, b) => a.folder.localeCompare(b.folder));
+}
+
+function searchDocs(docsRoot, term) {
+ const folders = listDocFolders(docsRoot);
+ const matches = [];
+ const lowerTerm = term.toLowerCase();
+
+ for (const entry of folders) {
+ if (!entry.hasReadme) continue;
+ try {
+ const content = readFileSync(entry.readmePath, "utf-8");
+ const lines = content.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].toLowerCase().includes(lowerTerm)) {
+ matches.push({
+ doc: entry.folder,
+ line: i + 1,
+ text: lines[i].trim().substring(0, 200),
+ });
+ }
+ }
+ } catch {
+ // Skip unreadable files
+ }
+ }
+ return matches;
+}
+
+const session = await joinSession({
+ hooks: {
+ onSessionStart: async () => {
+ await session.log("Research-First extension loaded");
+ },
+
+ onUserPromptSubmitted: async (input) => {
+ if (!input.prompt) return undefined;
+
+ // Skip injection if prompt already contains research intent
+ if (RESEARCH_KEYWORDS.test(input.prompt)) return undefined;
+
+ // Inject only when implementation intent is detected
+ if (IMPLEMENTATION_KEYWORDS.test(input.prompt)) {
+ return {
+ additionalContext: [
+ "RESEARCH-FIRST PRINCIPLE: Before making changes, explore the existing codebase to understand current patterns.",
+ "Check docs/ for feature documentation (use the check_docs tool if needed).",
+ "Understand the layer this change belongs to (Domain/Application/Infrastructure/Presentation).",
+ "Verify existing tests and patterns before creating new code.",
+ ].join(" "),
+ };
+ }
+
+ return undefined;
+ },
+ },
+
+ tools: [
+ {
+ name: "cloudzen_check_docs",
+ description:
+ "Lists available feature documentation in EscrowApp/docs/ and optionally searches README.md files for a term.",
+ parameters: {
+ type: "object",
+ properties: {
+ search_term: {
+ type: "string",
+ description:
+ "Optional keyword to search for within README.md files.",
+ },
+ },
+ },
+ handler: async (args, invocation) => {
+ const cwd = process.cwd();
+ const docsRoot = findDocsRoot(cwd);
+
+ if (!docsRoot) {
+ return "Could not locate EscrowApp/docs/ directory. Searched from: " + cwd;
+ }
+
+ const folders = listDocFolders(docsRoot);
+ const lines = ["# Available Feature Documentation", ""];
+ lines.push(`Location: ${docsRoot}`, "");
+
+ for (const entry of folders) {
+ const status = entry.hasReadme ? "✓ README.md" : "✗ no README.md";
+ lines.push(` ${entry.folder} [${status}]`);
+ }
+
+ if (args.search_term) {
+ const matches = searchDocs(docsRoot, args.search_term);
+ lines.push("", `# Search results for "${args.search_term}"`, "");
+
+ if (matches.length === 0) {
+ lines.push(" No matches found.");
+ } else {
+ for (const m of matches) {
+ lines.push(` [${m.doc}] line ${m.line}: ${m.text}`);
+ }
+ }
+ }
+
+ return lines.join("\n");
+ },
+ },
+ ],
+});
diff --git a/.github/extensions/security-scanner/extension.mjs b/.github/extensions/security-scanner/extension.mjs
new file mode 100644
index 0000000..b607c26
--- /dev/null
+++ b/.github/extensions/security-scanner/extension.mjs
@@ -0,0 +1,291 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { readFileSync } from "node:fs";
+import { readFile, readdir, stat } from "node:fs/promises";
+import { resolve, extname, join, relative } from "node:path";
+
+// --- Security pattern definitions ---
+
+const CONNECTION_STRING_PATTERNS = [
+ { regex: /["'](?:Server|Data Source)\s*=[^"']+(?:Password|Pwd)\s*=[^"']+["']/gi, id: "hardcoded-connstr", category: "Sensitive Data Exposure", severity: "HIGH" },
+ { regex: /["']mongodb(?:\+srv)?:\/\/[^"']+["']/gi, id: "hardcoded-mongodb", category: "Sensitive Data Exposure", severity: "HIGH" },
+ { regex: /["'](?:Host|Server)\s*=\s*[^"']+;.*(?:Password|Pwd)\s*=[^"']+["']/gi, id: "hardcoded-pg-connstr", category: "Sensitive Data Exposure", severity: "HIGH" },
+];
+
+const SECRET_PATTERNS = [
+ { regex: /["']sk_(?:live|test)_[A-Za-z0-9]{20,}["']/g, id: "stripe-key", category: "Sensitive Data Exposure", severity: "CRITICAL" },
+ { regex: /["'](?:Bearer\s+)[A-Za-z0-9\-._~+/]+=*["']/g, id: "bearer-token", category: "Sensitive Data Exposure", severity: "HIGH" },
+ { regex: /(?:api[_-]?key|apikey|secret[_-]?key|client[_-]?secret)\s*[:=]\s*["'][A-Za-z0-9\-._]{16,}["']/gi, id: "api-key-assignment", category: "Sensitive Data Exposure", severity: "HIGH" },
+ { regex: /["'](?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}["']/g, id: "github-token", category: "Sensitive Data Exposure", severity: "CRITICAL" },
+ { regex: /["']AKIA[A-Z0-9]{16}["']/g, id: "aws-access-key", category: "Sensitive Data Exposure", severity: "CRITICAL" },
+];
+
+const SQL_INJECTION_PATTERNS = [
+ { regex: /string\.Format\s*\(\s*["'].*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\b/gi, id: "sql-string-format", category: "Injection", severity: "HIGH" },
+ { regex: /\$"[^"]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\b[^"]*\{/gi, id: "sql-interpolation", category: "Injection", severity: "HIGH" },
+ { regex: /(?:["'].*(?:SELECT|INSERT|UPDATE|DELETE)\b.*["'])\s*\+\s*(?:\w+)/gi, id: "sql-concat", category: "Injection", severity: "MEDIUM" },
+ { regex: /ExecuteSqlRaw\s*\(\s*\$"/gi, id: "ef-raw-sql-interpolated", category: "Injection", severity: "HIGH" },
+ { regex: /FromSqlRaw\s*\(\s*\$"/gi, id: "ef-fromsql-interpolated", category: "Injection", severity: "HIGH" },
+];
+
+const XSS_PATTERNS = [
+ { regex: /MarkupString\s*\(\s*(?!\s*["']<)/g, id: "markup-string-dynamic", category: "XSS", severity: "MEDIUM" },
+ { regex: /\bHtml\.Raw\s*\(/g, id: "html-raw", category: "XSS", severity: "MEDIUM" },
+];
+
+const AUTH_PATTERNS = [
+ { regex: /\[AllowAnonymous\]/g, id: "allow-anonymous", category: "Broken Access Control", severity: "INFO" },
+ { regex: /(?:password|pwd)\s*[:=]\s*["'][^"']+["']/gi, id: "hardcoded-password", category: "Broken Authentication", severity: "HIGH" },
+];
+
+const MASS_ASSIGNMENT_PATTERNS = [
+ { regex: /\[Bind\s*\(\s*\)\s*\]/g, id: "empty-bind", category: "Mass Assignment", severity: "MEDIUM" },
+ { regex: /TryUpdateModelAsync\s*<\s*\w+\s*>\s*\([^)]*\)/g, id: "tryupdatemodel", category: "Mass Assignment", severity: "INFO" },
+];
+
+const ALL_PATTERNS = [
+ ...CONNECTION_STRING_PATTERNS,
+ ...SECRET_PATTERNS,
+ ...SQL_INJECTION_PATTERNS,
+ ...XSS_PATTERNS,
+ ...AUTH_PATTERNS,
+ ...MASS_ASSIGNMENT_PATTERNS,
+];
+
+const SCAN_EXTENSIONS = new Set([".cs", ".razor", ".json", ".csproj", ".config"]);
+const SKIP_DIRS = new Set([".git", "bin", "obj", "node_modules", ".vs", "wwwroot"]);
+
+function scanContent(content, source) {
+ const findings = [];
+ for (const pattern of ALL_PATTERNS) {
+ const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ const lineNum = content.substring(0, match.index).split("\n").length;
+ findings.push({
+ id: pattern.id,
+ category: pattern.category,
+ severity: pattern.severity,
+ line: lineNum,
+ match: match[0].substring(0, 80),
+ source,
+ });
+ }
+ }
+ return findings;
+}
+
+function formatFindings(findings) {
+ if (findings.length === 0) return "✅ No security issues found.";
+ const grouped = {};
+ for (const f of findings) {
+ if (!grouped[f.category]) grouped[f.category] = [];
+ grouped[f.category].push(f);
+ }
+ let out = `⚠️ Found ${findings.length} potential security issue(s):\n`;
+ for (const [cat, items] of Object.entries(grouped)) {
+ out += `\n### ${cat}\n`;
+ for (const item of items) {
+ out += `- [${item.severity}] ${item.id} at ${item.source}:${item.line} — \`${item.match}\`\n`;
+ }
+ }
+ return out;
+}
+
+function isTargetFile(filePath) {
+ if (!filePath || typeof filePath !== "string") return false;
+ const ext = extname(filePath).toLowerCase();
+ return ext === ".cs" || ext === ".razor";
+}
+
+function isTargetFileExtended(filePath) {
+ if (!filePath || typeof filePath !== "string") return false;
+ const ext = extname(filePath).toLowerCase();
+ return SCAN_EXTENSIONS.has(ext);
+}
+
+async function walkDirectory(dir, rootDir) {
+ const files = [];
+ try {
+ const entries = await readdir(dir, { withFileTypes: true });
+ for (const entry of entries) {
+ if (SKIP_DIRS.has(entry.name)) continue;
+ const fullPath = join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...await walkDirectory(fullPath, rootDir));
+ } else if (entry.isFile()) {
+ const ext = extname(entry.name).toLowerCase();
+ if (ext === ".cs" || ext === ".json") {
+ files.push(fullPath);
+ }
+ }
+ }
+ } catch {
+ // Skip inaccessible directories
+ }
+ return files;
+}
+
+const session = await joinSession({
+ hooks: {
+ onPreToolUse: async (input) => {
+ const toolName = input.toolName;
+ if (toolName !== "create" && toolName !== "edit") return;
+
+ const args = input.toolArgs;
+ if (!args || typeof args !== "object") return;
+
+ const filePath = args.path;
+ if (!isTargetFileExtended(filePath)) return;
+
+ // Scan the content being written
+ const content = toolName === "create" ? args.file_text : args.new_str;
+ if (!content || typeof content !== "string") return;
+
+ const findings = scanContent(content, `${toolName}:${filePath}`);
+ if (findings.length === 0) return;
+
+ const highSeverity = findings.filter(f => f.severity === "CRITICAL" || f.severity === "HIGH");
+ if (highSeverity.length > 0) {
+ await session.log(`🔒 Security scan found ${highSeverity.length} HIGH/CRITICAL issue(s) in pending ${toolName}`, { level: "warning" });
+ }
+
+ return {
+ additionalContext: `🔒 SECURITY SCANNER WARNING — The ${toolName} operation on "${filePath}" contains potential security issues:\n${formatFindings(findings)}\nPlease address these before proceeding. Use parameterized queries, IOptions for config, and Azure Key Vault / user-secrets for sensitive values.`,
+ };
+ },
+
+ onPostToolUse: async (input) => {
+ const toolName = input.toolName;
+ if (toolName !== "create" && toolName !== "edit") return;
+
+ const args = input.toolArgs;
+ if (!args || typeof args !== "object") return;
+
+ const filePath = args.path;
+ if (!filePath || typeof filePath !== "string") return;
+ if (!isTargetFile(filePath)) return;
+
+ const lower = filePath.toLowerCase();
+ const isSensitive = lower.includes("auth") || lower.includes("payment") || lower.includes("program.cs")
+ || lower.includes("startup") || lower.includes("security") || lower.includes("credential")
+ || lower.includes("stripe") || lower.includes("escrow") || lower.includes("fund");
+
+ if (!isSensitive) return;
+
+ return {
+ additionalContext: "🔒 OWASP Compliance Reminder: This file is in a security-sensitive area. Verify: (1) No hardcoded secrets — use IOptions + Azure Key Vault, (2) Input validation with FluentValidation, (3) Parameterized queries only, (4) [Authorize] on all endpoints, (5) DTOs for mass-assignment protection, (6) CancellationToken propagation.",
+ };
+ },
+
+ onUserPromptSubmitted: async (input) => {
+ const prompt = input.prompt;
+ if (!prompt || typeof prompt !== "string") return;
+
+ if (/\b(?:payment|stripe|pay(?:out)?|escrow|fund|refund)\b/i.test(prompt)) {
+ return {
+ additionalContext: "🔒 FINTECH SECURITY CONTEXT: This involves payment/financial operations. Requirements: (1) Idempotency keys on all payment mutations, (2) PCI-DSS: never log or store raw card numbers, (3) Use Stripe SDK — never call API directly with raw HTTP, (4) Audit trail for all financial state transitions, (5) Use decimal (not float/double) for monetary amounts, (6) Implement retry with Polly + circuit breaker for payment gateway calls.",
+ };
+ }
+
+ if (/\b(?:auth|login|credential|token|session|identity|password|jwt|oauth|oidc)\b/i.test(prompt)) {
+ return {
+ additionalContext: "🔒 AUTH SECURITY CONTEXT: (1) Use Microsoft.Identity.Web or Duende IdentityServer — never roll custom auth, (2) Policy-based authorization with [Authorize(Policy = \"...\")], (3) Never store tokens in localStorage, (4) Implement token refresh, (5) Hash passwords with bcrypt/scrypt via ASP.NET Identity, (6) Enforce MFA for admin operations, (7) Log auth failures with correlation IDs.",
+ };
+ }
+ },
+ },
+
+ tools: [
+ {
+ name: "cloudzen_owasp_security_scan",
+ description: "Scans a file for OWASP Top 10 security issues including injection, broken auth, sensitive data exposure, XSS, security misconfiguration, and mass assignment. Returns structured findings with severity levels.",
+ parameters: {
+ type: "object",
+ properties: {
+ filePath: { type: "string", description: "Absolute path to the file to scan" },
+ },
+ required: ["filePath"],
+ additionalProperties: false,
+ },
+ handler: async (args) => {
+ const filePath = args.filePath;
+ if (!filePath) return "Error: filePath is required.";
+
+ const resolved = resolve(filePath);
+ try {
+ const content = readFileSync(resolved, "utf-8");
+ const findings = scanContent(content, relative(process.cwd(), resolved));
+
+ let result = `## OWASP Security Scan: ${relative(process.cwd(), resolved)}\n`;
+ result += `Scanned ${content.split("\n").length} lines against ${ALL_PATTERNS.length} patterns.\n\n`;
+ result += formatFindings(findings);
+ return result;
+ } catch (err) {
+ return `Error reading file: ${err.message}`;
+ }
+ },
+ },
+ {
+ name: "cloudzen_check_secrets",
+ description: "Recursively scans .cs and .json files in a directory for hardcoded secrets, API keys, connection strings, and credentials. Reports findings with file path and line number.",
+ parameters: {
+ type: "object",
+ properties: {
+ directory: { type: "string", description: "Directory to scan (defaults to current working directory)" },
+ },
+ additionalProperties: false,
+ },
+ handler: async (args) => {
+ const rootDir = resolve(process.cwd());
+ const targetDir = args.directory ? resolve(args.directory) : rootDir;
+
+ // Scope check: must be within the repo root
+ if (!targetDir.startsWith(rootDir)) {
+ return "Error: Directory must be within the project root.";
+ }
+
+ await session.log("🔍 Scanning for secrets...", { ephemeral: true });
+
+ try {
+ const files = await walkDirectory(targetDir, rootDir);
+ const allFindings = [];
+
+ for (const filePath of files) {
+ try {
+ const content = await readFile(filePath, "utf-8");
+ const relPath = relative(rootDir, filePath);
+ const findings = scanContent(content, relPath);
+ // Only report secret-related findings
+ const secretFindings = findings.filter(f =>
+ f.category === "Sensitive Data Exposure" || f.category === "Broken Authentication"
+ );
+ allFindings.push(...secretFindings);
+ } catch {
+ // Skip unreadable files
+ }
+ }
+
+ let result = `## Secret Scan Results\n`;
+ result += `Scanned ${files.length} files in ${relative(rootDir, targetDir) || "."}\n\n`;
+
+ if (allFindings.length === 0) {
+ result += "✅ No hardcoded secrets detected.";
+ } else {
+ result += `⚠️ Found ${allFindings.length} potential secret(s):\n\n`;
+ for (const f of allFindings) {
+ result += `- [${f.severity}] **${f.source}:${f.line}** — ${f.id}: \`${f.match}\`\n`;
+ }
+ result += "\n**Recommendation:** Move secrets to Azure Key Vault, `dotnet user-secrets`, or environment variables. Use `IOptions` pattern for configuration.";
+ }
+
+ await session.log(`Secret scan complete: ${allFindings.length} finding(s) in ${files.length} files`, { ephemeral: true });
+ return result;
+ } catch (err) {
+ return `Error scanning directory: ${err.message}`;
+ }
+ },
+ },
+ ],
+});
+
+await session.log("🔒 OWASP Security Scanner loaded");
diff --git a/.github/extensions/superpowers/extension.mjs b/.github/extensions/superpowers/extension.mjs
new file mode 100644
index 0000000..c265aba
--- /dev/null
+++ b/.github/extensions/superpowers/extension.mjs
@@ -0,0 +1,153 @@
+import { joinSession } from "@github/copilot-sdk/extension";
+import { readFileSync } from "node:fs";
+import { join, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+// Resolve paths relative to this extension, not cwd
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const SKILLS_DIR = join(__dirname, "skills");
+
+// Manifest: single source of truth for all skills
+const MANIFEST = {
+ brainstorming: {
+ title: "Brainstorming Ideas Into Designs",
+ file: "brainstorming.md",
+ description: "Socratic design refinement — explore intent, propose approaches, get approval before code",
+ related: ["writing-plans"],
+ recommended_next: "writing-plans",
+ },
+ "writing-plans": {
+ title: "Writing Implementation Plans",
+ file: "writing-plans.md",
+ description: "Break specs into bite-sized TDD tasks with exact file paths, code, and verification steps",
+ related: ["brainstorming", "executing-plans", "subagent-driven-development"],
+ recommended_next: "executing-plans",
+ },
+ "executing-plans": {
+ title: "Executing Plans",
+ file: "executing-plans.md",
+ description: "Load plan, review critically, execute tasks sequentially with verification",
+ related: ["writing-plans", "subagent-driven-development", "verification-before-completion"],
+ recommended_next: "verification-before-completion",
+ },
+ tdd: {
+ title: "Test-Driven Development",
+ file: "test-driven-development.md",
+ description: "RED-GREEN-REFACTOR — write failing test, minimal code to pass, then clean up",
+ related: ["systematic-debugging", "verification-before-completion"],
+ recommended_next: null,
+ },
+ "systematic-debugging": {
+ title: "Systematic Debugging",
+ file: "systematic-debugging.md",
+ description: "4-phase root cause analysis — investigate before fixing, never guess",
+ related: ["tdd", "verification-before-completion"],
+ recommended_next: "verification-before-completion",
+ },
+ "subagent-driven-development": {
+ title: "Subagent-Driven Development",
+ file: "subagent-driven-development.md",
+ description: "Dispatch fresh agent per task with two-stage review (spec + quality)",
+ related: ["writing-plans", "executing-plans", "requesting-code-review"],
+ recommended_next: "requesting-code-review",
+ },
+ "verification-before-completion": {
+ title: "Verification Before Completion",
+ file: "verification-before-completion.md",
+ description: "Evidence before claims — run verification, read output, THEN report status",
+ related: ["tdd", "systematic-debugging"],
+ recommended_next: null,
+ },
+ "requesting-code-review": {
+ title: "Requesting Code Review",
+ file: "requesting-code-review.md",
+ description: "Dispatch critic agent to review changes against spec and quality standards",
+ related: ["subagent-driven-development", "verification-before-completion"],
+ recommended_next: null,
+ },
+};
+
+const ALLOWED_SKILLS = new Set(Object.keys(MANIFEST));
+
+function loadSkill(skillId) {
+ if (!ALLOWED_SKILLS.has(skillId)) {
+ return `Unknown skill: "${skillId}". Use superpowers_catalog to see available skills.`;
+ }
+ const entry = MANIFEST[skillId];
+ const filePath = join(SKILLS_DIR, entry.file);
+ try {
+ const content = readFileSync(filePath, "utf-8");
+ let result = content;
+ if (entry.related.length > 0) {
+ result += `\n\n---\n**Related skills:** ${entry.related.join(", ")}`;
+ }
+ if (entry.recommended_next) {
+ result += `\n**Recommended next:** superpowers_skill(skill: "${entry.recommended_next}")`;
+ }
+ return result;
+ } catch {
+ return `Error: Could not load skill file "${entry.file}". Ensure the extension is properly installed.`;
+ }
+}
+
+function buildCatalog() {
+ const lines = [
+ "# Superpowers Skills Catalog",
+ "",
+ "On-demand workflow skills ported from obra/superpowers (MIT). Call `superpowers_skill` with a skill ID to load.",
+ "",
+ "| Skill ID | Title | Description |",
+ "|----------|-------|-------------|",
+ ];
+ for (const [id, entry] of Object.entries(MANIFEST)) {
+ lines.push(`| \`${id}\` | ${entry.title} | ${entry.description} |`);
+ }
+ lines.push("");
+ lines.push("## Typical Flow");
+ lines.push("```");
+ lines.push("brainstorming → writing-plans → executing-plans / subagent-driven-development");
+ lines.push(" ↕ ↕");
+ lines.push(" tdd + systematic-debugging + verification-before-completion");
+ lines.push(" ↕");
+ lines.push(" requesting-code-review");
+ lines.push("```");
+ lines.push("");
+ lines.push("*Attribution: Based on obra/superpowers (MIT License) — adapted for Copilot CLI*");
+ return lines.join("\n");
+}
+
+const session = await joinSession({
+ tools: [
+ {
+ name: "superpowers_catalog",
+ description:
+ "List all available Superpowers workflow skills with descriptions and recommended flow. Zero-cost overview — no skill content loaded.",
+ parameters: {
+ type: "object",
+ properties: {},
+ additionalProperties: false,
+ },
+ handler: async () => buildCatalog(),
+ },
+ {
+ name: "superpowers_skill",
+ description:
+ "Load a specific Superpowers workflow skill on-demand. Returns the full skill methodology for the agent to follow. Use superpowers_catalog first to see available skills.",
+ parameters: {
+ type: "object",
+ properties: {
+ skill: {
+ type: "string",
+ description: "The skill ID to load",
+ enum: Object.keys(MANIFEST),
+ },
+ },
+ required: ["skill"],
+ additionalProperties: false,
+ },
+ handler: async (params) => loadSkill(params.skill),
+ },
+ ],
+});
+
+await session.log("⚡ Superpowers extension loaded with 8 workflow skills");
diff --git a/.github/extensions/superpowers/skills/brainstorming.md b/.github/extensions/superpowers/skills/brainstorming.md
new file mode 100644
index 0000000..8b207a4
--- /dev/null
+++ b/.github/extensions/superpowers/skills/brainstorming.md
@@ -0,0 +1,83 @@
+# Brainstorming Ideas Into Designs
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Help turn ideas into fully formed designs through collaborative dialogue.
+
+## HARD GATE
+
+Do NOT write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
+
+## Checklist
+
+Complete these in order:
+
+1. **Explore project context** — check files, docs, recent commits
+2. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
+3. **Propose 2-3 approaches** — with trade-offs and your recommendation
+4. **Present design** — in sections scaled to complexity, get approval after each section
+5. **Write design doc** — save to session artifacts or docs/ and commit
+6. **Spec self-review** — check for placeholders, contradictions, ambiguity, scope
+7. **User reviews written spec** — ask user to review before proceeding
+8. **Transition** — use `superpowers_skill(skill: "writing-plans")` to create implementation plan
+
+## The Process
+
+### Understanding the Idea
+
+- Check project state first (files, docs, recent commits) — use `project_summary` tool if available
+- Assess scope: if multiple independent subsystems, flag immediately and decompose
+- Ask questions **one at a time** — prefer multiple choice when possible
+- Focus on: purpose, constraints, success criteria
+
+### Exploring Approaches
+
+- Propose 2-3 approaches with trade-offs
+- Lead with your recommendation and explain why
+- YAGNI ruthlessly — remove unnecessary features
+
+### Presenting the Design
+
+- Scale each section to its complexity (a few sentences if simple, up to 300 words if nuanced)
+- Ask after each section whether it looks right
+- Cover: architecture, components, data flow, error handling, testing
+
+### Design for Isolation
+
+- Break system into smaller units with one clear purpose each
+- Well-defined interfaces, testable independently
+- Smaller units = better reasoning, more reliable edits
+
+### Working in Existing Codebases
+
+- Explore current structure before proposing changes — follow existing patterns
+- Include targeted improvements only where existing code affects the work
+- Don't propose unrelated refactoring
+
+## After the Design
+
+1. **Write the spec** to docs/ or session artifacts — commit it
+2. **Self-review** the spec:
+ - Placeholder scan: any TBD, TODO, incomplete sections?
+ - Internal consistency: do sections contradict each other?
+ - Scope check: focused enough for a single plan?
+ - Ambiguity check: could any requirement be interpreted two ways?
+3. **User review gate**: Ask user to review before proceeding
+4. **Transition**: Load writing-plans skill to create implementation plan
+
+## Key Principles
+
+- **One question at a time** — don't overwhelm
+- **Multiple choice preferred** — easier to answer
+- **YAGNI ruthlessly** — remove unnecessary features
+- **Explore alternatives** — always 2-3 approaches before settling
+- **Incremental validation** — present, get approval, then move on
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Dispatch subagent | Use `task` tool with appropriate agent_type |
+| TodoWrite | SQL `todos` table |
+| Next skill | `superpowers_skill(skill: "writing-plans")` |
+| Project exploration | `project_summary` tool, `check_docs` tool, grep/glob |
diff --git a/.github/extensions/superpowers/skills/executing-plans.md b/.github/extensions/superpowers/skills/executing-plans.md
new file mode 100644
index 0000000..d04caea
--- /dev/null
+++ b/.github/extensions/superpowers/skills/executing-plans.md
@@ -0,0 +1,58 @@
+# Executing Plans
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Load plan, review critically, execute all tasks, report when complete.
+
+## The Process
+
+### Step 1: Load and Review Plan
+
+1. Read plan file (plan.md or docs/ path)
+2. Review critically — identify questions or concerns
+3. If concerns: raise them with user before starting
+4. If no concerns: populate SQL todos and proceed
+
+```sql
+-- Track all tasks
+INSERT INTO todos (id, title, description, status) VALUES
+ ('task-1', 'Task 1: [Title]', '[Description]', 'pending');
+
+-- Track dependencies
+INSERT INTO todo_deps (todo_id, depends_on) VALUES ('task-2', 'task-1');
+```
+
+### Step 2: Execute Tasks
+
+For each task:
+1. Mark as in_progress: `UPDATE todos SET status = 'in_progress' WHERE id = 'task-N'`
+2. Follow each step exactly (plan has bite-sized steps)
+3. Run verifications as specified — use `superpowers_skill(skill: "verification-before-completion")`
+4. Mark as done: `UPDATE todos SET status = 'done' WHERE id = 'task-N'`
+
+### Step 3: Complete Development
+
+After all tasks complete:
+- Run full test suite to verify nothing is broken
+- Use `superpowers_skill(skill: "requesting-code-review")` for final review
+- Commit with meaningful message
+
+## When to Stop and Ask
+
+**STOP executing immediately when:**
+- Hit a blocker (missing dependency, test fails, instruction unclear)
+- Plan has critical gaps
+- You don't understand an instruction
+- Verification fails repeatedly
+
+**Ask for clarification rather than guessing.**
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| TodoWrite | SQL `todos` + `todo_deps` tables |
+| Status tracking | `UPDATE todos SET status = '...'` |
+| Ready query | `SELECT * FROM todos WHERE status='pending' AND NOT EXISTS (...)` |
+| Build/test | `dotnet_build_check` / `dotnet_test_check` tools or `task` agent |
+| Verification | `superpowers_skill(skill: "verification-before-completion")` |
diff --git a/.github/extensions/superpowers/skills/requesting-code-review.md b/.github/extensions/superpowers/skills/requesting-code-review.md
new file mode 100644
index 0000000..a9a9df1
--- /dev/null
+++ b/.github/extensions/superpowers/skills/requesting-code-review.md
@@ -0,0 +1,95 @@
+# Requesting Code Review
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Dispatch a critic agent to catch issues before they cascade.
+
+**Core principle:** Review early, review often.
+
+## When to Request Review
+
+**Mandatory:**
+- After each task in subagent-driven development
+- After completing a major feature
+- Before merge to main
+
+**Optional but valuable:**
+- When stuck (fresh perspective)
+- Before refactoring (baseline check)
+- After fixing complex bug
+
+## How to Request
+
+### 1. Gather Context
+
+```bash
+git --no-pager log --oneline -5 # Recent commits
+git --no-pager diff HEAD~N # Changes to review
+```
+
+### 2. Dispatch Critic Agent
+
+```
+Use task tool:
+ agent_type: "critic"
+ prompt: |
+ Review these code changes for correctness, quality, and security.
+
+ **What was implemented:** [description]
+ **Requirements/spec:** [paste relevant section or file path]
+ **Files changed:** [list files]
+
+ Review criteria:
+ 1. Does the implementation match the spec? (completeness)
+ 2. Are there bugs, edge cases, or logic errors? (correctness)
+ 3. SOLID principles, clean code, naming? (quality)
+ 4. Input validation, authorization, injection prevention? (security)
+ 5. Test coverage adequate? (testing)
+
+ For each issue found:
+ - Severity: Critical / Important / Minor
+ - Location: file and line
+ - Issue: what's wrong
+ - Fix: specific suggestion
+```
+
+### 3. Act on Feedback
+
+| Severity | Action |
+|----------|--------|
+| **Critical** | Fix immediately — blocks everything |
+| **Important** | Fix before proceeding to next task |
+| **Minor** | Note for later, don't block progress |
+| **Reviewer wrong** | Push back with technical reasoning |
+
+### 4. Re-Review if Needed
+
+If critic found Critical or Important issues:
+1. Fix the issues
+2. Re-dispatch critic with same scope
+3. Repeat until clean
+
+## Integration with Workflows
+
+| Workflow | When to Review |
+|----------|---------------|
+| Subagent-driven development | After EACH task (mandatory) |
+| Executing plans | After each batch of 3 tasks |
+| Ad-hoc development | Before merge |
+
+## Red Flags
+
+- Skip review because "it's simple"
+- Ignore Critical issues
+- Proceed with unfixed Important issues
+- Argue without technical evidence
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Dispatch code reviewer | `task` tool, agent_type: "critic" |
+| Get review results | `read_agent` tool |
+| Follow-up with reviewer | `write_agent` tool |
+| Check git changes | `git --no-pager diff`, `git --no-pager log` |
+| Security-focused review | `owasp_security_scan` tool + critic agent |
diff --git a/.github/extensions/superpowers/skills/subagent-driven-development.md b/.github/extensions/superpowers/skills/subagent-driven-development.md
new file mode 100644
index 0000000..48a11b9
--- /dev/null
+++ b/.github/extensions/superpowers/skills/subagent-driven-development.md
@@ -0,0 +1,115 @@
+# Subagent-Driven Development
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Execute plans by dispatching a fresh agent per task, with two-stage review after each: spec compliance first, then code quality.
+
+**Why agents:** Isolated context per task prevents confusion. You construct exactly what each agent needs. This preserves your own context for coordination.
+
+## When to Use
+
+- Have an implementation plan with mostly independent tasks
+- Want fast iteration with quality gates
+- Tasks can be delegated to sub-agents
+
+## The Process
+
+For each task in the plan:
+
+### 1. Dispatch Implementer Agent
+
+```
+Use task tool:
+ agent_type: "general-purpose" (or "task" for simpler work)
+ prompt: [Full task text + project context + file paths]
+```
+
+**Include in the prompt:**
+- Complete task description with all steps
+- Relevant project context (architecture, patterns, conventions)
+- File paths to create/modify
+- Testing requirements
+- "Follow TDD: write failing test → verify fail → implement → verify pass → commit"
+
+### 2. Handle Agent Status
+
+| Status | Action |
+|--------|--------|
+| **Completed successfully** | Proceed to spec review |
+| **Completed with concerns** | Read concerns, address if about correctness/scope |
+| **Needs more context** | Provide missing info via `write_agent`, re-dispatch |
+| **Failed/blocked** | Assess: context problem → provide more; too complex → break down; plan wrong → escalate |
+
+### 3. Dispatch Spec Reviewer
+
+```
+Use task tool:
+ agent_type: "critic"
+ prompt: |
+ Review the changes for spec compliance.
+ Task spec: [paste task requirements]
+ Check: Does the implementation match EVERY requirement?
+ Flag: Missing requirements, extra unrequested features, deviations from spec.
+```
+
+- If issues found → implementer agent fixes → re-review
+- If clean → proceed to quality review
+
+### 4. Dispatch Quality Reviewer
+
+```
+Use task tool:
+ agent_type: "critic"
+ prompt: |
+ Review code quality of recent changes.
+ Check: naming, error handling, test coverage, SOLID, security.
+ Severity levels: Critical (blocks), Important (fix before next task), Minor (note for later).
+```
+
+- If issues found → implementer fixes → re-review
+- If clean → mark task done
+
+### 5. Mark Task Complete
+
+```sql
+UPDATE todos SET status = 'done' WHERE id = 'task-N';
+```
+
+### 6. Repeat for Next Task
+
+## Model Selection
+
+Use the least powerful model that can handle each role:
+
+| Task Type | Recommended agent_type |
+|-----------|----------------------|
+| Mechanical (1-2 files, clear spec) | "task" (fast/cheap) |
+| Integration (multi-file, judgment) | "general-purpose" (standard) |
+| Architecture/review | "critic" (most capable) |
+
+## After All Tasks
+
+1. Dispatch final code reviewer for the entire implementation
+2. Run full verification: `dotnet_build_check` + `dotnet_test_check`
+3. Use `superpowers_skill(skill: "verification-before-completion")`
+
+## Red Flags — Never Do These
+
+- Skip reviews (spec OR quality)
+- Proceed with unfixed issues
+- Dispatch multiple implementation agents in parallel (conflicts)
+- Start quality review before spec compliance passes
+- Move to next task while review has open issues
+- Try to fix manually instead of re-dispatching (context pollution)
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Dispatch implementer | `task` tool, agent_type: "general-purpose" or "task" |
+| Dispatch spec reviewer | `task` tool, agent_type: "critic" |
+| Dispatch quality reviewer | `task` tool, agent_type: "critic" |
+| TodoWrite | SQL `todos` table |
+| Follow-up to agent | `write_agent` tool with agent_id |
+| Read agent result | `read_agent` tool with agent_id |
+| Fresh subagent | Each `task` call creates isolated context |
diff --git a/.github/extensions/superpowers/skills/systematic-debugging.md b/.github/extensions/superpowers/skills/systematic-debugging.md
new file mode 100644
index 0000000..6701dc5
--- /dev/null
+++ b/.github/extensions/superpowers/skills/systematic-debugging.md
@@ -0,0 +1,124 @@
+# Systematic Debugging
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Random fixes waste time and create new bugs. Quick patches mask underlying issues.
+
+**Core principle:** ALWAYS find root cause before attempting fixes.
+
+## The Iron Law
+
+```
+NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
+```
+
+If you haven't completed Phase 1, you cannot propose fixes.
+
+## When to Use
+
+Use for ANY technical issue: test failures, bugs, unexpected behavior, performance problems, build failures.
+
+**Use ESPECIALLY when:**
+- Under time pressure (emergencies make guessing tempting)
+- "Just one quick fix" seems obvious
+- You've already tried multiple fixes
+- Previous fix didn't work
+
+## Phase 1: Root Cause Investigation
+
+**BEFORE attempting ANY fix:**
+
+1. **Read Error Messages Carefully**
+ - Don't skip past errors or warnings
+ - Read stack traces completely
+ - Note line numbers, file paths, error codes
+
+2. **Reproduce Consistently**
+ - Can you trigger it reliably?
+ - Exact steps?
+ - Every time?
+
+3. **Check Recent Changes**
+ - `git --no-pager log --oneline -10`
+ - `git --no-pager diff`
+ - New dependencies, config changes?
+
+4. **Gather Evidence in Multi-Component Systems**
+ For EACH component boundary:
+ - Log what data enters/exits the component
+ - Verify environment/config propagation
+ - Check state at each layer
+ - Run once to gather evidence showing WHERE it breaks
+
+5. **Trace Data Flow**
+ - Where does the bad value originate?
+ - What called this with the bad value?
+ - Keep tracing up until you find the source
+ - Fix at source, not at symptom
+
+## Phase 2: Pattern Analysis
+
+1. **Find Working Examples** — locate similar working code in same codebase
+2. **Compare Against References** — read reference implementations completely, not skimming
+3. **Identify Differences** — list every difference, however small
+4. **Understand Dependencies** — components, settings, config, environment, assumptions
+
+## Phase 3: Hypothesis and Testing
+
+1. **Form Single Hypothesis** — "I think X is the root cause because Y"
+2. **Test Minimally** — smallest possible change, one variable at a time
+3. **Verify Before Continuing** — worked → Phase 4; didn't → form NEW hypothesis
+4. **When You Don't Know** — say "I don't understand X", ask for help
+
+## Phase 4: Implementation
+
+1. **Create Failing Test** — use `superpowers_skill(skill: "tdd")` for the test
+2. **Implement Single Fix** — ONE change, no "while I'm here" improvements
+3. **Verify Fix** — test passes, no other tests broken, issue resolved
+4. **If Fix Doesn't Work:**
+ - Count fixes attempted
+ - If < 3: return to Phase 1 with new information
+ - **If ≥ 3: STOP — question the architecture**
+
+### 3+ Fixes Failed? Question Architecture
+
+Pattern indicating architectural problem:
+- Each fix reveals new coupling/problems elsewhere
+- Fixes require "massive refactoring"
+- Each fix creates new symptoms
+
+**STOP and discuss with user before attempting more fixes.**
+
+## Red Flags — STOP and Return to Phase 1
+
+- "Quick fix for now, investigate later"
+- "Just try changing X and see"
+- "Add multiple changes, run tests"
+- "It's probably X, let me fix that"
+- Proposing solutions before tracing data flow
+- "One more fix attempt" (when already tried 2+)
+
+## Quick Reference
+
+| Phase | Key Activities | Success Criteria |
+|-------|---------------|------------------|
+| **1. Root Cause** | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY |
+| **2. Pattern** | Find working examples, compare | Identify differences |
+| **3. Hypothesis** | Form theory, test minimally | Confirmed or new hypothesis |
+| **4. Implementation** | Create test, fix, verify | Bug resolved, tests pass |
+
+## Real-World Impact
+
+- Systematic approach: 15-30 minutes to fix
+- Random fixes approach: 2-3 hours of thrashing
+- First-time fix rate: 95% vs 40%
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Check recent changes | `git --no-pager log`, `git --no-pager diff` |
+| Run tests | `dotnet_test_check` tool |
+| Create failing test | `superpowers_skill(skill: "tdd")` |
+| Verify fix | `superpowers_skill(skill: "verification-before-completion")` |
+| Question architecture | Use `task` tool with agent_type: "critic" |
diff --git a/.github/extensions/superpowers/skills/test-driven-development.md b/.github/extensions/superpowers/skills/test-driven-development.md
new file mode 100644
index 0000000..44c3961
--- /dev/null
+++ b/.github/extensions/superpowers/skills/test-driven-development.md
@@ -0,0 +1,147 @@
+# Test-Driven Development (TDD)
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Write the test first. Watch it fail. Write minimal code to pass.
+
+**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.
+
+## The Iron Law
+
+```
+NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
+```
+
+Write code before the test? Delete it. Start over. No exceptions.
+
+## When to Use
+
+**Always:** New features, bug fixes, refactoring, behavior changes.
+
+**Exceptions (ask user):** Throwaway prototypes, generated code, configuration files.
+
+## Red-Green-Refactor Cycle
+
+### RED — Write Failing Test
+
+Write one minimal test showing what should happen.
+
+**Requirements:**
+- One behavior per test
+- Clear name: `MethodName_Scenario_ExpectedResult`
+- Real code (no mocks unless unavoidable)
+- Arrange-Act-Assert structure
+
+```csharp
+[Fact]
+public async Task HoldFunds_ValidTransaction_ReturnsSuccess()
+{
+ // Arrange
+ var command = new HoldFundsCommand(transactionId, 500m, "USD", idempotencyKey);
+
+ // Act
+ var result = await handler.Handle(command, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.IsSuccess.Should().BeTrue();
+}
+```
+
+### Verify RED — Watch It Fail
+
+**MANDATORY. Never skip.**
+
+Run: `dotnet test --filter "HoldFunds_ValidTransaction"`
+
+Confirm:
+- Test fails (not errors)
+- Failure is expected (feature missing, not typo)
+- Failure message makes sense
+
+**Test passes?** You're testing existing behavior. Fix test.
+
+### GREEN — Minimal Code
+
+Write the simplest code to pass the test. Nothing more.
+
+- Don't add features not required by the test
+- Don't refactor other code
+- Don't "improve" beyond the test
+
+### Verify GREEN — Watch It Pass
+
+**MANDATORY.**
+
+Run: `dotnet test --filter "HoldFunds_ValidTransaction"`
+
+Confirm:
+- Test passes
+- Other tests still pass
+- No warnings or errors
+
+**Test fails?** Fix code, not test.
+
+### REFACTOR — Clean Up
+
+After green only:
+- Remove duplication
+- Improve names
+- Extract helpers
+
+Keep tests green. Don't add behavior.
+
+## Good Tests
+
+| Quality | Good | Bad |
+|---------|------|-----|
+| **Minimal** | One thing. "and" in name? Split it. | `Test_ValidatesEmailAndDomainAndWhitespace` |
+| **Clear** | Name describes behavior | `Test1` |
+| **Shows intent** | Demonstrates desired API | Obscures what code should do |
+
+## Common Rationalizations
+
+| Excuse | Reality |
+|--------|---------|
+| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
+| "I'll test after" | Tests passing immediately prove nothing. |
+| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
+| "Test hard = design unclear" | Hard to test = hard to use. Listen to the test. |
+| "TDD will slow me down" | TDD faster than debugging. |
+
+## Red Flags — STOP and Start Over
+
+- Code before test
+- Test passes immediately (without new code)
+- Can't explain why test failed
+- Rationalizing "just this once"
+
+**ALL of these mean: Delete code. Start over with TDD.**
+
+## Bug Fix Flow
+
+1. **RED:** Write test reproducing the bug
+2. **Verify RED:** Watch it fail with the bug
+3. **GREEN:** Fix the bug with minimal code
+4. **Verify GREEN:** Test passes, all other tests pass
+5. **REFACTOR:** Clean up if needed
+
+## Verification Checklist
+
+Before marking work complete:
+
+- [ ] Every new function/method has a test
+- [ ] Watched each test fail before implementing
+- [ ] Each test failed for expected reason
+- [ ] Wrote minimal code to pass each test
+- [ ] All tests pass
+- [ ] Edge cases and errors covered
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Run tests | `dotnet_test_check` tool or `dotnet test` command |
+| Build check | `dotnet_build_check` tool |
+| Test framework | xUnit + FluentAssertions (per project conventions) |
+| Mocking | Moq (per project conventions) |
diff --git a/.github/extensions/superpowers/skills/verification-before-completion.md b/.github/extensions/superpowers/skills/verification-before-completion.md
new file mode 100644
index 0000000..9866abd
--- /dev/null
+++ b/.github/extensions/superpowers/skills/verification-before-completion.md
@@ -0,0 +1,105 @@
+# Verification Before Completion
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Claiming work is complete without verification is dishonesty, not efficiency.
+
+**Core principle:** Evidence before claims, always.
+
+## The Iron Law
+
+```
+NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
+```
+
+If you haven't run the verification command in this message, you cannot claim it passes.
+
+## The Gate Function
+
+Before claiming any status:
+
+1. **IDENTIFY:** What command proves this claim?
+2. **RUN:** Execute the FULL command (fresh, complete)
+3. **READ:** Full output, check exit code, count failures
+4. **VERIFY:** Does output confirm the claim?
+ - If NO → state actual status with evidence
+ - If YES → state claim WITH evidence
+5. **ONLY THEN:** Make the claim
+
+Skip any step = lying, not verifying.
+
+## Verification Requirements
+
+| Claim | Requires | NOT Sufficient |
+|-------|----------|----------------|
+| Tests pass | `dotnet_test_check` output: 0 failures | Previous run, "should pass" |
+| Build succeeds | `dotnet_build_check` output: succeeded | "Linter passed" |
+| Bug fixed | Test original symptom: passes | Code changed, assumed fixed |
+| Conventions met | `check_conventions` output: clean | "I followed patterns" |
+| Security clean | `owasp_security_scan` output: no issues | "I used parameterized queries" |
+| Requirements met | Line-by-line checklist against spec | "Tests passing" |
+
+## Red Flags — STOP
+
+If you catch yourself:
+- Using "should", "probably", "seems to"
+- Expressing satisfaction before verification ("Great!", "Done!")
+- About to commit without verification
+- Relying on partial verification
+- Thinking "just this once"
+
+## Rationalization Prevention
+
+| Excuse | Reality |
+|--------|---------|
+| "Should work now" | RUN the verification |
+| "I'm confident" | Confidence ≠ evidence |
+| "Just this once" | No exceptions |
+| "Linter passed" | Linter ≠ compiler ≠ tests |
+| "Agent said success" | Verify independently |
+| "Partial check is enough" | Partial proves nothing |
+
+## Key Patterns
+
+**Tests:**
+```
+✅ [Run dotnet_test_check] [See: 34/34 pass] "All 34 tests pass"
+❌ "Should pass now" / "Looks correct"
+```
+
+**Build:**
+```
+✅ [Run dotnet_build_check] [See: Build succeeded] "Build passes"
+❌ "Code compiles fine" (without running build)
+```
+
+**Requirements:**
+```
+✅ Re-read plan → Create checklist → Verify each → Report gaps or completion
+❌ "Tests pass, phase complete"
+```
+
+**Agent delegation:**
+```
+✅ Agent reports success → read_agent → Check actual output → Verify changes → Report
+❌ Trust agent report without reading output
+```
+
+## The Bottom Line
+
+**No shortcuts for verification.**
+
+Run the command. Read the output. THEN claim the result.
+
+Non-negotiable.
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Run tests | `dotnet_test_check` tool |
+| Run build | `dotnet_build_check` tool |
+| Check conventions | `check_conventions` tool |
+| Security scan | `owasp_security_scan` tool |
+| Check agent output | `read_agent` tool |
+| Check secrets | `check_secrets` tool |
diff --git a/.github/extensions/superpowers/skills/writing-plans.md b/.github/extensions/superpowers/skills/writing-plans.md
new file mode 100644
index 0000000..9e2441b
--- /dev/null
+++ b/.github/extensions/superpowers/skills/writing-plans.md
@@ -0,0 +1,110 @@
+# Writing Implementation Plans
+
+> Adapted from obra/superpowers (MIT) for Copilot CLI.
+
+Write comprehensive implementation plans assuming the engineer has zero context. Document everything: which files to touch, code, testing, how to verify. Bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
+
+## Scope Check
+
+If the spec covers multiple independent subsystems, break into separate plans — one per subsystem. Each plan should produce working, testable software on its own.
+
+## File Structure First
+
+Before defining tasks, map out which files will be created or modified:
+
+- Design units with clear boundaries and well-defined interfaces
+- Prefer smaller, focused files over large ones
+- Files that change together should live together
+- In existing codebases, follow established patterns
+
+## Bite-Sized Task Granularity
+
+Each step is one action (2-5 minutes):
+- "Write the failing test" — step
+- "Run it to make sure it fails" — step
+- "Implement the minimal code to make the test pass" — step
+- "Run the tests and make sure they pass" — step
+- "Commit" — step
+
+## Plan Document Header
+
+Every plan MUST start with:
+
+```markdown
+# [Feature Name] Implementation Plan
+
+**Goal:** [One sentence]
+**Architecture:** [2-3 sentences about approach]
+**Tech Stack:** [Key technologies]
+
+---
+```
+
+## Task Structure
+
+```markdown
+### Task N: [Component Name]
+
+**Files:**
+- Create: `exact/path/to/file.cs`
+- Modify: `exact/path/to/existing.cs`
+- Test: `tests/exact/path/to/test.cs`
+
+- [ ] **Step 1: Write the failing test**
+ [Actual test code]
+
+- [ ] **Step 2: Run test to verify it fails**
+ Run: [exact command]
+ Expected: FAIL with [reason]
+
+- [ ] **Step 3: Write minimal implementation**
+ [Actual implementation code]
+
+- [ ] **Step 4: Run test to verify it passes**
+ Run: [exact command]
+ Expected: PASS
+
+- [ ] **Step 5: Commit**
+ `git add [files] && git commit -m "feat: [description]"`
+```
+
+## No Placeholders — EVER
+
+These are plan failures:
+- "TBD", "TODO", "implement later"
+- "Add appropriate error handling"
+- "Write tests for the above" (without actual test code)
+- "Similar to Task N" (repeat the code)
+- Steps without code blocks for code steps
+- References to undefined types/functions
+
+## Self-Review
+
+After writing the complete plan:
+
+1. **Spec coverage:** Skim each requirement. Can you point to a task that implements it?
+2. **Placeholder scan:** Search for red flags from the "No Placeholders" section
+3. **Type consistency:** Do names/signatures match across tasks?
+
+Fix issues inline. If you find a spec requirement with no task, add the task.
+
+## Execution Handoff
+
+After saving the plan, track tasks in SQL todos:
+
+```sql
+INSERT INTO todos (id, title, description, status) VALUES
+ ('task-1-name', 'Task 1: [Title]', '[Full description]', 'pending');
+```
+
+Then load the execution skill: `superpowers_skill(skill: "executing-plans")` or `superpowers_skill(skill: "subagent-driven-development")`
+
+## Copilot CLI Mappings
+
+| Superpowers Concept | Copilot CLI Equivalent |
+|---|---|
+| Save plan | Create `plan.md` in session folder or docs/ |
+| TodoWrite | SQL `todos` table with `todo_deps` |
+| Subagent execution | `task` tool with agent_type: "general-purpose" |
+| Inline execution | Follow executing-plans skill in current session |
+| Next skill | `superpowers_skill(skill: "executing-plans")` |
diff --git a/.github/hooks/README.md b/.github/hooks/README.md
new file mode 100644
index 0000000..54b2a3f
--- /dev/null
+++ b/.github/hooks/README.md
@@ -0,0 +1,52 @@
+# Git Hooks
+
+Automated scripts that run on git events (pre-commit, post-commit, etc.).
+
+## At a Glance
+
+| Aspect | Detail |
+|--------|--------|
+| **Type** | Standard git hooks — not Copilot-specific |
+| **Trigger** | Git events (commit, push, merge, etc.) |
+| **Location** | Each hook is a folder with its script(s) |
+| **Registration** | Via `.git/hooks/` symlinks or a hook manager (husky, lefthook) |
+
+## Current Hooks
+
+| Hook | Trigger | Purpose |
+|------|---------|---------|
+| `secrets-scanner` | pre-commit | Scans staged files for hardcoded secrets, API keys, and credentials before allowing the commit |
+
+## How to Create a New Hook
+
+1. Create a folder under `.github/hooks/` with a descriptive name.
+2. Add the hook script (shell, PowerShell, or any executable).
+3. Register the script in `.git/hooks/` — either:
+ - Symlink manually: `ln -s ../../.github/hooks/my-hook/run.sh .git/hooks/pre-commit`
+ - Use a hook manager like **husky** or **lefthook** for automatic setup.
+
+## Hook Types Reference
+
+| Git Hook | When It Runs |
+|----------|-------------|
+| `pre-commit` | Before a commit is created — use for linting, secret scanning |
+| `commit-msg` | After commit message is entered — use for message format validation |
+| `pre-push` | Before pushing to remote — use for build/test verification |
+| `post-commit` | After a commit is created — use for notifications |
+
+## Hooks vs. Extensions
+
+| Concern | Git Hooks | Copilot Extensions |
+|---------|-----------|-------------------|
+| **Trigger** | Git operations (commit, push) | AI assistant session events |
+| **Runtime** | Shell / any executable | Node.js (ES modules) |
+| **Purpose** | Code quality gates at git time | AI workflow enhancement |
+| **Audience** | All developers | AI-assisted development |
+
+These are complementary: hooks enforce rules at commit time, extensions enforce rules during AI-assisted coding.
+
+## See Also
+
+- [`.github/extensions/`](../extensions/) — Copilot CLI extensions (AI-time hooks and tools)
+- [`.github/extensions/build-guardian/`](../extensions/build-guardian/) — Build verification (extension-based complement to git hooks)
+- [`.github/extensions/security-scanner/`](../extensions/security-scanner/) — OWASP scanning tools (extension-based complement to secrets-scanner)
diff --git a/.github/hooks/secrets-scanner/scan-secrets.ps1 b/.github/hooks/secrets-scanner/scan-secrets.ps1
new file mode 100644
index 0000000..a5781e9
--- /dev/null
+++ b/.github/hooks/secrets-scanner/scan-secrets.ps1
@@ -0,0 +1,197 @@
+#!/usr/bin/env pwsh
+#
+# Secrets Scanner — Git Pre-Commit Hook (PowerShell)
+# Adapted from github/awesome-copilot (MIT License) for Windows/cross-platform.
+#
+# Scans staged files for hardcoded secrets, credentials, and API keys.
+# Blocks the commit if critical/high severity secrets are found.
+#
+# Environment variables:
+# SCAN_MODE - "warn" (log only) or "block" (exit non-zero) (default: block)
+# SKIP_SECRETS_SCAN - "true" to disable scanning entirely
+# SECRETS_ALLOWLIST - Comma-separated patterns to ignore
+
+param(
+ [string]$Mode = $env:SCAN_MODE,
+ [string]$AllowlistRaw = $env:SECRETS_ALLOWLIST
+)
+
+if ($env:SKIP_SECRETS_SCAN -eq "true") {
+ Write-Host "⏭️ Secrets scan skipped (SKIP_SECRETS_SCAN=true)"
+ exit 0
+}
+
+if (-not $Mode) { $Mode = "block" }
+
+# ---------------------------------------------------------------------------
+# Secret detection patterns: Name, Severity, Regex
+# Ported from github/awesome-copilot hooks/secrets-scanner
+# ---------------------------------------------------------------------------
+$Patterns = @(
+ # Cloud provider credentials
+ @{ Name = "AWS_ACCESS_KEY"; Severity = "critical"; Regex = 'AKIA[0-9A-Z]{16}' }
+ @{ Name = "AWS_SECRET_KEY"; Severity = "critical"; Regex = 'aws_secret_access_key\s*[:=]\s*[''"]?[A-Za-z0-9/+=]{40}' }
+ @{ Name = "GCP_SERVICE_ACCOUNT"; Severity = "critical"; Regex = '"type"\s*:\s*"service_account"' }
+ @{ Name = "GCP_API_KEY"; Severity = "high"; Regex = 'AIza[0-9A-Za-z_-]{35}' }
+ @{ Name = "AZURE_CLIENT_SECRET"; Severity = "critical"; Regex = 'azure[_-]?client[_-]?secret\s*[:=]\s*[''"]?[A-Za-z0-9_~.-]{34,}' }
+
+ # GitHub tokens
+ @{ Name = "GITHUB_PAT"; Severity = "critical"; Regex = 'ghp_[0-9A-Za-z]{36}' }
+ @{ Name = "GITHUB_OAUTH"; Severity = "critical"; Regex = 'gho_[0-9A-Za-z]{36}' }
+ @{ Name = "GITHUB_APP_TOKEN"; Severity = "critical"; Regex = 'ghs_[0-9A-Za-z]{36}' }
+ @{ Name = "GITHUB_FINE_PAT"; Severity = "critical"; Regex = 'github_pat_[0-9A-Za-z_]{82}' }
+
+ # Private keys
+ @{ Name = "PRIVATE_KEY"; Severity = "critical"; Regex = '-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----' }
+
+ # Generic secrets and tokens
+ @{ Name = "GENERIC_SECRET"; Severity = "high"; Regex = '(secret|token|password|passwd|pwd|api[_-]?key|apikey|access[_-]?key|auth[_-]?token|client[_-]?secret)\s*[:=]\s*[''"]?[A-Za-z0-9_/+=~.-]{8,}' }
+ @{ Name = "CONNECTION_STRING"; Severity = "high"; Regex = '(mongodb(\+srv)?|postgres(ql)?|mysql|redis|amqp|mssql)://[^\s''"]{10,}' }
+ @{ Name = "BEARER_TOKEN"; Severity = "medium"; Regex = '[Bb]earer\s+[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}' }
+
+ # SaaS tokens
+ @{ Name = "SLACK_TOKEN"; Severity = "high"; Regex = 'xox[baprs]-[0-9]{10,}-[0-9A-Za-z-]+' }
+ @{ Name = "SLACK_WEBHOOK"; Severity = "high"; Regex = 'https://hooks\.slack\.com/services/T[0-9A-Z]{8,}/B[0-9A-Z]{8,}/[0-9A-Za-z]{24}' }
+ @{ Name = "STRIPE_SECRET_KEY"; Severity = "critical"; Regex = 'sk_live_[0-9A-Za-z]{24,}' }
+ @{ Name = "STRIPE_RESTRICTED"; Severity = "high"; Regex = 'rk_live_[0-9A-Za-z]{24,}' }
+ @{ Name = "SENDGRID_API_KEY"; Severity = "high"; Regex = 'SG\.[0-9A-Za-z_-]{22}\.[0-9A-Za-z_-]{43}' }
+ @{ Name = "TWILIO_API_KEY"; Severity = "high"; Regex = 'SK[0-9a-fA-F]{32}' }
+ @{ Name = "NPM_TOKEN"; Severity = "high"; Regex = 'npm_[0-9A-Za-z]{36}' }
+
+ # JWT (structured tokens)
+ @{ Name = "JWT_TOKEN"; Severity = "medium"; Regex = 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' }
+)
+
+# File extensions to scan (text files only)
+$TextExtensions = @(
+ '.cs', '.razor', '.css', '.js', '.ts', '.json', '.xml', '.yaml', '.yml',
+ '.toml', '.ini', '.cfg', '.conf', '.md', '.txt', '.sh', '.ps1', '.bat',
+ '.py', '.rb', '.go', '.rs', '.java', '.html', '.sql', '.env', '.resx',
+ '.csproj', '.sln', '.props', '.targets', '.config'
+)
+
+# Files to always skip
+$SkipFiles = @('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '*.lock')
+
+# Placeholder patterns to ignore (false positives)
+$PlaceholderPattern = '(example|placeholder|your[_-]|xxx|changeme|TODO|FIXME|replace[_-]?me|dummy|fake|test[_-]?key|sample)'
+
+# ---------------------------------------------------------------------------
+# Get staged files
+# ---------------------------------------------------------------------------
+$stagedFiles = git diff --cached --name-only --diff-filter=ACMR 2>$null
+if (-not $stagedFiles) {
+ Write-Host "✨ No staged files to scan"
+ exit 0
+}
+
+$files = $stagedFiles -split "`n" | Where-Object { $_.Trim() -ne "" }
+
+# Parse allowlist
+$allowlist = @()
+if ($AllowlistRaw) {
+ $allowlist = $AllowlistRaw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
+}
+
+# ---------------------------------------------------------------------------
+# Scan
+# ---------------------------------------------------------------------------
+$findings = @()
+
+foreach ($filePath in $files) {
+ # Skip lock files
+ $skip = $false
+ foreach ($pattern in $SkipFiles) {
+ if ($filePath -like $pattern) { $skip = $true; break }
+ }
+ if ($skip) { continue }
+
+ # Skip non-text files
+ $ext = [System.IO.Path]::GetExtension($filePath).ToLowerInvariant()
+ if ($ext -and $ext -notin $TextExtensions) { continue }
+
+ # Read staged content (not working tree — what will actually be committed)
+ $content = $null
+ try {
+ $content = git show ":$filePath" 2>$null
+ } catch { continue }
+ if (-not $content) { continue }
+
+ $lines = $content -split "`n"
+
+ for ($i = 0; $i -lt $lines.Count; $i++) {
+ $line = $lines[$i]
+
+ foreach ($p in $Patterns) {
+ if ($line -match $p.Regex) {
+ $matchValue = $Matches[0]
+
+ # Skip placeholders/examples
+ if ($matchValue -match $PlaceholderPattern) { continue }
+
+ # Skip allowlisted
+ $isAllowed = $false
+ foreach ($al in $allowlist) {
+ if ($matchValue -like "*$al*") { $isAllowed = $true; break }
+ }
+ if ($isAllowed) { continue }
+
+ # Redact for safe display
+ if ($matchValue.Length -le 12) {
+ $redacted = "[REDACTED]"
+ } else {
+ $redacted = "$($matchValue.Substring(0,4))...$($matchValue.Substring($matchValue.Length-4))"
+ }
+
+ $findings += [PSCustomObject]@{
+ File = $filePath
+ Line = $i + 1
+ Pattern = $p.Name
+ Severity = $p.Severity
+ Match = $redacted
+ }
+ }
+ }
+ }
+}
+
+# ---------------------------------------------------------------------------
+# Report
+# ---------------------------------------------------------------------------
+Write-Host "🔍 Scanned $($files.Count) staged file(s) for secrets..."
+
+if ($findings.Count -gt 0) {
+ Write-Host ""
+ Write-Host "⚠️ Found $($findings.Count) potential secret(s):" -ForegroundColor Yellow
+ Write-Host ""
+ Write-Host (" {0,-45} {1,-6} {2,-28} {3}" -f "FILE", "LINE", "PATTERN", "SEVERITY")
+ Write-Host (" {0,-45} {1,-6} {2,-28} {3}" -f "----", "----", "-------", "--------")
+
+ foreach ($f in $findings) {
+ $color = switch ($f.Severity) {
+ "critical" { "Red" }
+ "high" { "Yellow" }
+ default { "White" }
+ }
+ Write-Host (" {0,-45} {1,-6} {2,-28} {3}" -f $f.File, $f.Line, $f.Pattern, $f.Severity) -ForegroundColor $color
+ }
+
+ Write-Host ""
+
+ if ($Mode -eq "block") {
+ $criticalOrHigh = $findings | Where-Object { $_.Severity -in @("critical", "high") }
+ if ($criticalOrHigh.Count -gt 0) {
+ Write-Host "🚫 Commit blocked: $($criticalOrHigh.Count) critical/high finding(s). Remove secrets before committing." -ForegroundColor Red
+ Write-Host " Set SCAN_MODE=warn to log without blocking, or add patterns to SECRETS_ALLOWLIST." -ForegroundColor DarkGray
+ exit 1
+ } else {
+ Write-Host "💡 Medium-severity findings detected (not blocking). Review recommended." -ForegroundColor Yellow
+ }
+ } else {
+ Write-Host "💡 Review the findings above. Set SCAN_MODE=block to prevent commits with secrets." -ForegroundColor Yellow
+ }
+} else {
+ Write-Host "✅ No secrets detected in $($files.Count) scanned file(s)" -ForegroundColor Green
+}
+
+exit 0
diff --git a/.github/instructions/README.md b/.github/instructions/README.md
new file mode 100644
index 0000000..25ee09b
--- /dev/null
+++ b/.github/instructions/README.md
@@ -0,0 +1,74 @@
+# Copilot Pattern-Matched Instructions
+
+Markdown files that inject context into the AI agent when it works on files matching specific glob patterns.
+
+## At a Glance
+
+| Aspect | Detail |
+|--------|--------|
+| **Mechanism** | `applyTo` frontmatter with glob patterns |
+| **Format** | Markdown with YAML frontmatter |
+| **Trigger** | Automatically injected when the agent edits/creates a file matching the pattern |
+| **Scope** | Per-file-pattern — only loads when relevant files are touched |
+
+## File Format
+
+```markdown
+---
+applyTo: "**/*.cs"
+---
+
+# My Instruction Title
+
+Rules and patterns the AI should follow when working on matching files...
+```
+
+## Current Instruction Categories
+
+| Directory | `applyTo` Pattern | Purpose |
+|-----------|-------------------|---------|
+| `architecture/` | `**/*.cs` | Clean Architecture layer rules and dependency direction |
+| `blazor/` | `**/*.razor, **/*.razor.cs, **/*.razor.css` | Code-behind pattern, CSS isolation, component lifecycle |
+| `cqrs/` | `EscrowApp/Features/**/*.cs` | MediatR vertical slice structure and handler patterns |
+| `database/` | `EscrowApp/Data/**/*.cs, EscrowApp/Migrations/**/*.cs` | EF Core and PostgreSQL conventions |
+| `development/` | `**/*` | MVP-first development rules and anti-over-engineering |
+| `domain/` | `EscrowApp/Models/**/*.cs, EscrowApp/Events/**/*.cs` | DDD guidelines — rich models, value objects, aggregates |
+| `memory/` | `**/*` | Context window optimization and token budget rules |
+| `resilience/` | `EscrowApp/Services/**/*.cs, EscrowApp/Infrastructure/**/*.cs` | Polly retry, circuit breaker, timeout patterns |
+| `security/` | `**/*.cs, **/*.razor` | OWASP Top 10 security rules for fintech |
+| `testing/` | `**/*Tests*/**/*.cs, **/*Test*/**/*.cs` | xUnit + FluentAssertions testing standards |
+| `planning.instructions.md` | *(standalone file)* | Planning doc sync trigger |
+
+## How to Create a New Instruction
+
+1. Choose the appropriate category folder (or create a new one for a new concern).
+2. Create a `.md` file with `applyTo` frontmatter specifying the glob pattern.
+3. Write focused, actionable rules — the AI follows these as constraints.
+
+## Key Rules
+
+- **Keep instructions concise** — they consume context window tokens every time they fire.
+- **Use narrow `applyTo` patterns** — `EscrowApp/Features/**/*.cs` is better than `**/*.cs` to avoid prompt bloat.
+- **One concern per file** — don't mix security rules with testing standards.
+- **Actionable over informational** — write rules the AI can follow, not background essays.
+- **Test your patterns** — overly broad patterns cause instructions to fire on unrelated edits.
+
+## How It Works
+
+```
+Agent edits EscrowApp/Features/HoldFunds/Handler.cs
+ ↓
+Pattern match: **/*.cs → architecture/ instructions load
+Pattern match: EscrowApp/Features/**/*.cs → cqrs/ instructions load
+ ↓
+Agent receives both instruction sets as additional context
+ ↓
+Agent follows the combined rules while generating code
+```
+
+## See Also
+
+- [`.github/skills/`](../skills/) — On-demand methodology files (loaded explicitly, not pattern-matched)
+- [`.github/extensions/`](../extensions/) — Runtime tools and hooks (code, not instructions)
+- [`AGENTS.md`](../../AGENTS.md) — Base instructions for all AI agents (always loaded)
+- [`CLAUDE.md`](../../CLAUDE.md) — Claude-specific reasoning guidance
diff --git a/.github/instructions/architecture/clean-architecture.instructions.md b/.github/instructions/architecture/clean-architecture.instructions.md
new file mode 100644
index 0000000..afca27a
--- /dev/null
+++ b/.github/instructions/architecture/clean-architecture.instructions.md
@@ -0,0 +1,108 @@
+---
+applyTo: "**/*.cs"
+---
+
+# Clean Architecture — Project Conventions
+
+## Layer Overview
+
+```
+Presentation (Components/)
+ ↓
+Application (Features/)
+ ↓
+Domain (Models/, Events/, Services/Strategies/ interfaces)
+ ↑
+Infrastructure (Data/, Infrastructure/)
+```
+
+Inner layers **never** reference outer layers. Dependencies always point inward.
+
+---
+
+## Domain Layer
+
+**Namespaces:** `MyApp.Models`, `MyApp.Events`, `MyApp.Services.Strategies`
+
+Contains core business logic with zero framework dependencies. No references to EF Core, ASP.NET, MediatR, or infrastructure packages. Entities own their invariants — validate state transitions inside aggregates. Use `record` types for value objects and domain events. Strategy interfaces define **what** can happen, not **how**. Example: `Order.Process()` validates status before transition and throws `InvalidOperationException` on violations.
+
+| Directory | Contents | Examples |
+|---|---|---|
+| `Models/` | Entities, value objects, enums | `Order`, `Customer`, `Address`, `OrderStatus` |
+| `Events/` | Domain events | `OrderCreatedEvent`, `OrderCompletedEvent`, `OrderCancelledEvent` |
+| `Services/Strategies/` | Strategy interfaces | `IChargeable`, `IRefundable`, `ICancellable` |
+
+---
+
+## Application Layer
+
+**Namespace:** `MyApp.Features.Orders.*` (vertical slices)
+
+Orchestrates use cases via MediatR commands/queries. Depends on Domain; never on Infrastructure. Inject **interfaces** only (`IOrderRepository`, `IEventBus`) — never concrete types or `AppDbContext`. Return result DTOs — never expose domain entities to outer layers. FluentValidation validators live next to their commands.
+
+| Directory | Contents | Examples |
+|---|---|---|
+| `Features/Orders/CreateOrder/` | Command, handler, result DTO | `CreateOrderCommand`, `CreateOrderHandler`, `CreateOrderResult` |
+| `Features/Orders/CompleteOrder/` | Command, handler, result DTO | `CompleteOrderCommand`, `CompleteOrderHandler` |
+| `Features/Orders/CancelOrder/` | Command, handler, result DTO | `CancelOrderCommand`, `CancelOrderHandler` |
+| `Services/` | Application service interfaces | `IOrderManagerService` |
+
+---
+
+## Infrastructure Layer
+
+**Namespaces:** `MyApp.Data`, `MyApp.Infrastructure`
+
+Implements interfaces defined in Domain and Application. Owns all external concerns. External SDK usage (payment providers, messaging, etc.) is confined to this layer. EF Core configurations (Fluent API) live in `Data/Configurations/`. Never expose `DbContext` outside this layer. Repository implementations use `FirstOrDefaultAsync()` with parameterized predicates (never raw SQL concatenation).
+
+| Directory | Contents | Examples |
+|---|---|---|
+| `Data/` | EF Core context, repository implementations, migrations | `AppDbContext`, `OrderRepository` |
+| `Infrastructure/` | External integrations, auth middleware | Payment service, `InMemoryEventBus` |
+
+---
+
+## Presentation Layer
+
+**Namespace:** `MyApp.Components`
+
+Blazor Server pages, layouts, and shared UI components. Depends on Application only.
+
+**Rules:**
+- Never inject repositories, `DbContext`, or infrastructure services
+- Always go through `IMediator.Send()` or application service interfaces
+- Code-behind pattern mandatory (`.razor` + `.razor.cs` + `.razor.css`)
+- Use `[CascadingParameter] Task` for auth — not `IHttpContextAccessor`
+
+---
+
+## DI Registration in Program.cs
+
+Register dependencies with interface-to-implementation mapping: strategy implementations (domain interfaces), MediatR with assembly scanning, application services, `AppDbContext` with provider config, repositories, and event bus. Use `AddScoped` for request-scoped services, `AddSingleton` for stateless shared services.
+
+---
+
+## Namespace Conventions
+
+| Layer | Namespace Pattern | Example |
+|---|---|---|
+| Domain | `MyApp.Models`, `MyApp.Events` | `MyApp.Models.Order` |
+| Application | `MyApp.Features.{Aggregate}.{Action}` | `MyApp.Features.Orders.CreateOrder` |
+| Infrastructure | `MyApp.Data`, `MyApp.Infrastructure` | `MyApp.Data.AppDbContext` |
+| Presentation | `MyApp.Components.Pages` | `MyApp.Components.Pages.Dashboard` |
+
+---
+
+## Anti-Patterns — What NOT to Do
+
+- Domain must never reference EF Core (`using MyApp.Data` is a violation)
+- Application layer must never inject `AppDbContext` — use `IRepository` instead
+- Blazor components must never inject repositories directly — use `IMediator.Send()`
+- Handlers must never return domain entities to Presentation — map to result DTOs
+- Application interfaces must never expose infrastructure types (e.g., `DbSet` is EF Core only)
+
+---
+
+## Reference
+
+See `docs/` directory for full architecture documentation and decision records.
diff --git a/.github/instructions/blazor/component-patterns.instructions.md b/.github/instructions/blazor/component-patterns.instructions.md
new file mode 100644
index 0000000..ba8a418
--- /dev/null
+++ b/.github/instructions/blazor/component-patterns.instructions.md
@@ -0,0 +1,101 @@
+---
+applyTo: "**/*.razor, **/*.razor.cs, **/*.razor.css"
+---
+
+# Blazor Component Patterns — Project Conventions
+
+## Mandatory Code-Behind Pattern
+
+Every Blazor component consists of **three files**:
+
+```
+ComponentName.razor ← Markup only (HTML + Razor directives)
+ComponentName.razor.cs ← Logic (partial class, lifecycle, event handlers)
+ComponentName.razor.css ← Scoped styles (Bootstrap 5 overrides only)
+```
+
+### .razor — Markup
+Contains HTML, Razor directives, and component references. **No `@code {}` blocks.** Use `@inject IStringLocalizer L` for localized strings. Render loading indicators while data loads: `@if (_orders is null) { } else { }`.
+
+### .razor.cs — Code-Behind
+Sealed partial class with all logic. Inject services via `[Inject]` properties. Override `OnInitializedAsync` for data loading (not constructor). Implement `IDisposable` if component owns `CancellationTokenSource`. Use `protected` access on fields to allow markup binding. Call `IMediator.Send()` for all data operations.
+
+---
+
+## Component Lifecycle
+
+| Method | Use When |
+|---|---|
+| `OnInitializedAsync` | Loading data on first render — primary data-fetch location |
+| `OnParametersSetAsync` | Reacting to parameter changes from parent (e.g., selected order ID) |
+| `OnAfterRenderAsync(firstRender)` | JS interop setup, DOM measurements — guard with `if (firstRender)` |
+| `ShouldRender()` | Skipping re-renders on high-frequency updates (e.g., real-time feeds) |
+| `Dispose` / `DisposeAsync` | Cleaning up `CancellationTokenSource`, timers, event subscriptions |
+
+**Never** use the constructor for async work. Always use `OnInitializedAsync`.
+
+---
+
+## Bootstrap 5 Class Conventions
+
+Use these standard Bootstrap 5 classes consistently:
+
+| Element | Classes |
+|---|---|
+| Primary actions | `btn btn-primary` |
+| Danger/cancel | `btn btn-outline-danger` |
+| Data tables | `table table-striped table-hover` |
+| Table headers | `table-dark` on `` |
+| Status badges | `badge bg-success`, `badge bg-warning text-dark`, `badge bg-danger` |
+| Cards | `card`, `card-header`, `card-body` |
+| Forms | `form-control`, `form-label`, `form-select`, `form-check` |
+| Layout | `container-fluid`, `row`, `col-md-*` |
+| Spacing | `mt-3`, `mb-4`, `p-3` — use Bootstrap spacing utilities |
+| Alerts | `alert alert-info`, `alert alert-danger` |
+
+**Do NOT** use inline `style` attributes. Apply Bootstrap utility classes or scoped CSS instead.
+
+---
+
+## Localization
+
+Inject `IStringLocalizer` in every component that renders user-facing text. Resource keys: dot-separated, context-prefixed (e.g., `Dashboard.Title`, `Button.CreateOrder`). Never hardcode user-visible strings — always use localizer keys. In markup: `@Localizer["Key"]`. In code-behind: `L["Key"]`.
+
+---
+
+## Parent-Child Communication
+
+### EventCallback<T> — Child notifies parent
+
+Child component declares `[Parameter] public EventCallback OnComplete { get; set; }` and calls `await OnComplete.InvokeAsync(_orderId)`. Parent invokes: ``.
+
+### CascadingParameter — Reserved for auth state only
+
+Only use `[CascadingParameter] private Task AuthState` for authentication. Do **not** cascade custom state objects. Use `IMediator` or scoped DI services instead.
+
+---
+
+## StreamRendering for Progressive Loading
+
+Apply `[StreamRendering]` on pages that fetch data in `OnInitializedAsync`. Renders page shell immediately, streams content as data becomes available. Pair with a loading indicator that displays when `_data is null`.
+
+---
+
+## IDisposable Cleanup
+
+Implement `IDisposable` when component owns: `CancellationTokenSource`, `Timer`, `PeriodicTimer`, event handler subscriptions, or `IJSObjectReference`. Call `.Cancel()` on `CancellationTokenSource` in `Dispose()`. This prevents memory leaks and circuit issues from dangling async operations.
+
+---
+
+## Hard Rules
+
+| Rule | Rationale |
+|---|---|
+| ❌ No `@code { }` blocks in `.razor` files | Separation of concerns — logic lives in `.razor.cs` |
+| ❌ No inline `style="..."` attributes | Use Bootstrap utilities or scoped `.razor.css` |
+| ❌ No direct repository or DbContext injection | Go through `IMediator.Send()` only |
+| ❌ No `IHttpContextAccessor` in components | Use `[CascadingParameter] Task` |
+| ✅ Always `partial class` in `.razor.cs` | Required for code-behind to work |
+| ✅ Always scoped `.razor.css` per component | Prevents style leakage across components |
+| ✅ Always localize user-facing strings | Required for multi-locale support |
+| ✅ Always cancel async work on Dispose | Prevents memory leaks and circuit issues |
diff --git a/.github/instructions/cqrs/mediatr-patterns.instructions.md b/.github/instructions/cqrs/mediatr-patterns.instructions.md
new file mode 100644
index 0000000..a8c7c38
--- /dev/null
+++ b/.github/instructions/cqrs/mediatr-patterns.instructions.md
@@ -0,0 +1,106 @@
+---
+applyTo: "**/Features/**/*.cs"
+---
+
+# MediatR & CQRS Patterns — Project Conventions
+
+## Vertical Slice Structure
+
+Each use case is a self-contained slice within `Features/{Aggregate}/`:
+
+```
+Features/
+└── Orders/
+ ├── CreateOrder/
+ │ ├── CreateOrderCommand.cs ← IRequest
+ │ ├── CreateOrderCommandValidator.cs ← FluentValidation
+ │ ├── CreateOrderHandler.cs ← IRequestHandler<,>
+ │ └── CreateOrderResult.cs ← Result DTO
+ ├── CompleteOrder/
+ │ ├── CompleteOrderCommand.cs
+ │ ├── CompleteOrderCommandValidator.cs
+ │ ├── CompleteOrderHandler.cs
+ │ └── CompleteOrderResult.cs
+ ├── CancelOrder/
+ │ ├── CancelOrderCommand.cs
+ │ ├── CancelOrderCommandValidator.cs
+ │ ├── CancelOrderHandler.cs
+ │ └── CancelOrderResult.cs
+ └── GetOrders/
+ ├── GetOrdersQuery.cs
+ ├── GetOrdersHandler.cs
+ └── OrderDto.cs
+```
+
+**One command/query, one handler, one result per folder.** No shared handlers.
+
+---
+
+## Command vs Query Separation
+
+| Aspect | Command (Write) | Query (Read) |
+|---|---|---|
+| Purpose | Mutate state | Return data |
+| Naming | `{Verb}{Noun}Command` | `Get{Noun}Query` / `List{Noun}Query` |
+| Returns | Result DTO with success/error | DTO or collection |
+| Side effects | Yes — DB writes, events, payments | None — read-only |
+| Validation | Always — FluentValidation required | Optional |
+| Idempotency | Required for mutation commands | N/A |
+| EF Tracking | Default tracking | `AsNoTracking()` |
+
+**Examples:**
+- Commands: `CreateOrderCommand`, `CompleteOrderCommand`, `CancelOrderCommand`, `RefundOrderCommand`
+- Queries: `GetOrdersQuery`, `GetOrderByIdQuery`, `ListCancelledOrdersQuery`
+
+---
+
+## Command Definition
+
+Commands are immutable `record` types implementing `IRequest`. Naming: `{Action}{Aggregate}Command` (e.g., `CreateOrderCommand`, `CompleteOrderCommand`). Use business language, not technical language. Always include `IdempotencyKey` for mutation commands to prevent duplicate submissions on retry.
+
+---
+
+## Handler Structure
+
+Handlers are `sealed` classes with primary constructor injection. Implement `IRequestHandler`. Single responsibility: orchestrate one use case. Log with structured data (correlation IDs, never PII). Propagate `CancellationToken` through every async call. Never inject `DbContext` or repositories directly — use interfaces. Return typed result objects for flow control (never throw for business errors).
+
+---
+
+## Result DTOs
+
+Use result objects for flow control. Include `IsSuccess` boolean, typed `ErrorCode` enum, and `ErrorMessage` string. Static factory methods for each outcome make handler code readable. Never expose domain entities in results — map to DTOs.
+
+---
+
+## Pipeline Behaviors
+
+Register cross-cutting concerns as MediatR pipeline behaviors. **Validation Behavior**: runs FluentValidation before handler, throws `ValidationException` if rules fail. **Logging Behavior**: logs request entry/exit with elapsed time via `Stopwatch`. Compose via `cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>))` in Program.cs.
+
+---
+
+## Calling from Blazor Components
+
+**Never call services directly from components.** Always go through MediatR. Component dispatches via `IMediator.Send(new CreateOrderCommand(...))`. On success, navigate or update state. On failure, display `result.ErrorMessage` to user. This ensures validation, logging, and side effects run consistently.
+
+---
+
+## Idempotency for Mutation Commands
+
+All commands that trigger external operations **must** include an `IdempotencyKey` string property. Generate keys client-side using `Guid.CreateVersion7().ToString()`. Check for existing idempotency key in handler before processing. Return cached result for duplicate requests. Pass key to external provider's API for safe retries.
+
+---
+
+## Quick Reference
+
+| Concept | Convention |
+|---|---|
+| Folder structure | `Features/{Aggregate}/{Action}/{Command,Handler,Validator,Result}.cs` |
+| Command naming | `{Verb}{Noun}Command` — `CreateOrderCommand` |
+| Query naming | `Get{Noun}Query` — `GetOrdersQuery` |
+| Handler class | `sealed class`, primary constructor, inject interfaces |
+| Result type | `sealed record` with `IsSuccess`, `ErrorCode`, `ErrorMessage` |
+| Validation | FluentValidation `AbstractValidator` per command |
+| Pipeline | `ValidationBehavior` → `LoggingBehavior` → Handler |
+| Component access | `IMediator.Send()` only — never bypass the pipeline |
+| Mutation commands | Always include `IdempotencyKey` property |
+| CancellationToken | Propagate through every async call in the chain |
diff --git a/.github/instructions/database/ef-core-patterns.instructions.md b/.github/instructions/database/ef-core-patterns.instructions.md
new file mode 100644
index 0000000..541027e
--- /dev/null
+++ b/.github/instructions/database/ef-core-patterns.instructions.md
@@ -0,0 +1,66 @@
+---
+applyTo: "**/Data/**/*.cs, **/Migrations/**/*.cs"
+---
+
+# Entity Framework Core & PostgreSQL Patterns — Project Data Layer
+
+## PostgreSQL-Specific Conventions
+
+- Use `Npgsql.EntityFrameworkCore.PostgreSQL` as the database provider.
+- Map C# `decimal` to `numeric(18,4)` for monetary values — never use `real` or `double precision`.
+- Use `jsonb` columns for semi-structured data (e.g., metadata dictionaries) via `.HasColumnType("jsonb")`.
+- Use `uuid` for primary keys — PostgreSQL handles `Guid` natively.
+- Use `timestamptz` for all `DateTimeOffset` properties.
+- If project conventions dictate **snake_case** column names, configure via `UseSnakeCaseNamingConvention()` from `EFCore.NamingConventions` — do not rename manually in Fluent API.
+
+## AppDbContext Configuration
+
+One `DbDbContext` class: `AppDbContext` — registered as scoped. Apply entity configurations via `IEntityTypeConfiguration` in separate files, loaded with `modelBuilder.ApplyConfigurationsFromAssembly(...)`. Define unique constraints on `IdempotencyKey` (prevents duplicates) and composite keys as needed. Define indexes on `Status` (filtered queries), `CreatedAt` (time-range queries), and `ExternalPaymentId` (webhook correlation). Configure relationships explicitly in Fluent API — never rely on convention in DDD models.
+
+## Repository Pattern
+
+Define `IOrderRepository` in Application/Domain layer (expresses domain intent). Implementation (`OrderRepository`) lives in Infrastructure/Data and depends on `AppDbContext`. Provide only operations the domain needs: `GetByIdAsync`, `AddAsync`, `UpdateAsync`, `ExistsByIdempotencyKeyAsync`. Return domain entities (DTOs are mapper's job in handlers). Never expose `IQueryable` from repository — leaks persistence concerns into Application layer.
+
+```csharp
+public interface IOrderRepository
+{
+ Task GetByIdAsync(Guid id, CancellationToken ct);
+ Task AddAsync(Order order, CancellationToken ct);
+ Task ExistsByIdempotencyKeyAsync(string key, CancellationToken ct);
+}
+```
+
+## Read-Only Query Patterns
+
+Use `AsNoTracking()` on every read-only query (eliminates change-tracker overhead). Prefer projections with `Select()` over loading full entities when consumer needs subset. Use compiled queries (`EF.CompileAsyncQuery`) for hot-path lookups (e.g., transaction status).
+
+## Split Queries
+
+When `Include()` chain loads multiple collections, use `.AsSplitQuery()` to avoid Cartesian explosion. Single-collection includes can stay as single query — split only when needed.
+
+## Migration Conventions
+
+Migration names must be **descriptive**: `AddIdempotencyKeyIndex`, `CreateCustomersTable` (never `Migration1`). Always review generated SQL (`dotnet ef migrations script`) before applying to shared environment. Keep migrations **additive** — avoid destructive changes unless behind planned strategy. Never put seed data or business logic in migrations. Use `migrationBuilder.Sql(...)` sparingly — only for DDL that EF cannot express.
+
+## Connection String Management
+
+Never hardcode connection strings in code or `appsettings.json` for production. Use **Options pattern**: bind `PostgresOptions` from configuration, inject `IOptions`. Development: use `dotnet user-secrets` or `appsettings.Development.json`. Production: use environment variables or Azure Key Vault. Configure connection pooling and timeouts explicitly in connection string.
+
+## Concurrency Control
+
+`Order` must use **optimistic concurrency** with `RowVersion` / `xmin` concurrency token. For PostgreSQL, use `xmin` system column via `.UseXminAsConcurrencyToken()`. Handle `DbUpdateConcurrencyException` in Application layer — retry or return conflict result, never silently overwrite.
+
+## Seeding
+
+Use `HasData()` **only** for reference/lookup data: `OrderStatus` enum table, `Currency` codes. Never seed transactional business data. Seed data must be deterministic and idempotent across migration runs.
+
+## Anti-Patterns to Avoid
+
+| Anti-Pattern | Why It's Harmful | Correct Approach |
+|---|---|---|
+| `DbContext` in Application/Presentation layers | Bypasses repository abstraction, couples layers | Access data only through `IOrderRepository` |
+| Lazy loading enabled | Silent N+1 queries, unpredictable performance | Use eager loading with explicit `Include()` |
+| Returning `IQueryable` from repository | Leaks persistence concerns, untestable | Return materialized collections or single entities |
+| `SaveChanges()` inside repository methods | Breaks unit-of-work boundaries | Call `SaveChangesAsync()` in the handler or via `IUnitOfWork` |
+| String interpolation in raw SQL | SQL injection risk | Use `FromSqlInterpolated` or parameterized queries |
+| `Find()` / `FindAsync()` for read-only queries | Pollutes change tracker unnecessarily | Use `AsNoTracking().SingleOrDefaultAsync()` |
diff --git a/.github/instructions/development/mvp-first.instructions.md b/.github/instructions/development/mvp-first.instructions.md
new file mode 100644
index 0000000..4eb84da
--- /dev/null
+++ b/.github/instructions/development/mvp-first.instructions.md
@@ -0,0 +1,77 @@
+---
+applyTo: "**/*.cs, **/*.razor, **/*.razor.cs, **/*.razor.css, **/*.ts, **/*.js"
+---
+
+# MVP-First Development Rules
+
+> Ship a working product fast. Iterate from there. These rules override perfectionism.
+
+**Core Principle:** Working software > perfect architecture. Every decision should be filtered through: _"Does this get us closer to a usable product, or is it premature optimization?"_
+
+## 1. The MVP Decision Filter
+
+Before implementing anything, ask: Does the user see or interact with this? (YES → build it) Does the app crash without it? (YES → build it) Is it a security requirement? (YES → build it) Is it "nice to have"? (NO) Building for 10K users with 10 users? (NO) Abstracting one-off code? (NO).
+
+## 2. What MVP Means (and Doesn't)
+
+**MVP IS:** Smallest thing delivering user value with end-to-end vertical slice (UI → API → DB). Hardcoded config instead of admin panels. Direct service calls instead of queues. One database instead of microservices. Manual processes if rare.
+
+**MVP IS NOT:** Buggy without error handling. Missing authentication/validation. Unpayable technical debt. Throwaway code (should be improvable, not disposable).
+
+## 3. Build Order for Any Feature
+
+1. Domain model (entity + value objects) — 30 min
+2. Simplest data access (EF Core, direct) — Repository interface + implementation
+3. One happy-path API endpoint — MediatR command/query
+4. Basic UI that calls it — Blazor page with form
+5. Basic validation — FluentValidation on command
+6. Basic error handling — Try-catch at handler level
+7. One integration test — WebApplicationFactory happy path
+
+**✅ SHIP IT — everything below is v1.1+**
+
+8–14: Edge cases, comprehensive tests, performance, UI polish, caching, background jobs, admin dashboards.
+
+## 4. Anti-Over-Engineering Rules
+
+**MUST NOT in MVP Phase:** Generic repositories (use specific per aggregate; generalize at 5+ entities). CQRS read models (same EF model for read/write until performance fails). Event sourcing (use simple updates; sourcing is v2+). Microservices (start modular monolith; extract when bottleneck proven). Message queues (direct calls; queues for cross-service communication). Custom middleware (use built-in ASP.NET). Abstract factories (inject directly; factory at 3+ runtime implementations). Specification pattern (use LINQ; spec at 5+ reusable filters). Custom result types (use IActionResult/exceptions; Result when pattern emerges). GraphQL (use REST; GraphQL at 10+ client variations).
+
+**MUST DO in MVP Phase:** Clean Architecture layers (free, prevents rewrites). Interfaces for external services (swappability). FluentValidation on every command. `[Authorize]` on every endpoint (default deny). One happy-path test per feature. Code-behind pattern (`.razor` + `.razor.cs` from day one). Parameterized queries (never concatenate SQL). Structured logging (`ILogger`). Dependency injection (no `new SomeService()` in business logic).
+
+## 5. The "Rule of Three" for Abstraction
+
+Don't abstract until written the same pattern three times:
+- **1st time:** Write inline. Ship it.
+- **2nd time:** Note duplication. Ship it.
+- **3rd time:** Extract abstraction. You have 3 real examples to design from.
+
+Prevents abstractions for hypothetical futures that never arrive.
+
+## 6. Time-Boxing Decisions
+
+Database choice (15 min → PostgreSQL). Auth provider (15 min → ASP.NET Identity). CSS framework (10 min → Bootstrap or Tailwind). Architecture pattern (10 min → Clean Architecture + MediatR). ORM (5 min → EF Core). Testing framework (5 min → xUnit + FluentAssertions). State management (10 min → Scoped services). API style (5 min → Minimal APIs). Logging (5 min → Serilog). Caching (skip — add when measuring).
+
+## 7. Definition of "Done" for MVP Features
+
+✅ Happy path end-to-end (UI → API → DB → response). ✅ Input validation prevents bad data. ✅ Authentication required (no anonymous business ops). ✅ Basic error handling (friendly message, not stack trace). ✅ One integration test happy path. ✅ No hardcoded secrets. ✅ Code compiles, zero warnings.
+
+❌ NOT DONE: Only works in Swagger, no UI. Happy path but crashes on empty input. Works but bypasses authentication.
+
+## 8. Iteration Cadence
+
+- Sprint 0: Project scaffold, auth, first entity, CI pipeline
+- Sprint 1–3: Core features end-to-end + user feedback
+- Sprint 4–5: Polish, edge cases, performance, production readiness
+- **✅ MVP RELEASE**
+- Sprint 6+: Iterate based on real user feedback, not assumptions
+
+## 9. When to Break These Rules
+
+- **Compliance requirements:** PCI-DSS, SOC2 — build regardless of MVP scope
+- **Data integrity:** Getting it wrong means data loss/corruption — invest time
+- **Security:** Never cut auth, validation, secret management
+- **Irreversible decisions:** Database schema choices that hurt to change deserve more thought
+
+## 10. Red Flags You're Over-Engineering
+
+Stop if you catch yourself: Building admin panels before users exist. Writing "plugin systems" for one implementation. Debating architecture patterns >30 minutes. Creating more interfaces than concrete classes. Unit testing trivial getters/setters. Caching layer without measuring response times. Designing for millions on day one. Spending more time on infrastructure than features. Creating NuGet packages for single-project code.
diff --git a/.github/instructions/domain/ddd-guidelines.instructions.md b/.github/instructions/domain/ddd-guidelines.instructions.md
new file mode 100644
index 0000000..36aea99
--- /dev/null
+++ b/.github/instructions/domain/ddd-guidelines.instructions.md
@@ -0,0 +1,56 @@
+---
+applyTo: "**/Models/**/*.cs, **/Events/**/*.cs"
+---
+
+# Domain-Driven Design Guidelines — Project Domain
+
+## Rich Domain Models
+
+`Order` is the **aggregate root** — all state mutations flow through its public methods. Encapsulate behavior: `Process()`, `Complete()`, `Cancel()`, `AddItem()`. Never expose public setters. Use factory methods or constructors for creation, behavior methods for transitions. Guard every state transition with precondition checks — throw domain-specific exceptions when invariants are violated.
+
+## Value Objects
+
+Use Value Objects for concepts with **no identity** — equality based on structural value. Candidates: `Money` (amount + currency), `Currency`, `EmailAddress`, `PhoneNumber`. Implement as `record` or `readonly struct` with self-validation in constructor. Override equality/hash semantics (records do automatically).
+
+## Aggregate Boundaries
+
+`Order` is the **aggregate root** for order lifecycle. Child entities (`OrderItem`, line items) accessed **only** through aggregate root — never loaded independently. Persist and load entire aggregate in single unit of work for transactional consistency. Keep aggregates small — resist pulling unrelated concepts (e.g., user profiles) inside boundary.
+
+## Domain Events
+
+Raise events **from within aggregate** using base-class `AddDomainEvent()` helper. Events are **past-tense facts**: `OrderCreatedEvent`, `OrderCompletedEvent`. Carry only data needed by handlers (IDs and state), never full graphs. Events are pure data — no service dependencies or async calls. Dispatch **after** persistence (outbox pattern or `SaveChanges` interception) to avoid side effects on rollback.
+
+## Strategy Interfaces
+
+Belong in **Domain layer** — define *what* domain needs, not *how* fulfilled. `IChargeable` — charge funds, `IRefundable` — refund on cancellation, `ICancellable` — void pending charge. Infrastructure provides implementations (e.g., `StripePaymentProcessor`). Aggregate references by interface; Application injects concrete via DI.
+
+## Entity Invariants
+
+Validate **in constructor** — entity must never exist in invalid state. Use guard clauses at method entry mutating state. Required fields enforced at construction, not by external validators. Status transitions follow explicit state machine (document allowed transitions).
+
+```
+Created → Processing → Completed | Cancelled
+Cancelled → Refunded
+```
+
+## Pure Domain — No Framework Dependencies
+
+Domain classes must be **plain C# POCOs** — no EF Core attributes. No references to MediatR, ASP.NET Core, Entity Framework, or infrastructure NuGet packages. Mapping to persistence handled in Infrastructure via Fluent API (`IEntityTypeConfiguration`). Domain events implement thin marker interface (`IDomainEvent`) defined in Domain project — not `INotification` from MediatR.
+
+## Participant Model
+
+`Customer` represents **participant** in order (buyer, seller, or role). Entity with identity but not standalone aggregate root. Store role, display name, reference to auth identity. Associated during creation — never modified independently.
+
+## Address & Contact Value Objects
+
+- Value objects like `Address` and `EmailAddress` encapsulate validated, identity-less data.
+- Modeled as `record` types with self-validation in the constructor.
+- Use these to avoid primitive obsession — prefer `EmailAddress` over raw `string` for email fields.
+- Validate format in the Value Object; validate existence (e.g., uniqueness) at the Application layer.
+
+## General Rules
+
+- Prefer `Guid` for entity identifiers — generated at creation, not by database
+- Use `DateTimeOffset` for all timestamps — never `DateTime`
+- Collections from aggregates must be `IReadOnlyCollection` — mutation only through aggregate methods
+- All domain code must be **synchronous** — async belongs in Application and Infrastructure
diff --git a/.github/instructions/memory/memory-optimization.instructions.md b/.github/instructions/memory/memory-optimization.instructions.md
new file mode 100644
index 0000000..ffe854c
--- /dev/null
+++ b/.github/instructions/memory/memory-optimization.instructions.md
@@ -0,0 +1,64 @@
+---
+applyTo: "**/*.cs, **/*.razor, **/*.razor.cs, **/*.razor.css, **/*.ts, **/*.js"
+---
+
+# Memory & Context Window Optimization Rules
+
+> Universal rules for all AI models working on this codebase.
+> Goal: maximize useful context, minimize waste, maintain continuity across sessions.
+
+## 1. Context Window Discipline
+
+**Load Only What You Need:** Never bulk-read directories; use `glob`/`grep` first, then read only matched files. Use `view_range` for specific line ranges. Batch parallel reads in a single turn. Suppress verbose output (`--quiet`, `--no-pager`, pipe to `head`). Don't re-read files you've seen unless modified. Don't echo content back unless asked. Trim build/test output — report "Build succeeded" not full logs.
+
+**Structured Over Verbose:** Use tables, bullet points, and summaries over prose when reporting findings. Show only relevant code snippets with 5–10 lines context, not entire files.
+
+## 2. Session Priming Strategy
+
+**First Turn Efficiency:** Use `project_summary` tool instead of reading multiple files. Check `docs/` first to find relevant feature documentation. Narrow `grep`/`glob` to layer directories: `Components/` (UI), `Features/` (business logic), `Data/` (access), `Services/Strategies/` (external providers), `Models/` / `Events/` (domain).
+
+**Context Checkpoints:** After completing logical units, summarize what's done. Use `/compact` when context grows large. Before compacting, ensure decisions/findings are captured in plan.md or todos.
+
+## 3. File Access Patterns
+
+**Read Order (Most Efficient First):**
+1. `docs/{feature}/README.md` — high-level understanding
+2. Interface/contract files — API surface
+3. MediatR command/handler — business flow
+4. Implementation — only if needed
+5. Tests — only for verification
+
+**Write Order:** Plan first. Edit bottom-up (Domain → Application → Infrastructure → Presentation). Batch edits per file in a single turn. Don't interleave reads/writes.
+
+## 4. Search Efficiency
+
+Fast: `grep pattern:"IPaymentService" glob:"**/*.cs" output_mode:"files_with_matches"` (file paths only)
+
+Wasteful: `grep pattern:"IPaymentService" output_mode:"content" -A:50` (loads unnecessary context)
+
+**Progressive Disclosure:** Find files → Count matches (`count`) → Read specific matches (`content` + `-n`) → Deep dive with `view_range`.
+
+## 5. Sub-Agent Delegation
+
+**When to Delegate:** Read 1-3 files yourself. Search symbols yourself. Delegate 5+ independent areas to explore agents (parallel benefit). Delegate complex multi-file refactors to general-purpose agents. Delegate build/test to task agents (summary-only return).
+
+**Context Rules:** Give complete context to sub-agents (no memory sharing). Don't re-read their findings. Trust their status (pass/fail), verify only if suspicious.
+
+## 6. Memory Across Sessions
+
+**Session Store Usage:** Before starting major work, check session history with DuckDB session_store_sql. Find prior approaches to similar problems. Check plan.md for unfinished work. Query todos for pending items (`WHERE status != 'done'`). Reference previous sessions on "continue" requests.
+
+## 7. Token Budget Guidelines
+
+| Context % | Action |
+|-----------|--------|
+| < 30% | Normal — read freely |
+| 30-60% | Selective — use view_range, prefer summaries |
+| 60-80% | Conservative — delegate, summarize |
+| > 80% | Critical — suggest /compact, stop reading new files |
+
+**Cost Estimates:** `grep`/`glob` = very low. `grep` (5 matches) = low. `view` (50 lines) = low. `view` (200 lines) = medium. `view` (500+ lines) = high. Multiple full reads = very high.
+
+## 8. Anti-Patterns (Never Do These)
+
+Cat-then-grep (use grep directly). Exploratory full reads without specific question. Re-reading edited files. Verbose confirmations (say "Created X" not full content). Sequential single-file reads (batch parallel). Ignoring docs/ when it has a README. Global unrestricted grep (always scope to directory/type).
diff --git a/.github/instructions/planning.instructions.md b/.github/instructions/planning.instructions.md
new file mode 100644
index 0000000..a3cff85
--- /dev/null
+++ b/.github/instructions/planning.instructions.md
@@ -0,0 +1,13 @@
+---
+applyTo: "EscrowApp/Features/**/*.cs, EscrowApp/Components/Pages/**/*.razor, EscrowApp/Components/Pages/**/*.razor.cs, EscrowApp/Models/**/*.cs, EscrowApp/Events/**/*.cs, EscrowApp/Services/**/*.cs, EscrowApp/Data/**/*.cs, EscrowApp/Infrastructure/**/*.cs, EscrowApp.Tests/**/*.cs, EscrowApp/Program.cs"
+---
+
+# Planning Documentation Sync
+
+When you complete work that changes project status — implementing a feature, writing tests, replacing stubs, adding new handlers/components/strategies — you **must** update:
+
+1. **`docs/planning/implementation-plan.md`** — Update phase completion %, move items between "What's Built" and "What's Missing", update MVP priorities.
+2. **`docs/planning/task-checklist.md`** — Check off completed items (`[x]`), add new tasks, update phase status markers.
+3. **Update `Last synced with codebase` date** at the top of each file.
+
+Use the `planning_status` tool to verify these files are current before finishing your task.
diff --git a/.github/instructions/resilience/polly-patterns.instructions.md b/.github/instructions/resilience/polly-patterns.instructions.md
new file mode 100644
index 0000000..3111b4a
--- /dev/null
+++ b/.github/instructions/resilience/polly-patterns.instructions.md
@@ -0,0 +1,48 @@
+---
+applyTo: "**/Services/**/*.cs, **/Infrastructure/**/*.cs"
+---
+
+# Polly Resilience Patterns — External API Integration
+
+## Retry Policies — External API Calls
+
+Use **exponential backoff with jitter** for transient failures. Retry on HTTP `429`, `500`, `502`, `503`, and on `HttpRequestException` / `TimeoutRejectedException`. Start with **3 retries**, base delay 1 second, exponential multiplier 2, plus random jitter. Never retry `4xx` client errors (except `429`) — they indicate invalid requests that won't succeed on retry.
+
+## Circuit Breaker — External API Availability
+
+Break after **5 consecutive failures** in **30-second sampling window**. Stay in **open** state for **60 seconds** before **half-open**. In half-open, allow **one probe request** — succeeds → close, fails → re-open. When open, fail fast with `BrokenCircuitException` — don't queue. Log every state transition for visibility.
+
+## Timeout Policies
+
+**Always** pass and honor `CancellationToken` on every async method in call chain. Apply **optimistic timeout** of **15 seconds** per external API call (cancels underlying `HttpClient`). Apply **pessimistic timeout** of **30 seconds** as outer policy for entire operation. Handle `TimeoutRejectedException` explicitly — return timeout-specific error result.
+
+## Bulkhead Isolation
+
+Limit **concurrent external API operations** to prevent resource exhaustion cascades. Configure bulkhead of **10 concurrent executions** with **queue depth 5** for burst absorption. When rejected, return `503 Service Unavailable` with `Retry-After` header. Use separate bulkheads for critical operations vs. non-critical queries.
+
+## IHttpClientFactory + Polly Integration
+
+Register **named or typed `HttpClient`** via `IHttpClientFactory` — never instantiate manually. Attach policies using `.AddPolicyHandler()`. Compose policies via `Policy.WrapAsync()` — order matters: **Bulkhead → Circuit Breaker → Retry → Timeout** (outermost → innermost).
+
+## Idempotency Keys — Safe Retries
+
+**Every** state-changing mutation (create, capture, refund) must include `Idempotency-Key` header. Generate **deterministically** from domain operation: `{OrderId}:{Operation}:{Attempt}`. Store on aggregate — check for duplicates before initiating. Payment APIs honor idempotency keys for 24h — retries return original response, preventing duplicates.
+
+## Fallback Policy
+
+Define fallback for **every** policy chain — never let unhandled exceptions propagate silently. On failure after retries exhausted, return structured error result with context. Log final failure at `Error` level with exception details, correlation ID, operation context. Never swallow exceptions — fallback must re-throw domain exception or return typed failure.
+
+## Health Checks
+
+Expose circuit breaker state as ASP.NET Core `IHealthCheck`. Report `Degraded` when half-open, `Unhealthy` when open, `Healthy` when closed. Register at `/health/external-api` for infrastructure monitoring. Include circuit state in structured logs for incident correlation.
+
+## Configuration via Options Pattern
+
+Never hardcode policy values (retry count, timeout, concurrency limits). Bind settings from configuration using `IOptions`. Allow environment-specific overrides (shorter timeouts in tests, higher retry counts in production).
+
+## General Rules
+
+- Compose policies in a **PolicyWrap** — do not apply policies ad-hoc in individual service methods.
+- Use `Context` to pass correlation IDs and operation metadata through the policy chain for structured logging.
+- Test resilience behavior: use Simmy (Polly's chaos engineering library) to inject faults in integration tests.
+- Review Polly policy telemetry in production — alert on elevated retry rates or frequent circuit breaks.
diff --git a/.github/instructions/security/owasp-top10.instructions.md b/.github/instructions/security/owasp-top10.instructions.md
new file mode 100644
index 0000000..d6d6aed
--- /dev/null
+++ b/.github/instructions/security/owasp-top10.instructions.md
@@ -0,0 +1,115 @@
+---
+applyTo: "**/*.cs, **/*.razor"
+---
+
+# OWASP Top 10 Security — Project Conventions
+
+> Every code change must be evaluated through a security-first lens. When in doubt, choose the more restrictive option.
+
+---
+
+## A01 — Broken Access Control
+
+The #1 web application security risk. Default posture: **deny all, allow explicitly.**
+
+### Mandatory Practices
+
+Apply `[Authorize]` on **every** Blazor page and API endpoint — no anonymous defaults. Use **policy-based authorization** (never inline role strings). Define all policies in `AuthorizationPolicies` class: constant names, registered via `AddPolicy`. Use **resource-based authorization** for entity-level checks via `AuthorizationService.AuthorizeAsync()`. **Never** rely on UI hiding alone — always enforce server-side.
+
+---
+
+## A02 — Cryptographic Failures
+
+### Secrets Management
+
+Never store secrets in `appsettings.json`, source code, or production environment variables. Use **Azure Key Vault** with **Managed Identity** for production. Use `dotnet user-secrets` for local development. Store Stripe API keys in Key Vault, inject via `IOptions` Options pattern.
+
+### Data Protection
+
+Enforce **HTTPS everywhere** via `app.UseHsts()` and `app.UseHttpsRedirection()`. Encrypt sensitive fields at rest (PII, financial data). Never log tokens, API keys, connection strings, or PII — log only correlation IDs.
+
+---
+
+## A03 — Injection
+
+### SQL Injection Prevention
+
+**Always** use EF Core parameterized queries — never string-concatenate user input. If raw SQL required, use `FromSqlInterpolated` (never `FromSqlRaw` with concatenation).
+
+### Input Validation
+
+Validate **all** input at application boundary using FluentValidation. Every MediatR command must have corresponding validator.
+
+```csharp
+public sealed class CreateOrderCommandValidator : AbstractValidator
+{
+ public CreateOrderCommandValidator()
+ {
+ RuleFor(x => x.OrderId).NotEmpty();
+ RuleFor(x => x.Amount).GreaterThan(0).LessThanOrEqualTo(1_000_000);
+ }
+}
+```
+
+### XSS Prevention
+
+Blazor encodes output by default — never use `@((MarkupString)untrustedContent)`. Sanitize any user-provided HTML before rendering.
+
+---
+
+## A05 — Security Misconfiguration
+
+### Secure Headers
+
+Configure in `Program.cs` or middleware: `app.UseHsts()`, `app.UseHttpsRedirection()`, set `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`, CSP headers.
+
+### Environment Configuration
+
+Never enable Swagger/OpenAPI in production. Use `builder.Environment.IsDevelopment()` guards for debug features. Disable detailed error pages in production — use `UseExceptionHandler`.
+
+---
+
+## A07 — Identification and Authentication Failures
+
+### Authentication Strategy
+
+Use **Microsoft Entra ID** (primary) or **Duende IdentityServer** for authentication. Never implement custom auth or store plaintext passwords. Enforce MFA for privileged operations. Use `Microsoft.Identity.Web` for Entra integration. For Blazor Server: use `RevalidatingServerAuthenticationStateProvider`. Configure reasonable session timeout for workflows.
+
+---
+
+## Data Security Standards
+
+### Sensitive Data Handling
+
+Never store raw card numbers, CVVs, or full magnetic stripe. Delegate payment processing to PCI-compliant provider (Stripe) — use tokenized references only. Store only external references (PaymentIntent IDs) in Order — never raw credentials. Audit log all sensitive operations with timestamps and user identity.
+
+### Third-Party API Key Management
+
+Keys injected via Options pattern, sourced from Key Vault. Register Stripe client with DI. Rotate keys on schedule. Use restricted keys with minimum permissions. Validate Stripe webhook signatures on every event.
+
+### Idempotency Keys
+
+All state-changing commands **must** include `IdempotencyKey` to prevent duplicates. Generate client-side (GUID v7 recommended). Store and check server-side before processing. Return cached results for duplicates.
+
+---
+
+## Mass Assignment Prevention
+
+Never bind request data directly to domain entities. Use DTOs with explicit properties instead.
+
+---
+
+## Anti-Pattern Summary
+
+| Anti-Pattern | Risk | Fix |
+|---|---|---|
+| `[AllowAnonymous]` on protected pages | Unauthorized access | `[Authorize(Policy = "...")]` |
+| Hardcoded API keys or connection strings | Credential leak | Key Vault + Options pattern |
+| `FromSqlRaw` with string concatenation | SQL injection | `FromSqlInterpolated` or LINQ |
+| Logging user emails, tokens, card data | Data exposure | Log correlation IDs only |
+| Binding domain entities in endpoints | Mass assignment | DTOs with explicit properties |
+| Missing FluentValidation on commands | Invalid state / injection | Validator per command |
+| Custom password hashing | Broken authentication | Entra ID / IdentityServer |
+| Missing `[Authorize]` on new pages | Access control bypass | Default deny-all posture |
+| `@((MarkupString)userInput)` in Razor | XSS | Never render untrusted HTML |
+| Storing raw card numbers | PCI-DSS violation | Tokenized payment references only |
diff --git a/.github/instructions/testing/testing-standards.instructions.md b/.github/instructions/testing/testing-standards.instructions.md
new file mode 100644
index 0000000..fd6421f
--- /dev/null
+++ b/.github/instructions/testing/testing-standards.instructions.md
@@ -0,0 +1,77 @@
+---
+applyTo: "**/*Tests*/**/*.cs, **/*Test*/**/*.cs"
+---
+
+# Testing Standards — Project Conventions
+
+## Framework & Tooling
+
+- **Test framework:** xUnit — use `[Fact]` for single cases, `[Theory]` with `[InlineData]` or `[MemberData]` for parameterized tests.
+- **Assertions:** FluentAssertions — prefer `.Should().Be()`, `.Should().Throw()` over xUnit's `Assert.*`.
+- **Mocking:** Moq or NSubstitute — pick one per project, do not mix.
+- **Integration:** `Microsoft.AspNetCore.Mvc.Testing` (`WebApplicationFactory`) for API-level tests.
+- **Database:** Testcontainers for PostgreSQL — spin up a real database per test class for integration tests.
+
+## Naming Convention
+
+Use pattern: **MethodName_Scenario_ExpectedResult**. Example: `CreateOrder_ValidInput_ReturnsSuccess()`, `CreateOrder_InsufficientBalance_ThrowsPaymentException()`.
+
+## Arrange-Act-Assert (AAA)
+
+Every test must have **clearly separated** AAA sections using blank lines and optional comments. Arrange: set up test data with builders. Act: execute single operation. Assert: verify results and mock invocations.
+
+## Unit Tests
+
+### MediatR Handler Tests
+
+Test each command/query handler **in isolation** with mocked dependencies. Mock `IOrderRepository` and all strategy interfaces. Verify correct repository/strategy calls with expected arguments. Test both success and failure paths — assert thrown exceptions.
+
+### Domain Model Tests
+
+Test aggregate root methods directly. Verify domain events raised after state transitions. Verify invariant violations throw expected domain exceptions. Test Value Object validation (negative amounts, empty strings rejected).
+
+### Validation Rule Tests
+
+Test FluentValidation validators independently — call `validator.TestValidateAsync(model)`. Cover required fields, boundary values, format constraints, and cross-field rules.
+
+## Integration Tests
+
+### API / Endpoint Tests
+
+Use `WebApplicationFactory` to bootstrap application. Override DI registrations to swap real infrastructure with test doubles. Use **Testcontainers** for PostgreSQL (real database). Test full pipeline: routing → binding → validation → handler → persistence → response.
+
+### Database Integration Tests
+
+Verify EF Core mappings, constraints, indexes against real PostgreSQL. Test repository implementations end-to-end: persist, retrieve, verify. Each test class gets **fresh database** (Testcontainers per fixture) — never share mutable state.
+
+## What to Test
+
+| Layer | What to Test |
+|---|---|
+| Domain Models | Constructor validation, behavior methods, state transitions, domain event emission, Value Object equality |
+| MediatR Handlers | Business logic orchestration, correct repository/strategy calls, error handling |
+| Strategy Implementations | `StripePaymentProcessor` with mocked Stripe SDK, correct PaymentIntent parameters |
+| FluentValidation Rules | Required fields, boundary values, format constraints |
+| API Endpoints (integration) | Full HTTP request/response cycle, status codes, response bodies, error payloads |
+
+## What NOT to Test
+
+- **EF Core mappings directly** — these are validated by integration tests against a real database.
+- **Private methods** — test through the public interface that exercises them.
+- **Framework behavior** — do not test that ASP.NET Core routing works or that DI resolves correctly (unless custom logic is involved).
+- **Third-party library internals** — mock the boundary, don't test Stripe SDK behavior.
+
+## Test Data — Builder Pattern
+
+Use builders for complex domain objects to keep tests readable and decoupled from constructor changes. Builders with fluent interface allow test-specific configuration.
+
+## Coverage Targets
+
+- **Critical business flows** (create, complete, cancel, refund): **>90% line coverage**.
+- **Domain model invariants**: **100%** — every state transition path must be tested.
+- **API endpoints**: every documented status code (201, 400, 404, 409, 500) must have at least one test.
+- Coverage is a guideline, not a goal — a well-tested critical path is more valuable than chasing a vanity metric across utility code.
+
+## General Rules
+
+Tests must be **deterministic** — no dependency on wall-clock time, random data, or external services. Use `CancellationToken.None` in unit tests; integration tests should test cancellation explicitly. Clean up resources in `Dispose` / `IAsyncDisposable`. Run tests in parallel by default — ensure no shared mutable state.
diff --git a/.github/lsp.json b/.github/lsp.json
new file mode 100644
index 0000000..eb59508
--- /dev/null
+++ b/.github/lsp.json
@@ -0,0 +1,24 @@
+{
+ "lspServers": {
+ "csharp": {
+ "command": "dotnet",
+ "args": [
+ "tool",
+ "run",
+ "csharp-ls",
+ "--solution",
+ "MyApp.sln"
+ ],
+ "initializationOptions": {
+ "AutomaticWorkspaceInit": true
+ },
+ "fileExtensions": {
+ ".cs": "csharp",
+ ".csproj": "xml",
+ ".razor": "razor",
+ ".razor.cs": "csharp"
+ },
+ "description": "C# Language Server (csharp-ls) for .NET 10. Install with: dotnet tool install --global csharp-ls"
+ }
+ }
+}
diff --git a/.github/skills/CATALOG.md b/.github/skills/CATALOG.md
new file mode 100644
index 0000000..6335855
--- /dev/null
+++ b/.github/skills/CATALOG.md
@@ -0,0 +1,260 @@
+# Skills Catalog
+
+> 42 cross-platform skills organized by category. Each skill follows the Jeffallan `references/` pattern for memory-optimized lazy loading.
+> **Version:** 2.2.0 | **Platforms:** Copilot CLI, Claude, Gemini
+
+---
+
+## Quick Reference
+
+| # | Category | Skills | Description |
+|---|----------|--------|-------------|
+| 1 | [code-quality](#1-code-quality) | 7 | Code review, refactoring, documentation, debugging, quality metrics, smart refactor, tech debt |
+| 2 | [security](#2-security) | 5 | OWASP audit, secret scanning, threat modeling, authentication, authorization |
+| 3 | [architecture](#3-architecture) | 5 | Architecture review, design patterns, dependencies, legacy modernization, polyglot analysis |
+| 4 | [testing](#4-testing) | 3 | Test generation, TDD coaching, coverage analysis |
+| 5 | [database](#5-database) | 2 | Schema review, query optimization |
+| 6 | [devops](#6-devops) | 4 | CI/CD, deployment preflight, monitoring, chaos engineering |
+| 7 | [documentation](#7-documentation) | 3 | README, ADR, API docs |
+| 8 | [research](#8-research) | 4 | Codebase exploration, tech spikes, spec mining, deep context generation |
+| 9 | [project-management](#9-project-management) | 3 | Spec writing, issue creation, feature requirements |
+| 10 | [ai](#10-ai) | 3 | MCP development, prompt engineering, agent orchestration |
+| 11 | [language](#11-language) | 2 | .NET Core expert, C# developer |
+| 12 | [workflow](#12-workflow) | 1 | Context window and token optimization |
+
+---
+
+## All Skills
+
+### 1. Code Quality
+
+Skills for reviewing, refactoring, documenting, and debugging code.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **code-reviewer** | Review code changes for correctness, style, security, and maintainability | review code, check PR, code review | [SKILL.md](./code-reviewer/SKILL.md) |
+| **refactor-planner** | Analyze code and produce a prioritized refactoring plan | refactor, clean up code, reduce tech debt | [SKILL.md](./refactor-planner/SKILL.md) |
+| **code-documenter** | Generate inline documentation, XML doc comments, and usage examples | document code, add comments, explain code | [SKILL.md](./code-documenter/SKILL.md) |
+| **debugging-wizard** | Systematic debugging with root cause analysis and fix verification | debug, error, stack trace, exception, crash | [SKILL.md](./debugging-wizard/SKILL.md) |
+| **quality-analyzer** | Analyze code quality metrics — cyclomatic complexity, cognitive complexity, maintainability index, SATD | analyze quality, code metrics, complexity analysis, maintainability | [SKILL.md](./quality-analyzer/SKILL.md) |
+| **smart-refactor** | Metrics-driven refactoring with baseline/after comparison and Fowler patterns | smart refactor, measure refactor, complexity reduction | [SKILL.md](./smart-refactor/SKILL.md) |
+| **tech-debt-tracker** | Detect, quantify, and prioritize technical debt — SATD detection, hour estimation, sprint planning | track tech debt, SATD scan, debt inventory, debt report | [SKILL.md](./tech-debt-tracker/SKILL.md) |
+
+---
+
+### 2. Security
+
+Skills for auditing, scanning, and modeling security threats.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **owasp-audit** | Audit code against OWASP Top 10 vulnerabilities | security audit, owasp check, vulnerability scan | [SKILL.md](./owasp-audit/SKILL.md) |
+| **secret-scanner** | Detect hardcoded secrets, API keys, and credentials in source code | scan secrets, find credentials, check for keys | [SKILL.md](./secret-scanner/SKILL.md) |
+| **threat-modeler** | Create STRIDE-based threat models for system components | threat model, security design, risk analysis | [SKILL.md](./threat-modeler/SKILL.md) |
+| **authentication** | Implement authentication with Entra ID, IdentityServer, or ASP.NET Core Identity | authentication, login, Entra ID, JWT, OIDC | [SKILL.md](./authentication/SKILL.md) |
+| **authorization** | Implement policy-based, role-based, and resource-based authorization | authorization, policies, roles, claims, access control | [SKILL.md](./authorization/SKILL.md) |
+
+---
+
+### 3. Architecture
+
+Skills for reviewing architecture, recommending patterns, analyzing dependencies, and modernizing legacy systems.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **architecture-reviewer** | Review system architecture for quality attributes and anti-patterns | review architecture, check design, architecture audit | [SKILL.md](./architecture-reviewer/SKILL.md) |
+| **design-pattern-advisor** | Recommend and apply appropriate design patterns to solve structural problems | suggest pattern, which pattern, design advice | [SKILL.md](./design-pattern-advisor/SKILL.md) |
+| **dependency-analyzer** | Analyze project dependencies for risks, updates, and license compliance | check dependencies, audit packages, outdated packages | [SKILL.md](./dependency-analyzer/SKILL.md) |
+| **legacy-modernizer** | Plan and execute modernization of legacy codebases to modern architectures | modernize, migrate, upgrade legacy, rewrite | [SKILL.md](./legacy-modernizer/SKILL.md) |
+| **polyglot-analyzer** | Analyze multi-language codebases — language distribution, cross-language quality comparison, unified gates | polyglot analysis, multi-language, language distribution | [SKILL.md](./polyglot-analyzer/SKILL.md) |
+
+---
+
+### 4. Testing
+
+Skills for generating tests, coaching TDD, and analyzing coverage.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **test-generator** | Generate unit and integration tests with Arrange-Act-Assert structure | write tests, generate tests, add test coverage | [SKILL.md](./test-generator/SKILL.md) |
+| **tdd-coach** | Guide test-driven development with red-green-refactor cycle | tdd, test first, red green refactor | [SKILL.md](./tdd-coach/SKILL.md) |
+| **test-coverage-analyzer** | Analyze test coverage gaps and recommend high-value tests to add | coverage gaps, missing tests, improve coverage | [SKILL.md](./test-coverage-analyzer/SKILL.md) |
+
+---
+
+### 5. Database
+
+Skills for reviewing schemas and optimizing queries.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **schema-reviewer** | Review database schema design for normalization, indexing, and integrity | review schema, check database design, schema audit | [SKILL.md](./schema-reviewer/SKILL.md) |
+| **query-optimizer** | Analyze and optimize SQL queries for performance | optimize query, slow query, query performance | [SKILL.md](./query-optimizer/SKILL.md) |
+
+---
+
+### 6. DevOps
+
+Skills for building CI/CD pipelines, validating deployments, monitoring, and chaos engineering.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **ci-cd-builder** | Create or improve CI/CD pipeline configurations | build pipeline, create CI, setup CD, github actions | [SKILL.md](./ci-cd-builder/SKILL.md) |
+| **deployment-preflight** | Run pre-deployment checks and generate go/no-go reports | preflight check, ready to deploy, deployment review | [SKILL.md](./deployment-preflight/SKILL.md) |
+| **monitoring-expert** | Design observability stacks with metrics, logs, traces, and alerting | monitoring, observability, alerts, dashboards, SLO | [SKILL.md](./monitoring-expert/SKILL.md) |
+| **chaos-engineer** | Design and execute chaos experiments to verify system resilience | chaos testing, resilience, fault injection, game day | [SKILL.md](./chaos-engineer/SKILL.md) |
+
+---
+
+### 7. Documentation
+
+Skills for generating READMEs, ADRs, and API documentation.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **readme-generator** | Generate comprehensive README files from project analysis | create readme, write readme, project documentation | [SKILL.md](./readme-generator/SKILL.md) |
+| **adr-creator** | Create Architecture Decision Records following the ADR standard | create ADR, document decision, architecture decision | [SKILL.md](./adr-creator/SKILL.md) |
+| **api-documenter** | Generate API documentation from code with examples and schemas | document API, API docs, endpoint documentation | [SKILL.md](./api-documenter/SKILL.md) |
+
+---
+
+### 8. Research
+
+Skills for exploring codebases, planning technical spikes, and mining specifications.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **codebase-explorer** | Explore and map unfamiliar codebases to build understanding | explore codebase, understand code, map architecture | [SKILL.md](./codebase-explorer/SKILL.md) |
+| **tech-spike-planner** | Plan time-boxed technical investigations with clear success criteria | plan spike, technical investigation, research task | [SKILL.md](./tech-spike-planner/SKILL.md) |
+| **spec-miner** | Extract implicit specifications from code, tests, and documentation | mine specs, extract requirements, reverse engineer | [SKILL.md](./spec-miner/SKILL.md) |
+| **deep-context-generator** | Generate LLM-optimized codebase context for onboarding and architecture understanding | generate context, codebase overview, onboarding, architecture map | [SKILL.md](./deep-context-generator/SKILL.md) |
+
+---
+
+### 9. Project Management
+
+Skills for writing specifications, creating issues, and forging feature requirements.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **spec-writer** | Write comprehensive technical specifications from feature requests | write spec, create specification, define requirements | [SKILL.md](./spec-writer/SKILL.md) |
+| **issue-creator** | Create structured GitHub issues with acceptance criteria and sub-task decomposition | create issue, write issue, file bug, create ticket | [SKILL.md](./issue-creator/SKILL.md) |
+| **feature-forge** | Generate complete feature breakdowns with stories, tasks, and acceptance criteria | feature breakdown, user stories, requirements, epic | [SKILL.md](./feature-forge/SKILL.md) |
+
+---
+
+### 10. AI
+
+Skills for MCP development, prompt engineering, and multi-agent orchestration.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **mcp-developer** | Build, debug, and extend MCP servers/clients — tool handlers, resources, transports, schemas | MCP, Model Context Protocol, MCP server, AI tools, JSON-RPC | [SKILL.md](./mcp-developer/SKILL.md) |
+| **prompt-engineer** | Write, refactor, and evaluate LLM prompts — templates, structured outputs, evaluation rubrics | prompt engineering, prompt optimization, chain-of-thought, few-shot, system prompts | [SKILL.md](./prompt-engineer/SKILL.md) |
+| **agent-orchestrator** | Orchestrate parallel sub-agent fleets with token-aware delegation, DAG dependencies, and result aggregation | orchestrate agents, parallel tasks, multi-agent, fleet management, token budget | [SKILL.md](./agent-orchestrator/SKILL.md) |
+
+---
+
+### 11. Language
+
+Language-specific skills for .NET Core and C# development.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **dotnet-core-expert** | Deep .NET 10 expertise — minimal APIs, Clean Architecture, EF Core, CQRS/MediatR, JWT auth, AOT | .NET Core, .NET 10, ASP.NET Core, C# 13, minimal API, Entity Framework Core, microservices | [SKILL.md](./dotnet-core-expert/SKILL.md) |
+| **csharp-developer** | Senior C# 13 developer — records, pattern matching, primary constructors, Blazor, performance | C#, .NET, Blazor, Entity Framework, EF Core, SignalR, Minimal API | [SKILL.md](./csharp-developer/SKILL.md) |
+
+---
+
+### 12. Workflow
+
+Skills for optimizing AI-assisted development workflows and context management.
+
+| Skill | Description | Triggers | Link |
+|-------|-------------|----------|------|
+| **memory-optimization** | Context window and token optimization rules — load less, achieve more | optimize context, reduce tokens, context window, memory management, token budget | [SKILL.md](./memory-optimization/SKILL.md) |
+
+---
+
+## Directory Structure
+
+```
+.github/
+└── skills/
+ ├── CATALOG.md # ← This file (master index)
+ ├── adr-creator/SKILL.md + references/
+ ├── agent-orchestrator/SKILL.md + references/
+ ├── api-documenter/SKILL.md + references/
+ ├── architecture-reviewer/SKILL.md + references/
+ ├── authentication/SKILL.md + references/
+ ├── authorization/SKILL.md + references/
+ ├── chaos-engineer/SKILL.md + references/
+ ├── ci-cd-builder/SKILL.md + references/
+ ├── code-documenter/SKILL.md + references/
+ ├── code-reviewer/SKILL.md + references/
+ ├── codebase-explorer/SKILL.md + references/
+ ├── csharp-developer/SKILL.md + references/
+ ├── debugging-wizard/SKILL.md + references/
+ ├── deep-context-generator/SKILL.md + references/
+ ├── dependency-analyzer/SKILL.md + references/
+ ├── deployment-preflight/SKILL.md + references/
+ ├── design-pattern-advisor/SKILL.md + references/
+ ├── dotnet-core-expert/SKILL.md + references/
+ ├── feature-forge/SKILL.md + references/
+ ├── issue-creator/SKILL.md + references/
+ ├── legacy-modernizer/SKILL.md + references/
+ ├── mcp-developer/SKILL.md + references/
+ ├── memory-optimization/SKILL.md + references/
+ ├── monitoring-expert/SKILL.md + references/
+ ├── owasp-audit/SKILL.md + references/
+ ├── polyglot-analyzer/SKILL.md + references/
+ ├── prompt-engineer/SKILL.md + references/
+ ├── quality-analyzer/SKILL.md + references/
+ ├── query-optimizer/SKILL.md + references/
+ ├── readme-generator/SKILL.md + references/
+ ├── refactor-planner/SKILL.md + references/
+ ├── schema-reviewer/SKILL.md + references/
+ ├── secret-scanner/SKILL.md + references/
+ ├── smart-refactor/SKILL.md + references/
+ ├── spec-miner/SKILL.md + references/
+ ├── spec-writer/SKILL.md + references/
+ ├── tdd-coach/SKILL.md + references/
+ ├── tech-debt-tracker/SKILL.md + references/
+ ├── tech-spike-planner/SKILL.md + references/
+ ├── test-coverage-analyzer/SKILL.md + references/
+ ├── test-generator/SKILL.md + references/
+ └── threat-modeler/SKILL.md + references/
+```
+
+## Using Skills
+
+### Copilot CLI
+
+```bash
+# Skills in .github/skills/ are automatically discovered
+# Trigger by name or keyword:
+copilot "Use the agent-orchestrator skill to coordinate parallel analysis"
+copilot "Use the csharp-developer skill to write a Blazor component"
+```
+
+### Claude
+
+1. **Project Knowledge** — Add `SKILL.md` files to your Claude project's knowledge base
+2. **Direct Reference** — Ask Claude: *"Follow the prompt-engineer skill to design a prompt for [task]"*
+
+### Gemini
+
+1. **Context Window** — Paste the `SKILL.md` content at the start of your conversation
+2. **Gems** — Create a custom Gem with the skill content as instructions
+
+## Conventions
+
+- Each skill lives in `//SKILL.md` with a `references/` directory
+- Reference files are lazy-loaded — only read when their "Load When" condition is met
+- All skills target v2.0.0 with `allowed-tools`, `related-skills`, and `output-format` metadata
+- Skills are self-contained but declare related skills for cross-referencing
+- All skills target the same three platforms: `copilot-cli`, `claude`, `gemini`
+
+---
+
+*Skills Catalog — MIT License*
diff --git a/.github/workflows/azure-functions.yml b/.github/workflows/azure-functions.yml
index 56cd5eb..fad8b91 100644
--- a/.github/workflows/azure-functions.yml
+++ b/.github/workflows/azure-functions.yml
@@ -1,26 +1,9 @@
name: Deploy Azure Function
-# ==============================================================================
-# WORKFLOW OVERVIEW
-# ==============================================================================
-# This workflow implements a Blue/Green deployment strategy for an Azure Function App.
-# It is split into 3 jobs that run in sequence:
-#
-# 1. BUILD – Compiles and packages the Function App (runs on every trigger).
-# 2. DEPLOY-STAGING – Deploys to a staging Function App (runs ONLY on pull requests).
-# 3. DEPLOY-PRODUCTION – Deploys to the live Function App (runs ONLY on push/manual to master).
-#
-# Flow: build ──► deploy-staging (if PR)
-# ──► deploy-production (if push to master or manual trigger)
-# ==============================================================================
+# Deployment Strategy:
+# - Push to 'master' (i.e. a merged PR from 'development') deploys to the live Function App.
+# - Pull requests to 'master' only build/compile-check — no deploy, no staging environment.
-# ==============================================================================
-# SECTION 1: TRIGGERS
-# Defines the events that cause this workflow to run.
-# - push: Runs when code is pushed to 'master' (only if Api/ or this file changed).
-# - pull_request: Runs when a PR targeting 'master' is opened/updated (same path filter).
-# - workflow_dispatch: Allows running the workflow manually from the GitHub Actions UI.
-# ==============================================================================
on:
push:
branches:
@@ -37,25 +20,12 @@ on:
- '.github/workflows/azure-functions.yml'
workflow_dispatch: # Allow manual trigger
-# ==============================================================================
-# SECTION 2: GLOBAL ENVIRONMENT VARIABLES
-# Shared constants available to all jobs. Centralises names and versions so they
-# only need to be updated in one place.
-# ==============================================================================
env:
- AZURE_FUNCTIONAPP_NAME: 'cloudzen-api-func-e4gehdaef9ftdhbn' # Production Function App name
- AZURE_FUNCTIONAPP_NAME_STAGING: 'cloudzen-api-func-staging' # Staging Function App name
- AZURE_FUNCTIONAPP_PACKAGE_PATH: 'Api' # Path to the Azure Function project
- DOTNET_VERSION: '8.0.x' # .NET SDK version to use
+ AZURE_FUNCTIONAPP_NAME: 'cloudzen-api-func-e4gehdaef9ftdhbn' # Update if your production Function App hostname differs
+ AZURE_FUNCTIONAPP_PACKAGE_PATH: 'Api' # Path to your Azure Function project
+ DOTNET_VERSION: '8.0.x'
jobs:
- # ============================================================================
- # JOB 1: BUILD
- # Runs on EVERY trigger (push, PR, manual).
- # Restores NuGet packages, compiles the project in Release mode, publishes
- # the output, and uploads it as a pipeline artifact so downstream deploy
- # jobs can consume it.
- # ============================================================================
build:
runs-on: ubuntu-latest
steps:
@@ -67,20 +37,15 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- # Restore NuGet dependencies before building
- name: Restore dependencies
run: dotnet restore ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
- # Compile the project in Release configuration
- name: Build
run: dotnet build ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} --configuration Release --no-restore
- # Create the deployment-ready package under ./output
- name: Publish
run: dotnet publish ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} --configuration Release --no-build --output ./output
- # Upload the published output as an artifact named 'function-app'
- # so the deploy jobs can download and deploy it
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
@@ -88,43 +53,11 @@ jobs:
path: ./output
include-hidden-files: true
- # ============================================================================
- # JOB 2: DEPLOY TO STAGING
- # Runs ONLY when the trigger is a pull_request.
- # Downloads the build artifact and deploys it to the STAGING Function App
- # using a publish profile stored in GitHub Secrets.
- # This lets reviewers test the API changes alongside the SWA preview environment.
- # ============================================================================
- deploy-staging:
- if: github.event_name == 'pull_request'
- needs: build # Waits for the build job to complete
- runs-on: ubuntu-latest
- environment: staging # Uses the 'staging' GitHub environment (for secrets/protection rules)
- steps:
- - name: Download build artifact
- uses: actions/download-artifact@v4
- with:
- name: function-app
- path: ./output
-
- # Deploy the artifact to the staging Azure Function App
- - name: Deploy to Staging Function App
- uses: Azure/functions-action@v1
- with:
- app-name: ${{ env.AZURE_FUNCTIONAPP_NAME_STAGING }}
- package: './output'
- publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE_STAGING }}
-
- # ============================================================================
- # JOB 3: DEPLOY TO PRODUCTION
- # Runs ONLY when code is pushed to 'master' or the workflow is triggered manually.
- # Downloads the build artifact and deploys it to the PRODUCTION Function App.
- # ============================================================================
deploy-production:
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master'
- needs: build # Waits for the build job to complete
+ needs: build
runs-on: ubuntu-latest
- environment: production # Uses the 'production' GitHub environment (for secrets/protection rules)
+ environment: production
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
@@ -132,7 +65,6 @@ jobs:
name: function-app
path: ./output
- # Deploy the artifact to the production Azure Function App
- name: Deploy to Production Function App
uses: Azure/functions-action@v1
with:
diff --git a/.github/workflows/azure-static-web-apps.yml b/.github/workflows/azure-static-web-apps.yml
index e92516a..34b89e2 100644
--- a/.github/workflows/azure-static-web-apps.yml
+++ b/.github/workflows/azure-static-web-apps.yml
@@ -1,37 +1,19 @@
-# ==============================================================================
-# WORKFLOW OVERVIEW
-# ==============================================================================
-# This workflow implements a Blue/Green deployment strategy for the Blazor WASM
-# Static Web App. It is split into 4 jobs:
+# Azure CloudZen Static Web Apps CI/CD
#
-# 1. BUILD – Compiles and publishes the Blazor WASM app (runs on every trigger).
-# For PRs, staging config is applied before publish so the preview
-# environment points to the staging Function App.
-# 2. DEPLOY-STAGING – Deploys to an SWA preview environment (runs ONLY on pull requests).
-# 3. DEPLOY-PRODUCTION – Deploys to the live Static Web App (runs ONLY on push/manual to master).
-# 4. CLOSE-STAGING – Destroys the preview environment when a PR is closed/merged.
+# Deployment Strategy:
+# - Push to 'master' (i.e. a merged PR from 'development') deploys to the live Static Web App.
+# - Pull requests to 'master' only build/compile-check — no deploy, no preview environment.
#
-# Flow: build ──► deploy-staging (if PR opened/updated)
-# ──► deploy-production (if push to master or manual trigger)
-# close-staging (if PR closed — independent, no build needed)
-#
-# Build & Deploy details:
-# - "dotnet publish" compiles the Blazor WASM app and outputs to "publish_output/wwwroot".
-# - "skip_app_build: true" tells Azure SWA to skip its built-in Oryx build engine and
-# just upload the pre-built files. Portal Build Details settings are ignored entirely.
-# - "app_location: publish_output/wwwroot" tells the SWA deploy action where to find
-# the pre-built files to upload.
-# ==============================================================================
+# Build & Deploy Pipeline:
+# 1. "dotnet publish" compiles the Blazor WASM app and outputs to "publish_output/wwwroot".
+# 2. "skip_app_build: true" tells Azure Static Web Apps to skip its built-in Oryx build
+# engine and just upload the pre-built files. This means the portal's Build Details
+# settings (App location, Api location, Artifact location) are ignored entirely.
+# 3. "app_location: publish_output/wwwroot" tells the SWA deploy action where to find
+# the pre-built files to upload.
name: Azure CloudZen Static Web Apps CI/CD
-# ==============================================================================
-# SECTION 1: TRIGGERS
-# Defines the events that cause this workflow to run.
-# - push: Runs when code is pushed to 'master' (ignores Api/ and markdown changes).
-# - pull_request: Runs when a PR targeting 'master' is opened, updated, or closed.
-# - workflow_dispatch: Allows running the workflow manually from the GitHub Actions UI.
-# ==============================================================================
on:
push:
branches:
@@ -41,7 +23,7 @@ on:
- '.github/workflows/azure-functions.yml'
- '*.md' # Don't trigger for markdown changes
pull_request:
- types: [opened, synchronize, reopened, closed]
+ types: [opened, synchronize, reopened]
branches:
- master
paths-ignore:
@@ -50,140 +32,57 @@ on:
- '*.md'
workflow_dispatch: # Allow manual trigger
-# ==============================================================================
-# SECTION 2: GLOBAL ENVIRONMENT VARIABLES
-# Shared constants available to all jobs. The hostnames are used to swap the
-# API endpoint when building for staging vs production.
-# ==============================================================================
-env:
- PRODUCTION_FUNC_HOSTNAME: 'cloudzen-api-func-e4gehdaef9ftdhbn.westus2-01.azurewebsites.net' # Production Function App hostname
- STAGING_FUNC_HOSTNAME: 'cloudzen-api-func-staging-hch0amaed0gke2dv.westus2-01.azurewebsites.net' # Staging Function App hostname
- DOTNET_VERSION: '8.0.x' # .NET SDK version to use
-
jobs:
- # ============================================================================
- # JOB 1: BUILD
- # Runs on EVERY trigger except PR close.
- # Restores NuGet packages, compiles the Blazor WASM project in Release mode,
- # applies staging configuration when building for a PR, publishes the output,
- # and uploads it as a pipeline artifact so downstream deploy jobs can consume it.
- # ============================================================================
build:
- if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
+ if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
+ name: Build Job (PR check, no deploy)
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
- - name: Setup .NET ${{ env.DOTNET_VERSION }}
+ - name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
+ dotnet-version: '8.0.x'
- # Restore NuGet dependencies before building
- name: Restore dependencies
run: dotnet restore CloudZen.csproj
- # Compile the project in Release configuration
- name: Build
run: dotnet build CloudZen.csproj --configuration Release --no-restore
- # For PR builds (staging), swap in staging configuration before publish
- # so the preview environment points to the staging Function App API
- - name: Apply staging configuration
- if: github.event_name == 'pull_request'
- run: |
- echo "Applying staging configuration for preview environment..."
- cp wwwroot/appsettings.Staging.json wwwroot/appsettings.Production.json
- sed -i "s|${{ env.PRODUCTION_FUNC_HOSTNAME }}|${{ env.STAGING_FUNC_HOSTNAME }}|g" wwwroot/staticwebapp.config.json
-
- # Publish the Blazor WASM app to publish_output/wwwroot
- # BlazorEnableCompression=false because SWA handles compression at the edge
- - name: Publish Blazor App
- run: dotnet publish CloudZen.csproj -c Release -o publish_output -p:BlazorEnableCompression=false
-
- # Upload the published output as an artifact named 'swa-app'
- # so the deploy jobs can download and deploy it
- - name: Upload build artifact
- uses: actions/upload-artifact@v4
- with:
- name: swa-app
- path: publish_output/wwwroot
- include-hidden-files: true
-
- # ============================================================================
- # JOB 2: DEPLOY TO STAGING
- # Runs ONLY when the trigger is a pull_request (opened/updated).
- # Downloads the build artifact and deploys it to an SWA preview environment
- # with a unique URL, so reviewers can test the Blazor app alongside the
- # staging Function App.
- # ============================================================================
- deploy-staging:
- if: github.event_name == 'pull_request' && github.event.action != 'closed'
- needs: build # Waits for the build job to complete
+ build-and-deploy:
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
- environment: staging # Uses the 'staging' GitHub environment (for secrets/protection rules)
+ name: Build and Deploy Job
steps:
- - name: Download build artifact
- uses: actions/download-artifact@v4
+ - name: Checkout repository
+ uses: actions/checkout@v4
with:
- name: swa-app
- path: ./swa-output
+ submodules: true
- # Deploy the pre-built Blazor app to an SWA preview environment
- # skip_app_build: true — tells SWA to use our pre-built files as-is
- - name: Deploy to SWA Preview Environment
- uses: Azure/static-web-apps-deploy@v1
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
with:
- azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- action: "upload"
- skip_app_build: true
- app_location: "./swa-output"
+ dotnet-version: '8.0.x'
- # ============================================================================
- # JOB 3: DEPLOY TO PRODUCTION
- # Runs ONLY when code is pushed to 'master' or the workflow is triggered manually.
- # Downloads the build artifact and deploys it to the live Static Web App.
- # ============================================================================
- deploy-production:
- if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master'
- needs: build # Waits for the build job to complete
- runs-on: ubuntu-latest
- environment: production # Uses the 'production' GitHub environment (for secrets/protection rules)
- steps:
- - name: Download build artifact
- uses: actions/download-artifact@v4
- with:
- name: swa-app
- path: ./swa-output
+ - name: Restore dependencies
+ run: dotnet restore CloudZen.csproj
- # Deploy the pre-built Blazor app to the live SWA production slot
- # skip_app_build: true — tells SWA to use our pre-built files as-is
- - name: Deploy to SWA Production
+ - name: Build
+ run: dotnet build CloudZen.csproj --configuration Release --no-restore
+
+ - name: Publish Blazor App
+ run: dotnet publish CloudZen.csproj -c Release -o publish_output -p:BlazorEnableCompression=false
+
+ - name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
skip_app_build: true
- app_location: "./swa-output"
-
- # ============================================================================
- # JOB 4: CLOSE STAGING
- # Runs ONLY when a PR is closed (merged or abandoned).
- # Destroys the SWA preview environment to free up resources.
- # This job is independent — it does NOT need a build.
- # ============================================================================
- close-staging:
- if: github.event_name == 'pull_request' && github.event.action == 'closed'
- runs-on: ubuntu-latest
- steps:
- # Tell SWA to tear down the preview environment for this PR
- - name: Destroy preview environment
- uses: Azure/static-web-apps-deploy@v1
- with:
- azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
- action: "close"
\ No newline at end of file
+ app_location: "publish_output/wwwroot"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index cb1b301..677c37c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -366,3 +366,15 @@ MigrationBackup/
/Api/local.settings.json
/Api/local.settings.json
/cloudzen-chatbot.jsx
+
+# Claude Code — sensitive/runtime files
+.claude/settings.local.json
+.claude/settings.local*.json
+.claude/settings.local.*.json
+.claude/*.bak
+.claude/*.backup
+.claude/hooks/.rate-limit-timestamp
+.claude/hooks/notifications.log
+.claude/hooks/notification-config.json
+.claude/hooks/smtp-cred-*.xml
+.claude/hooks/test.txt
diff --git a/.impeccable/design.json b/.impeccable/design.json
new file mode 100644
index 0000000..708cedc
--- /dev/null
+++ b/.impeccable/design.json
@@ -0,0 +1,118 @@
+{
+ "schemaVersion": 2,
+ "generatedAt": "2026-06-04T21:11:38.000Z",
+ "title": "Design System: CloudZen",
+ "extensions": {
+ "colorMeta": {
+ "teal-brand": { "role": "primary", "displayName": "Aqua Signal", "canonical": "#61C2C8", "tonalRamp": ["#DAF6F9","#B8EFF4","#89D6DC","#78BCC2","#659FA5","#61C2C8","#538488","#40676B","#2F4E51","#1F3638","#0F1E1F"] },
+ "cta-orange": { "role": "secondary", "displayName": "Action Orange", "canonical": "#f97316", "tonalRamp": ["#fff7ed","#ffedd5","#fed7aa","#fb923c","#f97316","#ea580c","#c2410c"] },
+ "teal-600": { "role": "accent", "displayName": "Deep Signal Teal", "canonical": "#40676B" },
+ "teal-700": { "role": "ink-body", "displayName": "Ink Teal", "canonical": "#2F4E51" },
+ "teal-800": { "role": "ink-heading","displayName": "Heading Teal", "canonical": "#1F3638" },
+ "teal-900": { "role": "dark-surface","displayName": "Deep Teal Surface", "canonical": "#0F1E1F" },
+ "teal-50": { "role": "tint", "displayName": "Teal Mist", "canonical": "#DAF6F9" },
+ "surface-white":{ "role": "neutral-bg", "displayName": "Surface White", "canonical": "#ffffff" },
+ "ink-body": { "role": "neutral-ink","displayName": "Ink Body", "canonical": "#374151" },
+ "ink-heading": { "role": "neutral-ink","displayName": "Ink Heading", "canonical": "#1f2937" }
+ },
+ "typographyMeta": {
+ "display": { "displayName": "Display", "purpose": "Hero headlines. One per view. Never exceed 3.75rem." },
+ "headline": { "displayName": "Headline", "purpose": "Section headings, card group headers." },
+ "title": { "displayName": "Title", "purpose": "Individual card titles, step labels." },
+ "body": { "displayName": "Body", "purpose": "All prose. Max 65–72ch line length in reading sections." },
+ "label": { "displayName": "Label", "purpose": "Buttons, nav, badges. IBM Plex Sans always." },
+ "caption": { "displayName": "Caption", "purpose": "Metadata, timestamps, stat labels." }
+ },
+ "shadows": [
+ { "name": "ambient-low", "value": "0 2px 8px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04)", "purpose": "Workflow node cards at rest; small floating elements." },
+ { "name": "ambient-mid", "value": "0 4px 16px rgba(0,0,0,0.10)", "purpose": "Modals, dropdowns, badges floating over content." },
+ { "name": "hover-lift", "value": "0 8px 24px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06)","purpose": "Interactive card hover state." },
+ { "name": "glow-teal", "value": "0 0 20px rgba(97,194,200,0.45), 0 0 40px rgba(97,194,200,0.2)","purpose": "Animated node pulse — never static elevation." },
+ { "name": "glow-orange", "value": "0 0 20px rgba(249,115,22,0.45), 0 0 40px rgba(249,115,22,0.2)","purpose": "Animated orange-highlight node pulse." },
+ { "name": "cta-button", "value": "0 4px 12px rgba(249,115,22,0.3)", "purpose": "CTA button hover shadow." }
+ ],
+ "motion": [
+ { "name": "ease-standard", "value": "cubic-bezier(0.4, 0, 0.2, 1)", "purpose": "Default easing for all state transitions." },
+ { "name": "ease-in-out-soft", "value": "ease-in-out", "purpose": "Float and pulse animations." },
+ { "name": "duration-fast", "value": "150ms", "purpose": "Micro-interactions (button active state)." },
+ { "name": "duration-base", "value": "200ms", "purpose": "Hover color/border transitions." },
+ { "name": "duration-lift", "value": "300ms", "purpose": "Card hover transform and shadow." },
+ { "name": "duration-flow", "value": "4s", "purpose": "Dash-flow and node-pulse animations." },
+ { "name": "stagger-increment","value": "0.3s–0.5s", "purpose": "Delay increment between sequentially animated siblings." }
+ ],
+ "breakpoints": [
+ { "name": "sm", "value": "640px" },
+ { "name": "md", "value": "768px" },
+ { "name": "lg", "value": "1024px" },
+ { "name": "xl", "value": "1280px" },
+ { "name": "2xl", "value": "1536px" }
+ ],
+ "zIndex": [
+ { "name": "dropdown", "value": 10 },
+ { "name": "sticky-header", "value": 40 },
+ { "name": "mobile-overlay", "value": 45 },
+ { "name": "modal-backdrop", "value": 50 },
+ { "name": "modal", "value": 60 },
+ { "name": "toast", "value": 70 },
+ { "name": "tooltip", "value": 80 }
+ ],
+ "northStar": "The Working Prototype — built things, visible craft, purposeful motion. Every element is evidence of technical judgment.",
+ "rules": [
+ "The Orange Monopoly Rule: orange appears on one element per screen — the primary CTA button.",
+ "The Teal Contrast Rule: body text minimum is teal-700 (#2F4E51) on white. Never use teal-500+ for prose.",
+ "The Two-Family Rule: IBM Plex Sans for structure, Helvetica Neue for reading. No third typeface.",
+ "The Flat-by-Default Rule: cards have no box-shadow at rest; only border-gray-100 boundary and tonal background.",
+ "The Uppercase Restriction: all-caps only on labels ≤ 4 words at ≤ 0.75rem with letter-spacing ≥ 0.08em.",
+ "The Motion-as-Flow Rule: stagger animated siblings 0.3–0.5s so reveals read as sequence, not simultaneous burst.",
+ "The Reduced-Motion Rule: every animation has a @media(prefers-reduced-motion: reduce) fallback."
+ ]
+ },
+ "components": [
+ {
+ "name": "Primary CTA Button",
+ "kind": "button",
+ "refersTo": "button-primary",
+ "description": "Primary call-to-action. Orange, rounded-full. One per screen.",
+ "html": "",
+ "css": ".ds-btn-primary { background: #f97316; color: #fff; padding: 0.75rem 1.75rem; border: none; border-radius: 9999px; font: 600 0.875rem 'IBM Plex Sans', sans-serif; letter-spacing: 0.01em; cursor: pointer; transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; } .ds-btn-primary:hover { background: #ea6c0a; transform: translateY(-2px) scale(1.02); box-shadow: 0 4px 12px rgba(249,115,22,0.3); } .ds-btn-primary:focus-visible { outline: 2px solid #40676B; outline-offset: 3px; } @media (prefers-reduced-motion: reduce) { .ds-btn-primary { transition: background 0.2s ease; } .ds-btn-primary:hover { transform: none; } }"
+ },
+ {
+ "name": "Secondary Button",
+ "kind": "button",
+ "refersTo": "button-secondary",
+ "description": "Ghost variant for secondary actions. White fill, gray border, rounded-full.",
+ "html": "",
+ "css": ".ds-btn-secondary { background: #fff; color: #374151; border: 2px solid #e5e7eb; padding: 0.625rem 1.5rem; border-radius: 9999px; font: 500 0.875rem 'IBM Plex Sans', sans-serif; cursor: pointer; transition: all 0.2s ease; } .ds-btn-secondary:hover { background: #DAF6F9; color: #40676B; border-color: #78BCC2; } .ds-btn-secondary:focus-visible { outline: 2px solid #40676B; outline-offset: 3px; }"
+ },
+ {
+ "name": "Standard Card",
+ "kind": "card",
+ "refersTo": "card",
+ "description": "White rounded-2xl card with a subtle border. Lifts on hover. No shadow at rest.",
+ "html": "Feature Title
Description text goes here.
",
+ "css": ".ds-card { background: #fff; border-radius: 1rem; border: 1px solid #e5e7eb; padding: 1.5rem; transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), box-shadow 0.3s cubic-bezier(0.4,0,0.2,1), border-color 0.3s ease; } .ds-card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06); border-color: #89D6DC; } .ds-card-title { font: 600 1.125rem 'IBM Plex Sans', sans-serif; color: #1f2937; margin: 0 0 0.5rem; } .ds-card-body { font: 400 1rem 'Helvetica Neue', sans-serif; color: #374151; line-height: 1.65; margin: 0; } @media (prefers-reduced-motion: reduce) { .ds-card { transition: none; } .ds-card:hover { transform: none; } }"
+ },
+ {
+ "name": "Text Input",
+ "kind": "input",
+ "refersTo": "input",
+ "description": "Rounded-xl input, gray surface at rest, white on focus with teal ring.",
+ "html": "",
+ "css": ".ds-input { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 0.75rem; padding: 0.75rem 1rem; font: 400 1rem 'Helvetica Neue', sans-serif; color: #374151; width: 100%; transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; } .ds-input:focus { background: #fff; border-color: transparent; outline: none; box-shadow: 0 0 0 2px #40676B; } .ds-input:disabled { opacity: 0.5; cursor: not-allowed; }"
+ },
+ {
+ "name": "Section Badge",
+ "kind": "chip",
+ "description": "Teal-50 pill for section categorization. Short labels only.",
+ "html": "Services",
+ "css": ".ds-badge { display: inline-block; background: #DAF6F9; color: #40676B; font: 600 0.875rem 'IBM Plex Sans', sans-serif; padding: 0.375rem 1rem; border-radius: 9999px; letter-spacing: 0.01em; }"
+ },
+ {
+ "name": "Workflow Node Card",
+ "kind": "custom",
+ "description": "Animated pipeline node. Pulses with teal or orange glow in staggered sequence to simulate live data flow. The hero's proof-of-craft element.",
+ "html": "",
+ "css": ".ds-node { display:flex; flex-direction:column; align-items:center; gap:0.5rem; background:#fff; padding:0.875rem 1.25rem; border-radius:0.75rem; box-shadow:0 2px 8px rgba(0,0,0,0.08),0 1px 3px rgba(0,0,0,0.04); border:1px solid rgba(0,0,0,0.06); position:relative; min-width:90px; transition:all 0.4s cubic-bezier(0.4,0,0.2,1); } .ds-node--teal { border:1.5px solid rgba(97,194,200,0.4); animation:node-pulse-teal 4s ease-in-out infinite; } .ds-node-icon { width:2.25rem; height:2.25rem; border-radius:0.5rem; display:flex; align-items:center; justify-content:center; font-size:1rem; } .ds-node-label { font:600 0.75rem 'IBM Plex Sans',sans-serif; color:#374151; text-align:center; white-space:nowrap; } .ds-node-dot { position:absolute; width:10px; height:10px; border-radius:50%; top:-5px; right:-5px; border:2px solid #fff; } .ds-node-dot--teal { background:#61C2C8; } @keyframes node-pulse-teal { 0%,100% { box-shadow:0 2px 8px rgba(0,0,0,0.08); border-color:rgba(97,194,200,0.4); } 50% { box-shadow:0 0 20px rgba(97,194,200,0.45),0 0 40px rgba(97,194,200,0.2); border-color:#61C2C8; } } @media (prefers-reduced-motion:reduce) { .ds-node--teal { animation:none; } }"
+ }
+ ]
+}
diff --git a/.impeccable/live/config.json b/.impeccable/live/config.json
new file mode 100644
index 0000000..3c4b616
--- /dev/null
+++ b/.impeccable/live/config.json
@@ -0,0 +1,7 @@
+{
+ "files": ["wwwroot/index.html"],
+ "insertBefore": "