Skip to content

Commit f4240ba

Browse files
committed
Switching to xml for 110
1 parent cdbce46 commit f4240ba

12 files changed

Lines changed: 251 additions & 169 deletions

File tree

.cursor/rules/110-java-maven-best-practices.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Before applying Maven best practices recommendations, ensure the project is in a
3737

3838
- **MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations
3939
- **VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications
40-
- **PREREQUISITE**: Project must compile and pass basic validation checks before optimization
4140
- **SAFETY**: If validation fails, not continue and ask the user to fix the issues before continuing
4241
- **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
4342
- **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)

skills-generator/src/main/java/info/jab/pml/SkillsGenerator.java

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package info.jab.pml;
22

33
import java.io.InputStream;
4+
import java.io.StringWriter;
45
import java.util.Optional;
56
import java.util.stream.Stream;
67
import javax.xml.parsers.DocumentBuilder;
78
import javax.xml.parsers.DocumentBuilderFactory;
9+
import javax.xml.transform.Transformer;
10+
import javax.xml.transform.TransformerFactory;
11+
import javax.xml.transform.stream.StreamResult;
12+
import javax.xml.transform.stream.StreamSource;
13+
import javax.xml.validation.Schema;
14+
import javax.xml.validation.SchemaFactory;
815
import org.w3c.dom.Document;
916
import org.w3c.dom.Element;
1017
import org.w3c.dom.NodeList;
@@ -36,7 +43,7 @@ public SkillsGenerator() {
3643
*/
3744
public Stream<SkillOutput> generateAllSkills() {
3845
return SkillsInventory.skillDescriptors()
39-
.map(d -> generateSkill(d.skillId(), d.requiresSystemPrompt()));
46+
.map(d -> generateSkill(d.skillId(), d.requiresSystemPrompt(), d.useXml()));
4047
}
4148

4249
/**
@@ -47,7 +54,7 @@ public Stream<SkillOutput> generateAllSkills() {
4754
* @throws RuntimeException if resources cannot be loaded or generation fails
4855
*/
4956
public SkillOutput generateSkill(String skillId) {
50-
return generateSkill(skillId, true);
57+
return generateSkill(skillId, true, false);
5158
}
5259

5360
/**
@@ -58,10 +65,24 @@ public SkillOutput generateSkill(String skillId) {
5865
* @return the generated skill output
5966
*/
6067
public SkillOutput generateSkill(String skillId, boolean requiresSystemPrompt) {
68+
return generateSkill(skillId, requiresSystemPrompt, false);
69+
}
70+
71+
/**
72+
* Generates SKILL.md and reference content for a given skill.
73+
*
74+
* @param skillId the skill identifier (e.g. 110-java-maven-best-practices)
75+
* @param requiresSystemPrompt when false, skips system-prompt XML and reference generation
76+
* @param useXml when true, loads skill from skills/{numericId}-skill.xml, validates against schema, transforms via XSLT
77+
* @return the generated skill output
78+
*/
79+
public SkillOutput generateSkill(String skillId, boolean requiresSystemPrompt, boolean useXml) {
6180
String referenceContent = requiresSystemPrompt
6281
? generateReferenceContent(skillId, parseMetadata(skillId))
6382
: "";
64-
String skillMdContent = loadSkillSummary(skillId);
83+
String skillMdContent = useXml
84+
? loadSkillSummaryFromXml(skillId)
85+
: loadSkillSummary(skillId);
6586
return new SkillOutput(skillId, skillMdContent, referenceContent);
6687
}
6788

@@ -146,6 +167,49 @@ private String loadSkillSummary(String skillId) {
146167
}
147168
}
148169

170+
private String loadSkillSummaryFromXml(String skillId) {
171+
String numericId = extractNumericId(skillId);
172+
String xmlResource = "skills/" + numericId + "-skill.xml";
173+
String xsltResource = "schemas/skill-to-markdown.xslt";
174+
String schemaResource = "schemas/skill.xsd";
175+
try (
176+
InputStream xmlStream = getResource(xmlResource);
177+
InputStream xsltStream = getResource(xsltResource);
178+
InputStream schemaStream = getResource(schemaResource)
179+
) {
180+
if (xmlStream == null) {
181+
throw new RuntimeException("Skill XML not found: " + xmlResource);
182+
}
183+
if (xsltStream == null) {
184+
throw new RuntimeException("XSLT not found: " + xsltResource);
185+
}
186+
if (schemaStream == null) {
187+
throw new RuntimeException("Schema not found: " + schemaResource);
188+
}
189+
SchemaFactory factory = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
190+
Schema schema = factory.newSchema(new StreamSource(schemaStream));
191+
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
192+
docFactory.setSchema(schema);
193+
docFactory.setNamespaceAware(true);
194+
DocumentBuilder builder = docFactory.newDocumentBuilder();
195+
builder.parse(xmlStream);
196+
197+
TransformerFactory tf = TransformerFactory.newInstance();
198+
Transformer transformer = tf.newTransformer(new StreamSource(xsltStream));
199+
StringWriter writer = new StringWriter();
200+
try (InputStream xmlStreamForTransform = getResource(xmlResource)) {
201+
if (xmlStreamForTransform == null) {
202+
throw new RuntimeException("Skill XML not found: " + xmlResource);
203+
}
204+
transformer.transform(new StreamSource(xmlStreamForTransform), new StreamResult(writer));
205+
}
206+
String content = writer.toString();
207+
return appendProjectTagToDescription(content);
208+
} catch (Exception e) {
209+
throw new RuntimeException("Failed to load and transform skill XML: " + xmlResource, e);
210+
}
211+
}
212+
149213
private String appendProjectTagToDescription(String content) {
150214
boolean hasLicense = content.contains("license:");
151215
return content.lines()

skills-generator/src/main/java/info/jab/pml/SkillsInventory.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static Stream<String> skillIds() {
4646

4747
for (InventoryEntry entry : entries) {
4848
String numericId = entry.numericId();
49-
validateSkillSummaryExists(numericId);
49+
validateSkillSummaryExists(numericId, entry.useXml());
5050
String skillId = entry.requiresSystemPrompt()
5151
? resolveSkillIdFromPrefix(Integer.parseInt(numericId))
5252
: entry.skillId();
@@ -57,26 +57,26 @@ public static Stream<String> skillIds() {
5757
}
5858

5959
/**
60-
* Returns skill descriptors (skillId + requiresSystemPrompt) for generator use.
60+
* Returns skill descriptors (skillId + requiresSystemPrompt + useXml) for generator use.
6161
*/
6262
public static Stream<SkillDescriptor> skillDescriptors() {
6363
List<InventoryEntry> entries = loadInventory();
6464
List<SkillDescriptor> descriptors = new ArrayList<>();
6565
for (InventoryEntry entry : entries) {
6666
String numericId = entry.numericId();
67-
validateSkillSummaryExists(numericId);
67+
validateSkillSummaryExists(numericId, entry.useXml());
6868
String skillId = entry.requiresSystemPrompt()
6969
? resolveSkillIdFromPrefix(Integer.parseInt(numericId))
7070
: entry.skillId();
71-
descriptors.add(new SkillDescriptor(skillId, entry.requiresSystemPrompt()));
71+
descriptors.add(new SkillDescriptor(skillId, entry.requiresSystemPrompt(), entry.useXml()));
7272
}
7373
return descriptors.stream();
7474
}
7575

7676
/**
77-
* Skill ID and whether it requires a system prompt for reference generation.
77+
* Skill ID, whether it requires a system prompt for reference generation, and whether to use XML source.
7878
*/
79-
public record SkillDescriptor(String skillId, boolean requiresSystemPrompt) {}
79+
public record SkillDescriptor(String skillId, boolean requiresSystemPrompt, boolean useXml) {}
8080

8181
/**
8282
* Resolves skillId by finding the system-prompt XML that starts with {@code {id}-}.
@@ -186,20 +186,39 @@ private static List<InventoryEntry> parseInventory(String json) {
186186
throw new RuntimeException("Entry with id " + numericId
187187
+ " has requiresSystemPrompt=false but no skillId specified.");
188188
}
189-
entries.add(new InventoryEntry(numericId, requiresSystemPrompt, skillId));
189+
boolean useXml = parseXmlFlag(node);
190+
entries.add(new InventoryEntry(numericId, requiresSystemPrompt, skillId, useXml));
190191
}
191192
return entries;
192193
} catch (Exception e) {
193194
throw new RuntimeException("Failed to parse skill inventory", e);
194195
}
195196
}
196197

197-
private static void validateSkillSummaryExists(String numericId) {
198-
String resourceName = "skills/" + numericId + "-skill.md";
198+
private static boolean parseXmlFlag(JsonNode node) {
199+
if (!node.has("xml")) {
200+
return false;
201+
}
202+
JsonNode xmlNode = node.get("xml");
203+
if (xmlNode.isBoolean()) {
204+
return xmlNode.asBoolean();
205+
}
206+
if (xmlNode.isTextual()) {
207+
String s = xmlNode.asText().toLowerCase();
208+
return "true".equals(s) || "yes".equals(s) || "1".equals(s);
209+
}
210+
return false;
211+
}
212+
213+
private static void validateSkillSummaryExists(String numericId, boolean useXml) {
214+
String resourceName = useXml
215+
? "skills/" + numericId + "-skill.xml"
216+
: "skills/" + numericId + "-skill.md";
199217
try (InputStream stream = getResource(resourceName)) {
200218
if (stream == null) {
201219
throw new RuntimeException("Skill summary not found: " + resourceName
202-
+ ". Add skills/" + numericId + "-skill.md for each skill in the inventory.");
220+
+ ". Add skills/" + numericId + (useXml ? "-skill.xml" : "-skill.md")
221+
+ " for each skill in the inventory.");
203222
}
204223
} catch (Exception e) {
205224
if (e instanceof RuntimeException re) {
@@ -225,6 +244,8 @@ private static InputStream getResource(String name) {
225244
* Single entry from skill-inventory.json. When requiresSystemPrompt is true,
226245
* skillId is derived by matching system-prompts with prefix {@code {numericId}-}.
227246
* When false, skillId must be provided and no system-prompt is required.
247+
* When useXml is true, skill summary is loaded from skills/{numericId}-skill.xml
248+
* and transformed via schema validation and XSLT; otherwise from skills/{numericId}-skill.md.
228249
*/
229-
public record InventoryEntry(String numericId, boolean requiresSystemPrompt, String skillId) {}
250+
public record InventoryEntry(String numericId, boolean requiresSystemPrompt, String skillId, boolean useXml) {}
230251
}

skills-generator/src/main/resources/schemas/skill-to-markdown.xslt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ metadata:
3535
<xsl:apply-templates select="reference"/>
3636
</xsl:template>
3737

38+
<!-- Preserve line breaks: do not use normalize-space() which collapses newlines -->
3839
<xsl:template match="goal">
39-
<xsl:value-of select="normalize-space(.)"/>
40+
<xsl:value-of select="."/>
4041
<xsl:text>
4142

4243
</xsl:text>

skills-generator/src/main/resources/skill-inventory.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{"id": "021", "requiresSystemPrompt": false, "skillId": "021-architecture-functional-requirements-rest"},
55
{"id": "030", "requiresSystemPrompt": false, "skillId": "030-architecture-non-functional-requirements"},
66
{"id": "040", "requiresSystemPrompt": false, "skillId": "040-planning-enhance-ai-plan-mode"},
7-
{"id": 110},
7+
{"id": 110, "xml": true},
88
{"id": 111},
99
{"id": 112},
1010
{"id": 113},

skills-generator/src/main/resources/skills/110-skill.md

Lines changed: 0 additions & 23 deletions
This file was deleted.

skills-generator/src/main/resources/skills/110-skill.xml

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,29 @@
1010

1111
<title>Maven Best Practices</title>
1212
<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.</description>
13-
<goal>
14-
<![CDATA[
15-
Improve Maven POM configuration using industry-standard best practices.
13+
<goal><![CDATA[
14+
Improve Maven POM configuration using industry-standard best practices.
1615
17-
Core areas:
18-
Dependency management via `<dependencyManagement>` and BOMs
19-
Standard directory layout (`src/main/java`, `src/test/java`)
20-
Centralized plugin management
21-
Build profiles for environment-specific settings
22-
Readable POM structure with version properties
23-
Explicit repository declaration
24-
Version centralization
25-
Multi-module project structure with proper inheritance
26-
Cross-module version consistency
16+
**What is covered in this Skill?**
2717
28-
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.
29-
Check each child for hardcoded versions that duplicate parent `<dependencyManagement>`, redundant `<pluginManagement>` blocks, properties that should be centralized, and version drift across sibling modules.
30-
]]>
31-
</goal>
18+
- Dependency management via `<dependencyManagement>` and BOMs
19+
- Standard directory layout (`src/main/java`, `src/test/java`)
20+
- Centralized plugin management
21+
- Build profiles for environment-specific settings
22+
- Readable POM structure with version properties
23+
- Explicit repository declaration
24+
- Version centralization
25+
- Multi-module project structure with proper inheritance
26+
- Cross-module version consistency
27+
- 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.
28+
- Check each child for hardcoded versions that duplicate parent `<dependencyManagement>`, redundant `<pluginManagement>` blocks, properties that should be centralized, and version drift across sibling modules.
29+
]]></goal>
3230

3331
<constraints>
3432
<constraints-description>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.</constraints-description>
3533
<constraint-list>
3634
<constraint>**MANDATORY**: Run `./mvnw validate` or `mvn validate` before applying any Maven best practices recommendations</constraint>
3735
<constraint>**VERIFY**: Ensure all validation errors are resolved before proceeding with POM modifications</constraint>
38-
<constraint>**PREREQUISITE**: Project must compile and pass basic validation checks before optimization</constraint>
3936
<constraint>**SAFETY**: If validation fails, do not continue and ask the user to fix the issues before continuing</constraint>
4037
<constraint><![CDATA[**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>
4138
<constraint><![CDATA[**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>

skills-generator/src/test/java/info/jab/pml/SkillXmlToMarkdownTest.java

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)