athar-ifc 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. athar_ifc-0.1.0/PKG-INFO +281 -0
  2. athar_ifc-0.1.0/README.md +260 -0
  3. athar_ifc-0.1.0/athar/__init__.py +1 -0
  4. athar_ifc-0.1.0/athar/__main__.py +193 -0
  5. athar_ifc-0.1.0/athar/_native/Cargo.lock +347 -0
  6. athar_ifc-0.1.0/athar/_native/Cargo.toml +40 -0
  7. athar_ifc-0.1.0/athar/_native/src/canon.rs +825 -0
  8. athar_ifc-0.1.0/athar/_native/src/descriptor.rs +103 -0
  9. athar_ifc-0.1.0/athar/_native/src/edges.rs +309 -0
  10. athar_ifc-0.1.0/athar/_native/src/lib.rs +912 -0
  11. athar_ifc-0.1.0/athar/_native/src/spatial.rs +335 -0
  12. athar_ifc-0.1.0/athar/_native/src/step.rs +500 -0
  13. athar_ifc-0.1.0/athar/bottom/__init__.py +1 -0
  14. athar_ifc-0.1.0/athar/bottom/_native.py +23 -0
  15. athar_ifc-0.1.0/athar/bottom/constants.py +6 -0
  16. athar_ifc-0.1.0/athar/bottom/native_schema.py +129 -0
  17. athar_ifc-0.1.0/athar/bottom/parser.py +169 -0
  18. athar_ifc-0.1.0/athar/bottom/properties.py +139 -0
  19. athar_ifc-0.1.0/athar/bottom/schema_dict.py +10 -0
  20. athar_ifc-0.1.0/athar/bottom/signatures.py +95 -0
  21. athar_ifc-0.1.0/athar/bottom/types.py +57 -0
  22. athar_ifc-0.1.0/athar/check.py +408 -0
  23. athar_ifc-0.1.0/athar/delta/__init__.py +1 -0
  24. athar_ifc-0.1.0/athar/delta/report.py +248 -0
  25. athar_ifc-0.1.0/athar/engine.py +235 -0
  26. athar_ifc-0.1.0/athar/matcher/__init__.py +1 -0
  27. athar_ifc-0.1.0/athar/matcher/core.py +117 -0
  28. athar_ifc-0.1.0/athar/matcher/types.py +13 -0
  29. athar_ifc-0.1.0/athar/policies/data-integrity.json +5 -0
  30. athar_ifc-0.1.0/athar/policies/georeferencing.json +9 -0
  31. athar_ifc-0.1.0/athar/policies/no-deletions.json +5 -0
  32. athar_ifc-0.1.0/athar/policies/safety-critical.json +9 -0
  33. athar_ifc-0.1.0/athar_bench/__init__.py +1 -0
  34. athar_ifc-0.1.0/athar_bench/ui.py +737 -0
  35. athar_ifc-0.1.0/athar_dev/__init__.py +1 -0
  36. athar_ifc-0.1.0/athar_dev/changeset_scoring.py +91 -0
  37. athar_ifc-0.1.0/athar_dev/ifc_mutations.py +423 -0
  38. athar_ifc-0.1.0/athar_git/__init__.py +7 -0
  39. athar_ifc-0.1.0/athar_git/cache.py +130 -0
  40. athar_ifc-0.1.0/athar_git/cli.py +155 -0
  41. athar_ifc-0.1.0/athar_git/pr_bot.py +302 -0
  42. athar_ifc-0.1.0/athar_git/render.py +227 -0
  43. athar_ifc-0.1.0/athar_qa/__init__.py +10 -0
  44. athar_ifc-0.1.0/athar_qa/render.py +131 -0
  45. athar_ifc-0.1.0/athar_store/__init__.py +21 -0
  46. athar_ifc-0.1.0/athar_store/cli.py +145 -0
  47. athar_ifc-0.1.0/athar_store/store.py +395 -0
  48. athar_ifc-0.1.0/athar_view/__init__.py +3 -0
  49. athar_ifc-0.1.0/athar_view/cli.py +187 -0
  50. athar_ifc-0.1.0/athar_view/server.py +326 -0
  51. athar_ifc-0.1.0/athar_view/static/assets/event-C6c_xMBB.js +1 -0
  52. athar_ifc-0.1.0/athar_view/static/assets/geometry.worker-BdUwmL7i.js +1 -0
  53. athar_ifc-0.1.0/athar_view/static/assets/ifc-lite_bg-D7jvTNoO.wasm +0 -0
  54. athar_ifc-0.1.0/athar_view/static/assets/index-CEeaJSse.css +1 -0
  55. athar_ifc-0.1.0/athar_view/static/assets/index-tCTOrRZ9.js +4108 -0
  56. athar_ifc-0.1.0/athar_view/static/assets/native-bridge-DJR5bS96.js +1 -0
  57. athar_ifc-0.1.0/athar_view/static/favicon.svg +1 -0
  58. athar_ifc-0.1.0/athar_view/static/index.html +15 -0
  59. athar_ifc-0.1.0/pyproject.toml +64 -0
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: athar-ifc
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Rust
7
+ Requires-Dist: ifcopenshell
8
+ Requires-Dist: ifcdiff ; extra == 'benchmark'
9
+ Requires-Dist: ifcfast ; extra == 'benchmark'
10
+ Requires-Dist: pytest ; extra == 'benchmark'
11
+ Requires-Dist: specklepy ; extra == 'benchmark'
12
+ Requires-Dist: fastapi ; extra == 'benchmark-ui'
13
+ Requires-Dist: uvicorn ; extra == 'benchmark-ui'
14
+ Provides-Extra: benchmark
15
+ Provides-Extra: benchmark-ui
16
+ Summary: Semantic IFC diff engine
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
19
+ Project-URL: Repository, https://github.com/Amixx/Athar
20
+
21
+ # Athar
22
+
23
+ Semantic diff for IFC files. Compares BIM models at the entity/property level — not line-by-line text.
24
+
25
+ *Athar (Arabic: أثار) — a trace left behind.*
26
+
27
+ ## How it works
28
+
29
+ For each IFC file, Athar builds a **signature vector** per product/spatial entity:
30
+
31
+ - `vh_geometry` — sha256 Merkle hash over the entity's geometry-domain subgraph, **excluding placement**, so identical components share it across locations.
32
+ - `vh_data` — Merkle hash over the data-domain subgraph (properties, materials, typing). GlobalId and OwnerHistory never enter any hash.
33
+ - `vh_topology` — WL-style gossip hash over context and spatial neighborhoods.
34
+ - World-space placement matrix (quantized), centroid, and AABB from the resolved `ObjectPlacement` chain.
35
+
36
+ Old/new entities are then matched by **tiered pool reduction**: each tier only
37
+ looks at still-unmatched entities and emits disjoint 1:1 pairs, so there is no
38
+ candidate explosion and no ambiguous fan-out by construction.
39
+
40
+ | Tier | Evidence | Score |
41
+ |------|----------|-------|
42
+ | `guid` | GlobalId unique on both sides + same class | 1.0 (identical vector) / 0.9 |
43
+ | `geometry_hash` | same class + full signature-vector equality (interchangeable entities, zipped 1:1) | 0.8 |
44
+
45
+ Anything weaker is reported as added+deleted rather than guessed: a corpus
46
+ survey over real revision, discipline, and unrelated model pairs
47
+ (`docs/corpus/2026-06-10-corpus-survey.md`) showed that topology-only and
48
+ spatial-proximity fallback matching never fired on a genuine revision pair and
49
+ only manufactured cross-model matches, so those tiers were removed. Duplicated
50
+ GlobalIds are never trusted as identity — those entities fall through to the
51
+ vector tier. Matching is deterministic and entirely algorithmic, with no
52
+ tuning knobs.
53
+
54
+ The delta report classifies every entity as `added`, `deleted`, `modified`, or
55
+ `unchanged`, with per-aspect detail (`geometry/data/topology/placement`,
56
+ `placement_delta_mm`), `change_scope` (`intrinsic` = the entity itself changed,
57
+ `transitive` = only its neighborhood changed, `mixed`), and matcher
58
+ diagnostics (per-tier match counts, duplicate-GUID counts). Reports also carry
59
+ an `audit` block with producer/canon metadata plus old/new input provenance:
60
+ path, schema, byte size, and sha256. That makes the raw JSON suitable for
61
+ archiving as milestone evidence even before a visual viewer or PDF/HTML export
62
+ exists. Default output stays byte-deterministic; pass `--generated-at now` or
63
+ an explicit timestamp when an archived copy should embed a generation time.
64
+
65
+ Schema support: **IFC4 and IFC2X3**, same-schema comparisons only (no
66
+ IFC2X3↔IFC4 translation).
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pipx install athar-ifc
72
+ ```
73
+
74
+ This installs the `athar` command. The PyPI distribution is `athar-ifc`; the
75
+ Python packages import as `athar`, `athar_git`, and so on. Wheels ship the Rust
76
+ signature pipeline (`athar_native`) and the viewer bundle for Linux (x86_64,
77
+ aarch64), macOS (arm64, x86_64), and Windows (x86_64); other platforms build
78
+ from the sdist, which needs a Rust toolchain. Requires Python 3.10+ and
79
+ [ifcopenshell](https://ifcopenshell.org/).
80
+
81
+ Local development (needs a Rust toolchain):
82
+
83
+ ```bash
84
+ python -m venv .venv
85
+ source .venv/bin/activate
86
+ make dev-setup
87
+ make native-build # builds athar_native and installs Athar editable
88
+ make test
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ ```bash
94
+ # Two-file diff (JSON output)
95
+ python -m athar old.ifc new.ifc
96
+
97
+ # Include an audit generation timestamp for an archived report
98
+ python -m athar old.ifc new.ifc --generated-at now
99
+
100
+ # CI policy gate over a fresh diff; exits 2 on policy violations
101
+ python -m athar check old.ifc new.ifc --policy athar-policy.json
102
+
103
+ # CI policy gate over an existing JSON report
104
+ python -m athar check --report report.json --policy athar-policy.json
105
+
106
+ # Stream output as NDJSON records (header, one record per entity, end with stats)
107
+ python -m athar old.ifc new.ifc --stream ndjson
108
+
109
+ # Stream output as chunked JSON
110
+ python -m athar old.ifc new.ifc --stream chunked_json --chunk-size 1000
111
+ ```
112
+
113
+ ### CI Policy Gates
114
+
115
+ `athar check` evaluates an Athar JSON report against a small JSON policy and
116
+ prints structured JSON: `{ "ok": bool, "violations": [...], "summary": ... }`.
117
+ It exits `0` when the policy passes, `2` when the diff is valid but violates
118
+ policy, and `1` for execution/configuration errors.
119
+
120
+ Example policy:
121
+
122
+ ```json
123
+ {
124
+ "forbid_schema_change": true,
125
+ "max_deleted": 0,
126
+ "max_deleted_by_class": {
127
+ "IfcWall": 0
128
+ },
129
+ "max_modified_change_scope": {
130
+ "intrinsic": 10,
131
+ "mixed": 0
132
+ },
133
+ "forbid_site_placement_change": true,
134
+ "max_placement_delta_mm": 50,
135
+ "max_placement_delta_mm_by_class": {
136
+ "IfcSite": 0
137
+ },
138
+ "forbid_aspect_changes": {
139
+ "data": ["IfcWall"],
140
+ "placement": "*"
141
+ }
142
+ }
143
+ ```
144
+
145
+ The gate is report-driven and CI-platform-agnostic. Current policies can only
146
+ inspect signals present in the report: schema equality, section counts,
147
+ class-specific counts, modified change scopes, changed aspects, and placement
148
+ translation deltas. Fine-grained checks such as “property `FireRating` was
149
+ removed” require future report detail beyond the current data hash.
150
+
151
+ ### Git IFC Diff Driver
152
+
153
+ Athar can be installed as a Git diff driver for `.ifc` files. The driver uses
154
+ the same semantic engine, renders a deterministic terminal summary, and caches
155
+ signature bundles on disk by Git blob id (falling back to a content hash for
156
+ working-tree files).
157
+
158
+ ```bash
159
+ # Configure the current repository and add .gitattributes guidance
160
+ athar git install
161
+
162
+ # Manual terminal rendering without Git plumbing
163
+ athar git diff old.ifc new.ifc
164
+ ```
165
+
166
+ `athar git install` sets `diff.athar.command` to
167
+ `athar git diff --external` and appends:
168
+
169
+ ```gitattributes
170
+ *.ifc diff=athar -merge
171
+ ```
172
+
173
+ The `-merge` marker is intentional. IFC is STEP text, but Git's default text
174
+ merge can silently splice two exported models into a corrupt file with
175
+ duplicate or dangling STEP ids. Athar provides semantic diffing; it does not
176
+ attempt semantic merge.
177
+
178
+ The persistent signature cache is versioned and deterministic, but v1 does not
179
+ evict old entries. Set `ATHAR_CACHE_DIR` to isolate or periodically clear a
180
+ large repository's cache.
181
+
182
+ ### GitHub PR Comments
183
+
184
+ Athar also includes a thin GitHub PR bot command for CI. It discovers `.ifc`
185
+ files changed between two Git refs, runs the same cached semantic diff for
186
+ modified/renamed pairs, and creates or updates one stable PR comment marked by
187
+ an HTML sentinel.
188
+
189
+ ```bash
190
+ athar git pr-comment \
191
+ --base "$BASE_SHA" \
192
+ --head "$HEAD_SHA" \
193
+ --repo owner/repo \
194
+ --pr 123
195
+ ```
196
+
197
+ Use `--dry-run` to print the Markdown comment body without calling GitHub.
198
+ When `--policy-result athar-check.json` is supplied, the comment includes a
199
+ pass/fail summary from an `athar check` result, but policy enforcement remains
200
+ separate. The checked-in `.github/workflows/athar-pr-diff.yml` workflow shows
201
+ the default pull-request setup using `GITHUB_TOKEN`.
202
+
203
+ ### Visual 3D Diff Viewer
204
+
205
+ `athar view` overlays both models in one 3D scene in the browser, colored by
206
+ the semantic report: green added, red deleted, blue modified, gray ghosted
207
+ unchanged, amber displacement lines for placement moves. An old↔new slider
208
+ crossfades between revisions; clicking an entity shows its class, GUID,
209
+ changed aspects, placement delta, and match evidence.
210
+
211
+ ```bash
212
+ athar view old.ifc new.ifc # build report, serve, open browser
213
+ athar view old.ifc new.ifc --no-open --port 4799 # print the URL instead
214
+ athar git install --difftool # then: git difftool -t athar -- model.ifc
215
+ ```
216
+
217
+ The browser does all tessellation (`@ifc-lite/geometry` WASM); rendering is
218
+ three.js. `--viewer-url` (or `ATHAR_VIEWER_URL`) hands off to a hosted viewer
219
+ Perfetto-style: the page fetches the models back from the local server via
220
+ `?src=`, so nothing is uploaded. Offline mode serves a local build from
221
+ `ATHAR_VIEWER_DIST`, a release-packaged `athar_view/static`, or a freshly built
222
+ `viewer/dist`. The same SPA also accepts plain drag-drop of `old.ifc`,
223
+ `new.ifc`, and a report JSON — no launcher needed.
224
+
225
+ Viewer development requires [bun](https://bun.sh) and lives entirely under
226
+ `viewer/` (see [viewer/README.md](viewer/README.md)). Build the local bundle
227
+ with `make viewer-build`; `make viewer-package-static` copies it into
228
+ `athar_view/static`, which the release workflow does before building wheels.
229
+
230
+ ## Testing
231
+
232
+ ```bash
233
+ make test # full default suite (~20 seconds)
234
+ make test-large-acceptance # opt-in large IFC acceptance checks
235
+ ```
236
+
237
+ The default suite runs corpus-wide invariant tests (same-file zero diff,
238
+ stats accounting, no cross-class matches, duplicate-GUID and dangling-ref
239
+ behavior, schema policy) over every small real IFC in the corpus — the
240
+ Building-Landscaping v0→v3 revision chain, the IFC2X3 Duplex architecture
241
+ model, two GNI BIM Fundamentals samples, and small external samples — plus
242
+ metamorphic
243
+ GUID-scramble tests that prove identity recovery without GlobalId evidence.
244
+ It also generates known-edit semantic scenarios from real seeds (GUID
245
+ scramble, moving one product by a known vector, deleting a leaf product,
246
+ editing a pset value, editing a type-level pset value inherited by its
247
+ occurrences, renaming, duplicating a GUID) in temp dirs and asserts
248
+ the report against expectations derived from the constructed edit itself,
249
+ never from blessing engine output. Small files from the optional external
250
+ corpus (default `../vscode-ifc/test-files`, override via
251
+ `ATHAR_EXTERNAL_CORPUS_DIR`) skip when absent.
252
+
253
+ Large acceptance checks are opt-in so day-to-day runs stay fast:
254
+
255
+ ```bash
256
+ ATHAR_RUN_LARGE_ACCEPTANCE=1 python -m pytest tests/test_acceptance_large_ifc.py -q
257
+ ```
258
+
259
+ The acceptance tier covers the medium/large corpus (8MB–182MB; same-file
260
+ invariants, a real 44MB revision pair, discipline pairs, unrelated pairs, and
261
+ a GUID-scramble at scale). Files that are missing or unfetched LFS pointers
262
+ skip individually. Bound wall-clock per test via `ATHAR_ACCEPTANCE_TIMEOUT_S`.
263
+
264
+ At every tier, tests assert structural invariants and expectations derived
265
+ from the inputs themselves — never exact whole-report JSON goldens. Corpus
266
+ files too big for the repo will be described by a checked-in manifest rather
267
+ than stored (design: `docs/corpus/REMOTE_CORPUS_MANIFEST.md`; not yet
268
+ implemented).
269
+
270
+ ## Documentation
271
+
272
+ Agent-facing architecture and conventions are split by ownership: start at
273
+ [AGENTS.md](AGENTS.md), then follow the nearest subdirectory `AGENTS.md` for
274
+ core engine, Git integration, tests, docs, and scripts. Dated performance
275
+ findings live under `docs/perf/`, with `docs/perf/STATUS.md` stating what is
276
+ still current. Corpus survey findings live under `docs/corpus/`.
277
+
278
+ ## License
279
+
280
+ MIT
281
+
@@ -0,0 +1,260 @@
1
+ # Athar
2
+
3
+ Semantic diff for IFC files. Compares BIM models at the entity/property level — not line-by-line text.
4
+
5
+ *Athar (Arabic: أثار) — a trace left behind.*
6
+
7
+ ## How it works
8
+
9
+ For each IFC file, Athar builds a **signature vector** per product/spatial entity:
10
+
11
+ - `vh_geometry` — sha256 Merkle hash over the entity's geometry-domain subgraph, **excluding placement**, so identical components share it across locations.
12
+ - `vh_data` — Merkle hash over the data-domain subgraph (properties, materials, typing). GlobalId and OwnerHistory never enter any hash.
13
+ - `vh_topology` — WL-style gossip hash over context and spatial neighborhoods.
14
+ - World-space placement matrix (quantized), centroid, and AABB from the resolved `ObjectPlacement` chain.
15
+
16
+ Old/new entities are then matched by **tiered pool reduction**: each tier only
17
+ looks at still-unmatched entities and emits disjoint 1:1 pairs, so there is no
18
+ candidate explosion and no ambiguous fan-out by construction.
19
+
20
+ | Tier | Evidence | Score |
21
+ |------|----------|-------|
22
+ | `guid` | GlobalId unique on both sides + same class | 1.0 (identical vector) / 0.9 |
23
+ | `geometry_hash` | same class + full signature-vector equality (interchangeable entities, zipped 1:1) | 0.8 |
24
+
25
+ Anything weaker is reported as added+deleted rather than guessed: a corpus
26
+ survey over real revision, discipline, and unrelated model pairs
27
+ (`docs/corpus/2026-06-10-corpus-survey.md`) showed that topology-only and
28
+ spatial-proximity fallback matching never fired on a genuine revision pair and
29
+ only manufactured cross-model matches, so those tiers were removed. Duplicated
30
+ GlobalIds are never trusted as identity — those entities fall through to the
31
+ vector tier. Matching is deterministic and entirely algorithmic, with no
32
+ tuning knobs.
33
+
34
+ The delta report classifies every entity as `added`, `deleted`, `modified`, or
35
+ `unchanged`, with per-aspect detail (`geometry/data/topology/placement`,
36
+ `placement_delta_mm`), `change_scope` (`intrinsic` = the entity itself changed,
37
+ `transitive` = only its neighborhood changed, `mixed`), and matcher
38
+ diagnostics (per-tier match counts, duplicate-GUID counts). Reports also carry
39
+ an `audit` block with producer/canon metadata plus old/new input provenance:
40
+ path, schema, byte size, and sha256. That makes the raw JSON suitable for
41
+ archiving as milestone evidence even before a visual viewer or PDF/HTML export
42
+ exists. Default output stays byte-deterministic; pass `--generated-at now` or
43
+ an explicit timestamp when an archived copy should embed a generation time.
44
+
45
+ Schema support: **IFC4 and IFC2X3**, same-schema comparisons only (no
46
+ IFC2X3↔IFC4 translation).
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pipx install athar-ifc
52
+ ```
53
+
54
+ This installs the `athar` command. The PyPI distribution is `athar-ifc`; the
55
+ Python packages import as `athar`, `athar_git`, and so on. Wheels ship the Rust
56
+ signature pipeline (`athar_native`) and the viewer bundle for Linux (x86_64,
57
+ aarch64), macOS (arm64, x86_64), and Windows (x86_64); other platforms build
58
+ from the sdist, which needs a Rust toolchain. Requires Python 3.10+ and
59
+ [ifcopenshell](https://ifcopenshell.org/).
60
+
61
+ Local development (needs a Rust toolchain):
62
+
63
+ ```bash
64
+ python -m venv .venv
65
+ source .venv/bin/activate
66
+ make dev-setup
67
+ make native-build # builds athar_native and installs Athar editable
68
+ make test
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```bash
74
+ # Two-file diff (JSON output)
75
+ python -m athar old.ifc new.ifc
76
+
77
+ # Include an audit generation timestamp for an archived report
78
+ python -m athar old.ifc new.ifc --generated-at now
79
+
80
+ # CI policy gate over a fresh diff; exits 2 on policy violations
81
+ python -m athar check old.ifc new.ifc --policy athar-policy.json
82
+
83
+ # CI policy gate over an existing JSON report
84
+ python -m athar check --report report.json --policy athar-policy.json
85
+
86
+ # Stream output as NDJSON records (header, one record per entity, end with stats)
87
+ python -m athar old.ifc new.ifc --stream ndjson
88
+
89
+ # Stream output as chunked JSON
90
+ python -m athar old.ifc new.ifc --stream chunked_json --chunk-size 1000
91
+ ```
92
+
93
+ ### CI Policy Gates
94
+
95
+ `athar check` evaluates an Athar JSON report against a small JSON policy and
96
+ prints structured JSON: `{ "ok": bool, "violations": [...], "summary": ... }`.
97
+ It exits `0` when the policy passes, `2` when the diff is valid but violates
98
+ policy, and `1` for execution/configuration errors.
99
+
100
+ Example policy:
101
+
102
+ ```json
103
+ {
104
+ "forbid_schema_change": true,
105
+ "max_deleted": 0,
106
+ "max_deleted_by_class": {
107
+ "IfcWall": 0
108
+ },
109
+ "max_modified_change_scope": {
110
+ "intrinsic": 10,
111
+ "mixed": 0
112
+ },
113
+ "forbid_site_placement_change": true,
114
+ "max_placement_delta_mm": 50,
115
+ "max_placement_delta_mm_by_class": {
116
+ "IfcSite": 0
117
+ },
118
+ "forbid_aspect_changes": {
119
+ "data": ["IfcWall"],
120
+ "placement": "*"
121
+ }
122
+ }
123
+ ```
124
+
125
+ The gate is report-driven and CI-platform-agnostic. Current policies can only
126
+ inspect signals present in the report: schema equality, section counts,
127
+ class-specific counts, modified change scopes, changed aspects, and placement
128
+ translation deltas. Fine-grained checks such as “property `FireRating` was
129
+ removed” require future report detail beyond the current data hash.
130
+
131
+ ### Git IFC Diff Driver
132
+
133
+ Athar can be installed as a Git diff driver for `.ifc` files. The driver uses
134
+ the same semantic engine, renders a deterministic terminal summary, and caches
135
+ signature bundles on disk by Git blob id (falling back to a content hash for
136
+ working-tree files).
137
+
138
+ ```bash
139
+ # Configure the current repository and add .gitattributes guidance
140
+ athar git install
141
+
142
+ # Manual terminal rendering without Git plumbing
143
+ athar git diff old.ifc new.ifc
144
+ ```
145
+
146
+ `athar git install` sets `diff.athar.command` to
147
+ `athar git diff --external` and appends:
148
+
149
+ ```gitattributes
150
+ *.ifc diff=athar -merge
151
+ ```
152
+
153
+ The `-merge` marker is intentional. IFC is STEP text, but Git's default text
154
+ merge can silently splice two exported models into a corrupt file with
155
+ duplicate or dangling STEP ids. Athar provides semantic diffing; it does not
156
+ attempt semantic merge.
157
+
158
+ The persistent signature cache is versioned and deterministic, but v1 does not
159
+ evict old entries. Set `ATHAR_CACHE_DIR` to isolate or periodically clear a
160
+ large repository's cache.
161
+
162
+ ### GitHub PR Comments
163
+
164
+ Athar also includes a thin GitHub PR bot command for CI. It discovers `.ifc`
165
+ files changed between two Git refs, runs the same cached semantic diff for
166
+ modified/renamed pairs, and creates or updates one stable PR comment marked by
167
+ an HTML sentinel.
168
+
169
+ ```bash
170
+ athar git pr-comment \
171
+ --base "$BASE_SHA" \
172
+ --head "$HEAD_SHA" \
173
+ --repo owner/repo \
174
+ --pr 123
175
+ ```
176
+
177
+ Use `--dry-run` to print the Markdown comment body without calling GitHub.
178
+ When `--policy-result athar-check.json` is supplied, the comment includes a
179
+ pass/fail summary from an `athar check` result, but policy enforcement remains
180
+ separate. The checked-in `.github/workflows/athar-pr-diff.yml` workflow shows
181
+ the default pull-request setup using `GITHUB_TOKEN`.
182
+
183
+ ### Visual 3D Diff Viewer
184
+
185
+ `athar view` overlays both models in one 3D scene in the browser, colored by
186
+ the semantic report: green added, red deleted, blue modified, gray ghosted
187
+ unchanged, amber displacement lines for placement moves. An old↔new slider
188
+ crossfades between revisions; clicking an entity shows its class, GUID,
189
+ changed aspects, placement delta, and match evidence.
190
+
191
+ ```bash
192
+ athar view old.ifc new.ifc # build report, serve, open browser
193
+ athar view old.ifc new.ifc --no-open --port 4799 # print the URL instead
194
+ athar git install --difftool # then: git difftool -t athar -- model.ifc
195
+ ```
196
+
197
+ The browser does all tessellation (`@ifc-lite/geometry` WASM); rendering is
198
+ three.js. `--viewer-url` (or `ATHAR_VIEWER_URL`) hands off to a hosted viewer
199
+ Perfetto-style: the page fetches the models back from the local server via
200
+ `?src=`, so nothing is uploaded. Offline mode serves a local build from
201
+ `ATHAR_VIEWER_DIST`, a release-packaged `athar_view/static`, or a freshly built
202
+ `viewer/dist`. The same SPA also accepts plain drag-drop of `old.ifc`,
203
+ `new.ifc`, and a report JSON — no launcher needed.
204
+
205
+ Viewer development requires [bun](https://bun.sh) and lives entirely under
206
+ `viewer/` (see [viewer/README.md](viewer/README.md)). Build the local bundle
207
+ with `make viewer-build`; `make viewer-package-static` copies it into
208
+ `athar_view/static`, which the release workflow does before building wheels.
209
+
210
+ ## Testing
211
+
212
+ ```bash
213
+ make test # full default suite (~20 seconds)
214
+ make test-large-acceptance # opt-in large IFC acceptance checks
215
+ ```
216
+
217
+ The default suite runs corpus-wide invariant tests (same-file zero diff,
218
+ stats accounting, no cross-class matches, duplicate-GUID and dangling-ref
219
+ behavior, schema policy) over every small real IFC in the corpus — the
220
+ Building-Landscaping v0→v3 revision chain, the IFC2X3 Duplex architecture
221
+ model, two GNI BIM Fundamentals samples, and small external samples — plus
222
+ metamorphic
223
+ GUID-scramble tests that prove identity recovery without GlobalId evidence.
224
+ It also generates known-edit semantic scenarios from real seeds (GUID
225
+ scramble, moving one product by a known vector, deleting a leaf product,
226
+ editing a pset value, editing a type-level pset value inherited by its
227
+ occurrences, renaming, duplicating a GUID) in temp dirs and asserts
228
+ the report against expectations derived from the constructed edit itself,
229
+ never from blessing engine output. Small files from the optional external
230
+ corpus (default `../vscode-ifc/test-files`, override via
231
+ `ATHAR_EXTERNAL_CORPUS_DIR`) skip when absent.
232
+
233
+ Large acceptance checks are opt-in so day-to-day runs stay fast:
234
+
235
+ ```bash
236
+ ATHAR_RUN_LARGE_ACCEPTANCE=1 python -m pytest tests/test_acceptance_large_ifc.py -q
237
+ ```
238
+
239
+ The acceptance tier covers the medium/large corpus (8MB–182MB; same-file
240
+ invariants, a real 44MB revision pair, discipline pairs, unrelated pairs, and
241
+ a GUID-scramble at scale). Files that are missing or unfetched LFS pointers
242
+ skip individually. Bound wall-clock per test via `ATHAR_ACCEPTANCE_TIMEOUT_S`.
243
+
244
+ At every tier, tests assert structural invariants and expectations derived
245
+ from the inputs themselves — never exact whole-report JSON goldens. Corpus
246
+ files too big for the repo will be described by a checked-in manifest rather
247
+ than stored (design: `docs/corpus/REMOTE_CORPUS_MANIFEST.md`; not yet
248
+ implemented).
249
+
250
+ ## Documentation
251
+
252
+ Agent-facing architecture and conventions are split by ownership: start at
253
+ [AGENTS.md](AGENTS.md), then follow the nearest subdirectory `AGENTS.md` for
254
+ core engine, Git integration, tests, docs, and scripts. Dated performance
255
+ findings live under `docs/perf/`, with `docs/perf/STATUS.md` stating what is
256
+ still current. Corpus survey findings live under `docs/corpus/`.
257
+
258
+ ## License
259
+
260
+ MIT
@@ -0,0 +1 @@
1
+