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
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

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

permissions:
contents: read

jobs:
tests:
name: tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install
run: |
python -m pip install -U pip
python -m pip install -e '.[mongo,postgres,mcp,dev]'
python -m pip install -e plugins/cfg_impact
- name: Run tests
run: python -m pytest -q

package-build:
name: package-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build and check distributions
run: |
python -m pip install -U build twine
rm -rf dist plugins/cfg_impact/dist
python -m build
python -m twine check dist/*
cd plugins/cfg_impact
python -m build
python -m twine check dist/*
56 changes: 56 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Publish Python packages

on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

jobs:
publish-cfgit:
name: Publish cfgit
runs-on: ubuntu-latest
environment: pypi-cfgit
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build
run: |
python -m pip install -U build twine
python -m build
python -m twine check dist/*
- name: Publish cfgit to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/

publish-cfgit-impact:
name: Publish cfgit-impact
runs-on: ubuntu-latest
needs: publish-cfgit
environment: pypi-cfgit-impact
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build
working-directory: plugins/cfg_impact
run: |
python -m pip install -U build twine
python -m build
python -m twine check dist/*
- name: Publish cfgit-impact to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: plugins/cfg_impact/dist/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ cfgit is pre-1.0 software. The current implementation includes:
- localhost web UI
- MCP server
- portable Codex or Claude Code skill
- optional `cfg-impact` plugin for deterministic impact summaries and opt-in LLM narration
- optional `cfgit-impact` plugin for deterministic impact summaries and opt-in LLM narration

The engine is intentionally DB-neutral. Mongo and Postgres are the first two
adapters to prove the storage seam.
Expand Down Expand Up @@ -410,7 +410,7 @@ standardize the config so that database always uses one env name.
paths, finds static references to changed values across configured records, and
reports a risk level.

Optional LLM narration lives in the separate `cfg-impact` plugin. It reads the
Optional LLM narration lives in the separate `cfgit-impact` plugin. It reads the
real before/after of the change plus a map of the surrounding records, then
explains in plain language what the change does, what it ripples into, and how to
roll it back:
Expand Down
82 changes: 82 additions & 0 deletions docs/PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Publishing

cfgit publishes two Python packages:

- `cfgit`: Git-style history, diff, drift detection, branch/PR review, and
rollback for live database records without migrating or owning the datastore.
- `cfgit-impact`: optional plugin for deterministic system-impact summaries and
opt-in LLM narration of database record diffs.

Current first release version: `0.1.0`.

## One-Time PyPI Setup

Create both projects on PyPI and configure Trusted Publishing for this repository.

For `cfgit`:

- Owner: `AusafMo`
- Repository: `cfgit`
- Workflow: `publish.yml`
- Environment: `pypi-cfgit`

For `cfgit-impact`:

- Owner: `AusafMo`
- Repository: `cfgit`
- Workflow: `publish.yml`
- Environment: `pypi-cfgit-impact`

The workflow uses PyPI OpenID Connect, so no PyPI API token is stored in GitHub
Secrets.

## Local Build Check

From the repository root:

```bash
python -m pip install -U build twine
rm -rf dist plugins/cfg_impact/dist
python -m build
python -m twine check dist/*
cd plugins/cfg_impact
python -m build
python -m twine check dist/*
```

## Clean Install Smoke

```bash
python -m venv /tmp/cfgit-publish-smoke
/tmp/cfgit-publish-smoke/bin/python -m pip install \
'dist/cfgit-0.1.0-py3-none-any.whl[mcp]' \
plugins/cfg_impact/dist/cfgit_impact-0.1.0-py3-none-any.whl
/tmp/cfgit-publish-smoke/bin/cfg --help
/tmp/cfgit-publish-smoke/bin/python -c 'import cfg; import cfg.mcp.server; import cfg_impact; print("imports ok")'
```

## Publish

After Trusted Publishing is configured on PyPI, publish by creating a GitHub
release from a tag:

```bash
git checkout main
git pull origin main
git tag v0.1.0
git push origin v0.1.0
gh release create v0.1.0 --title "v0.1.0" --notes "First public cfgit release."
```

The release triggers `.github/workflows/publish.yml`, which publishes `cfgit`
first and `cfgit-impact` second.

## Install

```bash
pip install cfgit
pip install 'cfgit[mongo]'
pip install 'cfgit[postgres]'
pip install 'cfgit[mongo,postgres,mcp]'
pip install cfgit-impact
```
17 changes: 13 additions & 4 deletions plugins/cfg_impact/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
[project]
name = "cfg-impact"
version = "0.0.0"
description = "Optional system-impact analysis plugin for cfgit"
name = "cfgit-impact"
version = "0.1.0"
description = "Optional cfgit plugin for deterministic system-impact summaries and opt-in LLM narration of database record diffs"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
authors = [{ name = "Mohammad Ausaf" }]
dependencies = [
"cfg-vcs",
"cfgit>=0.1.0,<0.2.0",
"httpx>=0.27",
]

[project.urls]
Homepage = "https://github.com/AusafMo/cfgit"
Repository = "https://github.com/AusafMo/cfgit"
Issues = "https://github.com/AusafMo/cfgit/issues"
Documentation = "https://github.com/AusafMo/cfgit/tree/main/plugins/cfg_impact#readme"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Expand Down
17 changes: 13 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "cfg-vcs"
version = "0.0.0"
description = "Non-custodial version control for live datastore records"
name = "cfgit"
version = "0.1.0"
description = "Git-style history, diff, drift detection, and rollback for live database records without migrating or owning your datastore"
readme = "README.md"
requires-python = ">=3.11"
license = { file = "LICENSE" }
Expand All @@ -15,6 +15,9 @@ keywords = [
"postgres",
"mcp",
"agents",
"rollback",
"drift-detection",
"database-versioning",
]

# Core has NO database driver and NO LLM SDK. That boundary is enforced in CI
Expand All @@ -29,9 +32,15 @@ mongo = ["pymongo>=4.6"]
postgres = ["psycopg[binary]>=3.2"]
cli = ["click>=8.1", "rich>=13.0"]
mcp = ["mcp>=1.0"]
impact = [] # the cfg-impact plugin declares its own model-client deps; never in core
impact = [] # the cfgit-impact plugin declares its own model-client deps; never in core
dev = ["pytest>=8.0", "pytest-asyncio", "ruff", "mypy", "httpx>=0.27"]

[project.urls]
Homepage = "https://github.com/AusafMo/cfgit"
Repository = "https://github.com/AusafMo/cfgit"
Issues = "https://github.com/AusafMo/cfgit/issues"
Documentation = "https://github.com/AusafMo/cfgit#readme"

[project.scripts]
cfg = "cfg.cli.main:main"
cfg-mcp = "cfg.mcp.server:main"
Expand Down
2 changes: 1 addition & 1 deletion src/cfg/adapters/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from pymongo.client_session import ClientSession
from pymongo.errors import OperationFailure
except ModuleNotFoundError as exc: # pragma: no cover
raise ModuleNotFoundError("install cfg-vcs[mongo] to use MongoAdapter") from exc
raise ModuleNotFoundError("install cfgit[mongo] to use MongoAdapter") from exc


class MongoAdapter:
Expand Down
2 changes: 1 addition & 1 deletion src/cfg/adapters/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb
except ModuleNotFoundError as exc: # pragma: no cover
raise ModuleNotFoundError("install cfg-vcs[postgres] to use PostgresAdapter") from exc
raise ModuleNotFoundError("install cfgit[postgres] to use PostgresAdapter") from exc


_IDENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
Expand Down
2 changes: 1 addition & 1 deletion src/cfg/interfaces/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def impact(
from cfg_impact.overview import overview
except ModuleNotFoundError as exc:
raise ValueError(
"cfg-impact plugin is not installed. Install plugins/cfg_impact or cfg-vcs[impact]."
"cfgit-impact plugin is not installed. Install plugins/cfg_impact or cfgit[impact]."
) from exc
return (
overview(engine, record, a=a, b=b, use_llm=use_llm, provider=provider, model=model, against=against),
Expand Down
4 changes: 2 additions & 2 deletions src/cfg/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
from cfg.interfaces import actions
from cfg.interfaces.actions import ActionContext

try: # pragma: no cover - exercised when cfg-vcs[mcp] is installed
try: # pragma: no cover - exercised when cfgit[mcp] is installed
from mcp.server.fastmcp import FastMCP
except ModuleNotFoundError: # pragma: no cover
FastMCP = None # type: ignore[assignment]


def _mcp() -> Any:
if FastMCP is None:
raise ModuleNotFoundError("install cfg-vcs[mcp] to run the cfgit MCP server")
raise ModuleNotFoundError("install cfgit[mcp] to run the cfgit MCP server")
return FastMCP("cfgit")


Expand Down
Loading