This skill turns PMD warnings — plus an LLM-driven antipattern scan that fills the gap PMD's rule engine cannot — into a personalized learning plan for one developer on a team. PMD tells you what mechanical issues are in the code; the catalog-driven LLM scan looks for design smells and antipatterns PMD does not detect; git blame tells you who wrote it. This skill closes the loop: it picks one author, derives their characteristic code quality blind spots from the merged findings on their lines, and writes a tailored LEARNING_PLAN.md with concept explanations, refactor sketches of their actual code, and verified external pointers.
If anything is missing, the script prints platform-specific install hints and exits non-zero. Pass those hints through to the user and stop the workflow.
-
Verify prerequisites. Run ${CLAUDE_SKILL_DIR}/scripts/check_prereqs.sh. On non-zero exit, surface the stderr to the user verbatim (it contains install hints) and stop.
-
Locate the repo root. Run ${CLAUDE_SKILL_DIR}/scripts/find_repo_root.sh. On non-zero exit, tell the user the current directory is not inside a git repository and stop. Capture the printed path as REPO_ROOT for subsequent steps.
-
Detect languages. Run ${CLAUDE_SKILL_DIR}/scripts/detect_languages.sh "$REPO_ROOT". The output is a JSON array of detected PMD-supported languages, sorted by metric descending. If the array is empty, tell the user PMD has no default ruleset for any language present in the repo, and stop.
-
Confirm language selection (optional). If the array has more than one entry, use AskUserQuestion to confirm which languages to scan. The default option is "all detected languages." Provide up to three additional options for individual languages (the top three by metric). The "Other" branch the tool offers automatically lets the user type a custom subset.
-
Run PMD. Concatenate the ruleset fields of the selected languages with commas. Run ${CLAUDE_SKILL_DIR}/scripts/run_pmd.sh "$REPO_ROOT" "<ruleset-csv>". The script writes $REPO_ROOT/.code-quality-mentor/pmd-report.json and prints its path. On non-zero exit, surface the stderr log path to the user and stop.
-
Decide on the LLM antipattern scan. Use AskUserQuestion to offer:
- "Run the LLM antipattern scan" (default) — scans both PMD-flagged files and a sample of the largest source files.
- "Run only on PMD-flagged files" — cheaper; scope is the files PMD already touched.
- "Skip" — proceed with PMD findings alone.
If the broader session has suppressed prompts, silently proceed with the default — do not narrate the reason (no "since the user disabled pauses", no "per the skill I'll proceed with the default"). Just continue. Skip this step entirely if PMD found zero violations and the user has explicitly asked for a PMD-only run.
-
Run the LLM antipattern scan. Skip if step 6 chose "Skip". Otherwise:
- Read
${CLAUDE_SKILL_DIR}/assets/antipatterns.json and ${CLAUDE_SKILL_DIR}/references/llm_scan_prompt.md. The prompt file is the canonical specification of how you (Claude) carry out the scan; follow it.
- The output is
$REPO_ROOT/.code-quality-mentor/llm-scan-report.json, written with the Write tool, in PMD-shape JSON ({formatVersion, pmdVersion: "llm-scan-<catalog.version>", files: [{filename, violations: [...]}]}).
- If the scan ends with an empty
files array, that is fine — write the wrapper anyway so the next step has a stable input.
-
Merge findings. If llm-scan-report.json exists, run ${CLAUDE_SKILL_DIR}/scripts/merge_reports.sh "$REPO_ROOT/.code-quality-mentor/findings-report.json" "$REPO_ROOT/.code-quality-mentor/pmd-report.json" "$REPO_ROOT/.code-quality-mentor/llm-scan-report.json". Otherwise copy the PMD report to findings-report.json (so the next step has a stable input path). If the combined report contains zero violations, congratulate the user and stop.
-
Attribute warnings. Run ${CLAUDE_SKILL_DIR}/scripts/blame_warnings.sh "$REPO_ROOT" "$REPO_ROOT/.code-quality-mentor/findings-report.json". This writes $REPO_ROOT/.code-quality-mentor/blame-report.json (an array of authors with their warnings, sorted by warning count descending). If the array is empty, tell the user findings exist but git blame could not attribute any of them (possible if all flagged files were deleted in HEAD) and stop.
-
Author selection. Read the blame report. If exactly one author has attributable warnings, skip the question entirely: tell the user "Only has attributable warnings; using them" and continue. Otherwise, use AskUserQuestion to present the top authors. Build options as <Name> <<email>>: N warnings. The tool supports up to 4 options. Use the first three slots for the top three authors by warning count. Use the fourth slot for "Show full list". When the user picks "Show full list", render a numbered list inline and ask them to reply with a number or with name <email> directly. The tool's automatic "Other" fallback also lets them type a name or email directly.
-
Tailoring. Use AskUserQuestion to gather three preferences, one question at a time. These three questions are mandatory and must always be asked, even if the broader session has instructed the agent to "work without stopping" or "skip clarifying questions". They are not clarifications — they are inputs to the artifact's shape. The depth, focus, and seniority answers materially change what LEARNING_PLAN.md covers, and silently picking defaults produces a generic plan rather than a tailored one. Do not invent a "no-pause" or "non-interactive" exception.
- Depth. Options: "Terse — top 3 rules", "Standard — top 4 rules", "Deeper — top 5 rules". This drives how many rules the plan covers.
- Focus area. Options: "Whatever is most frequent in their warnings" (default), "Best practices and error-prone code", "Design and maintainability", "Performance and security". When the user picks a focus area, filter the author's warnings by the matching PMD ruleset or catalog
family before ranking.
- Seniority framing. Options: "Junior — explain fundamentals", "Mid-level — concise, link to deeper sources", "Senior — assume the basics, focus on edge cases and tradeoffs". This drives prose tone, not content scope.
-
Pick the rules. From the selected author's warnings (filtered by focus area when applicable), count occurrences per rule and take the top N (3, 4, or 5 from the depth choice). If fewer distinct rules exist than N, just use what is available; do not pad. Treat PMD rule names (e.g., EmptyCatchBlock) and catalog antipattern ids (e.g., long_method) as the same kind of identifier; both are first-class entries in the count. When a catalog id is selected, read its entry from assets/antipatterns.json and use the catalog's description, refactoring, language_notes, and canonical_references as the source material for the corresponding LEARNING_PLAN section.
-
Read the author's flagged code. For each selected rule, pick 1 or 2 representative warnings, by default the most recent two by file modification time, unless one offers a materially different angle than the other (different pattern, different file type). For each chosen warning, use the Read tool with the warning's line (and the line range of beginline to endline when the report provides it) to read the actual source snippet plus a few lines of surrounding context. Quote the snippet verbatim; do not invent code.
-
Synthesize LEARNING_PLAN.md. Target 1200–2000 words total (a 5–8 minute read). The plan is a coaching artifact, not a lint report. Concepts come first; code is evidence. The structure below is mechanical so the plan is both readable and parseable — a downstream tool can split on the headings and field markers, and a future revise mode can act on findings.
# Learning plan for <Author Name>
- **Author:** <Name> <<email>>
- **Findings reviewed:** <N>
- **Rules covered:** <N>
- **Depth:** <terse | standard | deeper>
- **Focus:** <focus area>
- **Seniority framing:** <junior | mid-level | senior>
## Profile
<One paragraph, 3–5 sentences. Theme the patterns by *concept*, not by rule name. Example: "Your warnings cluster around two themes: exception handling as control flow (EmptyCatchBlock, AvoidThrowingRawExceptionTypes) and unclear ownership of state (UnusedPrivateField, DataClass). Both come back to making the program's intent visible from the code.">
---
## <rule_id>
- **Family:** <catalog family, e.g., Bloaters / Couplers / OO Abusers; or PMD ruleset>
- **Source:** <catalog | PMD>
- **Occurrences:** <N>
- **Locations:**
- `<file:line>` — <one-sentence cite of the specific signal seen here>
- `<file:line>` — ...
### Concept
<3–5 sentences explaining the underlying design principle and the named refactoring vocabulary (e.g., "Extract Function", "Encapsulate Variable", "Replace Conditional with Polymorphism"). Plain prose; no bold paragraph leads inside this subsection.>
### Why it matters here
<2–4 sentences. Specific consequence in *this* codebase — name the file, the call site, the kind of change that would break next. Plain prose.>
### Refactoring
<2–4 sentences. The named refactoring + the specific shape it would take here (concrete class names, method names, file moves). Plain prose.>
### Evidence
```<language>
// <relative-path>:<line>
<one verbatim snippet, 3–8 lines, drawn via the `Read` tool from the author's actual flagged file>
```
### Further reading
- **<Author, *Title*, ed., year, chapter/section + page if known.>**
<Two- to four-sentence paraphrase (or short excerpt) that captures the specific idea this reference contributes. Label paraphrases as such — do not assert verbatim quotes you cannot verify. The reader should be able to see *what is in there* without leaving the document.>
Applies here: <one sentence connecting the reference's idea to this finding>.
- [<short link title, e.g., "refactoring.guru: Long Method">](<URL>) — <one-line description of what is at the URL, e.g., "quick lookup if you do not have the book at hand">
Every URL goes inside `[title](url)`. Never write a raw URL, a bolded URL (`**https://...**`), or `<https://...>`. The link title is short and human-readable; the description is what is at the URL and why it is useful. The same rule applies to a URL that supplements a book/paper citation (e.g., a conference talk's video) — wrap it as `[video, ~30 min](url)`, not as a trailing bare URL.
---
## <rule_id_2>
... (same shape as above)
---
## Habits going forward
- <2–4 short, concrete *thinking* habits, not just commands. Example: "Before you write a `try`/`catch`, ask: am I genuinely recovering, or am I hiding a failure? If you cannot answer the first half, let the exception propagate.">
Each rule section targets ~300–450 words once metadata, prose subsections, evidence snippet, and Further reading are combined.
-
Critical content rules.
- Lead with the concept, not the code. The first paragraph of every rule section explains the underlying principle in plain language; the code snippet appears later, as evidence that the principle was violated here. If a reader can skip the code and still understand the lesson, the section is doing its job.
- Name the principle. Every concept paragraph should name an existing design principle, refactoring, or heuristic — "fail fast", "tell don't ask", "single responsibility", "Extract Method", "Replace Type Code with Subclasses". Catalog entries already carry their
refactoring field; use it. PMD rules map to the same vocabulary even when the rule name does not.
- Use verbatim snippets from the
Read tool. Do not invent or paraphrase the author's code. Keep snippets short — 3 to 8 lines, with one or two lines of surrounding context.
- Tag every Evidence fence with a language. The opening backticks of the
### Evidence block must be followed by a language hint derived from the file's extension (e.g., ```java for .java, ```python for .py, ```typescript for .ts or .tsx, ```go for .go, etc.). For files in a language without a common pygments alias, use ```text rather than leaving the fence bare. Languageless fences fail the linter's md040 rule and force a revise iteration.
- For PMD rules, construct the docs URL deterministically using the per-language
pmd_docs_base_url from assets/pmd-languages.json and the rule name as the anchor (e.g., https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#emptycatchblock). PMD anchor names are lowercase. If unsure of the exact category for a rule, list the language-level docs URL instead — never fabricate.
- For catalog antipatterns, use the entry's
canonical_references directly. Every reference in the catalog already carries a URL.
- Every rule section must include at least one canonical non-tool reference in "Further reading" — a specific Refactoring / Effective Java / Clean Code item, a peer-reviewed paper with full title and year, or a named conference talk with speaker and venue. Tool docs alone are not enough. For catalog antipatterns this is automatic (the catalog ships them). For PMD rules, if you genuinely cannot recall a good canonical reference, say so in the plan rather than fabricating one.
- Never invent URLs. Books and papers may be cited without a URL in LEARNING_PLAN.md when the title + author + section is enough to locate them.
-
Write the file. Write LEARNING_PLAN.md to $REPO_ROOT/LEARNING_PLAN.md. Report the path back to the user, along with a one-line summary like: "Learning plan written for covering rules across warnings."
-
Slop carve-out check (internal). Read ${CLAUDE_SKILL_DIR}/references/slop-checks.md. The file lists a small set of focused checks against AI-slop tropes specific to coaching-artifact prose. Re-read the LEARNING_PLAN.md you just wrote and apply each check; if you find any matches, rewrite the offending passages in place and overwrite the file. This is an internal pre-publication scrub: do the check in memory, fix the plan, and do not write a separate report file or list the findings to the user. The carve-out is in-skill and focused on purpose: do not depend on or vendor the ai-slop plugin, and do not extend the carve-out beyond the cap stated in the file.
-
Lint and finalize (internal). Run python3 ${CLAUDE_SKILL_DIR}/scripts/lint_markdown.py --fix "$REPO_ROOT/LEARNING_PLAN.md". If the linter exits non-zero, read its stdout findings (one per line, tab-separated <file>:<line>\t<rule>\t<message>), revise the plan in place to address each, and re-run the linter. Repeat at most three iterations; after the third pass proceed regardless of the linter's state. The lint loop is internal quality control — do not mention lint output, rule names, exit codes, or iteration counts in the user-facing summary.
This command takes no positional arguments. It always operates on the enclosing git repository of the current working directory.
It does not modify any source file in the user's repository.