Skip to main content

A CLI and library for analyzing FileMaker DDRs. Built for agents — JSON-first, scriptable, headless.

Project description

fmddr

A CLI and library for analyzing FileMaker DDRs (Database Design Reports).

fmddr parses a DDR XML once into a SQLite index, then answers structural and cross-reference queries in milliseconds. The output is JSON-first and stable, so an agent — or a script, or a developer at the terminal — can drive analysis at the same depth a GUI tool offers, without the GUI.

Install

Requires Python 3.11+.

pip install fmddr
# or for an isolated CLI install:
pipx install fmddr

fmddr --version
# fmddr 0.1.0

Listed on PyPI →

From source (for development)

git clone https://github.com/proofsh/fmddr.git
cd fmddr
pip install -e .[dev]    # adds pytest, ruff, build, twine
pip install -e .[lxml]   # adds libxml2-bound parser (optional)

Building a wheel locally

pip install build
python -m build
# → dist/fmddr-<version>-py3-none-any.whl

Quickstart

# 1. Explode the DDR into a per-object XML tree (grep/diff friendly).
fmddr split path/to/Profile.xml --out ./xmls-split

# 2. Build the SQLite index (the "ingest" step).
#    Works on a single DDR (`type="Report"`) or a `Summary.xml` that links
#    multiple files together — both produce one queryable index.
fmddr index path/to/Profile.xml --out ./profile.fmddr.db
fmddr index path/to/Summary.xml --out ./solution.fmddr.db

# 3. Query.
fmddr stats --db ./profile.fmddr.db
fmddr table fields "Production Run" -o table
fmddr field script-refs "Production Run::ProductionRunStatus"
fmddr script chain "Save Case Request" --depth 2 --format mermaid
fmddr cf called-by "FindWordPartsInText"
fmddr impact field "Customer::__UID"
fmddr unused --kind script
fmddr search "Customer*" --kind script

When stdout is a TTY, results render as Rich tables. Pipe to a file or another process and you get JSON. Override with -o {json,jsonl,table,markdown,csv,tsv}.

Why

A FileMaker file with thousands of fields, scripts, and layouts is hard to refactor safely. Every change requires structural reasoning:

  • What scripts touch this field, and at which step?
  • Which layouts show it, and which objects on those layouts?
  • What does this script call, and who calls it?
  • If this field is removed, what breaks?
  • Where is this custom function used?
  • Which steps run risky ExecuteSQL or Evaluate?
  • What's dead code? What's an orphan reference?

fmddr answers each of these in a one-line shell command with structured output, so an agent can run thousands of these queries during a refactor — programmatically, with stable output an LLM can parse.

Performance

On a 491 MB DDR from a long-running production file:

  • Index build: ~7 s (one-time, streams through the XML)
  • 257 tables / 10,403 fields / 1,877 scripts / 189,026 steps
  • 873 layouts / 70,564 layout refs / 3,083 TOs / 2,758 relationships
  • 123 custom functions / 503 value lists / 8,810 predicate refs
  • Queries return in <50 ms

Streaming iterparse + element-clearing keeps memory bounded regardless of DDR size. SQLite with journal_mode=MEMORY, synchronous=OFF during build, plus VACUUM + PRAGMA optimize at end.

Command reference

Ingest

Command Purpose
fmddr split <ddr> Per-object XML tree (grep/diff friendly)
fmddr index <ddr> / <Summary.xml> Build the SQLite index (single file or multi-file)
fmddr stats Counts and metadata recorded in the index
fmddr file list Files ingested into this index, with per-file counts
fmddr open Interactive sqlite3 shell against the index

Tables and fields

Command Purpose
fmddr table list All base tables with field counts
fmddr table show <name|id> Table metadata (records, calc/unstored counts)
fmddr table fields <name|id> [--type ...] [--unstored] Fields on a table, optionally filtered
fmddr field show <Table::Field|id> Field metadata (calc text, AutoEnter, storage flags)
fmddr field references <token> [--kind ...] All references, denormalized
fmddr field script-refs <token> One row per (script, step) that names the field
fmddr field on-layouts <token> Distinct layouts that show the field
fmddr field field-refs <token> Other fields whose calcs name this field
fmddr field where <token> Counts grouped by owner_kind

Scripts and steps

Command Purpose
fmddr script list [--folder ...] [--name ...] All scripts, optionally filtered
fmddr script show <token> Script header + step count
fmddr script steps <token> [--type ...] [--has ...] Steps; --has execute_sql|evaluate|get_script_parameter|get_script_result
fmddr script calls <token> Scripts called from this script
fmddr script called-by <token> Everything that calls this script
fmddr script fields <token> Distinct fields touched anywhere in this script
fmddr script elements <token> Every distinct (field, script, layout, CF) referenced
fmddr script calcs <token> Every step's text
fmddr script chain <token> --depth N --format mermaid|dot BFS call-chain diagram (Mermaid / Graphviz / JSON)
fmddr script grep <token> <regex> Regex search step text within a script
fmddr step show "<Script>:<index>" Full step detail (FMP step id, has_* flags, var name, text)
fmddr step refs "<Script>:<index>" All fields/scripts/layouts/CFs this single step references
fmddr risky-steps --has <flag> [--limit N] Cross-script audit of risky steps (execute_sql etc.)

Layouts

Command Purpose
fmddr layout list [--folder ...] All layouts
fmddr layout show <token> Layout header
fmddr layout objects <token> [--type ...] Objects on the layout (Field/Button/Portal/TabControl/...)
fmddr layout fields <token> Distinct fields shown on the layout
fmddr layout scripts <token> Scripts referenced (buttons + ScriptTriggers)

Custom functions

Command Purpose
fmddr cf list All custom functions
fmddr cf show <token> CF metadata + body
fmddr cf calls <token> Other CFs this CF calls
fmddr cf fields <token> Fields referenced inside this CF body
fmddr cf called-by <token> Everywhere this CF is used

Relationship graph

Command Purpose
fmddr graph relationships All relationships
fmddr graph tos [--base-table ...] Table occurrences, optionally filtered
fmddr graph predicates <relationship_id> Join predicates for a relationship
fmddr graph path <from-TO> <to-TO> [--max-hops N] Shortest TO→TO path through relationships

Higher-order

Command Purpose
fmddr impact field <token> What breaks if this field is removed
fmddr impact script <token> [--depth N] Transitive script callers
fmddr unused --kind <field|script|layout|custom_function|value_list|table_occurrence> Dead code candidates
fmddr orphans Refs whose target rows don't exist
fmddr cleanup-candidates Names matching debt patterns (BACKUP/COPY/OLD/TEMP/...)

Search

Command Purpose
fmddr search <fts5-query> [--kind ...] [--limit N] FTS5 over names of every kind

Clipboard (macOS only, v1)

Edit a per-object split-tree XML file, then paste straight into FileMaker.

Command Purpose
fmddr copy <split-xml-path> Validate, wrap as <fmxmlsnippet>, push to OS clipboard
fmddr copy <script-xml> --steps all|5-12 Copy script steps (all or a subset) as a ScriptStep — paste into a script
fmddr paste [--type Script|Table|...] Read FM clipboard XML and emit <fmxmlsnippet>
fmddr validate <split-xml-path> Run pre-clipboard guards only (no clipboard side effects)
fmddr clipboard formats List FM-recognized clipboard formats currently present

Supports <Script>, <BaseTable>, <CustomFunction> roots. See docs/guides/clipboard.

Global flags

--db <path>                   Override default DB location (also $FMDDR_DB)
-o, --output {json,jsonl,table,markdown,csv,tsv}   Force output format
--version                     Show version and exit

DB lookup order: --db flag → $FMDDR_DB → unique *.fmddr.db in CWD → error.

Exit codes

Code Meaning
0 OK
1 Not found (no field/script/layout matched)
2 Ambiguous (qualify with Table::Field or --by-id)
64 Bad usage
70 Internal error

Output envelope

Every command emits a stable JSON shape:

{
  "kind": "field.script-refs",
  "version": 1,
  "ddr": {"path": "...", "sha256": "...", "indexed_at": "..."},
  "query": {"field": {"id": 8712, "name": "ProductionRunStatus", "table": "Production Run"}},
  "result": [
    {
      "script_id": 1245, "script_name": "Save Case Request", "folder": "case-management",
      "step_index": 82, "fm_line": 83, "step_type_name": "Perform Script",
      "step_text": "Perform Script [ \"Send Alert Notification\" ... ]",
      "via_to_id": 207, "via_to_name": "Production Run"
    }
  ],
  "result_count": 1,
  "truncated": false
}

kind is the agent contract — agents can dispatch on it without parsing free text. version: 1 allows future schema bumps without breaking older callers.

Tests double as runnable examples

tests/test_demo_questions.py runs through 22 representative analyses against a synthetic mini DDR fixture. Run it with -s to see the formatted output for each one:

pytest tests/test_demo_questions.py -s

When this file passes, the library answers all of them.

Architecture

src/fmddr/
├── cli.py                 # Typer entry point (thin shell)
├── parser/
│   ├── stream.py          # UTF-16 → UTF-8 decode + iterparse driver
│   ├── split.py           # Per-object XML tree explosion
│   └── refs.py            # The uniform Chunk[FieldRef|CustomFunctionRef] walker
├── index/
│   ├── schema.sql         # Canonical SQLite DDL
│   └── build.py           # Streaming insert with executemany batches + staged ref resolution
├── query/
│   ├── tables.py          # base_table + field queries
│   ├── fields.py          # Field reference drills
│   ├── scripts.py         # script + step + steps/refs/calls/elements/calcs
│   ├── layouts.py         # layouts + objects + fields + scripts
│   ├── cf.py              # Custom function queries
│   ├── graph.py           # TOs + relationships + predicates + path
│   ├── impact.py          # impact / unused / orphans / script-chain
│   ├── search.py          # FTS5 + script grep
│   └── resolvers.py       # name|id → (kind, id) with ambiguity handling
├── format/
│   └── output.py          # Stable envelope + multi-format rendering
└── db.py                  # Connection helpers + meta envelope info

The reference graph is the heart: field_ref, script_ref, layout_ref, custom_function_ref, value_list_ref, table_ref — every "where used / depends on" question reduces to a one- or two-hop SQL query.

Docs site

A full Astro/Starlight docs site lives in docs/:

cd docs
npm install
npm run dev          # http://localhost:4321
npm run build        # static site → docs/dist/, deployable anywhere

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fmddr-0.8.0.tar.gz (215.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fmddr-0.8.0-py3-none-any.whl (207.5 kB view details)

Uploaded Python 3

File details

Details for the file fmddr-0.8.0.tar.gz.

File metadata

  • Download URL: fmddr-0.8.0.tar.gz
  • Upload date:
  • Size: 215.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for fmddr-0.8.0.tar.gz
Algorithm Hash digest
SHA256 6a695ddb4956036115f390dc7e5e97e8b894e8e7c914542d711929a87706b45f
MD5 a6cfe76da64152c6f354a574fe39a062
BLAKE2b-256 23b8c236a4fc46ee67856244e27bbb6496101bb4a302595b63cfc74564dea9e8

See more details on using hashes here.

File details

Details for the file fmddr-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: fmddr-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 207.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for fmddr-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c4948d5b9807f3622b68ca6b05e6bd12ff83964fddb60e7ec255cd9f9e6afcf
MD5 80a938c5ceccca797340c2f3c85e7b72
BLAKE2b-256 8c6143511a191b0bdbd51628f5c73b3a1ff42957b9d0fcfd13672150f7de9d3a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page