--- name: ignition-exchange-conformance description: Check whether an Ignition project follows Inductive Automation's Exchange Resources Style Guide before publishing it to the Ignition Exchange — resource namespacing, Perspective/Vision/Python naming, session-property and client-tag prohibitions, database and logger conventions. Use when asked whether a project is Exchange-ready, when reviewing a project intended for the Exchange, or before packaging one for upload. Bundles a stdlib-only checker (exchange_lint.py) with 38 rules traceable to guide sections. Not for converting a project into that shape (use ignition-exchange-convert) or for packaging and upload (use ignition-exchange-publish). --- # Ignition Exchange conformance Tests an Ignition project against the **Exchange Resources Style Guide v1.1.0**. Run `exchange_lint.py` (next to this file) rather than checking by eye — the rules are numerous, mechanical, and easy to half-apply. ```bash python3 skills/ignition-exchange-conformance/exchange_lint.py /path/to/Project ``` Stdlib only, Python 3.8+. Accepts a project folder, a folder containing several projects (a gateway's `projects/` directory, an unpacked backup, a repo), or a project export `.zip`. Infers the resource name from the namespace the project already uses — no configuration. Exit codes: `0` clean · `1` findings · `2` bad invocation. ## The one rule that is actually mandatory The guide states exactly one `must`: > Views, Styles, Scripts, etc. **must** all be contained within an appropriately > named folder. This allows easy imports into existing projects, where those > project resources won't overlap, interfere, or overwrite existing parts of a > user's project. Everything else — every naming convention — the guide explicitly calls optional: > We encourage you to follow the naming conventions outlined here, however > **these conventions are not required**. So `exchange_lint.py` reports **FAIL** only for namespacing, the storage prohibitions, and the project-name character set. Naming is **warn**. Don't report warnings as blockers, and don't contort a project to silence them — a consistent house style is defensible and the guide says so. ## Namespace roots — two spellings, both correct Per the guide's Project Browser screenshots: | Under `Exchange//` | Under `exchange//` | |---|---| | Views, Windows, Templates | Style classes | | Named queries, Reports | Project library (scripts) | | SFCs, Transaction groups | WebDev sources | | Alarm pipelines, Tags | Images | Uppercase roots take Title Case or PascalCase names; lowercase roots take kebab-case — **except the project library**, which is code-facing and uses lowercase or camelCase (`exchange.resourceName.scriptName`), matching the logger convention `exchange.resourceName.LoggerName`. ## The storage prohibitions Both are FAILs because both break a clean import, and both are easy to miss: - **Perspective session custom properties.** Importing merges them into the host project's session props. Use view `params`/`custom` plus, for genuinely non-reactive cross-session state, `system.util.getGlobals()['exchange']['resourceName']`. - **Vision client tags.** Same reasoning. ## What the checker cannot tell you A clean report is not "ready to publish". Four gaps, all of which have bitten: 1. **Content.** It reads names and structure, never label text. A visible string like `"Acme Internal — do not distribute"` passes silently. Always sweep user-visible strings separately: ```bash python3 - <<'PY' import json, glob, re bad = re.compile(r"internal|confidential|do not|contest|demo only|todo|fixme", re.I) for f in glob.glob("/com.inductiveautomation.perspective/views/**/view.json", recursive=True): v = json.load(open(f)) def walk(n): yield n for c in (n.get("children") or []): yield from walk(c) for n in walk(v.get("root") or {}): t = (n.get("props") or {}).get("text") if isinstance(t, str) and bad.search(t): print(f, repr(t)) PY ``` 2. **Referential integrity.** It does not check that embedded view paths, style classes or script calls resolve. See [`ignition-exchange-convert`](../ignition-exchange-convert/SKILL.md). 3. **Runtime.** Nothing is executed or rendered. Load it on a gateway. 4. **The shipped package.** `E-UP-DOCS` checks the working directory, not the export zip. See [`ignition-exchange-publish`](../ignition-exchange-publish/SKILL.md). ## Findings that need interpretation **`E-STYLE-HIER` false positives.** It decides where a style class belongs by finding which views reference it, reading only literal `props.style.classes` strings. A class name built in a binding — `'exchange/my-resource/priority/' + level.lower()` — is invisible, so a shared class gets reported as single-use. Verify before acting; do not duplicate style classes to silence it. **`E-PAGE-ROOT` is not in the guide.** It is an added caution: a page mapped to `/` takes over the root URL of any project the resource is imported into. Real trade-off — a resource with no `/` page cannot be launched from the gateway's Perspective project list, only from its full page URL (`/data/perspective/client//`). Flag the trade-off; let the user decide. **`E-PY-VAR` volume.** A project written in idiomatic snake_case produces hundreds of warnings, since the guide wants camelCase for variables and functions (deliberately diverging from PEP-8; `UPPER_SNAKE` constants are exempt). Offer `--ignore E-PY-VAR,E-PY-FUNC` rather than a mass rename, unless the user wants full conformance. ## Useful flags | Flag | Effect | |---|---| | `--strict` | Warnings become failures | | `--ignore E-PY-VAR,E-COMP-NAME` | Suppress rules | | `-v` | Every occurrence (default collapses at 8 per rule) | | `--json` | Machine-readable, for CI | | `--list-rules` | All 38 rules with descriptions | ## Reporting results The summary reads `N of 38 rules exercised`. Quote it. A project with no database, tags or named queries legitimately leaves those rules unexercised, and a narrow pass should never be presented as a broad one. ## Self-test `test_exchange_lint.py` builds a conforming and a non-conforming project in a temp dir, asserts the checker is silent on the first and fires the expected rule id on the second, and asserts every declared rule is reachable in the source. Run it after changing any rule. ```bash python3 skills/ignition-exchange-conformance/test_exchange_lint.py ``` ## Provenance Rules derived from Exchange Resources Style Guide v1.1.0. Checker exercised against four real Perspective projects and validated on Ignition **8.1.20** and **8.3.7** gateways (2026-09-15). Where a rule is an inference rather than guide text it says so in its description — currently only `E-PAGE-ROOT`.