| name | monthly-audit |
| description | Run the mandatory first-session-of-month documentation and data drift audit — README, ASSETS, RULES, SIGNALS, STRATEGY, dashboard, backtest, language sweep, secrets sweep. Use on the first session of each month, when Jose says "monthly audit", or whenever docs look out of sync with the portfolio. |
Monthly Audit
Non-daily docs drift silently — this repo once had README 60 days stale, ASSETS missing the two largest holdings, and 200+ Spanish strings despite the English-only rule (LEARNINGS.md 2026-03-31). This skill mechanizes the AGENTS.md "Monthly Audit" checklist so drift can't hide.
Output: one PASS/FAIL table, then fixes. Fix mechanically-derivable drift immediately; anything judgment-based (strategy, rules) gets flagged, not changed.
Step 1 — Ground truth
jq '{balance: .totals.balance_eur, pnl: .totals.pnl_pct, holdings: (.holdings | keys)}' data/portfolio.json
jq -r '.fetchedAt' data/.prices-latest.json
head -15 LEDGER.md
Everything below is compared against portfolio.json. It is the single source of truth for positions.
Step 2 — Automated checks
Run each; record PASS/FAIL + evidence.
README.md — regenerated by update-readme.js, so drift means the hook broke:
rg -o 'Current Balance \| €[0-9.]+' README.md
rg -o '\*Last updated: [0-9-]+' README.md
FAIL → run node scripts/update-readme.js, then check why hooks/pre-commit didn't (is it installed? ls -l .git/hooks/pre-commit; reinstall via node scripts/setup-hooks.js).
ASSETS.md vs holdings — every held non-CASH asset must appear:
for s in $(jq -r '.holdings | keys[] | select(. != "CASH")' data/portfolio.json); do
rg -q "\b$s\b" ASSETS.md && echo "OK $s" || echo "MISSING $s"
done
RULES.md limits vs reality:
node scripts/validate-rules.js
Breaches are live allocation issues → flag in the report for a rebalancing decision. Do not edit RULES.md and do not trade.
SIGNALS.md vs generated signals:
jq '.alerts' data/.signals-latest.json
Stop-loss/take-profit rows in SIGNALS.md must match (asset, trigger, action). Watchlist must match the quant universe. Executed alerts move to Historical — never delete them.
STRATEGY.md mandates — read the active mandate; confirm its dated rules still match what the scripts enforce (regime thresholds, deployment caps in execute-signals.js / generate-quant-signals.js constants). Numeric mismatch between doc and code is a FAIL — report which side is authoritative before touching either.
Backtest sanity:
node scripts/backtest.js --all
Record: strategy vs buy-and-hold, win rate. If the strategy stopped outperforming, that's a finding for Jose, not a license to change strategy.
Dashboard staleness:
ls -l dashboard.html 2>/dev/null && node scripts/generate-dashboard.js
Language sweep (English-only rule; allowed proper names: SOCIMIs, DGOJ, Letras del Tesoro, CNMV, MEFF):
rg -in 'según|posición|análisis|señal|compra|venta|cartera|también|razón' \
--glob '*.md' --glob '!LEDGER.md' .
rg -in 'según|posición|cartera' LEDGER.md | head -5
Secrets/PII sweep (public repo):
rg -in 'api[_-]?key|token|password|bearer|josemanuel@|josemunoz@|[0-9]{9,}' \
--glob '!data/*.json' --glob '!package.json' scripts/ *.md
Any hit = CRITICAL, fix before anything else (move to ~/.secrets/env).
Data integrity:
node scripts/validate-data.js
Summary freshness:
jq -r '.lastUpdated' data/summary.json
Step 3 — Report + fix
Produce:
## Monthly Audit — YYYY-MM
| Check | Status | Evidence / Fix |
|-------|--------|----------------|
| README current | PASS/FAIL | ... |
| ASSETS ↔ holdings | ... | ... |
| RULES compliance (live) | ... | flagged, not traded |
| SIGNALS ↔ generated | ... | ... |
| STRATEGY ↔ code constants | ... | ... |
| Backtest outperforms | ... | numbers |
| Dashboard fresh | ... | ... |
| English-only | ... | file:line list |
| No secrets/PII | ... | ... |
| validate-data | ... | ... |
| summary.json fresh | ... | ... |
Then:
- Apply mechanical fixes (doc sync, regeneration) — one commit:
docs: monthly audit YYYY-MM — sync drifted docs.
- Judgment items (rule breaches, strategy questions, underperforming backtest) → list under "Needs Jose" at the end; do not act on them.
- If a check failed because a pipeline step silently dropped out (the
summary.json/daily-routine.js pattern), fix the wiring AND add a freshness assertion to validate-data.js so it can't recur silently.