diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..991c2a9 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,24 @@ +name: Commit lint + +# Every commit in a PR to main must be a Conventional Commit, because Release Please reads those +# messages to compute the next version and the changelog. Enforced in CI (not just a local hook) so +# it holds regardless of a contributor's toolchain and cannot be skipped with --no-verify. +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + # Full history so the action can see every commit in the PR range, not just the tip. + fetch-depth: 0 + + - uses: wagoid/commitlint-github-action@v6 + with: + configFile: commitlint.config.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5adcc2b..f981677 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,50 @@ name: Release -# Publishing to nuget.org cannot be undone — a version can be unlisted, never replaced. -# Hence: a tag, a reviewed environment, and a test run before anything is pushed. +# Release Please drives versioning from Conventional Commits. On each push to main it opens/updates +# a "release PR" that bumps the version and CHANGELOG from the commits since the last release; the +# level of the bump comes from the commit types (fix: -> patch, feat: -> minor, ! / BREAKING CHANGE +# -> major). Merging that PR is the release: Release Please then creates the tag + GitHub release, +# and the `publish` job below packs and pushes to nuget.org. +# +# publish is a *downstream job in the same run* as the release creation (gated on release_created), +# NOT a separate workflow triggered by the new tag. That matters: a tag created with the default +# GITHUB_TOKEN would not trigger a tag-listening workflow, so wiring publish as `needs` is what keeps +# it firing without a PAT. (Trade-off of the default token: the release PR it opens does not trigger +# the Build workflow's pull_request runs. Switch to a PAT only if you want CI on the release PR.) on: push: - tags: ['v*'] + branches: [main] + +# Publishing to nuget.org cannot be undone — a version can be unlisted, never replaced — so releases +# are serialized and a publish under way is never cancelled. +concurrency: + group: release + cancel-in-progress: false permissions: contents: read jobs: + release-please: + runs-on: ubuntu-latest + permissions: + contents: write # create the release tag and GitHub release + pull-requests: write # open and update the release PR + outputs: + release_created: ${{ steps.rp.outputs.release_created }} + tag_name: ${{ steps.rp.outputs.tag_name }} + version: ${{ steps.rp.outputs.version }} + steps: + # Reads release-please-config.json + .release-please-manifest.json from the repo root. + - uses: googleapis/release-please-action@v4 + id: rp + publish: + needs: release-please + # Only the run that actually cut a release (the one where the release PR was merged) publishes. + if: needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-latest - # Add required reviewers to the `release` environment in repo settings, so cutting a tag - # proposes a publish rather than performing one. + # Add required reviewers to the `release` environment in repo settings to gate the push. environment: release permissions: contents: read @@ -22,7 +53,10 @@ jobs: id-token: write steps: + # Build the exact commit Release Please tagged. - uses: actions/checkout@v7 + with: + ref: ${{ needs.release-please.outputs.tag_name }} - name: Set up .NET uses: actions/setup-dotnet@v6 @@ -30,22 +64,18 @@ jobs: dotnet-version: 8.0.x global-json-file: global.json - - name: Version from the tag - # is hard-coded to 1.0.0 in the csproj. Without this, the second tag would - # push 1.0.0 again and nuget.org would reject it as a duplicate. v1.2.3 -> 1.2.3. - run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" - - name: Test run: dotnet test RuleCraft.slnx --configuration Release - name: Pack - # ContinuousIntegrationBuild makes the build deterministic and gives SourceLink the - # normalized paths it needs for symbols to resolve to the source of this commit. + # Release Please already bumped in the csproj, but pass it explicitly so the package + # version cannot drift from the tag. ContinuousIntegrationBuild makes the build deterministic + # and gives SourceLink the normalized paths it needs for symbols to resolve to this commit. run: > dotnet pack src/RuleCraft/RuleCraft.csproj --configuration Release --output artifacts - -p:Version=${{ env.VERSION }} + -p:Version=${{ needs.release-please.outputs.version }} -p:ContinuousIntegrationBuild=true # Trusted publishing: no long-lived secret, just a token good for one key for one hour. diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index f52662a..b47919d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,19 @@ # Changelog -All notable changes to RuleCraft are recorded here. The format follows -[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to follow -[Semantic Versioning](https://semver.org/spec/v2.0.0.html) — for a library, the public surface -that governs SemVer is the API *and* the rule-persistence format on disk. +Maintained automatically by [Release Please](https://github.com/googleapis/release-please) from +[Conventional Commit](https://www.conventionalcommits.org/) messages — new entries are prepended +above on each release; do not edit by hand. -## [Unreleased] +## [1.0.0](https://github.com/mkasperczyk90/RuleCraft/releases/tag/v1.0.0) (2026-07-17) -## [1.0.0] — unreleased -First public release. The engine, its three rule kinds and the persistence format are considered -stable from this version. +### Features -### Added -- **Runtime rule engine** for one contract/context pair: describe a rule in natural language, an - LLM implements it, it is verified and human-approved, then hot-loaded into the running process - with no redeploy. -- **Three rule kinds**, competing purely by priority: interpreted **JSON-DSL** rules (a real - sandbox), **compiled C#** rules (Roslyn, in-memory, into a collectible `AssemblyLoadContext`), - and **static** host-code rules. -- **Security gate for compiled rules**: a minimal whitelisted reference set plus a semantic-model - analyzer, resolving most-specific-first (member → type → namespace). -- **Approval workflow**: rules are parked as `PendingApproval` and nothing runs until approved; - `AutoApprove` defaults to off. Reject, disable, enable and quarantine transitions included. -- **Durable, crash-safe store**: source is the single source of truth, written atomically - alongside audit metadata (spec, status, priority, SHA-256, contract/context fingerprint, model - id, approver, full validation report). -- **DI integration** in-box: `services.AddRuleCraft(…)`. -- **Vendor-neutral generation** through `Microsoft.Extensions.AI.IChatClient` — no model id is - hard-coded. -- Ships as a single assembly, `RuleCraft.dll`, with XML docs and a symbol package. - -[Unreleased]: https://github.com/mkasperczyk90/RuleCraft/compare/v1.0.0...HEAD -[1.0.0]: https://github.com/mkasperczyk90/RuleCraft/releases/tag/v1.0.0 +* Runtime rule engine for one contract/context pair: describe a rule in natural language, an LLM implements it, it is verified and human-approved, then hot-loaded into the running process with no redeploy. +* Three rule kinds, competing purely by priority: interpreted JSON-DSL rules (a real sandbox), compiled C# rules (Roslyn, in-memory, into a collectible `AssemblyLoadContext`), and static host-code rules. +* Security gate for compiled rules: a minimal whitelisted reference set plus a semantic-model analyzer, resolving most-specific-first (member → type → namespace). +* Approval workflow: rules are parked as `PendingApproval` and nothing runs until approved; `AutoApprove` defaults to off. Reject, disable, enable and quarantine transitions included. +* Durable, crash-safe store: source is the single source of truth, written atomically alongside audit metadata (spec, status, priority, SHA-256, contract/context fingerprint, model id, approver, full validation report). +* DI integration in-box: `services.AddRuleCraft(…)`. +* Vendor-neutral generation through `Microsoft.Extensions.AI.IChatClient` — no model id is hard-coded. +* Ships as a single assembly, `RuleCraft.dll`, with XML docs and a symbol package. diff --git a/commitlint.config.mjs b/commitlint.config.mjs new file mode 100644 index 0000000..efd2db5 --- /dev/null +++ b/commitlint.config.mjs @@ -0,0 +1,10 @@ +// Conventional Commits, enforced in CI by .github/workflows/commitlint.yml. The commit types drive +// Release Please: fix -> patch, feat -> minor, `!` / BREAKING CHANGE -> major. +export default { + extends: ['@commitlint/config-conventional'], + rules: { + // Commit bodies here carry design rationale in prose; the 100-char wrap rule fights that. The + // subject (header-max-length) is still capped by config-conventional. + 'body-max-line-length': [0, 'always'], + }, +}; diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..e93bd5c --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "simple", + "package-name": "RuleCraft", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false, + "extra-files": [ + "src/RuleCraft/RuleCraft.csproj" + ] + } + } +} diff --git a/src/RuleCraft/RuleCraft.csproj b/src/RuleCraft/RuleCraft.csproj index d7fdfac..c1d2e20 100644 --- a/src/RuleCraft/RuleCraft.csproj +++ b/src/RuleCraft/RuleCraft.csproj @@ -7,7 +7,7 @@ RuleCraft - 1.0.0 + 1.0.0 mkasperczyk90@gmail.com Runtime rule engine whose implementations are generated by an LLM, verified, human-approved and hot-loaded into the running application. rules;rule-engine;llm;ai;roslyn;codegen;business-rules;hot-reload