Metadata-Version: 2.4
Name: enforce-cpg
Version: 0.3.0
Summary: Code Property Graph MCP server for DayZ Enforce Script
Project-URL: Homepage, https://github.com/tommyz4/enforce-cpg
Project-URL: Repository, https://github.com/tommyz4/enforce-cpg
Project-URL: Issues, https://github.com/tommyz4/enforce-cpg/issues
Author: tommyz4
License-Expression: AAL
License-File: LICENSE
License-File: NOTICE
Keywords: code-property-graph,dayz,enforce,enfusion,mcp,tree-sitter
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Attribution Assurance License
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.12
Requires-Dist: mcp>=1.2
Requires-Dist: tree-sitter-enforce>=0.4
Requires-Dist: tree-sitter>=0.25
Provides-Extra: remote
Requires-Dist: paramiko>=3; extra == 'remote'
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/logo.svg" alt="enforce-cpg — Code Property Graph for Enforce Script" width="460">
</p>

<h1 align="center">enforce-cpg</h1>

A Code Property Graph (CPG) MCP server for **DayZ Enforce Script and BI
config** (`config.cpp`/`config.bin`, e.g. `cfgVehicles`, `cfgWeapons`).

It parses Enforce Script (`.c`) source *and* config documents across a
vanilla/base layer and any number of mod layers, builds a graph of
declarations, inheritance, overrides, and calls, and exposes that graph over
the [Model Context Protocol](https://modelcontextprotocol.io) so an AI
coding assistant (e.g. Claude Code) can answer real DayZ-modding questions:
"what overrides `Speak` on `Animal`?", "who calls this method?", "what's the
effective `attachments[]` on `Plot` after every mod's layered
`+=`/`=`?", "what breaks if I change this field?" — across a real,
multi-mod, load-order-dependent codebase, without ever crashing on a bad
file, a corrupt PBO, or an unresolved reference.

**Config ingestion.** Both loose `config.cpp` (text) and `config.bin`
(BI's rapified binary form) are discovered — as standalone files under a
source root, or embedded inside any `*.pbo`, in either combination — and
folded into the same graph as script declarations. A corrupt PBO, an
unsupported/malformed rapified `config.bin`, or a syntactically broken
`config.cpp` is recorded as a diagnostic and skipped; it never aborts the
rest of the index run (see `doctor` below).

## Install

```bash
pip install enforce-cpg        # or: uvx enforce-cpg --version
```

No compiler needed — the Enforce grammar ships as prebuilt wheels
([`tree-sitter-enforce`](https://pypi.org/project/tree-sitter-enforce/)).

Or, from a local checkout:

```bash
uv sync
uv run enforce-cpg --version
```

## Configure

Point `enforce-cpg` at your vanilla scripts and mod source trees with an
`enforce-cpg.toml` file (see `enforce-cpg.example.toml`):

```toml
[project]
name = "my-server"
db_path = ".enforce-cpg/index.db"

# ORDERED: earlier = loaded first = base precedence. This IS your -mod= order.
[[sources]]
name = "vanilla"
kind = "core"
path = "/path/to/vanilla_scripts"

[[sources]]
name = "MyMod"
kind = "mod"
path = "/path/to/MyMod/Scripts"
```

**Load-order warning:** the order of `[[sources]]` in this file *is* the
override precedence used to resolve `Animal.Speak`-style method stacks —
it must mirror your server's real `-mod=` load order. A source listed
earlier is treated as the base that later sources override; get this
wrong and `resolve_method` / `get_inheritance` will report the wrong
override winner.

Build (or rebuild) the index:

```bash
uv run enforce-cpg index --config enforce-cpg.toml --full
```

### Optional: pulling PBOs from a live game host over SFTP

`pip install enforce-cpg[remote]` (or `uv sync --extra remote`) pulls in
`paramiko` and unlocks a `kind = "sftp"` source, e.g.:

```toml
[[sources]]
name = "live-server"
kind = "sftp"
path = "/mpmissions"
host = "game.example.com"
port = 22
username = "svc"
# either password or key_filename:
password = "..."
# key_filename = "/path/to/key"
```

`enforce_cpg.remote` gives sequential, single-retry `list_pbos`/`fetch_pbo`
helpers built specifically for a live DayZ server's SFTP endpoint (never
fanned out — a game server's SFTP side channel does not tolerate concurrent
requests).

**Honest gap:** as of this release, `kind="sftp"` is **not yet wired into
indexing** — `discover`/`discover_configs` never consult a source's `kind`,
so a configured sftp source currently contributes 0 files and 0 configs.
`doctor` warns about this explicitly for any sftp source in your config,
so it never fails silently. Actually pulling PBOs from the source over
SFTP and indexing them still requires wiring `enforce_cpg.remote` into
discovery — not done yet.

## Tools

Once registered with an MCP client, enforce-cpg exposes 14 tools:

| Tool | Description |
| --- | --- |
| `reindex` | Rebuild the code property graph index (script **and** config). `full=true` forces a from-scratch rebuild; otherwise only changed files are re-indexed (config documents are always re-processed). |
| `find_symbol` | Find declarations of a symbol by name (optionally filtered by kind). |
| `find_references` | Find references to a symbol by name (optionally filtered by kind; declaration sites excluded by default). |
| `find_callers` | Find callers of a class method, transitively up to the given depth. |
| `resolve_method` | Resolve a **script method** call to its override stack across the source layers (core -> mods), in resolution order, flagging cut chains. |
| `get_inheritance` | Get the inheritance chain for a class, optionally scoped to a method. |
| `impact_of_change` | Estimate the blast radius of changing a class member. |
| `get_overview` | Get a structural overview of a file or a class. |
| `audit_overrides` | Corpus-wide **script method** override audit: every modded layer that overrides without `super`, silently disabling earlier layers. **Script only** — says nothing about config; use `audit_configs` for that. |
| `resolve_config` | Resolve a **config** class+property (e.g. a `cfgVehicles`/`cfgWeapons` entry) to its effective runtime value by folding every source layer in load order — config's analogue of `resolve_method`. Returns the fold chain, the final value, and `class_found` (false only if the class itself was never declared anywhere — distinguishes a typo'd class name from a property that's genuinely never set). |
| `audit_configs` | Corpus-wide **config** clobber audit: every place a later layer's `=` silently discards one or more earlier layers of the same class's own property. Each finding is labeled `discards_append` (a discarded layer was a `+=` — an array silently thrown away, the headline case) and `value_changed` (false = a verbatim redeclaration, i.e. layer noise, not damage). On a real corpus most findings are `value_changed=false`; prioritize `discards_append=true` / `value_changed=true`. |
| `mod_surface` | Every method a given source overrides that another source also defines — the mod's footprint on the shared hierarchy. |
| `doctor` | Setup + index-health report: sources resolved, per-source **script and config** parse coverage (`cfg_cpp`/`cfg_bin`/`cfg_parse_ok`/`cfg_bin_skipped`/`pbos_opened`), missing-vanilla warning, config-specific warnings (scripts present but 0 configs indexed; a `config.bin` skipped as unrapifiable/unsupported version, named; **any** PBO under a source that couldn't be opened, even if others in the same source opened fine; a `kind="sftp"` source, which isn't wired into indexing yet), diagnostics, unresolved hotspots. |
| `ingest` | Register a source from an extracted directory (always) or a `.pbo` (de-rapified if a tool is available), copied under `.enforce-cpg/sources/` with the `[[sources]]` entry added. |

The CLI (`enforce-cpg <subcommand>`) covers only part of this surface —
`index`/`reindex`, `find-symbol`, `overview` (`get_overview`), `audit`
(`audit_overrides` only — **script** overrides, not `audit_configs`),
`mod-surface --source <name>`, `doctor`, and
`ingest <path> --name <n> --kind core|mod`. `find_references`,
`find_callers`, `resolve_method`, `get_inheritance`, `impact_of_change`,
`resolve_config`, and `audit_configs` — including both of this project's
config-analysis tools — have **no CLI command** and are reachable only
through an MCP client (e.g. Claude Code) or by importing `enforce_cpg.query`
directly in Python.

Every tool degrades honestly instead of crashing: an unresolved symbol or
a missing argument returns `{"results": [], "note": "..."}` rather than an
exception, so a single bad query never takes down the server.

## Register with Claude Code

```bash
claude mcp add enforce-cpg -- uvx --from enforce-cpg enforce-cpg-mcp
```

The server reads its config from the `ENFORCE_CPG_CONFIG` environment
variable (defaults to `enforce-cpg.toml` in the working directory), so
set that to your project's config path when registering.

## License

enforce-cpg is licensed under the **Attribution Assurance License (AAL)**,
an OSI-approved open-source license. The full license text is in
[`LICENSE`](./LICENSE); the required attribution notice is in
[`NOTICE`](./NOTICE).

Attribution notice (must be displayed per the AAL's attribution clause):

```
enforce-cpg — © 2026 tommyz4 — https://github.com/tommyz4/enforce-cpg
```
