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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Install test toolchain
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
python -m pip install -e . --group dev
- name: Test
# Existing tests intentionally import examples.* from the repository
# checkout. `python -m pytest` puts the checkout root on sys.path;
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ jobs:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- run: pip install ".[docs]"
- name: Install package and docs toolchain
run: |
python -m pip install --upgrade pip
python -m pip install . --group docs
- run: mkdocs build
- uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python-next.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
python -m pip install -e . --group dev
- name: Test
run: pytest -q
run: python -m pytest -q
10 changes: 5 additions & 5 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: security-audit

# Standalone supply-chain scan (#79). Deliberately scoped to vulnerability
# auditing only — the test matrix / lint / format / type checks are owned by a
# separate CI workflow (#22). This runs pip-audit over the installed extras so a
# known-vulnerable dependency in an optional SDK is surfaced early.
# separate CI workflow (#22). This runs pip-audit over the installed functional
# extras so a known-vulnerable optional provider/signing dependency is surfaced.

on:
push:
Expand All @@ -24,10 +24,10 @@ jobs:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install package with all extras and the audit tool
- name: Install package with user extras and audit toolchain
run: |
python -m pip install --upgrade pip
pip install ".[dev,llm,openai,sign,audit]"
python -m pip install ".[llm,openai,sign]" --group audit
- name: Run pip-audit
# Advisory mode first (does not fail the build) so we establish a clean
# baseline before making it blocking. Flip to remove `|| true` once green.
Expand All @@ -43,7 +43,7 @@ jobs:
- name: Generate a CycloneDX SBOM
run: |
python -m pip install --upgrade pip
pip install ".[audit]"
python -m pip install ".[llm,openai,sign]" --group audit
cyclonedx-py environment --output-format json --outfile sbom.json || \
cyclonedx-py --help
- name: Upload SBOM artifact
Expand Down
22 changes: 17 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ contract reviewers hold PRs to.
```bash
git clone https://github.com/dgenio/intentflow
cd intentflow
pip install -e ".[dev]"
python -m pip install --upgrade pip
python -m pip install -e . --group dev
python -m pytest
```

The runtime core has **zero third-party dependencies**; provider SDKs, signing,
schema validation, docs, and audit tooling are optional extras
(`.[llm]`, `.[openai]`, `.[sign]`, `.[docs]`, `.[audit]`). `.[dev]` pulls in
`pytest`, `jsonschema`, and `cryptography` so the whole suite runs.
The runtime core has **zero third-party dependencies**. Published functional
extras are only capabilities downstream users may intentionally install:
`intentflow[llm]` (Anthropic), `intentflow[openai]`, and `intentflow[sign]`.
Maintainer-only test, documentation, and supply-chain tooling lives in PEP 735
dependency groups instead of wheel optional-dependency metadata:

```bash
python -m pip install -e . --group dev # tests / schema validation / signing tests
python -m pip install -e . --group docs # MkDocs site tooling
python -m pip install -e . --group audit # pip-audit / CycloneDX tooling
```

Empty legacy `dev`, `docs`, and `audit` extras remain temporarily as harmless
source-install compatibility shims; new contributor documentation and CI must
use the dependency groups above.

## Design invariants (read before changing runtime code)

Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,24 @@ intentflow audit examples/opensource_triage.iflow result.json # NONCONFORMANT

## Install

Install the published package from PyPI. The core has zero runtime dependencies:

```bash
pip install -e . # core: zero runtime dependencies
pip install -e ".[dev]" # + pytest, jsonschema, cryptography
pip install -e ".[openai]" # + OpenAI-compatible backend
pip install -e ".[llm]" # + Anthropic backend
pip install -e ".[docs]" # + MkDocs (build the docs site)
pip install intentflow
```

Install only the user-facing capability you need:

```bash
pip install "intentflow[openai]" # OpenAI-compatible backend
pip install "intentflow[llm]" # Anthropic backend
pip install "intentflow[sign]" # signed traces / verification support
```

Editable installs, tests, docs tooling, and supply-chain audit tooling are
maintainer workflows and live in [CONTRIBUTING.md](CONTRIBUTING.md), not in the
public install path.

Or run it in a container without touching your Python — see
[Run in Docker](#run-in-docker).

Expand Down
27 changes: 22 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,34 @@ Changelog = "https://github.com/dgenio/intentflow/blob/main/CHANGELOG.md"
intentflow = "intentflow.cli:main"

[project.optional-dependencies]
# User-facing capabilities only. Maintainer test/docs/audit tooling lives in
# PEP 735 dependency groups below and is not part of the published dependency
# contract.
llm = ["anthropic>=0.40"]
openai = ["openai>=1.40"]
sign = ["cryptography>=42"]
# Backward-compatible source-install shims. Historical contributor/docs commands
# may still reference these extras; keep them harmless while new documentation
# uses PEP 735 groups. They intentionally publish no maintainer dependencies.
dev = []
docs = []
audit = []

[dependency-groups]
dev = [
"pytest>=7",
"jsonschema>=4.18",
"cryptography>=42",
"tomli>=2; python_version<'3.11'",
]
llm = ["anthropic>=0.40"]
openai = ["openai>=1.40"]
sign = ["cryptography>=42"]
docs = ["mkdocs>=1.6", "mkdocs-material>=9.5"]
audit = ["pip-audit>=2.7", "cyclonedx-bom>=4.0"]
docs = [
"mkdocs>=1.6",
"mkdocs-material>=9.5",
]
audit = [
"pip-audit>=2.7",
"cyclonedx-bom>=4.0",
]

[tool.setuptools.packages.find]
include = ["intentflow*"]
Expand Down
45 changes: 41 additions & 4 deletions tests/test_dependency_policy.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Dependency policy tests for IntentFlow's stdlib-only core.

The guard itself intentionally uses only the standard library plus pytest from
the dev extra. Runtime dependencies must stay empty; provider SDKs belong
behind optional extras and lazy imports.
the dev dependency group. Runtime dependencies must stay empty; user-facing
provider/signing SDKs belong behind optional extras, while maintainer tooling
belongs in PEP 735 dependency groups.
"""

from __future__ import annotations
Expand All @@ -18,12 +19,24 @@

ROOT = Path(__file__).resolve().parents[1]
STDLIB_MODULES = set(sys.stdlib_module_names) | {"__future__"}
PUBLIC_FUNCTIONAL_EXTRAS = {"llm", "openai", "sign"}
LEGACY_EMPTY_EXTRAS = {"dev", "docs", "audit"}
MAINTAINER_GROUPS = {"dev", "docs", "audit"}


def _runtime_dependencies(pyproject_text: str) -> list[str]:
def _load_pyproject(pyproject_text: str) -> dict:
try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.10 fallback
import tomli as tomllib

return tomllib.loads(pyproject_text)


def _runtime_dependencies(pyproject_text: str) -> list[str]:
try:
payload = _load_pyproject(pyproject_text)
except ModuleNotFoundError: # pragma: no cover - defensive fallback
match = re.search(
r"(?ms)^\[project\]\s*(.*?)(?:^\[|\Z)",
pyproject_text,
Expand All @@ -39,7 +52,6 @@ def _runtime_dependencies(pyproject_text: str) -> list[str]:
return []
return [item.strip().strip("\"'") for item in raw.split(",") if item.strip()]

payload = tomllib.loads(pyproject_text)
return list(payload["project"].get("dependencies", []))


Expand Down Expand Up @@ -86,6 +98,31 @@ def test_core_runtime_dependencies_stay_empty() -> None:
assert deps == []


def test_published_extras_only_expose_user_capabilities() -> None:
payload = _load_pyproject((ROOT / "pyproject.toml").read_text())
extras = payload["project"].get("optional-dependencies", {})
assert set(extras) == PUBLIC_FUNCTIONAL_EXTRAS | LEGACY_EMPTY_EXTRAS
assert all(extras[name] for name in PUBLIC_FUNCTIONAL_EXTRAS)
assert all(extras[name] == [] for name in LEGACY_EMPTY_EXTRAS)

groups = payload.get("dependency-groups", {})
assert set(groups) == MAINTAINER_GROUPS
assert all(groups[name] for name in MAINTAINER_GROUPS)


def test_public_install_section_is_registry_first() -> None:
readme = (ROOT / "README.md").read_text()
install = readme.split("## Install", 1)[1].split("## Quickstart", 1)[0]
assert "pip install intentflow" in install
assert 'pip install "intentflow[openai]"' in install
assert 'pip install "intentflow[llm]"' in install
assert 'pip install "intentflow[sign]"' in install
assert "pip install -e" not in install
assert "intentflow[dev]" not in install
assert "intentflow[docs]" not in install
assert "intentflow[audit]" not in install


def test_intentflow_modules_only_import_stdlib_or_intentflow_at_top_level() -> None:
violations = {
path.relative_to(ROOT).as_posix(): sorted(
Expand Down
Loading