From lurii-cc
Iterate on type/category rules via lurii-finance MCP — fix `unknown` types, fill missing categories, dedup rules, link cross-source transfers. Always dry-runs before create; confirms destructive deletes; never writes SQL directly. Use when the user says "categorize", "fix unknowns", "audit categories", "rule cleanup", "dedup rules", "link transfer", or invokes /categorize.
How this skill is triggered — by the user, by Claude, or both
Slash command
/lurii-cc:categorization-curatorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Drive the type/category rule loop end-to-end through the lurii-finance MCP. Discover the backlog, propose rules, dry-run them, confirm with the user, create, then batch-apply. Never write SQL directly. **Dry-run before every create. Confirm before every delete.**
Drive the type/category rule loop end-to-end through the lurii-finance MCP. Discover the backlog, propose rules, dry-run them, confirm with the user, create, then batch-apply. Never write SQL directly. Dry-run before every create. Confirm before every delete.
create_*_rule is preceded by dry_run_*_rule with the same args — same source filter (source_type XOR source_id) and the same priority. Surface result per references/dry-run-result.md. If matched == 0, stop. If changed == [] but shadowed_by_higher non-empty, raise precedence (lower the number) or drop.delete_*_rule, dry-run the rule's args and show its current match set + result. Wait for explicit "yes". For bulk deletes, collect candidates into a numbered list with each rule's args, ask for one batch confirmation ("all", "1, 3, 5", "none"), then call bulk_delete_*_rules. Always echo args in chat before deletion as a recovery path.apply_categorization is never implicit. Batch several rule edits per apply call.set_transaction_category is for one-off rows whose pattern doesn't generalize. If two rows share the same raw field, write a rule instead.dry_run.overlapping_rules per references/dry-run-result.md. If overlap is intentional (replacement), schedule the delete after the new rule lands.Style: narrative in memory/patterns.md output_language; tool names, regex patterns, category values, and source names stay in ticker_language (default English). If output_language is unset, ask once and persist via memory-curator.
Run in parallel:
mcp__lurii-finance__categorization_summary (no args) — per-source counts: total, unknown_type, no_category, internal_transfer, and the split transfer_linked / transfer_unpaired (flagged but not yet linked = real transfer work remaining; drives the transfer-linking sub-flow).mcp__lurii-finance__list_sources (no args) — (id, name, type, enabled, tx_count, snap_count) per configured account. Surfaces the source_id values needed when an account-specific rule is in scope.mcp__lurii-finance__list_categories (no args) — valid category values per tx_type.mcp__lurii-finance__get_rule_suggestions with min_evidence: 2 — patterns the user has manually confirmed via prior set_transaction_category calls. Server-side filtering already drops non-discriminating suggestions (same (source_type, field, value) mapping to >1 category). Pass include_non_discriminating: true only when auditing why a suspected pattern is missing — output then carries non_discriminating: true + conflicting_categories: [...].Pick the highest-pain source by unknown_type + no_category. If the user named a source, use that.
Surface the survey in chat as a compact table (header labels translate to output_language; source ids and counts stay verbatim):
<header-row in output_language: source | total | unknown | no_category | transfer_unpaired>
<source-id-1> | <total> | <n> | <n> | <n>
<source-id-2> | <total> | <n> | <n> | <n>
...
For the chosen source. Source-filter semantics (source_type vs source_id XOR pair) live in references/source-filter-adr030.md — apply when authoring rule args.
mcp__lurii-finance__list_uncategorized_transactions with source: <src>, missing_type: false, missing_category: false, limit: 100. Default response is raw_keys only — sufficient to spot which fields exist across the backlog. ~10× smaller payload than including samples.include_raw_sample: true and a narrower limit (e.g. 30–50). Returns raw_sample (each value truncated to 200 chars) for pattern authoring.mcp__lurii-finance__get_transaction_detail with the integer row id to see full raw_json, winning_category_rule (id, priority, field, value, result_category), and winning_type_rule (same shape on the type side). Either may be null — winning_type_rule is null when the source already supplies a concrete type and no rule fires. Use this to answer "why is this tx X" without iterating through rules.mcp__lurii-finance__list_transactions reaches every transaction — already-categorized rows and transfers included — each with its integer id and the transfer/category overlay (category, category_source, is_internal_transfer, transfer_pair_id, transfer_detected_by). Filters: source/source_name, tx_type, category, start/end, search, is_internal_transfer, has_pair, limit/offset; returns {count, total, offset, limit, transactions}. Use it for transfer linking, audits, and fixing already-categorized rows (list_uncategorized_transactions only returns the missing-type/category backlog).Group rows by raw_keys (and raw_sample on the discovery pass). Look for stable discriminators:
kind == "purchase") → eq rule on that field.description begins with FX ) → regex with ^FX\b.contains.merchant_name (derived per-source: kbank strips the volatile Ref Xxxx prefix and embedded ref codes from details to reveal the merchant; wise uses merchant/payeeName; crypto sources yield none). Prefer it over the payment-rail field (e.g. kbank channel, which spans dining/groceries/top-ups and produces non-discriminating suggestions). get_rule_suggestions already biases toward a discriminating merchant_name; author the rule with field_name: "merchant_name".For each candidate pattern:
regex for anchored / grouped patterns.contains for substring.eq for exact equality.mcp__lurii-finance__validate_rule_args with field_operator: "regex" and field_value: <pattern> first. Cheap compile-only check (no DB scan). On valid: false, fix the pattern before paying for the dry-run query.mcp__lurii-finance__dry_run_category_rule (or dry_run_type_rule) with the same args you would pass to create_*_rule, plus scope_source if source-specific, plus limit: 200. For wide-net rules likely to produce >50 entries, pass summary_only: true.references/dry-run-result.md (full field map, threshold rules, overlap protocol).Wait for user OK. On confirm:
mcp__lurii-finance__create_category_rule (or create_type_rule) with the same args used in dry-run (minus scope_source and limit).ValueError envelope (malformed regex), surface it and re-prompt.Track all rule ids created in this session — needed for the optional cleanup pass.
When the user signals "ready" (or after batching ~3–5 rule edits):
mcp__lurii-finance__apply_categorization with force: false. Surface the result diff: total, type_resolved, transfers, categorized.If the user wants to force a full re-categorize (e.g. after deleting a rule that previously won), pass force: true — but warn that it scans the full table.
Re-call categorization_summary for the source. If (unknown_type + no_category) / total >= 0.05, loop back to Step 2. Otherwise, hand back to the user with a one-line summary:
kbank: unknown 87 → 4, no_category 312 → 18. Created 6 rules. Applied.
Three conditional branches off the main loop — load references/sub-flows.md when the trigger fires:
set_transaction_category on a single row.categorization_summary.transfer_unpaired > 0, or user asks to link/unlink → suggest_transfer_links → bulk_link_transfers; repair_transfer_pairs to fix asymmetric links.audit_*_rules survey + bulk-confirm deletes.matched == 0. Pattern is wrong. Stop and re-inspect raw values.list_categories(tx_type). If the user wants a new category, point them to the SwiftUI app or a direct MetadataStore.add_category call (out of skill scope).apply_categorization per rule. The bulk pass itself is fast (~1k rows is essentially instant); the rule is about not hiding cost in iteration, not about avoiding apply. Batch several rule edits per apply call instead.raw_json field X represents, call get_transaction_detail and ask the user.contains a pattern that needs anchoring. "FX" as substring also matches "TAXFX1234". Use ^FX\b instead.tx_id (string) with id (integer). Listing tools (list_transactions, list_uncategorized_transactions) return both. Mutation tools (set_transaction_category, link_transfer, bulk_link_transfers, unlink_transfer) take the integer id. Every row — categorized or transfer — is reachable by id via list_transactions; never guess ids.suggest_transfer_links (server-side asset/amount/date matcher), not manual cross-source eyeballing. Confirm candidates with the user before bulk_link_transfers, and treat a score < 1.0 as a fee/valuation gap to sanity-check.dry_run_*_rule.overlapping_rules.memory/patterns.md output_language. Tool names, regex patterns, category values, and source names stay in ticker_language (default English).npx claudepluginhub chizhovyui/lurii-cc --plugin lurii-ccCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.