Skip to content

Latest commit

 

History

History
277 lines (225 loc) · 8.29 KB

File metadata and controls

277 lines (225 loc) · 8.29 KB

CI/CD integration

Copy-paste-ready integrations for running Forge gates in CI.

Status of native fail flags.

  • forge certify --fail-under Nshipped. Exits 1 when score < N.
  • forge wire --fail-on-violationsshipped (Lane G1). Exits 1 when any upward-import violation is found.

Roadmap items still pending:

  • Lane G2 — published atomadictech/forge-action GitHub Action.
  • Lane G5 — signed certificate output.

Until those land, the recipes below pair the native flags with a small python -c fallback for fields the CLI doesn't yet gate on.


GitHub Actions

.github/workflows/forge-certify.yml:

name: forge-certify

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  certify:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install Forge
        run: |
          pip install atomadic-forge          # PyPI (preferred)
          # Or pin to the exact release you are validating:
          #   pip install "atomadic-forge==<release-version>"
          # Or install from a tagged git ref:
          #   pip install "git+https://github.com/atomadictech/atomadic-forge@v<release-tag>"
          # Or vendor wheels: pip install --no-index --find-links wheels/ atomadic-forge

      - name: Wire scan (upward-import discipline)
        run: |
          # Native gate (Lane G1): non-zero exit on any violation.
          forge wire src/your_package --fail-on-violations --json > wire.json

      - name: Certify (gate at score >= 75)
        run: |
          # Native gate: --fail-under exits 1 below the threshold.
          forge certify . --package your_package --fail-under 75 --json > certify.json
          python -c "import json; r=json.load(open('certify.json')); \
            print(f\"forge certify score: {r.get('total_score', r.get('score', 0))}/100\")"

      - name: Upload reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: forge-reports
          path: |
            wire.json
            certify.json
            .atomadic-forge/

What this does:

  • forge wire --json is parsed for verdict == "PASS". Anything else fails the job.
  • forge certify --json is parsed for total_score. Below 75 fails the job. Adjust the threshold to match your team's policy.
  • Both reports plus the full .atomadic-forge/ provenance directory are uploaded as build artifacts so reviewers can inspect them on the PR.

GitLab CI

.gitlab-ci.yml:

stages:
  - certify

forge-certify:
  stage: certify
  image: python:3.11-slim
  timeout: 10 minutes
  before_script:
    - pip install atomadic-forge
  script:
    - forge wire src/your_package --fail-on-violations --json > wire.json
    - forge certify . --package your_package --fail-under 75 --json > certify.json
    - |
      python -c "import json,sys; r=json.load(open('certify.json')); \
        s=r.get('total_score',0); \
        print(f'forge certify score: {s}/100'); \
        sys.exit(0 if s >= 75 else 1)"
  artifacts:
    when: always
    expire_in: 1 week
    paths:
      - wire.json
      - certify.json
      - .atomadic-forge/
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Same shape as the Actions workflow: install, wire, certify, fail on threshold, upload artifacts.


pre-commit

For local-developer guard rails, expose Forge as a pre-commit hook. Forge ships a pre-commit-hooks.yaml you can publish from this repo. The shape is:

.pre-commit-hooks.yaml (in this repo):

- id: forge-wire
  name: forge wire (upward-import discipline)
  description: Scan src/ for upward imports; fails the hook on any violation.
  entry: forge wire
  language: system
  pass_filenames: false
  args: [src, --fail-on-violations]
  stages: [pre-commit, pre-push]

- id: forge-certify
  name: forge certify (architecture conformance)
  description: Score architecture conformance; fails below the threshold.
  entry: forge certify
  language: system
  pass_filenames: false
  args: [., --fail-under, "75"]
  stages: [pre-push]

Then in the consuming repo's .pre-commit-config.yaml:

repos:
  - repo: https://github.com/atomadictech/atomadic-forge
    rev: <release-tag>
    hooks:
      - id: forge-wire
      - id: forge-certify

The first file lives in this repo (Forge); the second is what your users add to their repo. Run pre-commit install once and the gates fire on every commit / push.

Replace <release-tag> with the exact Forge release you want the hook consumer to install. Avoid copying stale historical pins forward.

Note: forge certify is heavier than forge wire. The hook above runs wire on every commit and certify only on push, which is the right tradeoff for most teams. Adjust the stages: list if your workflow differs.


Surface drift gate (v0.12.0+)

If you author MCP tools, recipes, or schema versions on a fork of Forge, the canonical surface.json artifact at the repo root must stay synchronized with the in-code registries. The surface-drift.yml workflow gates this:

.github/workflows/surface-drift.yml:

name: surface-drift

on:
  pull_request:
    paths:
      - "src/atomadic_forge/a0_qk_constants/**"
      - "src/atomadic_forge/a1_at_functions/mcp_protocol.py"
      - "src/atomadic_forge/a3_og_features/recipe_*.py"
      - "src/atomadic_forge/a3_og_features/surface_export.py"
      - "surface.json"

jobs:
  surface-drift:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -e ".[dev]"
      - name: Surface drift check
        run: forge surface check

On drift: the job exits 1 with a one-line fix-up command:

forge surface emit --out surface.json && git add surface.json

The generated_at_utc timestamp is excluded from the comparison so re-emitting on unchanged inputs always passes.

For consumer projects (sites, badge workers, agent platforms), fetch surface.json directly from the GitHub raw URL or pin to a release tag — never hand-maintain a tool/recipe map locally.


Wisdom-aware CI (v0.55.0+)

The forge verify response now includes a wisdom_capture_prompt field when trust thresholds are met (≥3 lineage events, ≥1.0 score delta, or ≥5-minute session). For long-running CI runs that produce real lessons, you can wire this into a post-merge job:

- name: Verify + capture wisdom (post-merge only)
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
  run: |
    forge verify --json > verify.json
    PROMPT=$(python -c "import json; r=json.load(open('verify.json')); print(r.get('wisdom_capture_prompt', {}).get('next_command', ''))")
    if [ -n "$PROMPT" ]; then
      echo "Captured wisdom: $PROMPT"
      eval "$PROMPT"
      git add .atomadic-forge/wisdom.jsonl
      git commit -m "wisdom: capture from CI run ${{ github.sha }}" || true
      git push origin main || true
    fi

This is opt-in. Most consumer CI runs don't generate enough novel signal to justify a wisdom record (the threshold gate exists exactly to suppress noise). Use it on the Forge repo itself or on long-lived systems where institutional memory survives team rotation.


What's still on the roadmap

  • Lane G1 — ✅ shipped. forge certify --fail-under N and forge wire --fail-on-violations are now the canonical CI gates. Earlier python -c workarounds for these specific gates can be removed; the recipes above already use the native flags. The current python -c workaround is intentionally explicit so the semantics survive the migration.
  • Lane G2atomadictech/forge-action GitHub Action. Once published, the GitHub Actions workflow above becomes uses: atomadictech/forge-action@v1.
  • Lane G5 — cryptographically signed certify reports. The schema is finalized; signing is not yet wired. Today, treat the JSON as authoritative-but-unsigned.

When those land, this page will be updated and the manual recipes will be marked legacy (not deleted — a few teams will want to keep the unsigned, dependency-free path).