Skip to content

Repository files navigation

HapLint

CI Python 3.11+ License: MIT

简体中文

HapLint is an offline command-line linter for Apple Core Haptics .ahap files. It catches malformed assets before they reach a device, produces deterministic console, JSON, and SARIF reports for CI, verifies optional audio resources inside an explicit bundle root, and renders a script-free SVG timeline.

It is deliberately not another haptic editor, generator, or playback runtime. Existing tools are strongest at authoring and previewing; HapLint owns the headless validation step between an authored asset and a shipped bundle.

Static success proves the documented AHAP contract passed. It does not certify playback on Apple hardware or perceptual quality.

What works

  • Recursive, case-insensitive .ahap discovery with deterministic ordering.
  • UTF-8, 5 MiB size, JSON syntax, and duplicate-key checks.
  • AHAP version 1 events, fixed parameters, dynamic parameters, and curves.
  • Apple-documented value ranges, event/parameter compatibility, and the 30-second continuous-haptic limit.
  • Lexical traversal rejection and optional on-disk checks for AudioCustom resources.
  • Stable rule IDs with error/warning severity and JSON Pointer locations.
  • Console, machine-readable JSON, GitHub-compatible SARIF 2.1.0, and inert SVG output.
  • Zero runtime dependencies and no network access.

The complete rule catalog is in docs/rules.md.

Install

Download the wheel from the v0.1.0 release, then install it with Python 3.11 or newer:

python -m pip install haplint-0.1.0-py3-none-any.whl
haplint version

To work from source:

git clone https://github.com/KanadeK/haplint.git
cd haplint
uv sync --locked --dev
uv run --locked haplint version

Quick start

Check one file or any number of files and directories:

haplint check Assets/Haptics
haplint check tap.ahap confirmation.ahap --strict

Produce CI reports without changing the input:

haplint check Assets/Haptics --format json --output haplint.json
haplint check Assets/Haptics --format sarif --output haplint.sarif --strict

Render a valid pattern as a standalone SVG:

haplint render examples/valid/ramp.ahap --output ramp.svg

Check that every AudioCustom waveform exists as a regular file under one bundle root:

haplint check Assets/Haptics --bundle-root Assets

HapLint never searches for waveform files unless --bundle-root is supplied.

Commands and exit status

haplint check PATH [PATH ...] [--format console|json|sarif] [--output FILE]
              [--strict] [--bundle-root DIRECTORY]
haplint render FILE --output FILE.svg [--bundle-root DIRECTORY]
haplint rules [--format console|json]
haplint version
  • 0: no errors; warnings are allowed unless --strict is set.
  • 1: an input, resource, output, or lint failure occurred.
  • 2: invalid command syntax, reported by argparse.

Example warning:

examples/warnings/clamped.ahap
  /Pattern/0/Event/EventParameters/0/ParameterValue [warning AHP027] HapticIntensity will be clamped to the range 0 through 1.
Summary: 1 files, 0 errors, 1 warnings

CI

The repository workflow runs the same release gate on Linux/Python 3.11 and Windows/Python 3.14. In another repository, the minimum useful gate is:

haplint check . --strict --format sarif --output haplint.sarif

Upload haplint.sarif with the full-commit-pinned SARIF action already approved by your organization. Each result includes a stable rule ID, severity, source URI, and jsonPointer property.

If a check fails

Symptom Meaning Repair
AHP001, AHP003, or AHP004 The file cannot be decoded as unambiguous UTF-8 JSON. Save as UTF-8, repair JSON syntax, and remove duplicate object keys.
AHP023 A continuous event is missing a positive duration or a haptic duration exceeds 30 seconds. Add EventDuration; split haptics longer than 30 seconds.
AHP027 Core Haptics will clamp a parameter. Move the value into the range shown by the diagnostic, or omit --strict only when the clamp is intentional.
AHP030 or AHP031 An AudioCustom path is unsafe or missing under the selected bundle root. Use a relative path without .., then place a regular file at that exact location.
Exit 2 The command shape is invalid. Run haplint --help or haplint COMMAND --help and correct the arguments.
Local release gate fails before tests The locked development environment is absent or stale. Run uv sync --locked --dev, then rerun the acceptance command below.

Do not suppress an error to get a green build. Run haplint rules for the rule-specific fix, repair the asset or command boundary, and rerun the exact failing command.

Development and acceptance

The project uses Python 3.11+, uv 0.11.20, pytest, Ruff, mypy, build, coverage, and pip-audit. Behavior changes begin with a failing test.

uv sync --locked --dev
uv run --locked python scripts/check.py

That one acceptance command checks the lock file, formatting, lint, strict types, branch coverage, dependency advisories, committed pass/warn/fail examples, wheel and sdist contents, and a clean-environment wheel installation. See CONTRIBUTING.md for the small-step workflow.

Contract and trust boundary

The implementation follows Apple's public AHAP representation and event-duration documentation. Apple documents defaults for omitted values, clamping for out-of-range values, and ignored unsupported keys, so HapLint reports range and unknown-key findings as warnings while malformed required structure and checked-resource failures are errors.

HapLint does not execute asset content, contact a service, play haptics, inspect an app bundle without an explicit root, or replace real-device QA.

License

MIT. See LICENSE.

About

Offline AHAP static analysis and CI reporting for Apple Core Haptics

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages