diff --git a/.dockerignore b/.dockerignore index 61516750..652aedad 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,3 @@ -# The image installs the built wheel; nothing else belongs in the context. * -!dist/ +!rust/ +!rust/** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 34bf8fe4..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: CI - -# Run the test suite only when changes land on main (a merged PR or a direct -# push). Tests intentionally do NOT run on pull requests — see ADR-027 for the -# trigger policy and its post-merge-feedback tradeoff. workflow_dispatch allows -# running the full battery grid manually (e.g. to check a branch before merge). -on: - push: - branches: [main] - workflow_dispatch: - -# Serialize runs on the same ref. Don't cancel an in-progress run: every merge -# to main should be fully tested, since these runs are what gate releases. -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: false - -jobs: - tests: - uses: ./.github/workflows/tests.yml diff --git a/.github/workflows/native-publish.yml b/.github/workflows/native-publish.yml new file mode 100644 index 00000000..714a10f0 --- /dev/null +++ b/.github/workflows/native-publish.yml @@ -0,0 +1,79 @@ +name: Publish native release + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + binaries: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + archive: tar.gz + - os: macos-14 + target: aarch64-apple-darwin + archive: tar.gz + - os: windows-latest + target: x86_64-pc-windows-msvc + archive: zip + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v5 + - name: Install pinned Rust toolchain + working-directory: rust + run: rustup show + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + - name: Build native binaries + shell: bash + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="${RELEASE_TAG#v}" + (cd rust && DECIDED_RS_VERSION="$VERSION" cargo build --release --locked -p decided -p decided-mcp) + - name: Package Unix binaries + if: matrix.archive == 'tar.gz' + shell: bash + env: + TARGET: ${{ matrix.target }} + run: | + mkdir -p dist + tar -C rust/target/release -czf "dist/asdecided-${TARGET}.tar.gz" decided decided-mcp + - name: Package Windows binaries + if: matrix.archive == 'zip' + shell: pwsh + run: | + New-Item -ItemType Directory -Force dist | Out-Null + Compress-Archive -Path rust/target/release/decided.exe,rust/target/release/decided-mcp.exe -DestinationPath dist/asdecided-${{ matrix.target }}.zip + - name: Attach binaries to GitHub Release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.event.release.tag_name }}" dist/* --clobber + + image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + IMAGE: ghcr.io/itsthelore/rac + TAG: ${{ github.event.release.tag_name }} + steps: + - uses: actions/checkout@v5 + - name: Build and smoke-test image + run: | + docker build --build-arg DECIDED_VERSION="$TAG" -t "$IMAGE:$TAG" -t "$IMAGE:latest" . + docker run --rm "$IMAGE:$TAG" --version + - name: Publish image + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + docker push "$IMAGE:$TAG" + docker push "$IMAGE:latest" diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 1f3f10b3..c26e71ce 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -1,8 +1,6 @@ name: PR Checks -# Pre-merge distribution checks. Rust behavior and the bounded retirement -# oracle are owned by rust-spike.yml under ADR-120. This workflow verifies only -# the maintained Python packaging/SDK bridge and the three shipped Actions. +# Pre-merge dogfood checks for the three native Actions. on: pull_request: @@ -14,14 +12,11 @@ concurrency: cancel-in-progress: true jobs: - bridge: - uses: ./.github/workflows/tests.yml - # Watchkeeper dogfood (v0.12.3): every pull request here gets a product # knowledge review from the local action in source mode — which is also # the live end-to-end test of action.yml itself. watchkeeper: - name: watchkeeper (dogfood, source install) + name: watchkeeper (dogfood, native) runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -31,7 +26,6 @@ jobs: - uses: ./ with: path: decisions - install-from: source # Validate dogfood (v0.17.2): every pull request runs the local validate # action in source mode — the live end-to-end test of validate-action/action.yml @@ -39,7 +33,7 @@ jobs: # Code Scanning for same-repo PRs; it is skipped on forks, whose token cannot be # granted security-events: write, so the check never fails on a missing scope. validate: - name: validate (dogfood, source install) + name: validate (dogfood, native) runs-on: ubuntu-latest permissions: contents: read @@ -52,7 +46,6 @@ jobs: - uses: ./validate-action with: path: decisions - install-from: source upload-sarif: ${{ !github.event.pull_request.head.repo.fork }} # PR-gate dogfood (v0.21.14): every pull request runs the local PR-gate action @@ -64,7 +57,7 @@ jobs: # never collide). Skipped on forks, whose token cannot be granted # security-events: write. gate: - name: pr-gate (dogfood, source install) + name: pr-gate (dogfood, native) runs-on: ubuntu-latest permissions: contents: read @@ -77,5 +70,4 @@ jobs: - uses: ./pr-gate-action with: path: decisions - install-from: source upload-sarif: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/.github/workflows/publish-shim.yml b/.github/workflows/publish-shim.yml deleted file mode 100644 index e50f193e..00000000 --- a/.github/workflows/publish-shim.yml +++ /dev/null @@ -1,40 +0,0 @@ -# One-time Trusted Publishing of the requirements-as-code -> rac-core redirect -# shim (ADR-092). Manual, single-shot: run it once, AFTER rac-core's first -# release exists on PyPI, then delete the trusted publisher (see -# packaging/requirements-as-code-shim/PUBLISHING.md). -# -# Requires a Trusted Publisher on the **requirements-as-code** PyPI project: -# repo itsthelore/rac-core · workflow publish-shim.yml · environment pypi -# (same shape as the rac-core publisher, different project + workflow). -name: Publish requirements-as-code shim - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - publish-shim: - runs-on: ubuntu-latest - permissions: - # Mandatory for Trusted Publishing (OIDC); no PyPI token is used. - id-token: write - environment: - name: pypi - steps: - - uses: actions/checkout@v5 - - - uses: actions/setup-python@v6 - with: - python-version: "3.x" - - - name: Build the redirect shim (metadata-only) - run: | - python -m pip install --quiet build - python -m build packaging/requirements-as-code-shim --outdir dist - - - name: Publish to PyPI (Trusted Publishing) - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: dist/ diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 0991cd6a..00000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,221 +0,0 @@ -# This workflow will upload a Python Package to PyPI when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package - -on: - release: - types: [published] - -permissions: - contents: read - -jobs: - test: - # Gate the release on the full test suite (reusable workflow). Tests must - # pass before anything is built or published. - uses: ./.github/workflows/tests.yml - - verify-release: - # Fail-closed SemVer gate (REQ-Release-Versioning REQ-007, ADR-111): the - # release tag must be a well-formed vX.Y.Z identifier (a CalVer YYYY.MM.N tag - # is rejected) and have a matching CHANGELOG.md entry, or nothing is built or - # published. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - name: Install package - run: python -m pip install -e . - - name: Verify release version and changelog - run: python -m asdecided.release "${{ github.event.release.tag_name }}" - - release-build: - needs: - - test - - verify-release - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - with: - # setuptools-scm derives the version from git tags, so we need the - # full history + tags (the default shallow checkout omits them). - fetch-depth: 0 - - - uses: actions/setup-python@v6 - with: - python-version: "3.x" - - - name: Build release distributions - run: | - # NOTE: put your own distribution build steps here. - python -m pip install build - python -m build - - - name: Upload distributions - uses: actions/upload-artifact@v6 - with: - name: release-dists - path: dist/ - - # Per-platform wheels bundling the native (Rust) binaries (native-engine - # cutover, ADR-116). The pure-Python sdist/wheel above stays the universal - # fallback; these accelerate the covered surface on the three supported - # platforms. A platform without a native wheel installs the sdist and runs the - # Python engine — install never depends on a binary. (Cross-platform wheel - # tagging can need iteration in CI; the sdist path is unaffected either way.) - native-wheels: - needs: - - test - - verify-release - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - plat: manylinux2014_x86_64 - - os: macos-14 # Apple Silicon - plat: macosx_11_0_arm64 - - os: windows-latest - plat: win_amd64 - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 # setuptools-scm needs tags - - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - name: Install rust toolchain (pinned by rust/rust-toolchain.toml) - working-directory: rust - run: rustup show - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - - name: Build native binaries with the wheel version compiled in - shell: bash - run: | - python -m pip install --quiet build wheel setuptools-scm - VER="$(python -m setuptools_scm)" - echo "native binaries version: $VER" - ( cd rust && DECIDED_RS_VERSION="$VER" cargo build --release --locked -p decided -p decided-mcp ) - - name: Bundle binaries into asdecided.bin - shell: bash - run: | - if [ "${{ runner.os }}" = "Windows" ]; then - cp rust/target/release/decided.exe rust/target/release/decided-mcp.exe src/asdecided/bin/ - else - cp rust/target/release/decided rust/target/release/decided-mcp src/asdecided/bin/ - fi - - name: Build and platform-tag the wheel - shell: bash - run: | - python -m build --wheel - # The package bundles binaries but ships no C extension, so setuptools - # tags the wheel py3-none-any; retag it to this platform so pip picks - # the wheel carrying the right binary set (iterate here if needed). - python -m wheel tags --remove \ - --python-tag py3 --abi-tag none --platform-tag "${{ matrix.plat }}" \ - dist/*.whl - - name: Smoke test — the bundled native path runs - shell: bash - run: | - python -m pip install dist/*.whl - decided --version - - name: Upload native wheel - uses: actions/upload-artifact@v6 - with: - name: native-wheel-${{ matrix.plat }} - path: dist/*.whl - - pypi-publish: - runs-on: ubuntu-latest - needs: - - release-build - - native-wheels - permissions: - # IMPORTANT: this permission is mandatory for trusted publishing - id-token: write - - # Dedicated environments with protections for publishing are strongly recommended. - # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules - environment: - name: pypi - # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: - # url: https://pypi.org/p/YOURPROJECT - # - # ALTERNATIVE: if your GitHub Release name is the PyPI project version string - # ALTERNATIVE: exactly, uncomment the following line instead: - # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }} - - steps: - - name: Retrieve all distributions (sdist + native wheels) - uses: actions/download-artifact@v7 - with: - # release-dists (pure-Python sdist/wheel) + native-wheel-; each - # artifact drops its files into dist/, so pip/PyPI sees one set with the - # universal fallback and the three platform wheels. - pattern: "{release-dists,native-wheel-*}" - merge-multiple: true - path: dist/ - - - name: Publish release distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: dist/ - - image-publish: - # Official container image, published in lockstep with the PyPI release - # (decisions/roadmaps/future/oci-image.md): same trigger, same wheel, same - # SemVer tag. Runs after pypi-publish so the image never exists for a - # version PyPI does not have. - runs-on: ubuntu-latest - needs: - - pypi-publish - permissions: - contents: read - packages: write - env: - IMAGE: ghcr.io/itsthelore/rac - TAG: ${{ github.event.release.tag_name }} - - steps: - - uses: actions/checkout@v5 - - - name: Retrieve release distributions - uses: actions/download-artifact@v7 - with: - name: release-dists - path: dist/ - - - name: Build image - run: | - docker build \ - --build-arg DECIDED_VERSION="$TAG" \ - -t "$IMAGE:$TAG" \ - -t "$IMAGE:latest" \ - . - - - name: Smoke-test image - run: docker run --rm "$IMAGE:$TAG" --version - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Push image - run: | - docker push "$IMAGE:$TAG" - docker push "$IMAGE:latest" - - - name: Report pushed digest - run: docker inspect --format '{{ index .RepoDigests 0 }}' "$IMAGE:$TAG" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/rust-spike.yml b/.github/workflows/rust-spike.yml index 6ba6b3a5..82f7fcef 100644 --- a/.github/workflows/rust-spike.yml +++ b/.github/workflows/rust-spike.yml @@ -8,16 +8,12 @@ on: paths: - "rust/**" - "decisions/**" - - "src/asdecided/**" - - "pyproject.toml" - ".github/workflows/rust-spike.yml" push: branches: [main] paths: - "rust/**" - "decisions/**" - - "src/asdecided/**" - - "pyproject.toml" - ".github/workflows/rust-spike.yml" jobs: @@ -79,46 +75,3 @@ jobs: python rust/tools/live_corpus_invariants.py \ --engine rust/target/release/decided \ --corpus decisions - - retirement-certification: - name: Bounded Python retirement certification - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install rust toolchain (pinned by rust/rust-toolchain.toml) - run: rustup show - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - - - name: Build frozen Python retirement oracle - env: - SETUPTOOLS_SCM_PRETEND_VERSION: 0.1.dev50+g21c8be403 - run: | - python -m venv .venv-retirement - .venv-retirement/bin/pip install --quiet -e . - .venv-retirement/bin/python -m asdecided.cli --version - - - name: Build native release binary - working-directory: rust - run: cargo build --release --locked -p decided - - - name: Checkout authoritative rac-spec fixtures - uses: actions/checkout@v4 - with: - repository: itsthelore/rac-spec - path: rac-spec - - - name: Both engines reproduce the bounded rac-spec suite - run: | - python rust/tools/conformance_certify.py \ - --spec-dir "${{ github.workspace }}/rac-spec" \ - --engine "python=${{ github.workspace }}/.venv-retirement/bin/python -m asdecided.cli" \ - --engine "rust=${{ github.workspace }}/rust/target/release/decided" diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish.yml deleted file mode 100644 index d31f807e..00000000 --- a/.github/workflows/test-publish.yml +++ /dev/null @@ -1,91 +0,0 @@ -# Manual rehearsal of the release pipeline against TestPyPI -# (https://test.pypi.org), so a build can be exercised end-to-end — gate, -# build, upload, pip install — without the release hitting the real index. -# Run it from the Actions tab against any branch or tag. -# -# Mirrors python-publish.yml stage for stage (test gate -> build -> publish). -# The deliberate differences: -# - trigger: manual (workflow_dispatch) instead of a published release -# - index: TestPyPI via repository-url, never PyPI -# - version: the local segment (+g) is stripped at build time, because -# PyPI-family indexes reject local versions; the no-local-version override -# keeps rehearsal uploads unique per commit (e.g. 0.10.0.dev3). -# -# One-time setup before the first run: -# 1. On test.pypi.org, add a trusted publisher for rac-core -# pointing at this repository, this workflow file (test-publish.yml), -# and the `testpypi` environment. -# 2. In the repository settings, create the `testpypi` environment. -# -# Install a rehearsal build (deps come from real PyPI via the extra index): -# pip install --index-url https://test.pypi.org/simple/ \ -# --extra-index-url https://pypi.org/simple/ \ -# rac-core== - -name: Test Publish (TestPyPI) - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - test: - # Same gate as the real release: the full battery grid must pass before - # anything is built or uploaded, so the rehearsal proves the gate too. - uses: ./.github/workflows/tests.yml - - rehearsal-build: - needs: test - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - with: - # setuptools-scm derives the version from git tags, so we need the - # full history + tags (the default shallow checkout omits them). - fetch-depth: 0 - - - uses: actions/setup-python@v6 - with: - python-version: "3.x" - - - name: Build rehearsal distributions - env: - # Strip the +g local segment; TestPyPI rejects local versions. - SETUPTOOLS_SCM_OVERRIDES_FOR_REQUIREMENTS_AS_CODE: '{ local_scheme = "no-local-version" }' - run: | - python -m pip install build - python -m build - - - name: Upload distributions - uses: actions/upload-artifact@v6 - with: - name: testpypi-dists - path: dist/ - - testpypi-publish: - runs-on: ubuntu-latest - needs: - - rehearsal-build - permissions: - # Mandatory for trusted publishing (same mechanism as the real publish). - id-token: write - - environment: - name: testpypi - url: https://test.pypi.org/p/rac-core - - steps: - - name: Retrieve release distributions - uses: actions/download-artifact@v7 - with: - name: testpypi-dists - path: dist/ - - - name: Publish release distributions to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - packages-dir: dist/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index cb3469d1..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Python bridge - -# Rust is the product engine and owns normal behavioral CI. Python is tested -# only as the packaging/SDK bridge; the bounded retirement oracle lives in the -# native Rust workflow against rac-spec. - -on: - workflow_call: - -permissions: - contents: read - -jobs: - bridge: - name: packaging + SDK (py${{ matrix.python-version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Install bridge test dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e . pytest ruff - - - name: Lint maintained bridge surfaces - run: >- - python -m ruff check - src/asdecided/__init__.py - src/asdecided/dispatch.py - src/asdecided/release.py - tests/test_dispatch.py - tests/test_sdk_surface.py - tests/test_release_version.py - - - name: Test packaging and SDK contract - run: >- - python -m pytest -q - tests/test_dispatch.py - tests/test_sdk_surface.py - tests/test_release_version.py diff --git a/.github/workflows/watchkeeper.yml b/.github/workflows/watchkeeper.yml index 0996b739..3b7b2df5 100644 --- a/.github/workflows/watchkeeper.yml +++ b/.github/workflows/watchkeeper.yml @@ -9,9 +9,7 @@ # with: # path: decisions # -# Pin an exact release tag. No moving major tag is published: package -# versions derive from git tags via setuptools-scm, and a floating tag -# would corrupt version derivation (see docs/watchkeeper.md). +# Pin an exact release tag for reproducible native builds. name: Watchkeeper on: @@ -37,11 +35,6 @@ on: type: boolean required: false default: true - decided-version: - description: "Exact rac-core version from PyPI (empty: latest)." - type: string - required: false - default: "" permissions: contents: read @@ -53,8 +46,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - # The comparison needs the base revision's history, and a - # source-mode install needs git metadata for setuptools-scm. + # The comparison needs the base revision's history. fetch-depth: 0 # Reusable workflows run in the caller's repository, so the action is @@ -66,4 +58,3 @@ jobs: base: ${{ inputs.base }} fail-on: ${{ inputs.fail-on }} annotate: ${{ inputs.annotate }} - decided-version: ${{ inputs.decided-version }} diff --git a/.gitignore b/.gitignore index 20814220..8e339891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # macOS .DS_Store -# Virtual environments +# Python verification-tool environments .venv/ .venv-oracle/ venv/ @@ -14,7 +14,7 @@ build/ dist/ .pytest_cache/ -# Coverage data (pytest --cov; reported in CI, never committed) +# Legacy coverage data .coverage .coverage.* htmlcov/ @@ -22,8 +22,5 @@ htmlcov/ # MkDocs build output (deployed from CI, never committed) site/ -# Native engine binaries bundled into platform wheels at build time -# (native-engine cutover); never committed — the package keeps only __init__.py. -src/rac/bin/rac -src/rac/bin/rac-mcp -src/rac/bin/*.exe +# Native build output +rust/target/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a8af8ec8..44d047d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,10 @@ can preview and apply the explicit migration with `decided migrate layout . --dry-run` and `decided migrate layout .`; existing `RAC-*` artifact IDs and published machine-schema keys remain unchanged. Rust is the normal product engine, Explorer is retired, and ingest leaves core as an ancillary connector. -The Python implementation remains only as an SDK/packaging bridge and bounded -retirement-certification oracle. +The Python engine, package, PyPI release path, compatibility oracle, Explorer, +and ingest implementation have been removed from `rac-core`. Python API access +belongs in `itsthelore/rac-sdk`; the final former engine remains available only +at the immutable `python-engine-final` Git tag for historical review. **Decisions on pull requests — the Herald action** (`pr-decision-surfacing`). A pull request that edits governed code now gets diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2603e815..a0112901 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,93 +1,58 @@ -# Contributing to RAC - -Thanks for your interest in improving RAC. The project is early and evolving -quickly — contributions, ideas, and experiments are welcome. +# Contributing to AsDecided ## Local setup -Requires **Python 3.11+**. +The toolchain is pinned by `rust/rust-toolchain.toml`. ```bash git clone https://github.com/itsthelore/rac-core.git -cd rac-core -python3 -m venv .venv -source .venv/bin/activate -pip install -e '.[dev]' +cd rac-core/rust +rustup show +cargo build --workspace --locked ``` ## Verify a change -These commands must all pass before you open a pull request: - ```bash -pytest - -ruff check src/ tests/ -ruff format --check src/ tests/ -mypy src/ +cd rust +cargo fmt --all -- --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace --release -decided validate decisions/ - -decided relationships decisions/ --validate +target/release/decided validate ../decisions/ +target/release/decided relationships ../decisions/ --validate ``` -- `pytest` runs the full suite, including the **dogfood gate** (RAC's own - planning corpus under `decisions/` must pass RAC) and **golden output tests** - (CLI output is pinned byte-for-byte). -- If you intentionally changed what the CLI prints, refresh the goldens and - commit the diff — it will be reviewed as a product change: - - ```bash - DECIDED_UPDATE_GOLDEN=1 python -m pytest tests/test_golden.py - ``` - -See [docs/testing.md](docs/testing.md) for test layout, fixtures, and useful -pytest variations. +The small Python programs under `rust/tools/` and `rust/spec/` are repository +verification utilities. They are not a Python product engine or installable +SDK. Cross-repository compatibility is certified against +[`rac-spec`](https://github.com/itsthelore/rac-spec). ## Documentation expectations -- `docs/` is the public documentation: quickstart, CLI reference, artifact - types, relationships, repository workflow. If your change alters a command's - behavior, flags, output, or exit codes, update [docs/cli.md](docs/cli.md) in - the same pull request. -- User-visible changes get a line in [CHANGELOG.md](CHANGELOG.md) under - **Unreleased** — user impact over implementation details. -- JSON output is a stable, versioned contract: field changes must be additive - and `schema_version`-gated (see `decisions/decisions/`). - -## RAC artifact expectations +Update `docs/` when command behavior, flags, output, or exit codes change. +User-visible changes also need an entry under **Unreleased** in `CHANGELOG.md`. +Machine-readable output is a stable, versioned contract. -The `decisions/` directory is RAC's own product knowledge — requirements, decisions -(ADRs), roadmaps, prompts, and designs — maintained with the same care as code: +## Product-knowledge expectations -- Behavior changes that reflect a product decision should trace to an artifact - under `decisions/` (a roadmap initiative or an ADR). -- Artifacts you add or edit must keep the corpus green: `decided validate decisions/`, - `decided relationships decisions/ --validate`, and `decided review decisions/` all run in CI. +Behavioral changes should trace to an artifact under `decisions/`. Keep the +corpus green with `decided validate`, `decided relationships --validate`, and +`decided review`. ## Commit conventions -Follow [`decisions/prompts/rac-agent-commit-guidelines.md`](decisions/prompts/rac-agent-commit-guidelines.md): +Follow `decisions/prompts/rac-agent-commit-guidelines.md`: ```text (): [roadmap:vX.Y.Z] ``` -with `type` one of `feat`, `fix`, `test`, `docs`, `refactor`, `chore`. Keep -commits small and single-purpose. - ## License and sign-off -RAC is licensed under the [Apache License 2.0](LICENSE). By contributing you -agree your contributions are licensed under the same terms. - -Contributions must carry a [Developer Certificate of Origin](https://developercertificate.org/) -sign-off: certify that you wrote the change (or have the right to submit it) -by adding a `Signed-off-by` trailer to each commit. Git adds it for you: +RAC is licensed under Apache-2.0. Contributions must carry a +[Developer Certificate of Origin](https://developercertificate.org/) sign-off: ```bash git commit -s ``` - -This produces a `Signed-off-by: Your Name ` line matching your -commit author identity. There is no CLA. diff --git a/Dockerfile b/Dockerfile index ac456f98..d00f4389 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,15 @@ -# Official rac container image. Built from the release wheel in dist/ so the -# image carries exactly the distribution published to PyPI for the same CalVer -# tag (rac/roadmaps/future/oci-image.md). Packaging only: no behaviour, -# flags, or configuration beyond the CLI's own. +# Official native AsDecided container image. # # Local build: -# python -m build && docker build -t rac . +# docker build -t rac . # docker run --rm -v "$PWD:/work" asdecided validate decisions/ -FROM python:3.12-slim +FROM rust:1.94-bookworm AS builder + +COPY rust /src/rust +WORKDIR /src/rust +RUN cargo build --release --locked -p decided -p decided-mcp + +FROM debian:bookworm-slim ARG DECIDED_VERSION=dev LABEL org.opencontainers.image.title="rac" \ @@ -15,8 +18,11 @@ LABEL org.opencontainers.image.title="rac" \ org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.version="${DECIDED_VERSION}" -COPY dist/ /tmp/dist/ -RUN pip install --no-cache-dir /tmp/dist/*.whl && rm -rf /tmp/dist +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* +COPY --from=builder /src/rust/target/release/decided /usr/local/bin/decided +COPY --from=builder /src/rust/target/release/decided-mcp /usr/local/bin/decided-mcp WORKDIR /work ENTRYPOINT ["decided"] diff --git a/README.md b/README.md index 79741e64..56610c11 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,29 @@ # AsDecided -**Engineering decisions your agents can follow.** -Build, as decided. +**Engineering decisions your agents can follow. Build, as decided.** AsDecided keeps requirements, decisions, designs, roadmaps, and prompts as typed Markdown in your repository. Its native Rust engine validates that -knowledge, retrieves the relevant decisions deterministically, and serves it +knowledge, retrieves relevant decisions deterministically, and serves it read-only to agents over MCP. -No embeddings, model call, or hosted index is required. The same repository -state produces the same answer. +No embeddings, model call, hosted index, or Python runtime is required. The +same repository state produces the same answer. ## Install -The distribution is still published from this repository as `rac-core` while -the registry and repository rename are handled as a separate release gate: +Install the complete RAC toolchain through Homebrew: ```sh -pip install rac-core +brew install itsthelore/tap/rac-full ``` -This installs exactly two executable surfaces: +Native `decided` and `decided-mcp` archives are also published on +[GitHub Releases](https://github.com/itsthelore/rac-core/releases). -- `decided` — the native Rust CLI -- `decided-mcp` — the native read-only MCP server - -There is no `rac` command, Python CLI fallback, or `RAC_*` environment-variable -compatibility layer. +`rac-core` is no longer distributed through PyPI. Python API consumers should +use [`itsthelore/rac-sdk`](https://github.com/itsthelore/rac-sdk), which is a +client SDK rather than a second engine implementation. ## Start a repository @@ -48,16 +45,10 @@ not change with the product name. ## Migrate an existing repository -Migration is explicit and never runs during an ordinary command. Inspect the -plan first: +Migration is explicit and never runs during an ordinary command: ```sh decided migrate layout . --dry-run -``` - -Then apply it: - -```sh decided migrate layout . ``` @@ -66,8 +57,6 @@ refuses to overwrite either destination. ## MCP -Configure clients to run the native server directly: - ```json { "mcpServers": { @@ -79,27 +68,19 @@ Configure clients to run the native server directly: } ``` -## Runtime controls - -Native runtime controls use the `DECIDED_*` namespace, including -`DECIDED_CACHE_DIR`, `DECIDED_NO_CACHE`, `DECIDED_TIMING`, -`DECIDED_MAX_FILE_BYTES`, `DECIDED_AUDIT_PATH`, and -`DECIDED_AUDIT_PRINCIPAL`. - -Stable machine-readable fields such as `rac_version`, and existing `RAC-*` -artifact IDs, remain unchanged where they are part of a published contract. - ## Architecture -Rust is the product engine and the only normal CLI/MCP runtime. Python remains -only as packaging/SDK support and a bounded retirement-certification oracle; -it is not a second supported command implementation. Document ingestion lives -outside the core as an ancillary Python connector, and Explorer is retired. - +Rust is the product engine and the only CLI/MCP runtime in this repository. The authoritative language-neutral compatibility fixtures live in [`rac-spec`](https://github.com/itsthelore/rac-spec). Live-corpus validation is based on validity, determinism, freshness, and cache/no-cache equality. +Document ingestion remains an ancillary Python connector rather than part of +the core engine. The retired Python engine is preserved for historical review +at the immutable +[`python-engine-final`](https://github.com/itsthelore/rac-core/tree/python-engine-final) +tag; it is not maintained or run in normal CI. + ## License Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). diff --git a/action.yml b/action.yml index 81c0cec7..4bcf95fa 100644 --- a/action.yml +++ b/action.yml @@ -1,10 +1,10 @@ # RAC Watchkeeper composite action (v0.12.3). # -# A thin wrapper: install RAC, resolve the base revision, run one +# A thin wrapper: build the native CLI, resolve the base revision, run one # `decided watchkeeper --format github` invocation, and route its outputs — # stdout to the step summary, stderr (workflow-command annotations) to the # step log. All analysis, rendering, and failure policy live in the Python -# package (ADR-015); the action never reinterprets exit codes. +# engine (ADR-015); the action never reinterprets exit codes. name: "RAC Watchkeeper" description: >- Review product knowledge changes (requirements, decisions, roadmaps, @@ -40,45 +40,14 @@ inputs: Emit inline annotations via workflow commands (`true` or `false`). required: false default: "true" - decided-version: - description: >- - Exact rac-core version to install from PyPI. Empty - installs the latest release. Ignored when `install-from` is `source`. - required: false - default: "" - install-from: - description: >- - Where to install AsDecided from: `pypi` (the default for consumers) or - `source` (the action checkout itself — used by this repository's own - dogfood job; requires `uses: ./` on a full checkout). - required: false - default: "pypi" - runs: using: "composite" steps: - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install AsDecided + - name: Build native AsDecided shell: bash - env: - INSTALL_FROM: ${{ inputs.install-from }} - DECIDED_VERSION: ${{ inputs.decided-version }} run: | - python -m pip install --quiet --upgrade pip - if [ "$INSTALL_FROM" = "source" ]; then - (cd "$GITHUB_ACTION_PATH/rust" && cargo build --release --locked -p decided -p decided-mcp) - cp "$GITHUB_ACTION_PATH/rust/target/release/decided" \ - "$GITHUB_ACTION_PATH/rust/target/release/decided-mcp" \ - "$GITHUB_ACTION_PATH/src/asdecided/bin/" - python -m pip install --quiet "$GITHUB_ACTION_PATH" - elif [ -n "$DECIDED_VERSION" ]; then - python -m pip install --quiet "rac-core==$DECIDED_VERSION" - else - python -m pip install --quiet rac-core - fi + (cd "$GITHUB_ACTION_PATH/rust" && cargo build --release --locked -p decided -p decided-mcp) + echo "$GITHUB_ACTION_PATH/rust/target/release" >> "$GITHUB_PATH" - name: Resolve base revision shell: bash diff --git a/decisions/decisions/adr-023-clean-break-internal-refactors.md b/decisions/decisions/adr-023-clean-break-internal-refactors.md index 5c624b5d..392a040a 100644 --- a/decisions/decisions/adr-023-clean-break-internal-refactors.md +++ b/decisions/decisions/adr-023-clean-break-internal-refactors.md @@ -203,4 +203,4 @@ its CLI and JSON contracts), which would warrant a deliberate deprecation policy ## Applies To -- src/asdecided/ +- rust/rac-engine/ diff --git a/decisions/decisions/adr-115-shared-artifact-spec-registry.md b/decisions/decisions/adr-115-shared-artifact-spec-registry.md index ef0010d1..9f871f36 100644 --- a/decisions/decisions/adr-115-shared-artifact-spec-registry.md +++ b/decisions/decisions/adr-115-shared-artifact-spec-registry.md @@ -127,6 +127,5 @@ Architecture ## Applies To -- src/asdecided/spec/artifact-specs.json -- src/asdecided/core/artifacts.py +- rust/rac-engine/assets/spec/artifact-specs.json - rust/rac-engine/src/spec.rs diff --git a/docs/cli.md b/docs/cli.md index fe7f34bf..9731e05f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -206,9 +206,8 @@ decided ingest spec.docx -o spec.md # write to a file decided ingest report.pdf -o report.md --force ``` -Conversion uses optional extras. Install the readers you need: -`pip install 'rac-core[ingest]'` (DOCX/HTML), `[ingest-pdf]`, -`[ingest-office]` (PPTX/XLSX), or `[ingest-all]`. +Document ingestion is no longer shipped by `rac-core`. Use the ancillary +Python ingestion connector when creating an initial corpus. ### Note-tool exports (Obsidian, Logseq, Notion, Roam) @@ -991,14 +990,7 @@ it shows is also available through `decided portfolio`, `decided index`, `decide - **Exit codes:** `0` session quit · `2` not a directory, or the `explorer` extra is not installed -The TUI dependency ships as an optional extra, so the core install stays light: - -```bash -pip install 'rac-core[explorer]' -decided explorer decisions/ -``` - -Without the extra, `decided explorer` prints the install hint above and exits `2`. +Explorer is retired and is not part of the native product. --- @@ -1447,7 +1439,7 @@ against the repository root (the nearest `.decided/`). ```bash decided decisions-for src/decisions/mcp/server.py decisions/ -decided decisions-for .github/workflows/tests.yml decisions/ --json +decided decisions-for rust/Cargo.toml decisions/ --json ``` ```json diff --git a/docs/decisions-on-pr.md b/docs/decisions-on-pr.md index f211a538..2a681ae9 100644 --- a/docs/decisions-on-pr.md +++ b/docs/decisions-on-pr.md @@ -46,9 +46,6 @@ includes Herald is published. Inputs: - **`path`** — the corpus directory (default `rac`). - **`max-inline`** — decisions listed in full before the rest collapse into a details expander (default `5`). -- **`rac-version`** — pin an exact rac-core release from PyPI; empty installs - the latest. - On forks, where the token is read-only, the comment degrades to the step summary instead of failing the check. diff --git a/docs/index.md b/docs/index.md index 173482b1..1da4e7f9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,8 @@ AsDecided stores your requirements, decisions, designs, and roadmaps as typed Ma No AI in the core. No inference. No guessing. Just your team's recorded knowledge, in your Git, handed to the agent that needs it. -AsDecided is built on **RAC — Requirements as Code** — the open-source engine underneath. For now the package, CLI, and MCP server all ship under the `rac` name. +AsDecided is built on **RAC — Requirements as Code**. The native CLI and MCP +server are `decided` and `decided-mcp`. Point your agent at your repo and ask: @@ -41,7 +42,7 @@ Spec-driven development (SDD) tools — GitHub Spec Kit, OpenSpec, Kiro — mana | Change management | None — RAC does not manage the change cycle; pair it with an SDD tool | Slash-command workflow: specify, clarify, plan, tasks, implement | Slash-command workflow: propose, apply, archive | | Traceability | Typed `Related` links between artifacts; `decided relationships --validate` checks them in CI | `/speckit.analyze` runs cross-artifact consistency and coverage analysis | `openspec validate` checks changes and specs for structural issues | | Tool coupling | Read-only MCP server; works with any MCP client | Slash commands or skills installed per agent at init (GitHub Copilot, Claude Code, Cursor, and others) | Slash commands for 20+ AI assistants | -| Install footprint | `pip install rac-core` (Python 3.11+) | `uv tool install specify-cli` from the Git repository (Python 3.11+) | `npm install -g @fission-ai/openspec` (Node.js 20.19+) | +| Install footprint | `brew install itsthelore/tap/rac-full` (native) | `uv tool install specify-cli` from the Git repository (Python 3.11+) | `npm install -g @fission-ai/openspec` (Node.js 20.19+) | wit ```toml [mcp_servers.lore] -command = "rac" -args = ["mcp", "--root", "."] +command = "decided-mcp" +args = ["--root", "."] ``` ```yaml mcpServers: lore: - command: rac - args: [mcp, --root, .] + command: decided-mcp + args: [--root, .] ``` - **Project:** commit `` to share it with the team. diff --git a/examples/_recipe-template/config.example.toml b/examples/_recipe-template/config.example.toml index f9a845d2..bab1b04b 100644 --- a/examples/_recipe-template/config.example.toml +++ b/examples/_recipe-template/config.example.toml @@ -1,5 +1,5 @@ # The same `lore` server invocation as the JSON and YAML dialects, in TOML. # Some harnesses (e.g. OpenAI Codex) namespace MCP servers under `mcp_servers`. [mcp_servers.lore] -command = "rac" -args = ["mcp", "--root", "."] +command = "decided-mcp" +args = ["--root", "."] diff --git a/examples/_recipe-template/mcp.example.yaml b/examples/_recipe-template/mcp.example.yaml index 9535642d..0b37c460 100644 --- a/examples/_recipe-template/mcp.example.yaml +++ b/examples/_recipe-template/mcp.example.yaml @@ -1,5 +1,5 @@ # The same `lore` server invocation as the JSON and TOML dialects, in YAML. mcpServers: lore: - command: rac - args: [mcp, --root, .] + command: decided-mcp + args: [--root, .] diff --git a/examples/amp/README.md b/examples/amp/README.md index 2d435a75..136e1659 100644 --- a/examples/amp/README.md +++ b/examples/amp/README.md @@ -13,7 +13,7 @@ the full text and relationships when it needs them. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` You also need a repository with a RAC corpus under `decisions/` (run `decided quickstart` diff --git a/examples/claude-code/README.md b/examples/claude-code/README.md index 82bd85e5..4624137e 100644 --- a/examples/claude-code/README.md +++ b/examples/claude-code/README.md @@ -9,7 +9,7 @@ bundled authoring **skill** and the only platform seam that allows a real ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/cline/README.md b/examples/cline/README.md index 233d2c1f..122bd8a9 100644 --- a/examples/cline/README.md +++ b/examples/cline/README.md @@ -7,7 +7,7 @@ file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/codex/README.md b/examples/codex/README.md index 9e72fcc4..d12839f0 100644 --- a/examples/codex/README.md +++ b/examples/codex/README.md @@ -8,7 +8,7 @@ Codex-specific RAC code. A stranger can reproduce this from the file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this @@ -40,8 +40,8 @@ codex mcp add lore -- decided-mcp --root . ```toml [mcp_servers.lore] -command = "rac" -args = ["mcp", "--root", "."] +command = "decided-mcp" +args = ["--root", "."] ``` - **Global config** (most reliable): `~/.codex/config.toml` — use an absolute diff --git a/examples/codex/config.example.toml b/examples/codex/config.example.toml index 3c54e076..6914ff0d 100644 --- a/examples/codex/config.example.toml +++ b/examples/codex/config.example.toml @@ -2,5 +2,5 @@ # Add this to ~/.codex/config.toml (global) or .codex/config.toml (trusted # project). Use an absolute path for --root in the global file. [mcp_servers.lore] -command = "rac" -args = ["mcp", "--root", "."] +command = "decided-mcp" +args = ["--root", "."] diff --git a/examples/copilot/README.md b/examples/copilot/README.md index d36069ca..c1d4cdfa 100644 --- a/examples/copilot/README.md +++ b/examples/copilot/README.md @@ -8,7 +8,7 @@ file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/cursor/README.md b/examples/cursor/README.md index 6eb44751..2a74cdc0 100644 --- a/examples/cursor/README.md +++ b/examples/cursor/README.md @@ -7,7 +7,7 @@ reproduce this from the file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/omnigent/README.md b/examples/omnigent/README.md index e6dda4f6..35aeecde 100644 --- a/examples/omnigent/README.md +++ b/examples/omnigent/README.md @@ -14,7 +14,7 @@ test it against your Omnigent version before relying on it in production. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this @@ -52,8 +52,8 @@ first-class tool type, so add a `lore` entry under `tools:` (a sample is in tools: lore: type: mcp - command: rac - args: ["mcp", "--root", "."] + command: decided-mcp + args: ["--root", "."] ``` This exposes the read-only `lore` tools (`get_summary`, `search_artifacts`, diff --git a/examples/omnigent/config.example.yaml b/examples/omnigent/config.example.yaml index c9cef2c9..9a26225b 100644 --- a/examples/omnigent/config.example.yaml +++ b/examples/omnigent/config.example.yaml @@ -6,5 +6,5 @@ tools: lore: type: mcp - command: rac - args: ["mcp", "--root", "."] + command: decided-mcp + args: ["--root", "."] diff --git a/examples/opencode/README.md b/examples/opencode/README.md index 3d60dec4..b8ee263a 100644 --- a/examples/opencode/README.md +++ b/examples/opencode/README.md @@ -7,7 +7,7 @@ reproduce this from the file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/windsurf/README.md b/examples/windsurf/README.md index d5d4a1bb..c59e41fe 100644 --- a/examples/windsurf/README.md +++ b/examples/windsurf/README.md @@ -7,7 +7,7 @@ this from the file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/examples/zed/README.md b/examples/zed/README.md index 26a9a04f..5a606157 100644 --- a/examples/zed/README.md +++ b/examples/zed/README.md @@ -7,7 +7,7 @@ the file alone. ## Prerequisites ```bash -pip install rac-core # the `rac` CLI and the `lore` MCP server +brew install itsthelore/tap/rac-full # the `decided` CLI and the `decided-mcp` server ``` A repository with a RAC corpus under `decisions/` (run `decided quickstart`, or use this diff --git a/overrides/home.html b/overrides/home.html index cf479aa6..f331aaaf 100644 --- a/overrides/home.html +++ b/overrides/home.html @@ -25,10 +25,10 @@

Give your coding agent the decisions your team al
$ - pip install rac-core + brew install itsthelore/tap/rac-full
-

Requires Python 3.11+ · uv tool install requirements-as-code also works.

+

Native Rust CLI and MCP server · no Python runtime required.