opinionated-java-ecosystem/skills/java-programmer/SKILL.md
Java-specific tooling, documentation standards, testing practices, and modern idioms. Use when working with Java code or Java-based projects on the JVM.
npx skillsauth add pyroxin/opinionated-claude-skills java-programmerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides guidance on Java-specific tooling, documentation standards, testing practices, and modern Java idioms. Java is a statically-typed, object-oriented language with a massive ecosystem and strong backwards compatibility guarantees. This skill focuses on navigating the Java ecosystem effectively, writing maintainable Java code, and applying functional programming concepts within Java's constraints.
Use this skill when:
<core_philosophy>
For foundational software engineering principles, see the software-engineer skill. For OOP principles (SOLID, GoF patterns), see the object-oriented-programmer skill.
Java excels in specific contexts but has limitations compared to more modern languages.
Where Java shines:
Where Java struggles:
Staff insight: If you're writing Java professionally but prefer functional languages, focus on using modern Java features (streams, Optional, lambdas) to bring functional thinking into Java's constraints. Don't fight the language—work within its idioms.
Javadoc is more than comments—it's a contract between API and client. Treat it as seriously as the code itself.
Quote to remember: "Code is read more often than written." — Guido van Rossum. Documentation is read even more often than that. </core_philosophy>
<type_annotations>
Java's type system lacks many important guarantees. Type annotations with The Checker Framework enable compile-time verification of properties that the type system doesn't enforce.
Why type annotations and static analysis matter:
The Checker Framework is mandatory for all new projects:
@Nullable, @NonNull via defaults)Example with JSpecify nullness annotations:
import org.jspecify.annotations.Nullable;
// @NonNull is the default assumption (no annotation needed)
public class UserService {
/**
* Finds user by ID.
*
* @param id the user ID (must not be null, default assumption)
* @return user if found, null otherwise
*/
public @Nullable User findById(String id) {
// Checker verifies: id cannot be null (default @NonNull)
// Checker requires: callers handle possible null return
return repository.findById(id).orElse(null);
}
/**
* Gets user display name.
*
* @param user the user (must not be null, default assumption)
* @return display name (never null, default assumption)
*/
public String getDisplayName(User user) {
// Checker verifies: user cannot be null
// Checker enforces: return value cannot be null
String name = user.getName();
return name != null ? name : "Guest";
}
}
Example with other Checker Framework annotations:
import org.checkerframework.checker.regex.qual.Regex;
import org.checkerframework.checker.formatter.qual.FormatMethod;
public class ValidationService {
/**
* Validates input against pattern.
*
* @param input the input string
* @param pattern the regex pattern (verified at compile time)
* @return true if input matches pattern
*/
public boolean matches(String input, @Regex String pattern) {
// Checker verifies: pattern is valid regex at compile time
return input.matches(pattern);
}
/**
* Logs formatted message.
*
* @param format the format string (verified at compile time)
* @param args the format arguments
*/
@FormatMethod
public void logFormatted(String format, Object... args) {
// Checker verifies: format string matches args at compile time
logger.info(String.format(format, args));
}
}
When to use annotations:
@Nullable explicitly when null is permitted@NonNull (no annotation needed)@Regex for all regex pattern strings@FormatMethod and format string annotations for printf-style methods@GuardedBy for fields accessed under locksWhy both type annotations AND Javadoc:
@Nullable, Javadoc explains when null is returnedMaven/Gradle configuration:
<!-- Maven: Add Checker Framework -->
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.42.0</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Xplugin:ErrorProne</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-Xplugin:checkerframework -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker,org.checkerframework.checker.formatter.FormatterChecker</arg>
</compilerArgs>
</configuration>
</plugin>
CI/CD integration:
Staff insight: The Checker Framework is as important as comprehensive testing for code quality. JSpecify annotations provide nullness checking with multi-tool compatibility. Beyond nullness, use all applicable checkers (Regex, Format String, Index, Lock, etc.) to catch errors at compile time. Type annotations provide compile-time verification that tests and documentation cannot. </type_annotations>
<version_targeting>
All new Java projects must target the latest Java version or at least the latest LTS version. (Java 25 is the latest LTS release as of late 2025). You can find the latest version by reading this Wikipedia article. That article is also a good reference for new features you should look at adopting.
Why Java 25+:
For existing projects:
Staff insight: Aggressive adoption of Java features is a philosophy. Java 25 has the features that make Java competitive with modern languages (sealed classes, pattern matching, records). Using older versions means missing critical type safety and expressiveness improvements. Upgrade aggressively.
The aggressive adoption philosophy applies ONLY to codebases you own.
When contributing to open source projects or third-party codebases:
Why this matters:
When you can suggest modernization:
Staff insight: Your aggressive adoption philosophy is for codebases you control. When contributing to others' projects, you're a guest—respect the house rules. Propose improvements through proper channels, don't impose them through pull requests. Stability and community consensus matter more than your personal preferences in shared codebases. </version_targeting>
<tooling_stack>
All new Java projects must use the following tools with CI/CD integration. Builds must fail on violations.
Required tools:
Spotless (code formatting):
Checkstyle (code style enforcement):
./assets/checkstyle.xmlSpotBugs (static analysis for bugs):
ErrorProne (Google's bug pattern checker):
JaCoCo (code coverage):
JSpecify + Checker Framework (type annotations):
Maven/Gradle configuration example:
<!-- Maven pom.xml -->
<build>
<plugins>
<!-- Spotless for formatting -->
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.43.0</version>
<configuration>
<java>
<googleJavaFormat>
<version>1.19.1</version>
<style>GOOGLE</style>
</googleJavaFormat>
<removeUnusedImports />
<formatAnnotations />
</java>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<!-- Checkstyle -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<!-- SpotBugs -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.3.0</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<!-- ErrorProne -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>25</release>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.24.1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- JaCoCo -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
CI/CD integration:
Configuration files:
./assets/checkstyle.xml (reference this in your pom.xml/build.gradle)Staff insight: Don't waste time debating formatting or choosing between tools. Use Spotless for formatting (automatic, no configuration needed), Checkstyle for semantic rules, SpotBugs + ErrorProne for bug detection, JaCoCo for coverage. These tools are mandatory, not optional. They catch errors early and enforce consistency. </tooling_stack>
<jpms> ## Java Platform Module System (JPMS): Use When PracticalThe Java Platform Module System (JPMS) should be used aggressively for new projects, but pragmatism is required due to ecosystem limitations.
When to use JPMS:
Why JPMS matters:
Basic module-info.java example:
module com.example.myapp {
requires java.sql;
requires com.google.common; // Guava
requires org.slf4j; // SLF4J
exports com.example.myapp.api; // Public API
// Internal packages not exported (truly encapsulated)
}
The pragmatic problem: Third-party library adoption
Many third-party libraries don't have proper module descriptors. This creates friction.
Solutions for mixing modularized and non-modularized code:
1. Automatic modules (non-modularized JARs on module path):
module com.example.myapp {
requires commons.lang3; // Automatic module (from commons-lang3-3.x.jar)
}
2. Use --add-reads, --add-exports, --add-opens for opening up modules:
java --add-reads com.example.myapp=ALL-UNNAMED \
--add-exports java.base/sun.security.x509=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
-jar myapp.jar
--add-reads — Allows module to read unnamed module (classpath)--add-exports — Export internal JDK packages to your code--add-opens — Allow reflective access to internal packages3. Split packages problem (fatal in JPMS):
4. When to pragmatically abandon JPMS:
Don't force JPMS when it creates more problems than it solves.
Practical approach:
Staff insight: JPMS is excellent when it works (strong encapsulation, explicit dependencies) but the Java ecosystem hasn't fully adopted it. Start with JPMS, be prepared to fall back to classpath if third-party libraries cause too much pain. This is pragmatism, not failure—sometimes the tooling isn't ready for your philosophy. </jpms>
<javadoc> ## Javadoc Is MandatoryComprehensive Javadoc documentation is not optional. Every public element must be documented.
Always document (required for all production code):
Document private elements when:
Every test must have Javadoc:
Staff insight: Junior developers under-document. Senior developers document the "why" and constraints, not just the "what." Assume future readers (including LLMs) need to understand intent, not just implementation. Comprehensive Javadoc is as important as comprehensive tests.
HTML, not Markdown:
<ul> and <li> for lists (NOT Markdown bullets)<p> for paragraph breaks (with newline before tag)<code> and {@code} for inline code<pre> for code blocks{@link} for references to other types/methodsExample:
/**
* Processes user input and validates against business rules.
* <p>
* This method performs the following steps:
* <ul>
* <li>Sanitizes input to remove malicious content</li>
* <li>Validates against {@link ValidationRule} set</li>
* <li>Returns {@link Result} containing validated data or errors</li>
* </ul>
* <p>
* Thread safety: This method is thread-safe and can be called concurrently.
*
* @param input the user input to process (must not be null)
* @param rules the validation rules to apply
* @return validation result containing either data or errors
* @throws IllegalArgumentException if input is null
*/
public Result<Data> processInput(String input, Set<ValidationRule> rules) {
// implementation
}
Focus on the "why" and constraints:
Avoid restating the obvious:
// BAD: Restates signature
/**
* Gets the user name.
* @return the user name
*/
public String getUserName() { ... }
// GOOD: Adds context
/**
* Returns the user's display name for UI rendering.
* <p>
* This is not the username for authentication (see {@link #getLoginName()}).
* Returns "Guest" if user is not authenticated.
*
* @return display name for current user, never null
*/
public String getUserName() { ... }
Document design decisions:
/**
* Cache implementation using weak references to allow garbage collection.
* <p>
* This cache prioritizes memory efficiency over hit rate. Entries may be
* evicted at any time if memory pressure increases. For guaranteed retention,
* use {@link StrongReferenceCache} instead.
* <p>
* Thread safety: This implementation is thread-safe using concurrent data
* structures. However, cache size is approximate due to asynchronous cleanup.
*/
public class WeakReferenceCache<K, V> { ... }
</javadoc>
<junit_testing>
For general testing philosophy and TDD principles, see the test-driven-development skill. This section covers Java/JUnit-specific practices.
Use @Nested classes to create hierarchical test organization that mirrors domain concepts:
@DisplayName("UserService")
class UserServiceTest {
@Nested
@DisplayName("when creating new users")
class UserCreation {
@Test
@DisplayName("should generate unique ID for each user")
void generatesUniqueIds() {
// Given user creation scenario
// When creating multiple users
// Then each receives unique ID
}
@Test
@DisplayName("should validate email format before creation")
void validatesEmailFormat() {
// Test implementation with descriptive assertion messages
}
}
@Nested
@DisplayName("when updating existing users")
class UserUpdate {
// Tests for update scenarios
}
}
Why this matters:
Use Assertions class methods appropriately:
// GOOD: Use specific assertions
assertArrayEquals(expected, actual, "Byte arrays should match after serialization");
assertDoesNotThrow(() -> service.process(input), "Valid input should not throw exceptions");
// BAD: Manual comparison
assertTrue(Arrays.equals(expected, actual), "Arrays should be equal");
// BAD: Verbose try-catch
try {
service.process(input);
} catch (Exception e) {
fail("Should not throw exception");
}
Write meaningful assertion messages:
// BAD: Restates assertion
assertEquals(expected, actual, "Values should be equal");
// GOOD: Explains why expectation exists
assertEquals(
expectedTotal,
invoice.getTotal(),
"Invoice total should include all line items plus tax and exclude discounts for non-premium users"
);
Staff insight: Assertion messages are documentation. When a test fails, the message should help diagnose the root cause. Explain the business/technical reason for the expectation, not just the assertion itself.
Mock dependencies across architectural boundaries:
Use specific matchers:
// GOOD: Specific matching
when(repository.findById(eq(userId))).thenReturn(Optional.of(user));
// AVOID: Overly permissive
when(repository.findById(anyString())).thenReturn(Optional.of(user));
// LAST RESORT: When object equality doesn't work
when(service.process(any(Request.class))).thenReturn(response);
Scope mocks carefully:
If tempted to mock internals of the unit under test, refactor instead.
Every test should have Javadoc explaining:
Core testing principle (from test-driven-development skill): Mock at architectural boundaries (external systems, injected dependencies), not internal implementation details.
/**
* Verifies that user creation fails when email is already registered.
* <p>
* This test ensures we maintain email uniqueness constraint per requirements
* in USER-123. The test uses a pre-populated test user to simulate existing
* registration.
* <p>
* Known limitation: Does not test case-sensitivity of email comparison
* (covered separately in {@link #emailComparisonIsCaseInsensitive()}).
*/
@Test
@DisplayName("should reject duplicate email addresses")
void rejectsDuplicateEmails() {
// Test implementation
}
</junit_testing>
<build_tools>
Maven's strengths:
Use Maven when:
Gradle's strengths:
Use Gradle when:
Staff insight: For greenfield projects with standard requirements, Maven is often sufficient and simpler. Gradle's flexibility has cost—it enables more complex (and more fragile) builds. Choose based on actual needs, not perceived sophistication. </build_tools>
<modern_features>
Use streams when:
parallelStream() carefully)Avoid streams when:
Example transformation:
// Imperative (perfectly fine for simple cases)
List<String> names = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
names.add(user.getName());
}
}
// Streams (clearer for this pattern)
List<String> names = users.stream()
.filter(User::isActive)
.map(User::getName)
.collect(Collectors.toList());
Staff insight: Streams are a tool, not a requirement. Use them where they improve clarity. Don't reflexively convert every loop.
Use Optional for:
Don't use Optional for:
Example:
// GOOD: Return type making absence explicit
public Optional<User> findUserById(String id) {
// Returns Optional.empty() if not found
}
// Usage
findUserById(id)
.map(User::getEmail)
.ifPresent(email -> sendNotification(email));
// BAD: Optional as field
public class User {
private Optional<String> middleName; // Just use String, null is fine
}
// BAD: Optional as parameter
public void updateUser(Optional<String> newEmail) { ... } // Use overloading instead
Staff insight: Optional is for return values, not general null handling. It makes contracts explicit but shouldn't be used everywhere.
Use records for:
Don't use records for:
Example:
// Traditional POJO (verbose)
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
@Override
public boolean equals(Object o) { /* boilerplate */ }
@Override
public int hashCode() { /* boilerplate */ }
}
// Record (concise, same semantics)
public record Point(int x, int y) {
// Optional: custom constructor for validation
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
}
}
Use pattern matching when:
Example:
// Old style (verbose)
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.radius() * circle.radius();
} else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
return rect.width() * rect.height();
}
// Pattern matching (concise)
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
default -> throw new IllegalArgumentException("Unknown shape");
};
</modern_features>
<functional_java>
For deeper FP philosophy and when functional approaches are clearer, see the functional-programmer skill. This section covers Java-specific FP patterns.
Java retrofitted functional features (lambdas, streams, Optional) onto an OO foundation. This creates friction but enables functional thinking within Java's idioms.
What works well in Java:
What's awkward in Java:
Staff insight: Don't force pure FP in Java. Use functional features where they improve clarity (streams, immutability, Optional) but accept imperative code where it's clearer.
Make fields final by default:
public class Configuration {
private final String apiKey;
private final int timeout;
public Configuration(String apiKey, int timeout) {
this.apiKey = apiKey;
this.timeout = timeout;
}
// Getters only, no setters
}
Use records for value objects:
public record Money(BigDecimal amount, Currency currency) {
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
Collections.unmodifiable for defensive copying:*
public class Order {
private final List<LineItem> items;
public Order(List<LineItem> items) {
// Defensive copy + unmodifiable
this.items = Collections.unmodifiableList(new ArrayList<>(items));
}
public List<LineItem> getItems() {
return items; // Already unmodifiable, safe to return
}
}
Use standard functional interfaces:
Function<T, R> — TransformationsPredicate<T> — FilteringConsumer<T> — Side effectsSupplier<T> — Deferred computationCreate custom functional interfaces when:
@FunctionalInterface
public interface Parser<T> {
T parse(String input) throws ParseException;
// Default methods allowed
default Parser<T> withValidation(Predicate<T> validator) {
return input -> {
T result = this.parse(input);
if (!validator.test(result)) {
throw new IllegalArgumentException("Validation failed");
}
return result;
};
}
}
</functional_java>
<java_pitfalls>
The null problem:
Strategies:
Optional<T> for return types (makes absence explicit)@NonNull/@Nullable annotations (if using frameworks that support them)Don't fight null entirely—Java isn't Kotlin/Scala. Use defensive programming.
Checked vs unchecked:
Common mistake: Catching generic Exception and swallowing errors
// BAD: Swallows all errors
try {
doSomething();
} catch (Exception e) {
// Silent failure
}
// GOOD: Specific exceptions, proper handling
try {
doSomething();
} catch (IOException e) {
logger.error("Failed to read file", e);
throw new DataAccessException("Could not load configuration", e);
}
Premature optimization is still evil, but know the costs:
Measure before optimizing. Modern JVMs are sophisticated. </java_pitfalls>
<common_mistakes>
<from_python>
Mistake: Dynamic typing habits
Mistake: Expecting duck typing
Mistake: Simple scripting patterns
<from_cpp>
Mistake: Manual memory management thinking
Mistake: Pointer arithmetic and low-level operations
Mistake: Multiple inheritance
<from_javascript>
Mistake: Prototype-based thinking
Mistake: Loose typing and truthiness
Mistake: Callback hell patterns
<from_functional>
Mistake: Expecting powerful type inference
Mistake: Treating sealed classes as pure algebraic data types
Example - Using sealed classes to replace category enums:
// BEFORE: Enum to indicate shape category
enum ShapeType { CIRCLE, RECTANGLE }
class Shape {
private final ShapeType type;
private final double radius; // only for circles
private final double width, height; // only for rectangles
// Awkward: fields only valid for certain types
}
// AFTER: Sealed classes eliminate the category enum
sealed interface Shape permits Circle, Rectangle {
double area();
}
record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
record Rectangle(double width, double height) implements Shape {
public double area() { return width * height; }
}
Mistake: Using pattern matching to replace OOP patterns
Example - When to use Strategy vs pattern matching:
// GOOD: Strategy pattern for pluggable behavior
interface PaymentStrategy {
void processPayment(double amount);
}
class PaymentProcessor {
private final PaymentStrategy strategy;
public void process(double amount) {
strategy.processPayment(amount); // OOP dispatch, strategy is pluggable
}
}
// AVOID: Pattern matching for what should be polymorphic dispatch
class PaymentProcessor {
public void process(Payment payment, double amount) {
switch (payment) { // Anti-pattern: bypassing polymorphism
case CreditCardPayment cc -> processCreditCard(cc, amount);
case PayPalPayment pp -> processPayPal(pp, amount);
default -> throw new IllegalArgumentException();
}
}
}
// GOOD: Pattern matching for destructuring and validation
public BigDecimal calculateTotal(Order order) {
return switch (order) {
case EmptyOrder() -> BigDecimal.ZERO;
case SingleItemOrder(Item item) -> item.price();
case MultiItemOrder(List<Item> items) ->
items.stream()
.map(Item::price)
.reduce(BigDecimal.ZERO, BigDecimal::add);
};
}
Recognizing OO/FP equivalences to write better Java:
Use functional concepts to enhance OOP, not replace it:
Mistake: Purity everywhere
<from_kotlin>
Mistake: Expecting null safety
Mistake: Extension functions
Mistake: Data classes
Staff insight: Java is verbose, statically typed, and object-oriented. Don't fight these characteristics—work within them. Use modern Java features (streams, records, pattern matching) to reduce verbosity, but accept that Java will never be as concise as Python or as type-safe as Haskell. The Checker Framework and JSpecify annotations bridge some gaps. </common_mistakes>
<related_skills>
./assets/checkstyle.xml
</resources>
Java programming emphasizes:
All new Java projects must use:
Aggressive Java feature adoption philosophy:
Java is a pragmatic language. Work within its idioms rather than fighting them. Type annotations (JSpecify + Checker Framework), comprehensive Javadoc, and modern tooling are mandatory for all production code. Document thoroughly—assume future maintainers (including LLMs) need to understand intent, not just implementation.
data-ai
--- name: interactive-research description: Multi-source research orchestrated across a persistent agent team. Use for queries asking for research-style investigation of a topic — comparing, contrasting, surveying, investigating, evaluating, deeply researching, doing a literature review, or any similar research intent — or that ask what people are saying publicly about a topic (discourse, expert opinion, industry consensus, and related framings). Example phrasings (illustrative, not exhaustive):
development
Expert-level guidance for creating high-quality Claude Code skills. Use alongside Anthropic's skill-creator when creating new skills, improving existing skills, or needing guidance on skill content quality. Complements basic skill mechanics with research-driven content development, XML tag structuring, decision frameworks over mechanics, cross-references between skills, and systematic validation.
development
--- name: deep-research description: Deprecated name; the skill formerly here is now opinionated-research:interactive-research. Retained as a context-level notice because Claude Code now ships a built-in deep-research workflow that runs one-shot, non-interactive fan-out research (no conversation after the report). interactive-research instead runs a persistent agent team you can keep questioning once the report lands. Guidance for Claude: when the user makes a research request, ask whether they
development
Pedagogical framework for teaching programming through Socratic dialogue. Use when a learner wants to LEARN programming rather than have code written for them. Triggers include "teach me", "help me understand", "I'm learning", "tutor mode", or requests to not provide solutions. Emphasizes productive struggle, graduated hints, metacognitive scaffolding, and emotional support.