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 +21 -0
- fmsonar-1.3.0/PKG-INFO +330 -0
- fmsonar-1.3.0/README.md +312 -0
- fmsonar-1.3.0/fm_ddr/__init__.py +4 -0
- fmsonar-1.3.0/fm_ddr/cli.py +469 -0
- fmsonar-1.3.0/fm_ddr/parse.py +646 -0
- fmsonar-1.3.0/fm_ddr/report.py +122 -0
- fmsonar-1.3.0/fm_ddr/resolve.sql +189 -0
- fmsonar-1.3.0/fm_ddr/schema.sql +89 -0
- fmsonar-1.3.0/fm_ddr/skill/SKILL.md +113 -0
- fmsonar-1.3.0/fm_ddr/snippet.py +147 -0
- fmsonar-1.3.0/fm_ddr/web/index.html +1782 -0
- fmsonar-1.3.0/fmsonar.egg-info/PKG-INFO +330 -0
- fmsonar-1.3.0/fmsonar.egg-info/SOURCES.txt +18 -0
- fmsonar-1.3.0/fmsonar.egg-info/dependency_links.txt +1 -0
- fmsonar-1.3.0/fmsonar.egg-info/entry_points.txt +3 -0
- fmsonar-1.3.0/fmsonar.egg-info/top_level.txt +1 -0
- fmsonar-1.3.0/pyproject.toml +36 -0
- fmsonar-1.3.0/setup.cfg +4 -0
- fmsonar-1.3.0/tests/test_all.py +546 -0
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.*
|
fmsonar-1.3.0/README.md
ADDED
|
@@ -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.*
|