Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -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
56 changes: 43 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,30 +53,29 @@ 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
with:
dotnet-version: 8.0.x
global-json-file: global.json

- name: Version from the tag
# <Version> 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 <Version> 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.
Expand Down
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "1.0.0"
}
42 changes: 13 additions & 29 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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<TContract, TContext>(…)`.
- **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<TContract, TContext>(…)`.
* 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.
10 changes: 10 additions & 0 deletions commitlint.config.mjs
Original file line number Diff line number Diff line change
@@ -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'],
},
};
14 changes: 14 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
}
2 changes: 1 addition & 1 deletion src/RuleCraft/RuleCraft.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<PropertyGroup>
<PackageId>RuleCraft</PackageId>
<Version>1.0.0</Version>
<Version>1.0.0</Version> <!-- x-release-please-version -->
<Authors>mkasperczyk90@gmail.com</Authors>
<Description>Runtime rule engine whose implementations are generated by an LLM, verified, human-approved and hot-loaded into the running application.</Description>
<PackageTags>rules;rule-engine;llm;ai;roslyn;codegen;business-rules;hot-reload</PackageTags>
Expand Down