diff --git a/.cursor/rules/110-java-maven-best-practices.md b/.cursor/rules/110-java-maven-best-practices.md index 87fae532..c267461c 100644 --- a/.cursor/rules/110-java-maven-best-practices.md +++ b/.cursor/rules/110-java-maven-best-practices.md @@ -37,7 +37,6 @@ Before applying Maven best practices recommendations, ensure the project is in a - **MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations - **VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications -- **PREREQUISITE**: Project must compile and pass basic validation checks before optimization - **SAFETY**: If validation fails, not continue and ask the user to fix the issues before continuing - **MULTI-MODULE DISCOVERY**: After reading the root `pom.xml`, check whether it contains a `` section. If it does, read every child module's `pom.xml` before making any recommendations — analysis scope is the full module tree, not only the root - **CROSS-MODULE SCOPE**: When child modules exist, check each one for: hardcoded dependency versions that duplicate `` in the parent, plugin configurations that duplicate ``, properties that should be centralized in the parent, and version drift (same artifact declared at different versions across sibling modules) diff --git a/skills-generator/src/main/java/info/jab/pml/SkillsGenerator.java b/skills-generator/src/main/java/info/jab/pml/SkillsGenerator.java index e1415418..7f477d24 100644 --- a/skills-generator/src/main/java/info/jab/pml/SkillsGenerator.java +++ b/skills-generator/src/main/java/info/jab/pml/SkillsGenerator.java @@ -1,10 +1,17 @@ package info.jab.pml; import java.io.InputStream; +import java.io.StringWriter; import java.util.Optional; import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -36,7 +43,7 @@ public SkillsGenerator() { */ public Stream generateAllSkills() { return SkillsInventory.skillDescriptors() - .map(d -> generateSkill(d.skillId(), d.requiresSystemPrompt())); + .map(d -> generateSkill(d.skillId(), d.requiresSystemPrompt(), d.useXml())); } /** @@ -47,7 +54,7 @@ public Stream generateAllSkills() { * @throws RuntimeException if resources cannot be loaded or generation fails */ public SkillOutput generateSkill(String skillId) { - return generateSkill(skillId, true); + return generateSkill(skillId, true, false); } /** @@ -58,10 +65,24 @@ public SkillOutput generateSkill(String skillId) { * @return the generated skill output */ public SkillOutput generateSkill(String skillId, boolean requiresSystemPrompt) { + return generateSkill(skillId, requiresSystemPrompt, false); + } + + /** + * Generates SKILL.md and reference content for a given skill. + * + * @param skillId the skill identifier (e.g. 110-java-maven-best-practices) + * @param requiresSystemPrompt when false, skips system-prompt XML and reference generation + * @param useXml when true, loads skill from skills/{numericId}-skill.xml, validates against schema, transforms via XSLT + * @return the generated skill output + */ + public SkillOutput generateSkill(String skillId, boolean requiresSystemPrompt, boolean useXml) { String referenceContent = requiresSystemPrompt ? generateReferenceContent(skillId, parseMetadata(skillId)) : ""; - String skillMdContent = loadSkillSummary(skillId); + String skillMdContent = useXml + ? loadSkillSummaryFromXml(skillId) + : loadSkillSummary(skillId); return new SkillOutput(skillId, skillMdContent, referenceContent); } @@ -146,6 +167,49 @@ private String loadSkillSummary(String skillId) { } } + private String loadSkillSummaryFromXml(String skillId) { + String numericId = extractNumericId(skillId); + String xmlResource = "skills/" + numericId + "-skill.xml"; + String xsltResource = "schemas/skill-to-markdown.xslt"; + String schemaResource = "schemas/skill.xsd"; + try ( + InputStream xmlStream = getResource(xmlResource); + InputStream xsltStream = getResource(xsltResource); + InputStream schemaStream = getResource(schemaResource) + ) { + if (xmlStream == null) { + throw new RuntimeException("Skill XML not found: " + xmlResource); + } + if (xsltStream == null) { + throw new RuntimeException("XSLT not found: " + xsltResource); + } + if (schemaStream == null) { + throw new RuntimeException("Schema not found: " + schemaResource); + } + SchemaFactory factory = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = factory.newSchema(new StreamSource(schemaStream)); + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setSchema(schema); + docFactory.setNamespaceAware(true); + DocumentBuilder builder = docFactory.newDocumentBuilder(); + builder.parse(xmlStream); + + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(new StreamSource(xsltStream)); + StringWriter writer = new StringWriter(); + try (InputStream xmlStreamForTransform = getResource(xmlResource)) { + if (xmlStreamForTransform == null) { + throw new RuntimeException("Skill XML not found: " + xmlResource); + } + transformer.transform(new StreamSource(xmlStreamForTransform), new StreamResult(writer)); + } + String content = writer.toString(); + return appendProjectTagToDescription(content); + } catch (Exception e) { + throw new RuntimeException("Failed to load and transform skill XML: " + xmlResource, e); + } + } + private String appendProjectTagToDescription(String content) { boolean hasLicense = content.contains("license:"); return content.lines() diff --git a/skills-generator/src/main/java/info/jab/pml/SkillsInventory.java b/skills-generator/src/main/java/info/jab/pml/SkillsInventory.java index ab3d79ce..f18326c1 100644 --- a/skills-generator/src/main/java/info/jab/pml/SkillsInventory.java +++ b/skills-generator/src/main/java/info/jab/pml/SkillsInventory.java @@ -46,7 +46,7 @@ public static Stream skillIds() { for (InventoryEntry entry : entries) { String numericId = entry.numericId(); - validateSkillSummaryExists(numericId); + validateSkillSummaryExists(numericId, entry.useXml()); String skillId = entry.requiresSystemPrompt() ? resolveSkillIdFromPrefix(Integer.parseInt(numericId)) : entry.skillId(); @@ -57,26 +57,26 @@ public static Stream skillIds() { } /** - * Returns skill descriptors (skillId + requiresSystemPrompt) for generator use. + * Returns skill descriptors (skillId + requiresSystemPrompt + useXml) for generator use. */ public static Stream skillDescriptors() { List entries = loadInventory(); List descriptors = new ArrayList<>(); for (InventoryEntry entry : entries) { String numericId = entry.numericId(); - validateSkillSummaryExists(numericId); + validateSkillSummaryExists(numericId, entry.useXml()); String skillId = entry.requiresSystemPrompt() ? resolveSkillIdFromPrefix(Integer.parseInt(numericId)) : entry.skillId(); - descriptors.add(new SkillDescriptor(skillId, entry.requiresSystemPrompt())); + descriptors.add(new SkillDescriptor(skillId, entry.requiresSystemPrompt(), entry.useXml())); } return descriptors.stream(); } /** - * Skill ID and whether it requires a system prompt for reference generation. + * Skill ID, whether it requires a system prompt for reference generation, and whether to use XML source. */ - public record SkillDescriptor(String skillId, boolean requiresSystemPrompt) {} + public record SkillDescriptor(String skillId, boolean requiresSystemPrompt, boolean useXml) {} /** * Resolves skillId by finding the system-prompt XML that starts with {@code {id}-}. @@ -186,7 +186,8 @@ private static List parseInventory(String json) { throw new RuntimeException("Entry with id " + numericId + " has requiresSystemPrompt=false but no skillId specified."); } - entries.add(new InventoryEntry(numericId, requiresSystemPrompt, skillId)); + boolean useXml = parseXmlFlag(node); + entries.add(new InventoryEntry(numericId, requiresSystemPrompt, skillId, useXml)); } return entries; } catch (Exception e) { @@ -194,12 +195,30 @@ private static List parseInventory(String json) { } } - private static void validateSkillSummaryExists(String numericId) { - String resourceName = "skills/" + numericId + "-skill.md"; + private static boolean parseXmlFlag(JsonNode node) { + if (!node.has("xml")) { + return false; + } + JsonNode xmlNode = node.get("xml"); + if (xmlNode.isBoolean()) { + return xmlNode.asBoolean(); + } + if (xmlNode.isTextual()) { + String s = xmlNode.asText().toLowerCase(); + return "true".equals(s) || "yes".equals(s) || "1".equals(s); + } + return false; + } + + private static void validateSkillSummaryExists(String numericId, boolean useXml) { + String resourceName = useXml + ? "skills/" + numericId + "-skill.xml" + : "skills/" + numericId + "-skill.md"; try (InputStream stream = getResource(resourceName)) { if (stream == null) { throw new RuntimeException("Skill summary not found: " + resourceName - + ". Add skills/" + numericId + "-skill.md for each skill in the inventory."); + + ". Add skills/" + numericId + (useXml ? "-skill.xml" : "-skill.md") + + " for each skill in the inventory."); } } catch (Exception e) { if (e instanceof RuntimeException re) { @@ -225,6 +244,8 @@ private static InputStream getResource(String name) { * Single entry from skill-inventory.json. When requiresSystemPrompt is true, * skillId is derived by matching system-prompts with prefix {@code {numericId}-}. * When false, skillId must be provided and no system-prompt is required. + * When useXml is true, skill summary is loaded from skills/{numericId}-skill.xml + * and transformed via schema validation and XSLT; otherwise from skills/{numericId}-skill.md. */ - public record InventoryEntry(String numericId, boolean requiresSystemPrompt, String skillId) {} + public record InventoryEntry(String numericId, boolean requiresSystemPrompt, String skillId, boolean useXml) {} } diff --git a/skills-generator/src/main/resources/schemas/skill-to-markdown.xslt b/skills-generator/src/main/resources/schemas/skill-to-markdown.xslt new file mode 100644 index 00000000..81264deb --- /dev/null +++ b/skills-generator/src/main/resources/schemas/skill-to-markdown.xslt @@ -0,0 +1,80 @@ + + + + + + + --- +name: + + +description: + + +license: + + +metadata: + author: + + + version: + + +--- +# + + + + + + + + + + + + + + + + + + + + ## Constraints + + + + + + + + + + - + + + + + + + + + + + ## Reference + +For detailed guidance, examples, and constraints, see [ + + ]( + + ). + + + + + diff --git a/skills-generator/src/main/resources/schemas/skill.xsd b/skills-generator/src/main/resources/schemas/skill.xsd new file mode 100644 index 00000000..bd66524c --- /dev/null +++ b/skills-generator/src/main/resources/schemas/skill.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/skills-generator/src/main/resources/skill-inventory.json b/skills-generator/src/main/resources/skill-inventory.json index 77077079..6418e08d 100644 --- a/skills-generator/src/main/resources/skill-inventory.json +++ b/skills-generator/src/main/resources/skill-inventory.json @@ -4,7 +4,7 @@ {"id": "021", "requiresSystemPrompt": false, "skillId": "021-architecture-functional-requirements-rest"}, {"id": "030", "requiresSystemPrompt": false, "skillId": "030-architecture-non-functional-requirements"}, {"id": "040", "requiresSystemPrompt": false, "skillId": "040-planning-enhance-ai-plan-mode"}, - {"id": 110}, + {"id": 110, "xml": true}, {"id": 111}, {"id": 112}, {"id": 113}, diff --git a/skills-generator/src/main/resources/skills/110-skill.md b/skills-generator/src/main/resources/skills/110-skill.md deleted file mode 100644 index 5d7a9189..00000000 --- a/skills-generator/src/main/resources/skills/110-skill.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: 110-java-maven-best-practices -description: Use when you need to review, improve, or troubleshoot a Maven pom.xml file — including dependency management with BOMs, plugin configuration, version centralization, multi-module project structure, build profiles, or any situation where you want to align your Maven setup with industry best practices. -license: Apache-2.0 -metadata: - author: Juan Antonio Breña Moral - version: 0.13.0-SNAPSHOT ---- -# Maven Best Practices - -Improve Maven POM configuration using industry-standard best practices. - -**Prerequisites:** Run `./mvnw validate` or `mvn validate` before applying recommendations. If validation fails, **stop** and ask the user to fix issues—do not proceed until resolved. - -**Core areas:** Dependency management via `` and BOMs, standard directory layout (`src/main/java`, `src/test/java`), centralized plugin management, build profiles for environment-specific settings, readable POM structure with version properties, explicit repository declaration, version centralization, multi-module project structure with proper inheritance, and cross-module version consistency. - -**Multi-module scope:** After reading the root `pom.xml`, check for a `` section. If present, read **every** child module's `pom.xml` before making any recommendations. Check each child for hardcoded versions that duplicate parent ``, redundant `` blocks, properties that should be centralized, and version drift across sibling modules. - -**Before applying changes:** Read the reference for detailed examples, good/bad patterns, and constraints. - -## Reference - -For detailed guidance, examples, and constraints, see [references/110-java-maven-best-practices.md](references/110-java-maven-best-practices.md). diff --git a/skills-generator/src/main/resources/skills/110-skill.xml b/skills-generator/src/main/resources/skills/110-skill.xml new file mode 100644 index 00000000..19215193 --- /dev/null +++ b/skills-generator/src/main/resources/skills/110-skill.xml @@ -0,0 +1,46 @@ + + + + Juan Antonio Breña Moral + 0.13.0-SNAPSHOT + Apache-2.0 + + + Maven Best Practices + Use when you need to review, improve, or troubleshoot a Maven pom.xml file — including dependency management with BOMs, plugin configuration, version centralization, multi-module project structure, build profiles, or any situation where you want to align your Maven setup with industry best practices. + ` and BOMs +- Standard directory layout (`src/main/java`, `src/test/java`) +- Centralized plugin management +- Build profiles for environment-specific settings +- Readable POM structure with version properties +- Explicit repository declaration +- Version centralization +- Multi-module project structure with proper inheritance +- Cross-module version consistency +- Multi-module scope: After reading the root `pom.xml`, check for a `` section. If present, read **every** child module's `pom.xml` before making any recommendations. +- Check each child for hardcoded versions that duplicate parent ``, redundant `` blocks, properties that should be centralized, and version drift across sibling modules. +]]> + + + Before applying Maven best practices recommendations, ensure the project is in a valid state by running Maven validation. This helps identify any existing configuration issues that need to be resolved first. For multi-module projects, scope analysis must cover every child module POM — not just the root. + + **MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations + **VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications + **SAFETY**: If validation fails, do not continue and ask the user to fix the issues before continuing + ` section. If it does, read every child module's `pom.xml` before making any recommendations — analysis scope is the full module tree, not only the root]]> + ` in the parent, plugin configurations that duplicate ``, properties that should be centralized in the parent, and version drift (same artifact declared at different versions across sibling modules)]]> + **BEFORE APPLYING**: Read the reference for detailed examples, good/bad patterns, and constraints + + + + + For detailed guidance, examples, and constraints, see [references/110-java-maven-best-practices.md](references/110-java-maven-best-practices.md) + + diff --git a/skills-generator/src/test/java/info/jab/pml/SkillsGeneratorTest.java b/skills-generator/src/test/java/info/jab/pml/SkillsGeneratorTest.java index 908df3fa..56476ed2 100644 --- a/skills-generator/src/test/java/info/jab/pml/SkillsGeneratorTest.java +++ b/skills-generator/src/test/java/info/jab/pml/SkillsGeneratorTest.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -14,6 +15,10 @@ import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -44,21 +49,23 @@ private static Stream provideSkillDescriptors() @ParameterizedTest @MethodSource("provideSkillDescriptors") @DisplayName("Should generate valid SKILL.md and reference for each skill") - void should_generateValidSkill_when_skillIdProvided(SkillsInventory.SkillDescriptor descriptor) throws IOException { + void should_generateValidSkill_when_skillIdProvided(SkillsInventory.SkillDescriptor descriptor) throws Exception { String skillId = descriptor.skillId(); boolean requiresSystemPrompt = descriptor.requiresSystemPrompt(); + boolean useXml = descriptor.useXml(); - // Given - skill file in resources/skills/ is the source of truth - String expectedSkillMd = loadSkillFromResources(skillId); + // Given - skill file in resources/skills/ is the source of truth (.md or .xml when useXml) + String expectedSkillMd = useXml ? loadSkillFromXmlResources(skillId) : loadSkillFromResources(skillId); SkillsGenerator generator = new SkillsGenerator(); // When - SkillsGenerator.SkillOutput output = generator.generateSkill(skillId, requiresSystemPrompt); + SkillsGenerator.SkillOutput output = generator.generateSkill(skillId, requiresSystemPrompt, useXml); // Then - Generated SKILL.md must exactly match the skill source (user-editable) assertThat(output.skillMd()) - .withFailMessage("Generated SKILL.md must match skills/%s-skill.md. " - + "Update the skill file and run the build to promote changes.", numericId(skillId)) + .withFailMessage("Generated SKILL.md must match skills/%s-skill.%s. " + + "Update the skill file and run the build to promote changes.", + numericId(skillId), useXml ? "xml" : "md") .isEqualTo(expectedSkillMd); // Then - Validate reference content (only for skills with system prompt) @@ -105,17 +112,7 @@ private static Stream provideSkillDescriptorsWi void should_haveMatchingTitle_when_comparingSkillMdAndSystemPromptXml(SkillsInventory.SkillDescriptor descriptor) throws Exception { String skillId = descriptor.skillId(); String numId = numericId(skillId); - String skillMdResource = "skills/" + numId + "-skill.md"; - String markdownTitle; - try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(skillMdResource)) { - assertThat(stream).withFailMessage("Skill file not found: %s", skillMdResource).isNotNull(); - String content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - markdownTitle = content.lines() - .filter(line -> line.startsWith("# ")) - .findFirst() - .map(line -> line.substring(2).trim()) - .orElseThrow(() -> new AssertionError("No H1 heading found in " + skillMdResource)); - } + String markdownTitle = loadSkillTitle(numId); String xmlResource = "system-prompts/" + skillId + ".xml"; String xmlTitle; @@ -135,10 +132,34 @@ void should_haveMatchingTitle_when_comparingSkillMdAndSystemPromptXml(SkillsInve assertThat(markdownTitle) .withFailMessage( - "Skill markdown H1 '%s' in %s does not match XML '%s' in %s", - markdownTitle, skillMdResource, xmlTitle, xmlResource) + "Skill title '%s' does not match system-prompt XML <title> '%s' in %s", + markdownTitle, xmlTitle, xmlResource) .isEqualTo(xmlTitle); } + + private String loadSkillTitle(String numId) throws Exception { + String mdResource = "skills/" + numId + "-skill.md"; + try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(mdResource)) { + if (stream != null) { + String content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + return content.lines() + .filter(line -> line.startsWith("# ")) + .findFirst() + .map(line -> line.substring(2).trim()) + .orElseThrow(() -> new AssertionError("No H1 heading found in " + mdResource)); + } + } + String xmlResource = "skills/" + numId + "-skill.xml"; + try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(xmlResource)) { + assertThat(stream).withFailMessage("Skill file not found: %s or %s", mdResource, xmlResource).isNotNull(); + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream); + NodeList titleNodes = doc.getElementsByTagName("title"); + assertThat(titleNodes.getLength()) + .withFailMessage("No <title> element in %s", xmlResource) + .isGreaterThan(0); + return titleNodes.item(0).getTextContent().trim(); + } + } } @Nested @@ -156,24 +177,46 @@ private static Stream<SkillsInventory.SkillDescriptor> provideSkillDescriptors() @DisplayName("Should have metadata version matching project version from parent pom.xml when version is present") void should_haveMetadataVersionMatchingProjectVersion_when_versionPresent(SkillsInventory.SkillDescriptor descriptor) throws Exception { String numId = numericId(descriptor.skillId()); - String resourceName = "skills/" + numId + "-skill.md"; + Optional<String> skillVersion = loadSkillVersion(numId); - try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(resourceName)) { - assertThat(stream).withFailMessage("Skill file not found: %s", resourceName).isNotNull(); - String content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - Optional<String> skillVersion = extractVersionFromFrontmatter(content); + if (skillVersion.isEmpty()) { + return; + } - if (skillVersion.isEmpty()) { - return; - } + String expectedVersion = readProjectVersionFromParentPom(); + assertThat(skillVersion.get()) + .withFailMessage( + "Skill %s has metadata version '%s' but project version is '%s'. " + + "Update the version in skills/%s-skill.md or skills/%s-skill.xml to match pom.xml.", + numId, skillVersion.get(), expectedVersion, numId, numId) + .isEqualTo(expectedVersion); + } - String expectedVersion = readProjectVersionFromParentPom(); - assertThat(skillVersion.get()) - .withFailMessage( - "Skill %s has metadata version '%s' but project version is '%s'. " - + "Update the version in skills/%s-skill.md to match pom.xml.", - resourceName, skillVersion.get(), expectedVersion, numId) - .isEqualTo(expectedVersion); + private Optional<String> loadSkillVersion(String numId) throws Exception { + String mdResource = "skills/" + numId + "-skill.md"; + try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(mdResource)) { + if (stream != null) { + String content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + return extractVersionFromFrontmatter(content); + } + } + String xmlResource = "skills/" + numId + "-skill.xml"; + try (InputStream stream = SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(xmlResource)) { + if (stream == null) { + throw new AssertionError("Skill file not found: " + mdResource + " or " + xmlResource); + } + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream); + NodeList metadataNodes = doc.getElementsByTagName("metadata"); + if (metadataNodes.getLength() == 0) { + return Optional.empty(); + } + Element metadata = (Element) metadataNodes.item(0); + NodeList versionNodes = metadata.getElementsByTagName("version"); + if (versionNodes.getLength() == 0) { + return Optional.empty(); + } + String version = versionNodes.item(0).getTextContent(); + return Optional.ofNullable(version != null ? version.trim() : null); } } @@ -225,12 +268,43 @@ private String loadSkillFromResources(String skillId) throws IOException { throw new IllegalArgumentException("Skill file not found: " + resourceName); } String content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - return content.lines() - .map(line -> line.startsWith("description:") ? line + " Part of the skills-for-java project" : line) - .collect(Collectors.joining(System.lineSeparator(), "", System.lineSeparator())); + return appendProjectTagToDescription(content); } } + private String loadSkillFromXmlResources(String skillId) throws Exception { + String numId = numericId(skillId); + String xmlResource = "skills/" + numId + "-skill.xml"; + String xsltResource = "schemas/skill-to-markdown.xslt"; + try ( + InputStream xmlStream = getTestResource(xmlResource); + InputStream xsltStream = getTestResource(xsltResource) + ) { + if (xmlStream == null) { + throw new IllegalArgumentException("Skill XML not found: " + xmlResource); + } + if (xsltStream == null) { + throw new IllegalArgumentException("XSLT not found: " + xsltResource); + } + Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(xsltStream)); + StringWriter writer = new StringWriter(); + transformer.transform(new StreamSource(xmlStream), new StreamResult(writer)); + return appendProjectTagToDescription(writer.toString()); + } + } + + private String appendProjectTagToDescription(String content) { + return content.lines() + .map(line -> line.startsWith("description:") && !line.endsWith(" Part of the skills-for-java project") + ? line + " Part of the skills-for-java project" + : line) + .collect(Collectors.joining(System.lineSeparator(), "", System.lineSeparator())); + } + + private InputStream getTestResource(String name) { + return SkillsGeneratorTest.class.getClassLoader().getResourceAsStream(name); + } + private static String numericId(String skillId) { int dash = skillId.indexOf('-'); return dash > 0 ? skillId.substring(0, dash) : skillId; diff --git a/skills/110-java-maven-best-practices/SKILL.md b/skills/110-java-maven-best-practices/SKILL.md index 1007e7cf..569d8632 100644 --- a/skills/110-java-maven-best-practices/SKILL.md +++ b/skills/110-java-maven-best-practices/SKILL.md @@ -8,15 +8,34 @@ metadata: --- # Maven Best Practices + Improve Maven POM configuration using industry-standard best practices. -**Prerequisites:** Run `./mvnw validate` or `mvn validate` before applying recommendations. If validation fails, **stop** and ask the user to fix issues—do not proceed until resolved. +**What is covered in this Skill?** + +- Dependency management via `<dependencyManagement>` and BOMs +- Standard directory layout (`src/main/java`, `src/test/java`) +- Centralized plugin management +- Build profiles for environment-specific settings +- Readable POM structure with version properties +- Explicit repository declaration +- Version centralization +- Multi-module project structure with proper inheritance +- Cross-module version consistency +- Multi-module scope: After reading the root `pom.xml`, check for a `<modules>` section. If present, read **every** child module's `pom.xml` before making any recommendations. +- Check each child for hardcoded versions that duplicate parent `<dependencyManagement>`, redundant `<pluginManagement>` blocks, properties that should be centralized, and version drift across sibling modules. + -**Core areas:** Dependency management via `<dependencyManagement>` and BOMs, standard directory layout (`src/main/java`, `src/test/java`), centralized plugin management, build profiles for environment-specific settings, readable POM structure with version properties, explicit repository declaration, version centralization, multi-module project structure with proper inheritance, and cross-module version consistency. +## Constraints -**Multi-module scope:** After reading the root `pom.xml`, check for a `<modules>` section. If present, read **every** child module's `pom.xml` before making any recommendations. Check each child for hardcoded versions that duplicate parent `<dependencyManagement>`, redundant `<pluginManagement>` blocks, properties that should be centralized, and version drift across sibling modules. +Before applying Maven best practices recommendations, ensure the project is in a valid state by running Maven validation. This helps identify any existing configuration issues that need to be resolved first. For multi-module projects, scope analysis must cover every child module POM — not just the root. -**Before applying changes:** Read the reference for detailed examples, good/bad patterns, and constraints. +- **MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations +- **VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications +- **SAFETY**: If validation fails, do not continue and ask the user to fix the issues before continuing +- **MULTI-MODULE DISCOVERY**: After reading the root `pom.xml`, check whether it contains a `<modules>` section. If it does, read every child module's `pom.xml` before making any recommendations — analysis scope is the full module tree, not only the root +- **CROSS-MODULE SCOPE**: When child modules exist, check each one for: hardcoded dependency versions that duplicate `<dependencyManagement>` in the parent, plugin configurations that duplicate `<pluginManagement>`, properties that should be centralized in the parent, and version drift (same artifact declared at different versions across sibling modules) +- **BEFORE APPLYING**: Read the reference for detailed examples, good/bad patterns, and constraints ## Reference diff --git a/skills/110-java-maven-best-practices/references/110-java-maven-best-practices.md b/skills/110-java-maven-best-practices/references/110-java-maven-best-practices.md index 87fae532..c267461c 100644 --- a/skills/110-java-maven-best-practices/references/110-java-maven-best-practices.md +++ b/skills/110-java-maven-best-practices/references/110-java-maven-best-practices.md @@ -37,7 +37,6 @@ Before applying Maven best practices recommendations, ensure the project is in a - **MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations - **VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications -- **PREREQUISITE**: Project must compile and pass basic validation checks before optimization - **SAFETY**: If validation fails, not continue and ask the user to fix the issues before continuing - **MULTI-MODULE DISCOVERY**: After reading the root `pom.xml`, check whether it contains a `<modules>` section. If it does, read every child module's `pom.xml` before making any recommendations — analysis scope is the full module tree, not only the root - **CROSS-MODULE SCOPE**: When child modules exist, check each one for: hardcoded dependency versions that duplicate `<dependencyManagement>` in the parent, plugin configurations that duplicate `<pluginManagement>`, properties that should be centralized in the parent, and version drift (same artifact declared at different versions across sibling modules) diff --git a/system-prompts-generator/src/main/resources/system-prompts/110-java-maven-best-practices.xml b/system-prompts-generator/src/main/resources/system-prompts/110-java-maven-best-practices.xml index 36d5986f..8235d861 100644 --- a/system-prompts-generator/src/main/resources/system-prompts/110-java-maven-best-practices.xml +++ b/system-prompts-generator/src/main/resources/system-prompts/110-java-maven-best-practices.xml @@ -39,7 +39,6 @@ <constraint-list> <constraint>**MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations</constraint> <constraint>**VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications</constraint> - <constraint>**PREREQUISITE**: Project must compile and pass basic validation checks before optimization</constraint> <constraint>**SAFETY**: If validation fails, not continue and ask the user to fix the issues before continuing</constraint> <constraint>**MULTI-MODULE DISCOVERY**: After reading the root `pom.xml`, check whether it contains a `<modules>` section. If it does, read every child module's `pom.xml` before making any recommendations — analysis scope is the full module tree, not only the root</constraint> <constraint>**CROSS-MODULE SCOPE**: When child modules exist, check each one for: hardcoded dependency versions that duplicate `<dependencyManagement>` in the parent, plugin configurations that duplicate `<pluginManagement>`, properties that should be centralized in the parent, and version drift (same artifact declared at different versions across sibling modules)</constraint>