fmsonar 1.3.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.
fmsonar-1.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OOGI BV, Thomas De Smet (oogi.io)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fmsonar-1.3.0/PKG-INFO ADDED
@@ -0,0 +1,330 @@
1
+ Metadata-Version: 2.4
2
+ Name: fmsonar
3
+ Version: 1.3.0
4
+ Summary: Parse FileMaker Database Design Reports into a queryable SQLite cross-reference database, with a zero-install browser viewer
5
+ Author-email: "OOGI BV (Thomas De Smet)" <tdesmet@oogi.io>
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/oogi-io/fm-ddr-analyzer
8
+ Keywords: filemaker,ddr,database-design-report,cross-reference,sqlite
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Database
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # fmsonar
20
+
21
+ **Ping your FileMaker solution — every reference echoes back.**
22
+ One engine, two interfaces: **an explorer for you**, **a queryable index for your AI**.
23
+
24
+ *Live at **[fmsonar.com](https://fmsonar.com)** · repo/engine name: `fm-ddr-analyzer`*
25
+
26
+ fmsonar answers *"where is this field / script / table occurrence / custom
27
+ function actually used?"* for a whole FileMaker solution, starting from its
28
+ **Database Design Report** (DDR, the `*_fmp12.xml` export). One engine, two
29
+ interfaces:
30
+
31
+ - **For you:** [fmsonar.com](https://fmsonar.com) — drop the DDR on the page and
32
+ explore it in the browser (nothing is uploaded).
33
+ - **For your AI:** the `fm-ddr` CLI builds a normalized **SQLite** index that
34
+ assistants query directly, with [AGENTS.md](AGENTS.md) and a Claude Code skill
35
+ teaching them how.
36
+
37
+ Both parsers stream the huge FileMaker XML with SAX, so even a 400+ MB DDR is
38
+ handled without loading it all into memory.
39
+
40
+ Two front-ends over the same logic:
41
+
42
+ - **`fm_ddr/web/index.html`** — a zero-install, client-side web app. Open it, drop in a
43
+ DDR, and it parses **entirely in your browser** (nothing is uploaded — important,
44
+ since a DDR contains a client's whole schema). Best for sharing / non-technical
45
+ reach. The parser is a JS port of `parse.py`, validated to produce an identical
46
+ graph.
47
+ - **`fm_ddr/` (Python CLI)** — the scriptable / CI version: build a SQLite DB and
48
+ query it from the shell or hand it to an AI.
49
+
50
+ ## Quick start
51
+
52
+ 1. Open **[fmsonar.com](https://fmsonar.com)** — nothing installs, nothing uploads.
53
+ 2. In FileMaker Pro (advanced tools on): **Tools → Database Design Report → XML**, all files.
54
+ 3. **Drag the DDR folder onto the page.**
55
+
56
+ Seconds later your whole solution is explorable: search every name and every
57
+ line of code, see what references anything (and from where), read complete
58
+ scripts, walk call chains visually, run the health report, share findings as
59
+ tiny HTML files or CSV — and copy any script back into FileMaker as a
60
+ pasteable snippet. Your schema never leaves your machine.
61
+
62
+ **Want your AI assistant to answer questions about your solution?** Install
63
+ once, works from any directory, in any project — no cd-ing around. Needs
64
+ Python 3.10+ and [pipx](https://pipx.pypa.io) (macOS: `brew install pipx`):
65
+
66
+ ```bash
67
+ pipx install fmsonar
68
+ # or straight from the repo:
69
+ # pipx install git+https://github.com/oogi-io/fm-ddr-analyzer # the fm-ddr CLI
70
+ fm-ddr install-skill # Claude Code skill (global)
71
+ ```
72
+
73
+ Then, wherever you're working: *"analyze the DDR in ~/Desktop/MyDDR — which
74
+ scripts write to CTC::email?"* Claude Code builds the index into a central
75
+ cache (`~/.fmsonar/dbs/`) and answers with SQL-backed evidence. Cursor/other
76
+ tools: point them at **AGENTS.md** next to a built database.
77
+
78
+ ## Overview
79
+
80
+ - **Input:** DDR XML files (FileMaker: *Tools → Database Design Report → XML*) —
81
+ a single file, several, or the `Summary.xml` manifest of a multi-file solution.
82
+ Files are large (400+ MB) and UTF-16-LE; both parsers stream, so size doesn't
83
+ matter (measured on an M-series MacBook: a 510 MB 9-file solution builds in
84
+ ~26 s; the 416 MB main file parses in-browser in ~7 s using ~80 MB of memory).
85
+ - **Output:** a single `.db` SQLite file — a unified `entities` table, a generic
86
+ `refs` edge table (the heart of "where used"), and an FTS5 full-text index over
87
+ every calculation and script step as a catch-all.
88
+ - **General-purpose:** no solution-specific assumptions. Validated against two
89
+ unrelated production solutions and a 9-file, 510 MB multi-file solution.
90
+
91
+ ## Web app (no install)
92
+
93
+ Just use **[fmsonar.com](https://fmsonar.com)** — free, always the latest build.
94
+ Drop a DDR onto it; parsing, resolution, and the interactive viewer all run
95
+ client-side — no server, no upload. The **Download report** button exports a
96
+ self-contained HTML of the current solution to share.
97
+
98
+ Prefer to self-host? The whole app is one file: open `fm_ddr/web/index.html`
99
+ in a browser or serve it as a static page — it works identically.
100
+
101
+ ## Install (Python CLI)
102
+
103
+ Pure standard library — no dependencies, Python 3.10+.
104
+
105
+ ```bash
106
+ pipx install fmsonar
107
+ # or straight from the repo:
108
+ # pipx install git+https://github.com/oogi-io/fm-ddr-analyzer
109
+ fm-ddr build /path/to/Solution_fmp12.xml -o solution.db
110
+ # or from a clone, no install at all:
111
+ python3 -m fm_ddr.cli build /path/to/Solution_fmp12.xml -o solution.db
112
+ ```
113
+
114
+ **Updating:** a pipx install is a snapshot — it does not auto-update when this repo changes.
115
+ Pull the latest with:
116
+
117
+ ```bash
118
+ pipx upgrade fmsonar # PyPI install
119
+ pipx reinstall fmsonar # git-URL install: always fetches current main
120
+ ```
121
+
122
+ (Versions are bumped on every release, so `pipx upgrade` works from PyPI. A git clone
123
+ updates with `git pull` as usual. After updating, refresh the Claude skill:
124
+ `fm-ddr install-skill` — check drift anytime with `fm-ddr install-skill --check`.)
125
+
126
+ ## Usage
127
+
128
+ ```bash
129
+ # Parse a DDR into SQLite (prints entity + reference counts)
130
+ python3 -m fm_ddr.cli build Solution_fmp12.xml -o solution.db
131
+
132
+ # Where is something used? (field / script / layout / TO / custom function)
133
+ python3 -m fm_ddr.cli where solution.db "CONTACT::email"
134
+ python3 -m fm_ddr.cli where solution.db "Navigate to Dashboard"
135
+
136
+ # Full-text search across every calc / script step / name
137
+ python3 -m fm_ddr.cli search solution.db "GetContainerAttribute"
138
+
139
+ # Interactive HTML viewer (self-contained, opens in any browser, no server)
140
+ python3 -m fm_ddr.cli report solution.db -o solution.html
141
+
142
+ # Counts + reference-resolution health
143
+ python3 -m fm_ddr.cli stats solution.db
144
+
145
+ # Any SQL (this is the real power — see QUERIES.md)
146
+ python3 -m fm_ddr.cli sql solution.db "SELECT * FROM v_unused_fields LIMIT 20"
147
+ ```
148
+
149
+ Because the output is plain SQLite, an AI (or `sqlite3`, Datasette, DB Browser,
150
+ etc.) can query it directly. **[AGENTS.md](AGENTS.md)** teaches AI coding tools
151
+ (Claude Code, Cursor, Copilot — they read it automatically) how to work these
152
+ databases: the schema, the views-as-API, the investigation loop, and the
153
+ honesty guardrails. **[QUERIES.md](QUERIES.md)** has the canonical SQL recipes.
154
+
155
+ ## Data model
156
+
157
+ One database holds a whole solution — all files of a multi-file solution share
158
+ one entity space, so cross-file references resolve. The schema is also
159
+ snapshot-aware (`ddr_run`) so a future diff feature can store several DDR
160
+ exports side by side; today `build` always writes a fresh single-snapshot DB
161
+ (diffing is on the roadmap).
162
+
163
+ | Table | What it holds |
164
+ |-------|---------------|
165
+ | `ddr_run` | One parse run (source path, DDR version, timestamp, label) |
166
+ | `files` | Each FileMaker file in the run |
167
+ | `entities` | Every named thing — one row per `kind` (see below) |
168
+ | `refs` | Every "source **uses** target" edge; `target_entity_id` resolved after load |
169
+ | `text_index` | FTS5 mirror of names + calcs + step text (catch-all search) |
170
+ | `v_usage` | Friendly view over `refs` with readable source/target names |
171
+ | `v_unused_fields`, `v_orphan_scripts`, `v_unresolved` | Health hints |
172
+
173
+ **Entity kinds:** `base_table`, `field`, `table_occurrence`, `relationship`,
174
+ `layout`, `layout_group`, `script`, `script_group`, `script_step`,
175
+ `custom_function`, `value_list`, `privilege_set`, `account`, `extended_privilege`,
176
+ `custom_menu`, `custom_menu_set`, `external_data_source`, `theme`.
177
+
178
+ **Reference contexts** (`refs.context`): `calc`, `step_target` (the field a step
179
+ writes to — e.g. Set Field), `join_predicate`, `perform_script`, `go_to_layout`,
180
+ `trigger`, `layout_object`, `field_reference`, `value_list_source`,
181
+ `value_list_field`, `sort`, `to_reference`, `function_ref`.
182
+
183
+ ### How references resolve
184
+
185
+ Every edge is captured raw during parse (target name + FileMaker id), then
186
+ `resolve.sql` fills `target_entity_id` by matching against `entities`. On real
187
+ solutions ~98% resolve; the rest are genuinely unresolvable and expected:
188
+
189
+ - `perform_script` to scripts in **other files** (external),
190
+ - `go_to_layout` with a **calculated** destination,
191
+ - `layout_object` fields that are **globals / unbound**.
192
+
193
+ Built-in FileMaker functions are intentionally *not* stored as edges (only
194
+ `CustomFunctionRef` chunks become `custom_function` edges); use FTS to find
195
+ built-in usage.
196
+
197
+ ## Accuracy
198
+
199
+ Validated against a production solution's independently documented DDR summary —
200
+ every catalog count matches (base tables, table occurrences, relationships,
201
+ layouts, value lists, custom functions). A committed micro-fixture plus a full
202
+ test suite (structural counts, resolution semantics, UTF-16 round-trip,
203
+ edge-by-edge Python↔JS parity under torture chunking) runs in CI on every push.
204
+
205
+ ## Roadmap
206
+
207
+ - [x] **Phase 1 — Cross-reference engine.** SAX parser → SQLite, generic edge
208
+ table, FTS fallback, CLI (`build` / `where` / `search` / `sql` / `stats`).
209
+ - [x] **Phase 2 — Interactive HTML.** `report` command emits a self-contained
210
+ page (data embedded, no server): searchable entity list with kind filters,
211
+ click any field/script/TO to see inbound ("referenced by") and outbound
212
+ ("references") edges grouped by the OTHER entity's kind (Scripts / Layouts /
213
+ Fields / Custom functions / Relationships / ...), with the usage context
214
+ folded into each group header, and click-through navigation.
215
+ - [x] **Correctness hardening.** `where` resolves through the TO before
216
+ filtering (no more leaf-name over-matching), ambiguous picks are flagged
217
+ (`refs.ambiguous`, `v_ambiguous`), VL field sources and sort fields are
218
+ captured, non-DDR input errors clearly, and `build` warns on low resolution.
219
+ See **COVERAGE.md** for the explicit captured / not-captured matrix.
220
+ - [x] **Multi-file solutions.** `build Summary.xml` (or list the XMLs) ingests
221
+ all files into one DB; the web app accepts multi-drop. Cross-file references
222
+ resolve via explicit FileReference markers only — external Perform Script,
223
+ and field refs through external table occurrences (98.8% resolution measured
224
+ on a 9-file production solution). External refs whose file is absent stay unresolved
225
+ instead of silently mis-linking to same-id local objects.
226
+ - [x] **Explorer UX.** FMPerception-style flow in the browser: drop the whole
227
+ DDR folder (every `*_fmp12.xml` loads, cross-file links resolve), filter by
228
+ file, and click a script to read it as **full step text** — document order,
229
+ block indentation (If/Loop/Else), comment steps dimmed, step/comment/call
230
+ counts, copy per line / selection / whole script. Works in the web app, the
231
+ exported report, and the CLI report alike.
232
+ - [x] **Call chain diagram.** Toggle any script's detail between Steps and a
233
+ layered SVG call chain: callers flow in from the left (green), called
234
+ scripts to the right (orange), the **full chain** in both directions
235
+ (cycle-safe; each script appears once), externals dashed, per-level
236
+ fan-out capped with "+N more", every node clickable to re-root.
237
+ - [x] **Navigate like an app.** Browser Back/Forward work while exploring;
238
+ every entity has a deep link (`#e123`) that also works inside exported
239
+ reports; `#health` opens the health report directly.
240
+ - [x] **Search in code.** Enter-search scans every calculation and script
241
+ step; results show highlighted snippets and clicking a step match opens
242
+ the script scrolled to that exact line.
243
+ - [x] **Sort the entity list.** A compact "Sort" popover on the list orders
244
+ by Name or Referenced-by (and, when scripts are soloed, Steps / Calls /
245
+ Complexity); sorting by a metric shows that number inline on each row.
246
+ Lighter than a full table — the list stays the single browse surface.
247
+ - [x] **Solution health report.** Unused-field and orphan-script candidates,
248
+ unresolved and ambiguous references, hotspots and biggest scripts — every
249
+ list clickable, each downloadable as CSV, with the coverage caveats printed
250
+ on the page. The call chain is downloadable as a standalone SVG.
251
+ - [x] **Call chain, expanded.** Edge semantics (solid Perform / long-dash
252
+ PSoS / dotted trigger / dash-dot button, with tooltips), call-count weights
253
+ (×N), entry-point badges, hover-highlight of connected nodes, in-chain
254
+ search (matches surface out of "+N more"), click → steps preview below the
255
+ chain with Re-root / Open fully, drag-pan + wheel-zoom (double-click
256
+ resets), Copy as Mermaid, Download SVG, and a print stylesheet (Cmd+P →
257
+ clean PDF of chain, script text, or health report).
258
+ - [x] **Share one insight.** Every entity has a Share button that downloads
259
+ a small self-contained static HTML (no JavaScript inside): the script's
260
+ steps, its call chain exactly as arranged on screen, and its references.
261
+ Kilobytes — safe to mail or Slack without sharing the whole schema. For
262
+ whole-solution sharing, drop an exported report on a shared drive and use
263
+ deep links (`report.html#e123`).
264
+ - [ ] **Copy-link button.** One-click copy of an entity's deep link for the
265
+ shared-drive workflow.
266
+ - [ ] **Union impact graph.** Select several scripts and see one merged
267
+ call graph with shared dependencies emphasized — "the five scripts I'm
268
+ about to change, and everything they touch". Its own session.
269
+ - [ ] **Annotations.** Mark entities (deprecated / refactor / reviewed) with
270
+ notes; persisted per solution, exportable, embedded in shared reports.
271
+ Viewer-wide, not chain-only; pairs with the health report.
272
+ - [ ] **Signed helper installer.** Replace the unsigned zip / curl|bash
273
+ install paths with a signed + notarized .pkg (Developer ID) so macOS
274
+ installs the snippet watcher without any Gatekeeper friction. Parked
275
+ until a dedicated signing session.
276
+ - [ ] **Phase 3 — DDR diff.** Two snapshots in one DB → what changed between
277
+ deploys (added/removed/modified fields, scripts, layouts).
278
+ - [x] **Copy as FM snippet (web + CLI).** In the web app, every script has a
279
+ "Copy FM snippet" button that re-streams the source file, extracts that
280
+ script's raw steps, and copies fmxmlsnippet XML as text (byte-identical to
281
+ FileMaker's own clipboard copy). Browsers cannot write FileMaker's private
282
+ clipboard flavor, so paste needs a one-time bridge. Pick any: the bundled
283
+ helpers in `helpers/` (macOS `.command`, Windows `.ps1` — also downloadable
284
+ from the app after copying), `fm-ddr clip` (converts clipboard text in
285
+ place), or FmClipTools if you already use it.
286
+ - [x] **Copy as FM snippet (CLI, macOS).** `fm-ddr snippet DDR.xml "Script
287
+ Name" --clip` transforms a script's DDR steps into FileMaker's clipboard
288
+ format and places it on the private XMSS pasteboard flavor — paste straight
289
+ into Script Workspace. The transform reproduces FileMaker's own copied
290
+ output byte-for-byte (268/268 steps on the reference script) and is
291
+ paste-verified in Script Workspace; see SNIPPET_FORMAT.md. Browsers cannot set
292
+ the XMSS flavor, so the web app cannot paste directly — CLI only.
293
+ - [ ] **Edit → patch (idea).** Make selected changes in the viewer and emit
294
+ them as input for the FileMaker upgrade tool to apply. Shares the raw-XML
295
+ prerequisite with snippet copy; parked until the read-only explorer has
296
+ proven itself.
297
+ - [ ] Health report: dead fields, orphan scripts, missing references, TO sprawl.
298
+
299
+ ## Project structure
300
+
301
+ ```
302
+ fm-ddr-analyzer/
303
+ ├── README.md
304
+ ├── QUERIES.md # canonical cross-reference SQL recipes (for humans + AI)
305
+ └── fm_ddr/
306
+ ├── web/index.html # zero-install client-side web app (JS parser + viewer)
307
+ ├── __init__.py
308
+ ├── parse.py # SAX streaming parser -> SQLite
309
+ ├── schema.sql # entities / refs / FTS schema
310
+ ├── resolve.sql # reference resolution + convenience views
311
+ ├── report.py # self-contained interactive HTML generator
312
+ └── cli.py # build / where / search / sql / stats / report
313
+ ```
314
+
315
+ ## Tech stack
316
+
317
+ | Concern | Choice | Why |
318
+ |---------|--------|-----|
319
+ | Parsing | `xml.sax` (expat) | Streams 400 MB UTF-16-LE files; ignores line structure |
320
+ | Storage | SQLite | Portable, queryable by AI/tools, no server |
321
+ | Search | FTS5 | Catch-all text search where structured extraction is incomplete |
322
+ | Language | Python 3.10+, stdlib only | No dependencies to install |
323
+
324
+ ---
325
+
326
+ [Thomas De Smet](https://oogi.io) · [tdesmet@oogi.io](mailto:tdesmet@oogi.io) · MIT
327
+
328
+ *FileMaker and Claris are trademarks of Claris International Inc. fmsonar is an
329
+ independent tool, not affiliated with or endorsed by Claris. See
330
+ [SECURITY.md](SECURITY.md) for the privacy and threat model.*
@@ -0,0 +1,312 @@
1
+ # fmsonar
2
+
3
+ **Ping your FileMaker solution — every reference echoes back.**
4
+ One engine, two interfaces: **an explorer for you**, **a queryable index for your AI**.
5
+
6
+ *Live at **[fmsonar.com](https://fmsonar.com)** · repo/engine name: `fm-ddr-analyzer`*
7
+
8
+ fmsonar answers *"where is this field / script / table occurrence / custom
9
+ function actually used?"* for a whole FileMaker solution, starting from its
10
+ **Database Design Report** (DDR, the `*_fmp12.xml` export). One engine, two
11
+ interfaces:
12
+
13
+ - **For you:** [fmsonar.com](https://fmsonar.com) — drop the DDR on the page and
14
+ explore it in the browser (nothing is uploaded).
15
+ - **For your AI:** the `fm-ddr` CLI builds a normalized **SQLite** index that
16
+ assistants query directly, with [AGENTS.md](AGENTS.md) and a Claude Code skill
17
+ teaching them how.
18
+
19
+ Both parsers stream the huge FileMaker XML with SAX, so even a 400+ MB DDR is
20
+ handled without loading it all into memory.
21
+
22
+ Two front-ends over the same logic:
23
+
24
+ - **`fm_ddr/web/index.html`** — a zero-install, client-side web app. Open it, drop in a
25
+ DDR, and it parses **entirely in your browser** (nothing is uploaded — important,
26
+ since a DDR contains a client's whole schema). Best for sharing / non-technical
27
+ reach. The parser is a JS port of `parse.py`, validated to produce an identical
28
+ graph.
29
+ - **`fm_ddr/` (Python CLI)** — the scriptable / CI version: build a SQLite DB and
30
+ query it from the shell or hand it to an AI.
31
+
32
+ ## Quick start
33
+
34
+ 1. Open **[fmsonar.com](https://fmsonar.com)** — nothing installs, nothing uploads.
35
+ 2. In FileMaker Pro (advanced tools on): **Tools → Database Design Report → XML**, all files.
36
+ 3. **Drag the DDR folder onto the page.**
37
+
38
+ Seconds later your whole solution is explorable: search every name and every
39
+ line of code, see what references anything (and from where), read complete
40
+ scripts, walk call chains visually, run the health report, share findings as
41
+ tiny HTML files or CSV — and copy any script back into FileMaker as a
42
+ pasteable snippet. Your schema never leaves your machine.
43
+
44
+ **Want your AI assistant to answer questions about your solution?** Install
45
+ once, works from any directory, in any project — no cd-ing around. Needs
46
+ Python 3.10+ and [pipx](https://pipx.pypa.io) (macOS: `brew install pipx`):
47
+
48
+ ```bash
49
+ pipx install fmsonar
50
+ # or straight from the repo:
51
+ # pipx install git+https://github.com/oogi-io/fm-ddr-analyzer # the fm-ddr CLI
52
+ fm-ddr install-skill # Claude Code skill (global)
53
+ ```
54
+
55
+ Then, wherever you're working: *"analyze the DDR in ~/Desktop/MyDDR — which
56
+ scripts write to CTC::email?"* Claude Code builds the index into a central
57
+ cache (`~/.fmsonar/dbs/`) and answers with SQL-backed evidence. Cursor/other
58
+ tools: point them at **AGENTS.md** next to a built database.
59
+
60
+ ## Overview
61
+
62
+ - **Input:** DDR XML files (FileMaker: *Tools → Database Design Report → XML*) —
63
+ a single file, several, or the `Summary.xml` manifest of a multi-file solution.
64
+ Files are large (400+ MB) and UTF-16-LE; both parsers stream, so size doesn't
65
+ matter (measured on an M-series MacBook: a 510 MB 9-file solution builds in
66
+ ~26 s; the 416 MB main file parses in-browser in ~7 s using ~80 MB of memory).
67
+ - **Output:** a single `.db` SQLite file — a unified `entities` table, a generic
68
+ `refs` edge table (the heart of "where used"), and an FTS5 full-text index over
69
+ every calculation and script step as a catch-all.
70
+ - **General-purpose:** no solution-specific assumptions. Validated against two
71
+ unrelated production solutions and a 9-file, 510 MB multi-file solution.
72
+
73
+ ## Web app (no install)
74
+
75
+ Just use **[fmsonar.com](https://fmsonar.com)** — free, always the latest build.
76
+ Drop a DDR onto it; parsing, resolution, and the interactive viewer all run
77
+ client-side — no server, no upload. The **Download report** button exports a
78
+ self-contained HTML of the current solution to share.
79
+
80
+ Prefer to self-host? The whole app is one file: open `fm_ddr/web/index.html`
81
+ in a browser or serve it as a static page — it works identically.
82
+
83
+ ## Install (Python CLI)
84
+
85
+ Pure standard library — no dependencies, Python 3.10+.
86
+
87
+ ```bash
88
+ pipx install fmsonar
89
+ # or straight from the repo:
90
+ # pipx install git+https://github.com/oogi-io/fm-ddr-analyzer
91
+ fm-ddr build /path/to/Solution_fmp12.xml -o solution.db
92
+ # or from a clone, no install at all:
93
+ python3 -m fm_ddr.cli build /path/to/Solution_fmp12.xml -o solution.db
94
+ ```
95
+
96
+ **Updating:** a pipx install is a snapshot — it does not auto-update when this repo changes.
97
+ Pull the latest with:
98
+
99
+ ```bash
100
+ pipx upgrade fmsonar # PyPI install
101
+ pipx reinstall fmsonar # git-URL install: always fetches current main
102
+ ```
103
+
104
+ (Versions are bumped on every release, so `pipx upgrade` works from PyPI. A git clone
105
+ updates with `git pull` as usual. After updating, refresh the Claude skill:
106
+ `fm-ddr install-skill` — check drift anytime with `fm-ddr install-skill --check`.)
107
+
108
+ ## Usage
109
+
110
+ ```bash
111
+ # Parse a DDR into SQLite (prints entity + reference counts)
112
+ python3 -m fm_ddr.cli build Solution_fmp12.xml -o solution.db
113
+
114
+ # Where is something used? (field / script / layout / TO / custom function)
115
+ python3 -m fm_ddr.cli where solution.db "CONTACT::email"
116
+ python3 -m fm_ddr.cli where solution.db "Navigate to Dashboard"
117
+
118
+ # Full-text search across every calc / script step / name
119
+ python3 -m fm_ddr.cli search solution.db "GetContainerAttribute"
120
+
121
+ # Interactive HTML viewer (self-contained, opens in any browser, no server)
122
+ python3 -m fm_ddr.cli report solution.db -o solution.html
123
+
124
+ # Counts + reference-resolution health
125
+ python3 -m fm_ddr.cli stats solution.db
126
+
127
+ # Any SQL (this is the real power — see QUERIES.md)
128
+ python3 -m fm_ddr.cli sql solution.db "SELECT * FROM v_unused_fields LIMIT 20"
129
+ ```
130
+
131
+ Because the output is plain SQLite, an AI (or `sqlite3`, Datasette, DB Browser,
132
+ etc.) can query it directly. **[AGENTS.md](AGENTS.md)** teaches AI coding tools
133
+ (Claude Code, Cursor, Copilot — they read it automatically) how to work these
134
+ databases: the schema, the views-as-API, the investigation loop, and the
135
+ honesty guardrails. **[QUERIES.md](QUERIES.md)** has the canonical SQL recipes.
136
+
137
+ ## Data model
138
+
139
+ One database holds a whole solution — all files of a multi-file solution share
140
+ one entity space, so cross-file references resolve. The schema is also
141
+ snapshot-aware (`ddr_run`) so a future diff feature can store several DDR
142
+ exports side by side; today `build` always writes a fresh single-snapshot DB
143
+ (diffing is on the roadmap).
144
+
145
+ | Table | What it holds |
146
+ |-------|---------------|
147
+ | `ddr_run` | One parse run (source path, DDR version, timestamp, label) |
148
+ | `files` | Each FileMaker file in the run |
149
+ | `entities` | Every named thing — one row per `kind` (see below) |
150
+ | `refs` | Every "source **uses** target" edge; `target_entity_id` resolved after load |
151
+ | `text_index` | FTS5 mirror of names + calcs + step text (catch-all search) |
152
+ | `v_usage` | Friendly view over `refs` with readable source/target names |
153
+ | `v_unused_fields`, `v_orphan_scripts`, `v_unresolved` | Health hints |
154
+
155
+ **Entity kinds:** `base_table`, `field`, `table_occurrence`, `relationship`,
156
+ `layout`, `layout_group`, `script`, `script_group`, `script_step`,
157
+ `custom_function`, `value_list`, `privilege_set`, `account`, `extended_privilege`,
158
+ `custom_menu`, `custom_menu_set`, `external_data_source`, `theme`.
159
+
160
+ **Reference contexts** (`refs.context`): `calc`, `step_target` (the field a step
161
+ writes to — e.g. Set Field), `join_predicate`, `perform_script`, `go_to_layout`,
162
+ `trigger`, `layout_object`, `field_reference`, `value_list_source`,
163
+ `value_list_field`, `sort`, `to_reference`, `function_ref`.
164
+
165
+ ### How references resolve
166
+
167
+ Every edge is captured raw during parse (target name + FileMaker id), then
168
+ `resolve.sql` fills `target_entity_id` by matching against `entities`. On real
169
+ solutions ~98% resolve; the rest are genuinely unresolvable and expected:
170
+
171
+ - `perform_script` to scripts in **other files** (external),
172
+ - `go_to_layout` with a **calculated** destination,
173
+ - `layout_object` fields that are **globals / unbound**.
174
+
175
+ Built-in FileMaker functions are intentionally *not* stored as edges (only
176
+ `CustomFunctionRef` chunks become `custom_function` edges); use FTS to find
177
+ built-in usage.
178
+
179
+ ## Accuracy
180
+
181
+ Validated against a production solution's independently documented DDR summary —
182
+ every catalog count matches (base tables, table occurrences, relationships,
183
+ layouts, value lists, custom functions). A committed micro-fixture plus a full
184
+ test suite (structural counts, resolution semantics, UTF-16 round-trip,
185
+ edge-by-edge Python↔JS parity under torture chunking) runs in CI on every push.
186
+
187
+ ## Roadmap
188
+
189
+ - [x] **Phase 1 — Cross-reference engine.** SAX parser → SQLite, generic edge
190
+ table, FTS fallback, CLI (`build` / `where` / `search` / `sql` / `stats`).
191
+ - [x] **Phase 2 — Interactive HTML.** `report` command emits a self-contained
192
+ page (data embedded, no server): searchable entity list with kind filters,
193
+ click any field/script/TO to see inbound ("referenced by") and outbound
194
+ ("references") edges grouped by the OTHER entity's kind (Scripts / Layouts /
195
+ Fields / Custom functions / Relationships / ...), with the usage context
196
+ folded into each group header, and click-through navigation.
197
+ - [x] **Correctness hardening.** `where` resolves through the TO before
198
+ filtering (no more leaf-name over-matching), ambiguous picks are flagged
199
+ (`refs.ambiguous`, `v_ambiguous`), VL field sources and sort fields are
200
+ captured, non-DDR input errors clearly, and `build` warns on low resolution.
201
+ See **COVERAGE.md** for the explicit captured / not-captured matrix.
202
+ - [x] **Multi-file solutions.** `build Summary.xml` (or list the XMLs) ingests
203
+ all files into one DB; the web app accepts multi-drop. Cross-file references
204
+ resolve via explicit FileReference markers only — external Perform Script,
205
+ and field refs through external table occurrences (98.8% resolution measured
206
+ on a 9-file production solution). External refs whose file is absent stay unresolved
207
+ instead of silently mis-linking to same-id local objects.
208
+ - [x] **Explorer UX.** FMPerception-style flow in the browser: drop the whole
209
+ DDR folder (every `*_fmp12.xml` loads, cross-file links resolve), filter by
210
+ file, and click a script to read it as **full step text** — document order,
211
+ block indentation (If/Loop/Else), comment steps dimmed, step/comment/call
212
+ counts, copy per line / selection / whole script. Works in the web app, the
213
+ exported report, and the CLI report alike.
214
+ - [x] **Call chain diagram.** Toggle any script's detail between Steps and a
215
+ layered SVG call chain: callers flow in from the left (green), called
216
+ scripts to the right (orange), the **full chain** in both directions
217
+ (cycle-safe; each script appears once), externals dashed, per-level
218
+ fan-out capped with "+N more", every node clickable to re-root.
219
+ - [x] **Navigate like an app.** Browser Back/Forward work while exploring;
220
+ every entity has a deep link (`#e123`) that also works inside exported
221
+ reports; `#health` opens the health report directly.
222
+ - [x] **Search in code.** Enter-search scans every calculation and script
223
+ step; results show highlighted snippets and clicking a step match opens
224
+ the script scrolled to that exact line.
225
+ - [x] **Sort the entity list.** A compact "Sort" popover on the list orders
226
+ by Name or Referenced-by (and, when scripts are soloed, Steps / Calls /
227
+ Complexity); sorting by a metric shows that number inline on each row.
228
+ Lighter than a full table — the list stays the single browse surface.
229
+ - [x] **Solution health report.** Unused-field and orphan-script candidates,
230
+ unresolved and ambiguous references, hotspots and biggest scripts — every
231
+ list clickable, each downloadable as CSV, with the coverage caveats printed
232
+ on the page. The call chain is downloadable as a standalone SVG.
233
+ - [x] **Call chain, expanded.** Edge semantics (solid Perform / long-dash
234
+ PSoS / dotted trigger / dash-dot button, with tooltips), call-count weights
235
+ (×N), entry-point badges, hover-highlight of connected nodes, in-chain
236
+ search (matches surface out of "+N more"), click → steps preview below the
237
+ chain with Re-root / Open fully, drag-pan + wheel-zoom (double-click
238
+ resets), Copy as Mermaid, Download SVG, and a print stylesheet (Cmd+P →
239
+ clean PDF of chain, script text, or health report).
240
+ - [x] **Share one insight.** Every entity has a Share button that downloads
241
+ a small self-contained static HTML (no JavaScript inside): the script's
242
+ steps, its call chain exactly as arranged on screen, and its references.
243
+ Kilobytes — safe to mail or Slack without sharing the whole schema. For
244
+ whole-solution sharing, drop an exported report on a shared drive and use
245
+ deep links (`report.html#e123`).
246
+ - [ ] **Copy-link button.** One-click copy of an entity's deep link for the
247
+ shared-drive workflow.
248
+ - [ ] **Union impact graph.** Select several scripts and see one merged
249
+ call graph with shared dependencies emphasized — "the five scripts I'm
250
+ about to change, and everything they touch". Its own session.
251
+ - [ ] **Annotations.** Mark entities (deprecated / refactor / reviewed) with
252
+ notes; persisted per solution, exportable, embedded in shared reports.
253
+ Viewer-wide, not chain-only; pairs with the health report.
254
+ - [ ] **Signed helper installer.** Replace the unsigned zip / curl|bash
255
+ install paths with a signed + notarized .pkg (Developer ID) so macOS
256
+ installs the snippet watcher without any Gatekeeper friction. Parked
257
+ until a dedicated signing session.
258
+ - [ ] **Phase 3 — DDR diff.** Two snapshots in one DB → what changed between
259
+ deploys (added/removed/modified fields, scripts, layouts).
260
+ - [x] **Copy as FM snippet (web + CLI).** In the web app, every script has a
261
+ "Copy FM snippet" button that re-streams the source file, extracts that
262
+ script's raw steps, and copies fmxmlsnippet XML as text (byte-identical to
263
+ FileMaker's own clipboard copy). Browsers cannot write FileMaker's private
264
+ clipboard flavor, so paste needs a one-time bridge. Pick any: the bundled
265
+ helpers in `helpers/` (macOS `.command`, Windows `.ps1` — also downloadable
266
+ from the app after copying), `fm-ddr clip` (converts clipboard text in
267
+ place), or FmClipTools if you already use it.
268
+ - [x] **Copy as FM snippet (CLI, macOS).** `fm-ddr snippet DDR.xml "Script
269
+ Name" --clip` transforms a script's DDR steps into FileMaker's clipboard
270
+ format and places it on the private XMSS pasteboard flavor — paste straight
271
+ into Script Workspace. The transform reproduces FileMaker's own copied
272
+ output byte-for-byte (268/268 steps on the reference script) and is
273
+ paste-verified in Script Workspace; see SNIPPET_FORMAT.md. Browsers cannot set
274
+ the XMSS flavor, so the web app cannot paste directly — CLI only.
275
+ - [ ] **Edit → patch (idea).** Make selected changes in the viewer and emit
276
+ them as input for the FileMaker upgrade tool to apply. Shares the raw-XML
277
+ prerequisite with snippet copy; parked until the read-only explorer has
278
+ proven itself.
279
+ - [ ] Health report: dead fields, orphan scripts, missing references, TO sprawl.
280
+
281
+ ## Project structure
282
+
283
+ ```
284
+ fm-ddr-analyzer/
285
+ ├── README.md
286
+ ├── QUERIES.md # canonical cross-reference SQL recipes (for humans + AI)
287
+ └── fm_ddr/
288
+ ├── web/index.html # zero-install client-side web app (JS parser + viewer)
289
+ ├── __init__.py
290
+ ├── parse.py # SAX streaming parser -> SQLite
291
+ ├── schema.sql # entities / refs / FTS schema
292
+ ├── resolve.sql # reference resolution + convenience views
293
+ ├── report.py # self-contained interactive HTML generator
294
+ └── cli.py # build / where / search / sql / stats / report
295
+ ```
296
+
297
+ ## Tech stack
298
+
299
+ | Concern | Choice | Why |
300
+ |---------|--------|-----|
301
+ | Parsing | `xml.sax` (expat) | Streams 400 MB UTF-16-LE files; ignores line structure |
302
+ | Storage | SQLite | Portable, queryable by AI/tools, no server |
303
+ | Search | FTS5 | Catch-all text search where structured extraction is incomplete |
304
+ | Language | Python 3.10+, stdlib only | No dependencies to install |
305
+
306
+ ---
307
+
308
+ [Thomas De Smet](https://oogi.io) · [tdesmet@oogi.io](mailto:tdesmet@oogi.io) · MIT
309
+
310
+ *FileMaker and Claris are trademarks of Claris International Inc. fmsonar is an
311
+ independent tool, not affiliated with or endorsed by Claris. See
312
+ [SECURITY.md](SECURITY.md) for the privacy and threat model.*
@@ -0,0 +1,4 @@
1
+ """FM DDR Analyzer — parse FileMaker Database Design Reports into queryable SQLite."""
2
+ __version__ = "1.3.0"
3
+
4
+ from .parse import build # noqa: F401