From aa43489e0b28f19cb71c52583fa40b36e0124ea6 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 23 Jun 2026 13:00:17 +0200 Subject: [PATCH 1/2] docs: rename heylogs skill to changelog and drop create-* skills Rename the public `heylogs` skill to `changelog` so it no longer collides with the project/CLI name in skill pickers, and front-load action verbs in descriptions to avoid awkward truncation. Remove the internal `create-rule` and `create-versioning` skills, which are not used by maintainers and should not be public. Simplify the install command to `npx skills add nbbrd/heylogs` since the repo now ships a single skill. Co-Authored-By: Claude Opus 4.8 --- .agents/skills/create-rule/SKILL.md | 183 ------- .agents/skills/create-versioning/SKILL.md | 476 ------------------ AGENTS.md | 2 +- README.md | 2 +- skills/{heylogs => changelog}/README.md | 14 +- skills/{heylogs => changelog}/SKILL.md | 8 +- .../references/commands.md | 0 .../references/config-file.md | 0 .../references/rules.md | 0 .../references/running-cli.md | 0 10 files changed, 13 insertions(+), 672 deletions(-) delete mode 100644 .agents/skills/create-rule/SKILL.md delete mode 100644 .agents/skills/create-versioning/SKILL.md rename skills/{heylogs => changelog}/README.md (81%) rename skills/{heylogs => changelog}/SKILL.md (96%) rename skills/{heylogs => changelog}/references/commands.md (100%) rename skills/{heylogs => changelog}/references/config-file.md (100%) rename skills/{heylogs => changelog}/references/rules.md (100%) rename skills/{heylogs => changelog}/references/running-cli.md (100%) diff --git a/.agents/skills/create-rule/SKILL.md b/.agents/skills/create-rule/SKILL.md deleted file mode 100644 index 3b426411..00000000 --- a/.agents/skills/create-rule/SKILL.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -name: create-rule -description: > - Use this skill when asked to add, create, or implement a new validation rule - in the Heylogs project. It covers picking the right enum, adding the constant, - the validate method, the test, the resource file (if needed), and the - documentation row. ---- - -Add a new validation rule to `heylogs-ext-rules` by following these steps in order. Execute all steps without asking for confirmation. - -## Step 0 — Choose the right enum - -Rules live in three categorized enums under `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/`: - -| Enum | File | Category | Typical node type | -|----------------|---------------------|--------------------------------------|------------------------------------------| -| `ReleaseRules` | `ReleaseRules.java` | Release structure and versioning | `Document`, `Heading` | -| `LinkRules` | `LinkRules.java` | Link validation and reference checks | `Link`, `LinkNodeBase`, `BulletListItem` | -| `StyleRules` | `StyleRules.java` | Formatting and style conventions | `Document`, `BulletListItem` | - -Pick the enum whose category best matches the new rule, then use that file for Steps 1–3. - -> **Note**: Rules for core changelog structure (changelog heading, H2 version format, type-of-change headings, date display, version ordering) belong to `GuidingPrinciples` in `heylogs-api` and are not part of this skill. - -## Step 1 — Add the enum constant - -**File**: `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/.java` - -**Action**: Use `replace_string_in_file` to add a new constant BEFORE the final `;` of the enum (after the last existing constant). The rule ID is derived automatically from the constant name (`MY_RULE` → `my-rule`). - -**Template**: -```java -MY_RULE { - @Override - public @Nullable RuleIssue getRuleIssueOrNull(@NonNull Node node, @NonNull RuleContext context) { - return node instanceof BulletListItem ? validateMyRule((BulletListItem) node) : NO_RULE_ISSUE; - } - - @Override - public @NonNull String getRuleName() { - return "My rule"; - } - - // Only add this override when severity is not ERROR: - @Override - public @NonNull RuleSeverity getRuleSeverity() { - return RuleSeverity.WARN; // or OFF - } -}, -``` - -**Node type selection**: - -| Node type | What it covers | Common pattern | -|-------------------------|------------------------------------|------------------------------------------------------| -| `Document` | Whole-document checks | Use `ChangelogHeading.root(doc)` helper | -| `Heading` | Version or type-of-change headings | Check `Version.isVersionLevel(heading)` | -| `BulletListItem` | Changelog list entries | Use `item.getChars().trim().toString()` | -| `Link` / `LinkNodeBase` | Inline links | Parse URL with `Parser.onURL().parse(link.getUrl())` | - -## Step 2 — Add the static validate method - -**File**: Same as Step 1 - -**Action**: Use `replace_string_in_file` to add a `@VisibleForTesting` static method AFTER the last existing `validate*` method (before any inner class or `Batch` class at the end). - -**Template for BulletListItem rules**: -```java -@VisibleForTesting -static @Nullable RuleIssue validateMyRule(@NonNull BulletListItem item) { - String text = item.getChars().trim().toString(); - if (text.isEmpty()) return NO_RULE_ISSUE; - - // Early return NO_RULE_ISSUE for valid cases - if (someCondition) return NO_RULE_ISSUE; - - // Return issue for invalid cases - return RuleIssue - .builder() - .message("Descriptive error message") - .location(item) - .build(); -} -``` - -**Best practices**: -- Always use `.trim()` when checking `BulletListItem` text content -- Use early returns with `NO_RULE_ISSUE` for valid/skip cases -- Use `Character` utility methods for character checks (e.g., `Character.isLetter()`, `Character.isUpperCase()`) -- Use `String.format(ROOT, "...")` for parameterized messages -- For `Document` rules, delegate to a private helper: `ChangelogHeading.root(doc).map(MyEnum::validateHelper).orElse(NO_RULE_ISSUE)` -- If the new method needs `truncate()`, call `RulesUtil.truncate(text, maxLength)` (package-private helper in the same package) - -## Step 3 — Add the test - -**File**: `heylogs-ext-rules/src/test/java/nbbrd/heylogs/ext/rules/Test.java` - -**Action**: Use `replace_string_in_file` to add a test method AFTER the last existing test method (before the closing `}`). - -**Template**: -```java -@Test -public void testValidateMyRule() { - assertThat(validateMyRule(asBulletListItem("- Valid entry"))) - .describedAs("valid case") - .isNull(); - - assertThat(validateMyRule(asBulletListItem("- Invalid entry"))) - .describedAs("invalid case") - .isEqualTo(RuleIssue.builder().message("Expected message").line(1).column(1).build()); -} -``` - -**Tips**: -- Import the static methods at the top: `import static nbbrd.heylogs.ext.rules..*;` -- Use `.isNull()` instead of `.isEqualTo(NO_RULE_ISSUE)` (they are equivalent but `isNull` is cleaner) -- Use `.describedAs("...")` for each assertion to document test intent -- For `Document`-level rules, load a Markdown fixture with `using("/MyTestFile.md")` (see Step 3a) -- Helper methods available: `asBulletListItem(String)` and `asLink(String)` are in `RulesTestHelper`; `asHeading(String)` and `using(String)` are in `tests.heylogs.api.Sample` - -### Step 3a — Add test resource (only for Document-level rules) - -If the test uses `using("/MyTestFile.md")`, the `.md` file must exist in **both**: - -1. `heylogs-api/src/test/resources/MyTestFile.md` — original source of truth -2. `heylogs-ext-rules/src/test/resources/MyTestFile.md` — local copy required for the ext-rules test classpath - -Create both files with the minimal changelog content needed to trigger the rule. - -> **Why two copies?** The `heylogs-api` test-jar only packages `.class` files, not test resources. Each ext module that needs a resource must carry its own copy under `src/test/resources/`. - -## Step 4 — Update documentation - -**File**: `docs/feature-rules.md` - -**Action**: Use `replace_string_in_file` to add a row to the table in **alphabetical order** by rule ID (kebab-case). Use `rules` as the module name. - -**Row format**: -```markdown -| `rules` | [`my-rule`](#my-rule) | My rule | `ERROR` | -``` - -**Severity values**: `ERROR` (default), `WARN`, or `OFF` - -Also add a reference section entry at the bottom of the file: - -```markdown -### `my-rule` -Description of what the rule checks and when it fires. -``` - -## Post-implementation checks - -**Validation**: After all edits, call `get_errors` on both modified Java files to verify no compilation errors. - -**Files to validate**: -- `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/.java` -- `heylogs-ext-rules/src/test/java/nbbrd/heylogs/ext/rules/Test.java` - -## Testing (optional) - -```shell -# Fast test of the changed module (from workspace root) -mvn test -pl heylogs-ext-rules -am -Pyolo - -# Run only the new test method -mvn test -pl heylogs-ext-rules -am -Pyolo -Dtest=Test#testValidateMyRule - -# Full build with all checks -mvn clean install -``` - -## Constraints - -- Rule ID must match `ServiceId.KEBAB_CASE` (enforced at runtime by `RuleSupport`). -- Default severity is `ERROR`; only override when `WARN` or `OFF` is needed. -- The `test()` method in each `*Test` class automatically skips rules with `OFF` severity. -- Never reference `internal.*` packages from public API classes. The three rule enums are in the `nbbrd.heylogs.ext.rules` public package and may only access `internal.heylogs.*` directly (not `internal.heylogs.base.*`, `internal.heylogs.git.*`, etc.) — enforced by `ArchitectureTest`. -- `getRuleModuleId()` must return `"rules"` (already inherited from the enum body; do not change it). -- Target Java 8; avoid `String.repeat()`, `var`, `Stream.toList()`, `List.of()`. -- Use `trim()` before checking text content of `BulletListItem`. -- All common imports are already present in each enum; only add new ones if strictly required. diff --git a/.agents/skills/create-versioning/SKILL.md b/.agents/skills/create-versioning/SKILL.md deleted file mode 100644 index 76b6de0b..00000000 --- a/.agents/skills/create-versioning/SKILL.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -name: create-versioning -description: > - Use this skill when asked to add, create, or implement a new versioning scheme - in the Heylogs project. It covers creating the extension module, the public - SPI class, the internal parser/comparator, the tests, registering the module - in all POMs, and updating the documentation. ---- - -Add a new versioning scheme to Heylogs by creating a `heylogs-ext-` module. Execute all steps without asking for confirmation. - -## Prerequisites — Gather context - -Before starting, read these reference files to understand the patterns: - -1. **SPI interface**: `heylogs-api/src/main/java/nbbrd/heylogs/spi/Versioning.java` -2. **Support builder**: `heylogs-api/src/main/java/nbbrd/heylogs/spi/VersioningSupport.java` -3. **Test fixture**: `heylogs-api/src/test/java/tests/heylogs/spi/VersioningAssert.java` -4. **Reference implementation (no-arg)**: `heylogs-ext-semver/src/main/java/nbbrd/heylogs/ext/semver/SemVer.java` -5. **Reference implementation (with-arg)**: `heylogs-ext-calver/src/main/java/nbbrd/heylogs/ext/calver/CalVer.java` -6. **Reference test (no-arg)**: `heylogs-ext-semver/src/test/java/nbbrd/heylogs/ext/semver/SemVerTest.java` -7. **Reference test (with-arg)**: `heylogs-ext-calver/src/test/java/nbbrd/heylogs/ext/calver/CalVerTest.java` -8. **Reference POM (no external dep)**: `heylogs-ext-calver/pom.xml` -9. **Reference POM (external dep)**: `heylogs-ext-semver/pom.xml` - -## Step 0 — Determine the versioning characteristics - -Decide the following before writing code: - -| Decision | Option A (simpler) | Option B (complex) | -|----------------------|-------------------------------|---------------------------------------------| -| **Argument** | No argument (`withoutArg`) | Requires argument (`compilingArg` / custom) | -| **External library** | None (pure regex / algorithm) | External dependency (e.g., `semver4j`) | -| **Internal classes** | Single internal helper class | Multiple internal classes | - -This determines which reference files to follow most closely: -- No arg + no external dep → follow `heylogs-ext-pep440` pattern -- No arg + external dep → follow `heylogs-ext-semver` pattern -- With arg → follow `heylogs-ext-calver` pattern - -## Step 1 — Create the module POM - -**File**: `heylogs-ext-/pom.xml` - -**Action**: Create the file using the template below. - -**Template** (no external dependency): -```xml - - - 4.0.0 - - - com.github.nbbrd.heylogs - heylogs-parent - 0.18.2-SNAPSHOT - - - heylogs-ext- - jar - - heylogs-ext- - Keep-a-changelog tool - extension - https://github.com/nbbrd/heylogs - - - - - org.jspecify - jspecify - provided - - - org.projectlombok - lombok - provided - - - com.github.nbbrd.java-design-util - java-design-processor - provided - - - com.github.nbbrd.java-service-util - java-service-processor - provided - - - - - heylogs-api - ${project.groupId} - ${project.version} - - - - - - heylogs-api - ${project.groupId} - ${project.version} - tests - test-jar - test - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - -``` - -> **Note**: Read the current `` from the root `pom.xml` parent tag — do not hardcode the version shown above. - -> **External dependency**: If an external library is needed, add it in the `` section and also declare its version in the root POM `` section. - -## Step 2 — Create the public SPI class - -**File**: `heylogs-ext-/src/main/java/nbbrd/heylogs/ext//.java` - -**Action**: Create the file. The class must: -1. Be `public final` and implement `Versioning` -2. Be annotated with `@DirectImpl` and `@ServiceProvider` -3. Use `@lombok.experimental.Delegate` to delegate to a `VersioningSupport` instance -4. The `id` must be in `KEBAB_CASE` and match the module name convention - -**Template (no-arg versioning)**: -```java -package nbbrd.heylogs.ext.; - -import internal.heylogs.ext..; -import nbbrd.design.DirectImpl; -import nbbrd.heylogs.spi.Versioning; -import nbbrd.heylogs.spi.VersioningSupport; -import nbbrd.service.ServiceProvider; - -import static nbbrd.heylogs.spi.VersioningSupport.withoutArg; - -@DirectImpl -@ServiceProvider -public final class implements Versioning { - - @lombok.experimental.Delegate - private final Versioning delegate = VersioningSupport - .builder() - .id("") - .name("") - .urlOf("") - .moduleId("") - .validator(arg -> arg == null ? null : " does not take any arguments") - .predicate(withoutArg(::isValid)) - .comparator(withoutArg(::compare)) - .familyMapper(withoutArg(::toFamily)) - .build(); -} -``` - -**Template (with-arg versioning)** — see `CalVer.java` for reference: -```java -package nbbrd.heylogs.ext.; - -import internal.heylogs.ext..; -import nbbrd.design.DirectImpl; -import nbbrd.heylogs.spi.Validator; -import nbbrd.heylogs.spi.Versioning; -import nbbrd.heylogs.spi.VersioningSupport; -import nbbrd.service.ServiceProvider; - -import java.util.Objects; - -import static nbbrd.heylogs.spi.VersioningSupport.compilingArg; - -@DirectImpl -@ServiceProvider -public final class implements Versioning { - - @lombok.experimental.Delegate - private final Versioning delegate = VersioningSupport - .builder() - .id("") - .name("") - .urlOf("") - .moduleId("") - .validator(Validator.of(::parse)) - .predicate(compilingArg(::parse, ::isValidVersion)) - .comparator(arg -> { - format = .parse(Objects.requireNonNull(arg)); - return (a, b) -> compare(format, a, b); - }) - .familyMapper(arg -> { - format = .parse(Objects.requireNonNull(arg)); - return version -> toFamily(format, version); - }) - .build(); - - // Add private static helper methods for compare and toFamily -} -``` - -### `VersioningSupport` builder fields reference - -| Field | Type | Required | Description | -|----------------|----------------------------------------------------|----------|---------------------------------------------------------------------| -| `id` | `String` | Yes | Kebab-case identifier (e.g., `"pep440"`, `"semver"`) | -| `name` | `String` | Yes | Human-readable name | -| `url`/`urlOf` | `URL`/`CharSequence` | Yes | Link to the versioning spec | -| `moduleId` | `String` | Yes | Usually same as `id` | -| `validator` | `Function` | Yes | Returns null if arg valid, error message otherwise | -| `predicate` | `Function>` | Yes | Tests if a string is a valid version | -| `comparator` | `Function>` | No | Compares two versions; return 0 for incomparable | -| `familyMapper` | `Function>` | No | Maps version to family key (e.g., `"MAJOR.MINOR"`); null if invalid | - -### Helper methods in `VersioningSupport` - -| Method | Use when | Wraps argument handling | -|-----------------------------|--------------------------------------------|-------------------------------------| -| `withoutArg(Predicate)` | No argument needed | Returns predicate when arg is null | -| `withoutArg(Comparator)` | No argument needed | Returns comparator when arg is null | -| `withoutArg(Function)` | No argument needed | Returns mapper when arg is null | -| `compilingArg(factory, bi)` | Argument is compiled into an engine object | Compiles arg, then delegates | - -## Step 3 — Create the internal helper class - -**File**: `heylogs-ext-/src/main/java/internal/heylogs/ext//.java` - -**Action**: Create the file with the parsing, validation, comparison, and family-mapping logic. - -**Required static methods for no-arg versioning**: - -```java -package internal.heylogs.ext.; - -import lombok.NonNull; -import org.jspecify.annotations.Nullable; - -public final class { - - private () { - throw new UnsupportedOperationException(); - } - - /** Returns true if text is a valid version string. */ - public static boolean isValid(@NonNull CharSequence text) { ... } - - /** - * Compares two version strings. - * Returns 0 if either is invalid (incomparable). - */ - public static int compare(@NonNull CharSequence a, @NonNull CharSequence b) { ... } - - /** - * Returns the family key (typically "MAJOR.MINOR"), or null if invalid. - */ - public static @Nullable String toFamily(@NonNull CharSequence version) { ... } -} -``` - -**Implementation guidelines**: -- Use `java.util.regex.Pattern` for validation (compile as a `private static final` field) -- Return `0` from `compare` when either input is invalid (incomparable) -- Return `null` from `toFamily` when input is invalid -- Family is typically `"MAJOR.MINOR"` (first two numeric components) -- Target Java 8: no `var`, no `String.repeat()`, no `Stream.toList()`, no `List.of()` -- Use `Locale.ROOT` for any case conversions - -## Step 4 — Create the test class - -**File**: `heylogs-ext-/src/test/java/nbbrd/heylogs/ext//Test.java` - -**Action**: Create the file with four test methods. - -**Template (no-arg)**: -```java -package nbbrd.heylogs.ext.; - -import nbbrd.heylogs.spi.Versioning; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static tests.heylogs.spi.VersioningAssert.assertVersioningCompliance; - -class Test { - - @Test - public void testCompliance() { - assertVersioningCompliance(new ()); - } - - @Test - void testIsValidVersion() { - Versioning x = new (); - - // Verify that passing an argument is rejected - assertThatIllegalArgumentException() - .isThrownBy(() -> x.getVersioningPredicateOrNull("")) - .withMessage(" does not take any arguments"); - - // Verify valid and invalid versions - assertThat(x.getVersioningPredicateOrNull(null)) - .accepts(/* valid version examples */) - .rejects(/* invalid version examples */); - } - - @Test - void testComparator() { - Versioning x = new (); - - assertThatIllegalArgumentException() - .isThrownBy(() -> x.getVersioningComparatorOrNull("")) - .withMessage(" does not take any arguments"); - - assertThat(x.getVersioningComparatorOrNull(null)) - .isNotNull() - .satisfies(comparator -> { - // basic ordering - assertThat(comparator.compare("2.0.0", "1.0.0")).isPositive(); - assertThat(comparator.compare("1.0.0", "2.0.0")).isNegative(); - assertThat(comparator.compare("1.0.0", "1.0.0")).isZero(); - - // scheme-specific ordering tests - - // incomparable - assertThat(comparator.compare("invalid", "1.0.0")).isZero(); - }); - } - - @Test - void testFamilyMapper() { - Versioning x = new (); - - assertThatIllegalArgumentException() - .isThrownBy(() -> x.getVersioningFamilyMapperOrNull("")) - .withMessage(" does not take any arguments"); - - assertThat(x.getVersioningFamilyMapperOrNull(null)) - .isNotNull() - .satisfies(mapper -> { - assertThat(mapper.apply("2.4.0")).isEqualTo("2.4"); - assertThat(mapper.apply("2.4.1")).isEqualTo("2.4"); - assertThat(mapper.apply("invalid")).isNull(); - }); - } -} -``` - -**Template (with-arg)** — see `CalVerTest.java` for reference. Key differences: -- Passing `null` arg throws `IllegalArgumentException` (instead of being accepted) -- Tests pass a format string argument to each method - -**Test guidelines**: -- Always include a `testCompliance()` method calling `assertVersioningCompliance` -- Test at least 3 valid and 3 invalid versions in `testIsValidVersion` -- Test basic ordering, scheme-specific ordering, and incomparable cases in `testComparator` -- Test family mapping for multiple versions and the invalid case in `testFamilyMapper` -- Use AssertJ fluent assertions - -## Step 5 — Register the module in POMs - -Use `replace_string_in_file` for each of the following files. Insert the new module adjacent to existing versioning extensions (after `heylogs-ext-calver` or `heylogs-ext-semver`). - -### 5a — Root POM (`pom.xml`) - -Add `heylogs-ext-` in the `` section: -```xml -heylogs-ext- -``` - -### 5b — BOM (`heylogs-bom/pom.xml`) - -Add a `` entry in ``: -```xml - - heylogs-ext- - ${project.groupId} - ${project.version} - -``` - -### 5c — Consumer modules (runtime dependency) - -Add a `runtime` dependency in each of: -- `heylogs-cli/pom.xml` -- `heylogs-maven-plugin/pom.xml` -- `heylogs-enforcer-rules/pom.xml` - -```xml - - heylogs-ext- - ${project.groupId} - ${project.version} - runtime - -``` - -### 5d — Architecture tests (`heylogs-architecture-tests/pom.xml`) - -Add a `test` dependency: -```xml - - com.github.nbbrd.heylogs - heylogs-ext- - ${project.version} - test - -``` - -## Step 6 — Update documentation - -### Versioning table (`docs/feature-versioning.md`) - -Add a row to the versioning schemes table in **alphabetical order** by ID: - -```markdown -| `` | []() | `` | - | -``` - -The argument column should be `-` for no-arg versionings, or a description for with-arg. - -Also add a usage example: -```markdown -- `$ heylogs check -v ` -``` - -## Post-implementation checks - -**Validation**: After all edits, call `get_errors` on these files: -- `heylogs-ext-/src/main/java/nbbrd/heylogs/ext//.java` -- `heylogs-ext-/src/main/java/internal/heylogs/ext//.java` -- `heylogs-ext-/src/test/java/nbbrd/heylogs/ext//Test.java` - -## Testing - -```shell -# Fast test of the new module -mvn test -pl heylogs-ext- -am -Pyolo - -# Full build with all checks -mvn clean install -``` - -Expect: -- 4 tests run (compliance + isValidVersion + comparator + familyMapper) -- 0 failures, 0 errors -- BUILD SUCCESS - -## Constraints - -- **ID** must match `ServiceId.KEBAB_CASE` (lowercase letters, digits, hyphens; e.g., `pep440`, `semver`, `calver`). -- **Java 8 target**: no `var`, `String.repeat()`, `Stream.toList()`, `List.of()`, `Map.of()`. -- **No BOM in source files**: if creating files via PowerShell, use `[System.IO.File]::WriteAllText(path, content, (New-Object System.Text.UTF8Encoding $false))` to avoid UTF-8 BOM bytes. -- **Internal packages**: parsing/comparison logic goes in `internal.heylogs.ext.` — never expose internal types in the public API. -- **Annotations**: `@DirectImpl` + `@ServiceProvider` on the public class; `@lombok.experimental.Delegate` on the delegate field. -- **Backward compatibility**: the `Versioning` SPI contract requires `getVersioningPredicateOrNull` to throw `IllegalArgumentException` when the validator rejects the arg. -- **Incomparable versions**: `compare()` must return `0` when either input is not a valid version for the scheme. -- **Family mapping**: `toFamily()` must return `null` when the input is not a valid version. -- **No additional features**: do not add features beyond what the `Versioning` SPI defines. - -## Quick reference — Existing versioning modules - -| Module | ID | Arg? | External dep? | Internal class(es) | -|----------------------|----------|------|------------------|-----------------------------------| -| `heylogs-ext-semver` | `semver` | No | `semver4j` | (none — uses library directly) | -| `heylogs-ext-calver` | `calver` | Yes | No | `CalVerFormat`, `CalVerTag`, etc. | -| `heylogs-ext-pep440` | `pep440` | No | No | `Pep440Version` | -| `heylogs-ext-maven` | `maven` | No | `maven-artifact` | (none — uses library directly) | - diff --git a/AGENTS.md b/AGENTS.md index 49440543..5c189b22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Heylogs is a Java 8+ multi-module Maven project for validating and releasing changelogs in [Keep a Changelog](https://keepachangelog.com) format. It parses Markdown into an AST (via [flexmark-java](https://github.com/vsch/flexmark-java)) and operates on the tree. -> **This file** covers building and contributing to Heylogs itself. To *use* Heylogs (run `check`, tune rules, configure `heylogs.properties`) on a changelog, see the [`heylogs` skill](skills/heylogs/SKILL.md). +> **This file** covers building and contributing to Heylogs itself. To *use* Heylogs (run `check`, tune rules, configure `heylogs.properties`) on a changelog, see the [`changelog` skill](skills/changelog/SKILL.md). ## Architecture diff --git a/README.md b/README.md index 7b658b04..88f4485e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Heylogs is available in multiple formats to fit your workflow: - **[Maven Enforcer rules](docs/usage-enforcer-rules.md)** - Enforce changelog quality in builds - **[CI/CD pipelines](docs/usage-pipelines.md)** - Integrate into CI/CD environments -> Using an AI coding assistant? The [`heylogs` skill](skills/heylogs/SKILL.md) is an agent-generic guide to running `check`, tuning rules, and configuring `heylogs.properties`. Install it with `npx skills add nbbrd/heylogs --skill heylogs` ([details](skills/heylogs/README.md)). +> Using an AI coding assistant? The [`changelog` skill](skills/changelog/SKILL.md) is an agent-generic guide to running `check`, tuning rules, and configuring `heylogs.properties`. Install it with `npx skills add nbbrd/heylogs` ([details](skills/changelog/README.md)). ## Features diff --git a/skills/heylogs/README.md b/skills/changelog/README.md similarity index 81% rename from skills/heylogs/README.md rename to skills/changelog/README.md index 584f2b4b..9fc75f21 100644 --- a/skills/heylogs/README.md +++ b/skills/changelog/README.md @@ -1,6 +1,6 @@ -# Install the `heylogs` skill +# Install the `changelog` skill -The `heylogs` skill teaches an AI coding agent how to validate and maintain +The `changelog` skill teaches an AI coding agent how to validate and maintain [Keep a Changelog](https://keepachangelog.com)–formatted `CHANGELOG.md` files with the **[Heylogs](https://github.com/nbbrd/heylogs)** CLI — running `check`, tuning rules, and configuring `heylogs.properties`. The skill definition is in @@ -24,7 +24,7 @@ explains how to run it with no prerequisites via JBang (see ## Installation ```bash -npx skills add nbbrd/heylogs --skill heylogs +npx skills add nbbrd/heylogs ``` The installer fetches the skill from the repository, asks which agents to install @@ -36,23 +36,23 @@ Confirm to finish. Non-interactive (CI-friendly) install for Claude Code, global: ```bash -npx skills add nbbrd/heylogs --skill heylogs -g -a claude-code -y +npx skills add nbbrd/heylogs -g -a claude-code -y ``` ## Alternative: manual installation If you prefer not to use `npx skills`, download the skill folder -[`skills/heylogs/`](.) and register it as a local skill folder per your agent's +[`skills/changelog/`](.) and register it as a local skill folder per your agent's documentation (no Node.js required). ## Update ```bash -npx skills update heylogs +npx skills update changelog ``` ## Usage -Once installed, invoke `/heylogs` in the selected agent, or just ask it to check, +Once installed, invoke `/changelog` in the selected agent, or just ask it to check, fix, or release a changelog — the skill's `description` triggers it automatically. See the [skill definition](SKILL.md) for full capabilities. diff --git a/skills/heylogs/SKILL.md b/skills/changelog/SKILL.md similarity index 96% rename from skills/heylogs/SKILL.md rename to skills/changelog/SKILL.md index 1501797a..a6a78058 100644 --- a/skills/heylogs/SKILL.md +++ b/skills/changelog/SKILL.md @@ -1,9 +1,9 @@ --- -name: heylogs +name: changelog description: > - Validate and maintain Keep a Changelog–formatted CHANGELOG.md files with the - Heylogs CLI. Use this skill whenever the user asks to check, lint, validate, - fix, format, or release a changelog, sees Heylogs rule violations (e.g. + Check, lint, fix, format, or release Keep a Changelog CHANGELOG.md files with + the Heylogs CLI. Use this skill whenever the user asks to validate or maintain + a changelog, sees Heylogs rule violations (e.g. no-empty-group, latest-version-first, forge-ref), wants to get a repo to "0 problems" via heylogs.properties without editing the changelog, or needs to add/extract/export changelog entries. Covers the `check` command and rule diff --git a/skills/heylogs/references/commands.md b/skills/changelog/references/commands.md similarity index 100% rename from skills/heylogs/references/commands.md rename to skills/changelog/references/commands.md diff --git a/skills/heylogs/references/config-file.md b/skills/changelog/references/config-file.md similarity index 100% rename from skills/heylogs/references/config-file.md rename to skills/changelog/references/config-file.md diff --git a/skills/heylogs/references/rules.md b/skills/changelog/references/rules.md similarity index 100% rename from skills/heylogs/references/rules.md rename to skills/changelog/references/rules.md diff --git a/skills/heylogs/references/running-cli.md b/skills/changelog/references/running-cli.md similarity index 100% rename from skills/heylogs/references/running-cli.md rename to skills/changelog/references/running-cli.md From e105bd4334167dac144edd70d5c6161c87f77368 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 30 Jun 2026 18:25:46 +0200 Subject: [PATCH 2/2] docs(skills): group skills by category, restore contributing skills Move the changelog skill under skills/usage/ and restore create-rule and create-versioning under skills/contributing/ using the catalog layout (skills///SKILL.md) supported by npx skills. Add metadata.category to all skills and prefix the contributing-skill descriptions so the grouping is visible to consumers. Fix the relative links broken by the deeper nesting (README, AGENTS, in-skill ../../../). The npx skills install command (npx skills add nbbrd/heylogs) is unchanged. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 4 +- README.md | 2 +- skills/contributing/create-rule/SKILL.md | 186 +++++++ .../contributing/create-versioning/SKILL.md | 479 ++++++++++++++++++ skills/{ => usage}/changelog/README.md | 2 +- skills/{ => usage}/changelog/SKILL.md | 9 +- .../changelog/references/commands.md | 0 .../changelog/references/config-file.md | 0 .../{ => usage}/changelog/references/rules.md | 0 .../changelog/references/running-cli.md | 0 10 files changed, 675 insertions(+), 7 deletions(-) create mode 100644 skills/contributing/create-rule/SKILL.md create mode 100644 skills/contributing/create-versioning/SKILL.md rename skills/{ => usage}/changelog/README.md (95%) rename skills/{ => usage}/changelog/SKILL.md (96%) rename skills/{ => usage}/changelog/references/commands.md (100%) rename skills/{ => usage}/changelog/references/config-file.md (100%) rename skills/{ => usage}/changelog/references/rules.md (100%) rename skills/{ => usage}/changelog/references/running-cli.md (100%) diff --git a/AGENTS.md b/AGENTS.md index 5c189b22..18657dba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,9 @@ Heylogs is a Java 8+ multi-module Maven project for validating and releasing changelogs in [Keep a Changelog](https://keepachangelog.com) format. It parses Markdown into an AST (via [flexmark-java](https://github.com/vsch/flexmark-java)) and operates on the tree. -> **This file** covers building and contributing to Heylogs itself. To *use* Heylogs (run `check`, tune rules, configure `heylogs.properties`) on a changelog, see the [`changelog` skill](skills/changelog/SKILL.md). +> **This file** covers building and contributing to Heylogs itself. To *use* Heylogs (run `check`, tune rules, configure `heylogs.properties`) on a changelog, see the [`changelog` skill](skills/usage/changelog/SKILL.md). +> +> **Contributing skills** (step-by-step guides for extending the codebase): [`create-rule`](skills/contributing/create-rule/SKILL.md) to add a validation rule, [`create-versioning`](skills/contributing/create-versioning/SKILL.md) to add a versioning scheme. ## Architecture diff --git a/README.md b/README.md index 88f4485e..c9d3e0cc 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Heylogs is available in multiple formats to fit your workflow: - **[Maven Enforcer rules](docs/usage-enforcer-rules.md)** - Enforce changelog quality in builds - **[CI/CD pipelines](docs/usage-pipelines.md)** - Integrate into CI/CD environments -> Using an AI coding assistant? The [`changelog` skill](skills/changelog/SKILL.md) is an agent-generic guide to running `check`, tuning rules, and configuring `heylogs.properties`. Install it with `npx skills add nbbrd/heylogs` ([details](skills/changelog/README.md)). +> Using an AI coding assistant? The [`changelog` skill](skills/usage/changelog/SKILL.md) is an agent-generic guide to running `check`, tuning rules, and configuring `heylogs.properties`. Install it with `npx skills add nbbrd/heylogs` ([details](skills/usage/changelog/README.md)). ## Features diff --git a/skills/contributing/create-rule/SKILL.md b/skills/contributing/create-rule/SKILL.md new file mode 100644 index 00000000..bc504785 --- /dev/null +++ b/skills/contributing/create-rule/SKILL.md @@ -0,0 +1,186 @@ +--- +name: create-rule +description: > + Contributing to Heylogs — use this skill when asked to add, create, or + implement a new validation rule in the Heylogs project. It covers picking the + right enum, adding the constant, the validate method, the test, the resource + file (if needed), and the documentation row. +metadata: + source: https://github.com/nbbrd/heylogs + category: contributing +--- + +Add a new validation rule to `heylogs-ext-rules` by following these steps in order. Execute all steps without asking for confirmation. + +## Step 0 — Choose the right enum + +Rules live in three categorized enums under `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/`: + +| Enum | File | Category | Typical node type | +|----------------|---------------------|--------------------------------------|------------------------------------------| +| `ReleaseRules` | `ReleaseRules.java` | Release structure and versioning | `Document`, `Heading` | +| `LinkRules` | `LinkRules.java` | Link validation and reference checks | `Link`, `LinkNodeBase`, `BulletListItem` | +| `StyleRules` | `StyleRules.java` | Formatting and style conventions | `Document`, `BulletListItem` | + +Pick the enum whose category best matches the new rule, then use that file for Steps 1–3. + +> **Note**: Rules for core changelog structure (changelog heading, H2 version format, type-of-change headings, date display, version ordering) belong to `GuidingPrinciples` in `heylogs-api` and are not part of this skill. + +## Step 1 — Add the enum constant + +**File**: `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/.java` + +**Action**: Use `replace_string_in_file` to add a new constant BEFORE the final `;` of the enum (after the last existing constant). The rule ID is derived automatically from the constant name (`MY_RULE` → `my-rule`). + +**Template**: +```java +MY_RULE { + @Override + public @Nullable RuleIssue getRuleIssueOrNull(@NonNull Node node, @NonNull RuleContext context) { + return node instanceof BulletListItem ? validateMyRule((BulletListItem) node) : NO_RULE_ISSUE; + } + + @Override + public @NonNull String getRuleName() { + return "My rule"; + } + + // Only add this override when severity is not ERROR: + @Override + public @NonNull RuleSeverity getRuleSeverity() { + return RuleSeverity.WARN; // or OFF + } +}, +``` + +**Node type selection**: + +| Node type | What it covers | Common pattern | +|-------------------------|------------------------------------|------------------------------------------------------| +| `Document` | Whole-document checks | Use `ChangelogHeading.root(doc)` helper | +| `Heading` | Version or type-of-change headings | Check `Version.isVersionLevel(heading)` | +| `BulletListItem` | Changelog list entries | Use `item.getChars().trim().toString()` | +| `Link` / `LinkNodeBase` | Inline links | Parse URL with `Parser.onURL().parse(link.getUrl())` | + +## Step 2 — Add the static validate method + +**File**: Same as Step 1 + +**Action**: Use `replace_string_in_file` to add a `@VisibleForTesting` static method AFTER the last existing `validate*` method (before any inner class or `Batch` class at the end). + +**Template for BulletListItem rules**: +```java +@VisibleForTesting +static @Nullable RuleIssue validateMyRule(@NonNull BulletListItem item) { + String text = item.getChars().trim().toString(); + if (text.isEmpty()) return NO_RULE_ISSUE; + + // Early return NO_RULE_ISSUE for valid cases + if (someCondition) return NO_RULE_ISSUE; + + // Return issue for invalid cases + return RuleIssue + .builder() + .message("Descriptive error message") + .location(item) + .build(); +} +``` + +**Best practices**: +- Always use `.trim()` when checking `BulletListItem` text content +- Use early returns with `NO_RULE_ISSUE` for valid/skip cases +- Use `Character` utility methods for character checks (e.g., `Character.isLetter()`, `Character.isUpperCase()`) +- Use `String.format(ROOT, "...")` for parameterized messages +- For `Document` rules, delegate to a private helper: `ChangelogHeading.root(doc).map(MyEnum::validateHelper).orElse(NO_RULE_ISSUE)` +- If the new method needs `truncate()`, call `RulesUtil.truncate(text, maxLength)` (package-private helper in the same package) + +## Step 3 — Add the test + +**File**: `heylogs-ext-rules/src/test/java/nbbrd/heylogs/ext/rules/Test.java` + +**Action**: Use `replace_string_in_file` to add a test method AFTER the last existing test method (before the closing `}`). + +**Template**: +```java +@Test +public void testValidateMyRule() { + assertThat(validateMyRule(asBulletListItem("- Valid entry"))) + .describedAs("valid case") + .isNull(); + + assertThat(validateMyRule(asBulletListItem("- Invalid entry"))) + .describedAs("invalid case") + .isEqualTo(RuleIssue.builder().message("Expected message").line(1).column(1).build()); +} +``` + +**Tips**: +- Import the static methods at the top: `import static nbbrd.heylogs.ext.rules..*;` +- Use `.isNull()` instead of `.isEqualTo(NO_RULE_ISSUE)` (they are equivalent but `isNull` is cleaner) +- Use `.describedAs("...")` for each assertion to document test intent +- For `Document`-level rules, load a Markdown fixture with `using("/MyTestFile.md")` (see Step 3a) +- Helper methods available: `asBulletListItem(String)` and `asLink(String)` are in `RulesTestHelper`; `asHeading(String)` and `using(String)` are in `tests.heylogs.api.Sample` + +### Step 3a — Add test resource (only for Document-level rules) + +If the test uses `using("/MyTestFile.md")`, the `.md` file must exist in **both**: + +1. `heylogs-api/src/test/resources/MyTestFile.md` — original source of truth +2. `heylogs-ext-rules/src/test/resources/MyTestFile.md` — local copy required for the ext-rules test classpath + +Create both files with the minimal changelog content needed to trigger the rule. + +> **Why two copies?** The `heylogs-api` test-jar only packages `.class` files, not test resources. Each ext module that needs a resource must carry its own copy under `src/test/resources/`. + +## Step 4 — Update documentation + +**File**: `docs/feature-rules.md` + +**Action**: Use `replace_string_in_file` to add a row to the table in **alphabetical order** by rule ID (kebab-case). Use `rules` as the module name. + +**Row format**: +```markdown +| `rules` | [`my-rule`](#my-rule) | My rule | `ERROR` | +``` + +**Severity values**: `ERROR` (default), `WARN`, or `OFF` + +Also add a reference section entry at the bottom of the file: + +```markdown +### `my-rule` +Description of what the rule checks and when it fires. +``` + +## Post-implementation checks + +**Validation**: After all edits, call `get_errors` on both modified Java files to verify no compilation errors. + +**Files to validate**: +- `heylogs-ext-rules/src/main/java/nbbrd/heylogs/ext/rules/.java` +- `heylogs-ext-rules/src/test/java/nbbrd/heylogs/ext/rules/Test.java` + +## Testing (optional) + +```shell +# Fast test of the changed module (from workspace root) +mvn test -pl heylogs-ext-rules -am -Pyolo + +# Run only the new test method +mvn test -pl heylogs-ext-rules -am -Pyolo -Dtest=Test#testValidateMyRule + +# Full build with all checks +mvn clean install +``` + +## Constraints + +- Rule ID must match `ServiceId.KEBAB_CASE` (enforced at runtime by `RuleSupport`). +- Default severity is `ERROR`; only override when `WARN` or `OFF` is needed. +- The `test()` method in each `*Test` class automatically skips rules with `OFF` severity. +- Never reference `internal.*` packages from public API classes. The three rule enums are in the `nbbrd.heylogs.ext.rules` public package and may only access `internal.heylogs.*` directly (not `internal.heylogs.base.*`, `internal.heylogs.git.*`, etc.) — enforced by `ArchitectureTest`. +- `getRuleModuleId()` must return `"rules"` (already inherited from the enum body; do not change it). +- Target Java 8; avoid `String.repeat()`, `var`, `Stream.toList()`, `List.of()`. +- Use `trim()` before checking text content of `BulletListItem`. +- All common imports are already present in each enum; only add new ones if strictly required. diff --git a/skills/contributing/create-versioning/SKILL.md b/skills/contributing/create-versioning/SKILL.md new file mode 100644 index 00000000..fd66b0b7 --- /dev/null +++ b/skills/contributing/create-versioning/SKILL.md @@ -0,0 +1,479 @@ +--- +name: create-versioning +description: > + Contributing to Heylogs — use this skill when asked to add, create, or + implement a new versioning scheme in the Heylogs project. It covers creating + the extension module, the public SPI class, the internal parser/comparator, + the tests, registering the module in all POMs, and updating the documentation. +metadata: + source: https://github.com/nbbrd/heylogs + category: contributing +--- + +Add a new versioning scheme to Heylogs by creating a `heylogs-ext-` module. Execute all steps without asking for confirmation. + +## Prerequisites — Gather context + +Before starting, read these reference files to understand the patterns: + +1. **SPI interface**: `heylogs-api/src/main/java/nbbrd/heylogs/spi/Versioning.java` +2. **Support builder**: `heylogs-api/src/main/java/nbbrd/heylogs/spi/VersioningSupport.java` +3. **Test fixture**: `heylogs-api/src/test/java/tests/heylogs/spi/VersioningAssert.java` +4. **Reference implementation (no-arg)**: `heylogs-ext-semver/src/main/java/nbbrd/heylogs/ext/semver/SemVer.java` +5. **Reference implementation (with-arg)**: `heylogs-ext-calver/src/main/java/nbbrd/heylogs/ext/calver/CalVer.java` +6. **Reference test (no-arg)**: `heylogs-ext-semver/src/test/java/nbbrd/heylogs/ext/semver/SemVerTest.java` +7. **Reference test (with-arg)**: `heylogs-ext-calver/src/test/java/nbbrd/heylogs/ext/calver/CalVerTest.java` +8. **Reference POM (no external dep)**: `heylogs-ext-calver/pom.xml` +9. **Reference POM (external dep)**: `heylogs-ext-semver/pom.xml` + +## Step 0 — Determine the versioning characteristics + +Decide the following before writing code: + +| Decision | Option A (simpler) | Option B (complex) | +|----------------------|-------------------------------|---------------------------------------------| +| **Argument** | No argument (`withoutArg`) | Requires argument (`compilingArg` / custom) | +| **External library** | None (pure regex / algorithm) | External dependency (e.g., `semver4j`) | +| **Internal classes** | Single internal helper class | Multiple internal classes | + +This determines which reference files to follow most closely: +- No arg + no external dep → follow `heylogs-ext-pep440` pattern +- No arg + external dep → follow `heylogs-ext-semver` pattern +- With arg → follow `heylogs-ext-calver` pattern + +## Step 1 — Create the module POM + +**File**: `heylogs-ext-/pom.xml` + +**Action**: Create the file using the template below. + +**Template** (no external dependency): +```xml + + + 4.0.0 + + + com.github.nbbrd.heylogs + heylogs-parent + 0.18.2-SNAPSHOT + + + heylogs-ext- + jar + + heylogs-ext- + Keep-a-changelog tool - extension + https://github.com/nbbrd/heylogs + + + + + org.jspecify + jspecify + provided + + + org.projectlombok + lombok + provided + + + com.github.nbbrd.java-design-util + java-design-processor + provided + + + com.github.nbbrd.java-service-util + java-service-processor + provided + + + + + heylogs-api + ${project.groupId} + ${project.version} + + + + + + heylogs-api + ${project.groupId} + ${project.version} + tests + test-jar + test + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + +``` + +> **Note**: Read the current `` from the root `pom.xml` parent tag — do not hardcode the version shown above. + +> **External dependency**: If an external library is needed, add it in the `` section and also declare its version in the root POM `` section. + +## Step 2 — Create the public SPI class + +**File**: `heylogs-ext-/src/main/java/nbbrd/heylogs/ext//.java` + +**Action**: Create the file. The class must: +1. Be `public final` and implement `Versioning` +2. Be annotated with `@DirectImpl` and `@ServiceProvider` +3. Use `@lombok.experimental.Delegate` to delegate to a `VersioningSupport` instance +4. The `id` must be in `KEBAB_CASE` and match the module name convention + +**Template (no-arg versioning)**: +```java +package nbbrd.heylogs.ext.; + +import internal.heylogs.ext..; +import nbbrd.design.DirectImpl; +import nbbrd.heylogs.spi.Versioning; +import nbbrd.heylogs.spi.VersioningSupport; +import nbbrd.service.ServiceProvider; + +import static nbbrd.heylogs.spi.VersioningSupport.withoutArg; + +@DirectImpl +@ServiceProvider +public final class implements Versioning { + + @lombok.experimental.Delegate + private final Versioning delegate = VersioningSupport + .builder() + .id("") + .name("") + .urlOf("") + .moduleId("") + .validator(arg -> arg == null ? null : " does not take any arguments") + .predicate(withoutArg(::isValid)) + .comparator(withoutArg(::compare)) + .familyMapper(withoutArg(::toFamily)) + .build(); +} +``` + +**Template (with-arg versioning)** — see `CalVer.java` for reference: +```java +package nbbrd.heylogs.ext.; + +import internal.heylogs.ext..; +import nbbrd.design.DirectImpl; +import nbbrd.heylogs.spi.Validator; +import nbbrd.heylogs.spi.Versioning; +import nbbrd.heylogs.spi.VersioningSupport; +import nbbrd.service.ServiceProvider; + +import java.util.Objects; + +import static nbbrd.heylogs.spi.VersioningSupport.compilingArg; + +@DirectImpl +@ServiceProvider +public final class implements Versioning { + + @lombok.experimental.Delegate + private final Versioning delegate = VersioningSupport + .builder() + .id("") + .name("") + .urlOf("") + .moduleId("") + .validator(Validator.of(::parse)) + .predicate(compilingArg(::parse, ::isValidVersion)) + .comparator(arg -> { + format = .parse(Objects.requireNonNull(arg)); + return (a, b) -> compare(format, a, b); + }) + .familyMapper(arg -> { + format = .parse(Objects.requireNonNull(arg)); + return version -> toFamily(format, version); + }) + .build(); + + // Add private static helper methods for compare and toFamily +} +``` + +### `VersioningSupport` builder fields reference + +| Field | Type | Required | Description | +|----------------|----------------------------------------------------|----------|---------------------------------------------------------------------| +| `id` | `String` | Yes | Kebab-case identifier (e.g., `"pep440"`, `"semver"`) | +| `name` | `String` | Yes | Human-readable name | +| `url`/`urlOf` | `URL`/`CharSequence` | Yes | Link to the versioning spec | +| `moduleId` | `String` | Yes | Usually same as `id` | +| `validator` | `Function` | Yes | Returns null if arg valid, error message otherwise | +| `predicate` | `Function>` | Yes | Tests if a string is a valid version | +| `comparator` | `Function>` | No | Compares two versions; return 0 for incomparable | +| `familyMapper` | `Function>` | No | Maps version to family key (e.g., `"MAJOR.MINOR"`); null if invalid | + +### Helper methods in `VersioningSupport` + +| Method | Use when | Wraps argument handling | +|-----------------------------|--------------------------------------------|-------------------------------------| +| `withoutArg(Predicate)` | No argument needed | Returns predicate when arg is null | +| `withoutArg(Comparator)` | No argument needed | Returns comparator when arg is null | +| `withoutArg(Function)` | No argument needed | Returns mapper when arg is null | +| `compilingArg(factory, bi)` | Argument is compiled into an engine object | Compiles arg, then delegates | + +## Step 3 — Create the internal helper class + +**File**: `heylogs-ext-/src/main/java/internal/heylogs/ext//.java` + +**Action**: Create the file with the parsing, validation, comparison, and family-mapping logic. + +**Required static methods for no-arg versioning**: + +```java +package internal.heylogs.ext.; + +import lombok.NonNull; +import org.jspecify.annotations.Nullable; + +public final class { + + private () { + throw new UnsupportedOperationException(); + } + + /** Returns true if text is a valid version string. */ + public static boolean isValid(@NonNull CharSequence text) { ... } + + /** + * Compares two version strings. + * Returns 0 if either is invalid (incomparable). + */ + public static int compare(@NonNull CharSequence a, @NonNull CharSequence b) { ... } + + /** + * Returns the family key (typically "MAJOR.MINOR"), or null if invalid. + */ + public static @Nullable String toFamily(@NonNull CharSequence version) { ... } +} +``` + +**Implementation guidelines**: +- Use `java.util.regex.Pattern` for validation (compile as a `private static final` field) +- Return `0` from `compare` when either input is invalid (incomparable) +- Return `null` from `toFamily` when input is invalid +- Family is typically `"MAJOR.MINOR"` (first two numeric components) +- Target Java 8: no `var`, no `String.repeat()`, no `Stream.toList()`, no `List.of()` +- Use `Locale.ROOT` for any case conversions + +## Step 4 — Create the test class + +**File**: `heylogs-ext-/src/test/java/nbbrd/heylogs/ext//Test.java` + +**Action**: Create the file with four test methods. + +**Template (no-arg)**: +```java +package nbbrd.heylogs.ext.; + +import nbbrd.heylogs.spi.Versioning; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static tests.heylogs.spi.VersioningAssert.assertVersioningCompliance; + +class Test { + + @Test + public void testCompliance() { + assertVersioningCompliance(new ()); + } + + @Test + void testIsValidVersion() { + Versioning x = new (); + + // Verify that passing an argument is rejected + assertThatIllegalArgumentException() + .isThrownBy(() -> x.getVersioningPredicateOrNull("")) + .withMessage(" does not take any arguments"); + + // Verify valid and invalid versions + assertThat(x.getVersioningPredicateOrNull(null)) + .accepts(/* valid version examples */) + .rejects(/* invalid version examples */); + } + + @Test + void testComparator() { + Versioning x = new (); + + assertThatIllegalArgumentException() + .isThrownBy(() -> x.getVersioningComparatorOrNull("")) + .withMessage(" does not take any arguments"); + + assertThat(x.getVersioningComparatorOrNull(null)) + .isNotNull() + .satisfies(comparator -> { + // basic ordering + assertThat(comparator.compare("2.0.0", "1.0.0")).isPositive(); + assertThat(comparator.compare("1.0.0", "2.0.0")).isNegative(); + assertThat(comparator.compare("1.0.0", "1.0.0")).isZero(); + + // scheme-specific ordering tests + + // incomparable + assertThat(comparator.compare("invalid", "1.0.0")).isZero(); + }); + } + + @Test + void testFamilyMapper() { + Versioning x = new (); + + assertThatIllegalArgumentException() + .isThrownBy(() -> x.getVersioningFamilyMapperOrNull("")) + .withMessage(" does not take any arguments"); + + assertThat(x.getVersioningFamilyMapperOrNull(null)) + .isNotNull() + .satisfies(mapper -> { + assertThat(mapper.apply("2.4.0")).isEqualTo("2.4"); + assertThat(mapper.apply("2.4.1")).isEqualTo("2.4"); + assertThat(mapper.apply("invalid")).isNull(); + }); + } +} +``` + +**Template (with-arg)** — see `CalVerTest.java` for reference. Key differences: +- Passing `null` arg throws `IllegalArgumentException` (instead of being accepted) +- Tests pass a format string argument to each method + +**Test guidelines**: +- Always include a `testCompliance()` method calling `assertVersioningCompliance` +- Test at least 3 valid and 3 invalid versions in `testIsValidVersion` +- Test basic ordering, scheme-specific ordering, and incomparable cases in `testComparator` +- Test family mapping for multiple versions and the invalid case in `testFamilyMapper` +- Use AssertJ fluent assertions + +## Step 5 — Register the module in POMs + +Use `replace_string_in_file` for each of the following files. Insert the new module adjacent to existing versioning extensions (after `heylogs-ext-calver` or `heylogs-ext-semver`). + +### 5a — Root POM (`pom.xml`) + +Add `heylogs-ext-` in the `` section: +```xml +heylogs-ext- +``` + +### 5b — BOM (`heylogs-bom/pom.xml`) + +Add a `` entry in ``: +```xml + + heylogs-ext- + ${project.groupId} + ${project.version} + +``` + +### 5c — Consumer modules (runtime dependency) + +Add a `runtime` dependency in each of: +- `heylogs-cli/pom.xml` +- `heylogs-maven-plugin/pom.xml` +- `heylogs-enforcer-rules/pom.xml` + +```xml + + heylogs-ext- + ${project.groupId} + ${project.version} + runtime + +``` + +### 5d — Architecture tests (`heylogs-architecture-tests/pom.xml`) + +Add a `test` dependency: +```xml + + com.github.nbbrd.heylogs + heylogs-ext- + ${project.version} + test + +``` + +## Step 6 — Update documentation + +### Versioning table (`docs/feature-versioning.md`) + +Add a row to the versioning schemes table in **alphabetical order** by ID: + +```markdown +| `` | []() | `` | - | +``` + +The argument column should be `-` for no-arg versionings, or a description for with-arg. + +Also add a usage example: +```markdown +- `$ heylogs check -v ` +``` + +## Post-implementation checks + +**Validation**: After all edits, call `get_errors` on these files: +- `heylogs-ext-/src/main/java/nbbrd/heylogs/ext//.java` +- `heylogs-ext-/src/main/java/internal/heylogs/ext//.java` +- `heylogs-ext-/src/test/java/nbbrd/heylogs/ext//Test.java` + +## Testing + +```shell +# Fast test of the new module +mvn test -pl heylogs-ext- -am -Pyolo + +# Full build with all checks +mvn clean install +``` + +Expect: +- 4 tests run (compliance + isValidVersion + comparator + familyMapper) +- 0 failures, 0 errors +- BUILD SUCCESS + +## Constraints + +- **ID** must match `ServiceId.KEBAB_CASE` (lowercase letters, digits, hyphens; e.g., `pep440`, `semver`, `calver`). +- **Java 8 target**: no `var`, `String.repeat()`, `Stream.toList()`, `List.of()`, `Map.of()`. +- **No BOM in source files**: if creating files via PowerShell, use `[System.IO.File]::WriteAllText(path, content, (New-Object System.Text.UTF8Encoding $false))` to avoid UTF-8 BOM bytes. +- **Internal packages**: parsing/comparison logic goes in `internal.heylogs.ext.` — never expose internal types in the public API. +- **Annotations**: `@DirectImpl` + `@ServiceProvider` on the public class; `@lombok.experimental.Delegate` on the delegate field. +- **Backward compatibility**: the `Versioning` SPI contract requires `getVersioningPredicateOrNull` to throw `IllegalArgumentException` when the validator rejects the arg. +- **Incomparable versions**: `compare()` must return `0` when either input is not a valid version for the scheme. +- **Family mapping**: `toFamily()` must return `null` when the input is not a valid version. +- **No additional features**: do not add features beyond what the `Versioning` SPI defines. + +## Quick reference — Existing versioning modules + +| Module | ID | Arg? | External dep? | Internal class(es) | +|----------------------|----------|------|------------------|-----------------------------------| +| `heylogs-ext-semver` | `semver` | No | `semver4j` | (none — uses library directly) | +| `heylogs-ext-calver` | `calver` | Yes | No | `CalVerFormat`, `CalVerTag`, etc. | +| `heylogs-ext-pep440` | `pep440` | No | No | `Pep440Version` | +| `heylogs-ext-maven` | `maven` | No | `maven-artifact` | (none — uses library directly) | + diff --git a/skills/changelog/README.md b/skills/usage/changelog/README.md similarity index 95% rename from skills/changelog/README.md rename to skills/usage/changelog/README.md index 9fc75f21..1180d1fc 100644 --- a/skills/changelog/README.md +++ b/skills/usage/changelog/README.md @@ -42,7 +42,7 @@ npx skills add nbbrd/heylogs -g -a claude-code -y ## Alternative: manual installation If you prefer not to use `npx skills`, download the skill folder -[`skills/changelog/`](.) and register it as a local skill folder per your agent's +[`skills/usage/changelog/`](.) and register it as a local skill folder per your agent's documentation (no Node.js required). ## Update diff --git a/skills/changelog/SKILL.md b/skills/usage/changelog/SKILL.md similarity index 96% rename from skills/changelog/SKILL.md rename to skills/usage/changelog/SKILL.md index a6a78058..a8faf85b 100644 --- a/skills/changelog/SKILL.md +++ b/skills/usage/changelog/SKILL.md @@ -13,6 +13,7 @@ description: > metadata: source: https://github.com/nbbrd/heylogs agent-generic: "true" + category: usage --- # Working with Heylogs changelogs @@ -21,8 +22,8 @@ Agent-generic guide (works with any coding assistant) for using the [Heylogs](https://github.com/nbbrd/heylogs) CLI to validate and maintain [Keep a Changelog](https://keepachangelog.com)–formatted `CHANGELOG.md` files. -For project architecture and contribution conventions, see [`AGENTS.md`](../../AGENTS.md). -Full feature docs live under [`docs/`](../../docs/). +For project architecture and contribution conventions, see [`AGENTS.md`](../../../AGENTS.md). +Full feature docs live under [`docs/`](../../../docs/). ## What Heylogs does @@ -166,5 +167,5 @@ fully explicit, reproducible configuration. --- -See [`docs/`](../../docs/) for per-feature documentation and -[`docs/feature-matrix.md`](../../docs/feature-matrix.md) for support by usage mode. +See [`docs/`](../../../docs/) for per-feature documentation and +[`docs/feature-matrix.md`](../../../docs/feature-matrix.md) for support by usage mode. diff --git a/skills/changelog/references/commands.md b/skills/usage/changelog/references/commands.md similarity index 100% rename from skills/changelog/references/commands.md rename to skills/usage/changelog/references/commands.md diff --git a/skills/changelog/references/config-file.md b/skills/usage/changelog/references/config-file.md similarity index 100% rename from skills/changelog/references/config-file.md rename to skills/usage/changelog/references/config-file.md diff --git a/skills/changelog/references/rules.md b/skills/usage/changelog/references/rules.md similarity index 100% rename from skills/changelog/references/rules.md rename to skills/usage/changelog/references/rules.md diff --git a/skills/changelog/references/running-cli.md b/skills/usage/changelog/references/running-cli.md similarity index 100% rename from skills/changelog/references/running-cli.md rename to skills/usage/changelog/references/running-cli.md