Free, open-source linter for Acumatica customization projects. The full pipeline is AcuOps.
Project description
acumatica-lint
Free, open-source linter for Acumatica customization projects. Catches publish-time failure modes — silent
NullReferenceExceptions, dropped DAC extensions, orphan SiteMap rows, GI catalog exclusions, schema mutations withoutIF NOT EXISTSguards — before they restart your production app pool.Want this run automatically on every PR, plus sandbox dry-run before prod, snapshot/rollback with integrity verification, Codex AI review, Slack telemetry, and deploy-window enforcement? → AcuOps
Install
pip install acumatica-lint
Python 3.10+. One dependency (pyyaml for the ISV co-publish allowlist parser). Works on macOS, Linux, and Windows.
Use
Point it at a project.xml:
acumatica-lint Customization/MyPackage/project.xml
With strict mode (additional warnings for best-practice violations):
acumatica-lint --strict Customization/MyPackage/project.xml
ISV authors — enforce a class-name prefix on all <Graph> declarations:
acumatica-lint --isv-prefix AO Customization/MyISV/project.xml
Debug-only DLL-source plugin scan (skips XML/Graph/semantic checks):
acumatica-lint --dll-source-only Customization/MyPackage/project.xml
Exit codes
0— passed (with or without warnings)1— failed (one or more hard-fail errors)2— usage error (bad CLI args)
What it catches
acumatica-lint encodes ~60 failure-mode rules from real Acumatica production incidents Studio B has resolved. A non-exhaustive list:
- DAC fields without matching
<Sql>ALTER TABLE — auto-column creation has been broken on cloud Acumatica since early 2026.[PXDBx]fields without a corresponding<Sql>ALTER will silently fail at publish. - Phantom DAC references in
<GITable Name>— silently aborts GI processing without an error in the publish log. - GI Creation Contract — every new OData-exposed GI requires
<RolesInGraph Rolename="*" Accessrights="4" ApplicationName="/" />AND a matching entry in the package'sCustomizationPluginallowlist. Miss either and the GI silently catalog-excludes, returning HTTP 404 from/OData/{tenant}/$metadata. - SiteMap orphans (
ParentID="00000000-...") — invisible inSM205010 (Access Rights by Role), ungrantable, OData 404. - Modern GI format requirements (24.208+) —
GITable.Namemust be fully-qualified DAC name;<GIWhere Condition>must use Acumatica's vocabulary ("E ","NE","GE", etc., NOT"EQ");<GIWhere>attribute name isOperation=NOTOperator=;MUIScreen.SubcategoryIDmust point at tenant-real subcategory, not auto-created system-default. <Sql>re-execution warnings —<Sql>scripts are tracked by Name; bumping to_v2is silently skipped. For logic that must survive every publish, useCustomizationPlugin.PXDatabase.Execute().- DAC extension fields without
<EntityEndpoint>mapping —PUTreturns HTTP 200 + echoes value + silent-drops at commit. Three places must match: SQL column, DAC attribute, inline<EntityEndpoint>block. - C# CustomizationPlugin static checks — banned APIs (
WebConfigurationManager,HttpContext); SQL against GI* tables inUpdateDatabase(); SQL againstINUnit; INSERT/UPDATE/SELECT column refs on Acumatica system tables outside the allowlist. - DLL source reference auditing — gap-closer for compiled-DLL plugins: every plugin check runs on every
.csundersrc/<DllName>/whenproject.xmlreferences<File AppRelativePath="Bin\<DllName>.dll" />. - ISV prefix enforcement — when invoked with
--isv-prefix, validates that every<Graph ClassName=>follows your ISV convention. - Inline
<EntityEndpoint>block requirements — must be a top-level child of<Customization>inproject.xml; standaloneEntityEndpoint_*.xmlat zip root is NOT processed.
Every hard-fail check ships an escape-hatch comment marker for audited exceptions:
<!-- REVIEWED: gi-sql-safe — suppress validate_gi_sql -->
<!-- REVIEWED: inunit-sql-safe — suppress validate_inunit_sql -->
<!-- REVIEWED: schema-safe — suppress validate_plugin_sql_column_refs -->
What this linter is NOT
This linter is the deterministic check engine. It runs once, against one project, returns errors and warnings, exits.
It does not:
- Run continuously on every PR
- Push your customization to a sandbox tenant and run an end-to-end test suite before allowing the prod publish
- Take a snapshot of your current customization state with integrity verification before each publish (catching
merge=truepartial-publish corruption) - Roll back automatically when a publish fails midway
- Review the C# code in
CustomizationPlugin.UpdateDatabase()with AI to flag non-obvious correctness issues a static rule can't catch - Post
:rotating_light:Slack notifications when a publish fails - Enforce deploy windows (no schema changes during business hours)
- Coordinate co-publishes with ISV packages without inflating the
publishBeginpage count past Acumatica'sThreadAbortExceptionlimit - Issue + revoke per-VAR license keys with grace periods
All of that is AcuOps — the managed pipeline. $800/mo per Acumatica instance.
Founding partners
AcuOps is selecting 7–10 founding partners locked at $500/mo for the life of subscription in exchange for a published case study + reference availability. We're picking partners across VAR, ISV, in-house mid-market, and enterprise deployment patterns. → Apply
Contributing
Pull requests welcome. New rules should:
- Reference the originating production incident (AAR date, PR number, or both) in a comment
- Ship with both a positive example (
project.xmlsnippet that triggers the rule) and a negative example - Include an escape-hatch comment marker if the rule may have legitimate exceptions
Coding style is enforced by ruff. Run pip install -e ".[dev]" then ruff check src/.
License
Apache 2.0 — see LICENSE.
The Apache 2.0 patent grant protects all contributors from patent litigation over contributed code. This matters when the codebase encodes hard-won failure-mode knowledge from real production incidents.
Built by Studio B
Studio B (studiob.ai) runs Acumatica deployments at scale across its portfolio. acumatica-lint is the static-check engine extracted from the AcuOps pipeline — the same code paths Studio B uses against its own production deployments.
The full pipeline → acuops.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acumatica_lint-0.1.0.tar.gz.
File metadata
- Download URL: acumatica_lint-0.1.0.tar.gz
- Upload date:
- Size: 71.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b680c5bd64812d36d5719a53cbf6951dd575eed43edd8fe44294fac545880b58
|
|
| MD5 |
5c0e53f947f1a081f0abfddc2717e223
|
|
| BLAKE2b-256 |
5b6100eeac9899f5fd6857c68affa04d51062342af4df0e8e7210517f4eee55f
|
File details
Details for the file acumatica_lint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: acumatica_lint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 70.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6df4474734615ff00793949efeb8c6818054a6e631e8af367a616566f450f2b7
|
|
| MD5 |
5b4bda68db027b3de884ddf05c0be3ef
|
|
| BLAKE2b-256 |
df80df19ee92c4f52040f0d2871f3c0e04efe4252eecb6a4f0d1aa7084376a7a
|