isabelle-query 0.6.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. isabelle_query-0.6.7/.gitignore +51 -0
  2. isabelle_query-0.6.7/CONTRIBUTING.md +102 -0
  3. isabelle_query-0.6.7/LICENSE +21 -0
  4. isabelle_query-0.6.7/METRICS.md +156 -0
  5. isabelle_query-0.6.7/PKG-INFO +159 -0
  6. isabelle_query-0.6.7/README.md +131 -0
  7. isabelle_query-0.6.7/SCANNING.md +165 -0
  8. isabelle_query-0.6.7/configs/m3.toml +15 -0
  9. isabelle_query-0.6.7/pyproject.toml +105 -0
  10. isabelle_query-0.6.7/src/isabelle_query/__init__.py +12 -0
  11. isabelle_query-0.6.7/src/isabelle_query/_census_namespace.py +202 -0
  12. isabelle_query-0.6.7/src/isabelle_query/_corpus_constants.py +27 -0
  13. isabelle_query-0.6.7/src/isabelle_query/_dump_namespace.ML +54 -0
  14. isabelle_query-0.6.7/src/isabelle_query/_isabelle_namespace.py +114 -0
  15. isabelle_query-0.6.7/src/isabelle_query/_namespace_resolve.py +369 -0
  16. isabelle_query-0.6.7/src/isabelle_query/_notation.py +44 -0
  17. isabelle_query-0.6.7/src/isabelle_query/cli.py +1550 -0
  18. isabelle_query-0.6.7/src/isabelle_query/commands.py +1679 -0
  19. isabelle_query-0.6.7/src/isabelle_query/common.py +103 -0
  20. isabelle_query-0.6.7/src/isabelle_query/graph.py +822 -0
  21. isabelle_query-0.6.7/src/isabelle_query/model.py +413 -0
  22. isabelle_query-0.6.7/src/isabelle_query/parsing.py +2285 -0
  23. isabelle_query-0.6.7/src/isabelle_query/render.py +373 -0
  24. isabelle_query-0.6.7/src/isabelle_query/shape.py +2143 -0
  25. isabelle_query-0.6.7/src/isabelle_query/shape_cmds.py +490 -0
  26. isabelle_query-0.6.7/tests/README.md +147 -0
  27. isabelle_query-0.6.7/tests/fixtures/Nested.thy +28 -0
  28. isabelle_query-0.6.7/tests/fixtures/Owners.thy +18 -0
  29. isabelle_query-0.6.7/tests/fixtures/Shape.thy +108 -0
  30. isabelle_query-0.6.7/tests/fixtures/m3_configs.toml +9 -0
  31. isabelle_query-0.6.7/tests/support.py +164 -0
  32. isabelle_query-0.6.7/tests/test_annotations.py +302 -0
  33. isabelle_query-0.6.7/tests/test_bad_root.py +164 -0
  34. isabelle_query-0.6.7/tests/test_balanced.py +72 -0
  35. isabelle_query-0.6.7/tests/test_base_logic.py +65 -0
  36. isabelle_query-0.6.7/tests/test_bfs_depths.py +154 -0
  37. isabelle_query-0.6.7/tests/test_call_graph.py +215 -0
  38. isabelle_query-0.6.7/tests/test_census_sessions.py +351 -0
  39. isabelle_query-0.6.7/tests/test_cli_context_flag.py +62 -0
  40. isabelle_query-0.6.7/tests/test_cli_families.py +106 -0
  41. isabelle_query-0.6.7/tests/test_cli_names_flag.py +42 -0
  42. isabelle_query-0.6.7/tests/test_cli_no_global_only_options.py +77 -0
  43. isabelle_query-0.6.7/tests/test_cli_root_flag.py +80 -0
  44. isabelle_query-0.6.7/tests/test_cli_version.py +96 -0
  45. isabelle_query-0.6.7/tests/test_cli_with_comments.py +65 -0
  46. isabelle_query-0.6.7/tests/test_corpus.py +138 -0
  47. isabelle_query-0.6.7/tests/test_declared_names.py +533 -0
  48. isabelle_query-0.6.7/tests/test_deps_qualified.py +137 -0
  49. isabelle_query-0.6.7/tests/test_discover_roots.py +150 -0
  50. isabelle_query-0.6.7/tests/test_enclosing.py +306 -0
  51. isabelle_query-0.6.7/tests/test_enclosing_blocks.py +200 -0
  52. isabelle_query-0.6.7/tests/test_fact_citations.py +218 -0
  53. isabelle_query-0.6.7/tests/test_grep_window.py +80 -0
  54. isabelle_query-0.6.7/tests/test_heaps_dirs.py +211 -0
  55. isabelle_query-0.6.7/tests/test_indented_decls.py +235 -0
  56. isabelle_query-0.6.7/tests/test_induction.py +258 -0
  57. isabelle_query-0.6.7/tests/test_instantiation_spans.py +182 -0
  58. isabelle_query-0.6.7/tests/test_keywords.py +173 -0
  59. isabelle_query-0.6.7/tests/test_known_failures.py +61 -0
  60. isabelle_query-0.6.7/tests/test_largest.py +152 -0
  61. isabelle_query-0.6.7/tests/test_layout_surface.py +129 -0
  62. isabelle_query-0.6.7/tests/test_lines_forms.py +78 -0
  63. isabelle_query-0.6.7/tests/test_live_source.py +216 -0
  64. isabelle_query-0.6.7/tests/test_locus_format.py +170 -0
  65. isabelle_query-0.6.7/tests/test_methods.py +124 -0
  66. isabelle_query-0.6.7/tests/test_names.py +346 -0
  67. isabelle_query-0.6.7/tests/test_namespace_cache.py +260 -0
  68. isabelle_query-0.6.7/tests/test_namespace_fallback.py +115 -0
  69. isabelle_query-0.6.7/tests/test_nonisar_regions.py +822 -0
  70. isabelle_query-0.6.7/tests/test_open_ranges.py +192 -0
  71. isabelle_query-0.6.7/tests/test_outer_source.py +186 -0
  72. isabelle_query-0.6.7/tests/test_perf.py +153 -0
  73. isabelle_query-0.6.7/tests/test_preamble_attribution.py +121 -0
  74. isabelle_query-0.6.7/tests/test_proof_line.py +428 -0
  75. isabelle_query-0.6.7/tests/test_session_theories.py +149 -0
  76. isabelle_query-0.6.7/tests/test_shadowed_names.py +170 -0
  77. isabelle_query-0.6.7/tests/test_shape.py +999 -0
  78. isabelle_query-0.6.7/tests/test_shape_cli.py +282 -0
  79. isabelle_query-0.6.7/tests/test_shape_inline.py +165 -0
  80. isabelle_query-0.6.7/tests/test_show_inline_proof.py +102 -0
  81. isabelle_query-0.6.7/tests/test_src_doc_attribution.py +157 -0
  82. isabelle_query-0.6.7/tests/test_statement_slice.py +196 -0
  83. isabelle_query-0.6.7/tests/test_stdin_path.py +245 -0
  84. isabelle_query-0.6.7/tests/test_summary_aggregate.py +119 -0
  85. isabelle_query-0.6.7/tests/test_targets.py +395 -0
  86. isabelle_query-0.6.7/tests/test_thy_header.py +216 -0
@@ -0,0 +1,51 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Editor / OS / agent
14
+ .claude/*
15
+ !.claude/memory/
16
+ .DS_Store
17
+
18
+ # Reference papers (not distributed with the tool)
19
+ papers/
20
+
21
+ # End-of-session handoff scratchpad — rewritten each session, survives compaction.
22
+ prompt.md
23
+
24
+ # Generated data from batch runs — never tracked (regenerable, and bulky).
25
+ data/
26
+
27
+ # namespace_cache.py prototype scratch cache (QUERY_CACHE_DIR demo target).
28
+ .nscache/
29
+
30
+ # Scratch file for commit message bodies (see .claude/memory)
31
+ .commit-msg
32
+
33
+ # Drafted issue replies, written to be pasted into GitHub and then deleted.
34
+ # Ignored so a `git add -A` cannot sweep one into a commit.
35
+ .issue-*.md
36
+
37
+ # Session scratch: probe output, timing logs, mutation harnesses, and the
38
+ # throwaway session roots / HEAD checkouts a corpus comparison needs. Same
39
+ # reason as above — `git add -A` swept a scratch `.rootless/Bare.thy` into a
40
+ # commit once. Scratch lives in the working tree (not /tmp, which is gated),
41
+ # so the working tree has to know to ignore it.
42
+ .scratch*
43
+ .headtree/
44
+ .rootless/
45
+ .batchroot/
46
+ .census-*
47
+ .compare*
48
+ .mutate*
49
+ .timing*
50
+ .repro*
51
+ .variants*
@@ -0,0 +1,102 @@
1
+ # Contributing to isabelle-query
2
+
3
+ Normative rules for changing the tool. Orientation (what `query` is, how the
4
+ package is laid out) is in `CLAUDE.md`. User-facing docs are `README.md` (the
5
+ CLI surface), `SCANNING.md` (what counts as a declaration, a citation and a
6
+ session) and `METRICS.md` (the `query shape` reference).
7
+
8
+ ## Where design decisions are recorded
9
+
10
+ **In commit messages.** They carry the reasoning, the rejected alternatives and
11
+ the before/after, and they are the only record that cannot drift from the code.
12
+ Cross-reference a body of work with a stable `[tag]` handle, and recover it with
13
+
14
+ git log --grep='\[grep-owner-span\]'
15
+
16
+ `todo.md` holds only *open* work — things not yet done. It is not a changelog.
17
+ If you want to know why something is the way it is, read the commits.
18
+
19
+ **Cite only what a reader of this repo can open.** Comments, docstrings and
20
+ config headers must not reference paths outside it — a pointer the reader cannot
21
+ resolve is worse than no pointer, and the design material it names may be
22
+ unpublished. For the proof-shape metrics the public authority is
23
+ `src/isabelle_query/shape.py` itself (definitions at each metric) plus the
24
+ `M1`–`M6` table in `METRICS.md`. Check with:
25
+
26
+ grep -rn 'docs/' --include='*.py' --include='*.toml' src tests scripts configs
27
+
28
+ ## CLI contract (follow when adding or changing commands)
29
+
30
+ Two families, each matching an external convention; a command's primary
31
+ positional decides which one it is.
32
+
33
+ - **lookup** (git/brew: `git show REF...`, `brew deps FORMULA`) — the
34
+ primary positional is a **subject** (entry/theory name), one-or-more,
35
+ reported in turn. Add it with `_add_subject_list_arg`. **No trailing
36
+ PATH positionals**: "who calls X" is corpus-global, so scope with the
37
+ global `-R/--root` and narrow with *semantic* flags (`--external`,
38
+ `-r/--recursive`), never a file subset. Members: `show`, `callers`,
39
+ `callees`, `deps`, `uses`, `theory`, `defs`, `outline`, `methods`.
40
+ - **search** (grep/rg: `grep PAT PATH...`) — the primary positional is a
41
+ pattern (or nothing), and **paths are the trailing positionals**, added
42
+ with `_add_path_files_arg` (resolved by `_load_sections`). Members:
43
+ `grep`, `largest`, `sorry` (and `find` once it gains PATH/`--theory`
44
+ scope under `[theory-refs]`).
45
+
46
+ **Never return an empty success for a question you could not ask.** A silent
47
+ zero is indistinguishable from an honest zero, so a caller cannot tell a broken
48
+ run from a real one — `query -R /typo shape census` once printed nothing and
49
+ exited 0, and a shell path-expansion bug turned that into a run of plausible
50
+ zero-record censuses. A root that cannot be read reports on stderr and exits
51
+ `2`, a code deliberately distinct from `1`.
52
+
53
+ Shared-feature help text comes from one helper each, so wording can't
54
+ drift command-to-command — always add a feature through its helper, never
55
+ inline:
56
+
57
+ | helper | feature |
58
+ |---|---|
59
+ | `_add_subject_list_arg` | subject list |
60
+ | `_add_path_files_arg` | trailing `PATH` |
61
+ | `_add_names_flag` | `--names` (**no `-n`** — reserved for grep's line-number meaning) |
62
+ | `_add_count_flag` | `-c/--count` |
63
+ | `_add_with_comments_flag` | `--with-comments` — the *only* prose-search toggle on `find`/`grep` (**no `-a`**, which is `_add_mode_flags`' show-all) |
64
+ | `_add_mode_flags` | the `-a` / `--names` / `-c` bundle |
65
+ | `_add_verbatim_flag` | `-V/--verbatim` |
66
+ | `_add_comment_flags` | `--comments-off` / `--comments-only` |
67
+ | `_add_context_flag` | `-U/--context` — one short flag everywhere, default per-command |
68
+ | `_add_drop_names_flag` | `--drop-names-upto` |
69
+
70
+ ## Verification
71
+
72
+ The suite is not sufficient on its own for parser changes; unit tests cannot
73
+ see a scanner failing at scale. Two habits catch what they miss:
74
+
75
+ - **Diff the entry set** after any parser change — `scripts/dump_entries.py`
76
+ (add `--spans` when extents may move; a change can leave entries identical
77
+ while moving a thousand declaration ends).
78
+ - **Diff the discovered theory set** after any change to session or `imports`
79
+ parsing — and diff it **as a set, not a count**. `dump_entries.py` walks
80
+ `ent.rglob("*.thy")`, so it never calls `parse_thy_imports` and cannot see a
81
+ discovery regression: when `[thy-header]` silently dropped 72 theories it
82
+ reported an identical 55,838 entries. Compare
83
+ `{p for s in iter_sessions(root) for _, p in session_theories(s)}` against
84
+ the same set from `git archive <ref> | tar -x -C .scratch-head`. A count
85
+ alone hides a simultaneous gain and loss, which is exactly what happened.
86
+ - **Check a new test can fail.** Patch the behaviour it pins, run it, restore.
87
+ Several tests have been written that could not fail. Two traps in the loop
88
+ itself, both of which have produced a wrong verdict here:
89
+ - **Run the mutation with `PYTHONDONTWRITEBYTECODE=1`.** A mutation harness
90
+ rewrites the source several times a second, and CPython invalidates a
91
+ `.pyc` on `(mtime, size)` — so a same-second rewrite can leave the previous
92
+ bytecode in place and the subprocess runs code that is not on disk. That
93
+ reports a *live* mutation as SURVIVED. `CAUGHT` is always trustworthy;
94
+ `SURVIVED` is not, until the cache is off.
95
+ - **A survivor may be masked, not dead.** A unit test can be shadowed by a
96
+ downstream guard that ends the scan anyway. Before concluding a branch is
97
+ unreachable, diff the entry set with the mutation applied: two branches
98
+ that survived every unit test moved 706 and 330 entries.
99
+ When a branch really is unreachable from valid input, say so in the comment
100
+ and pin it at the helper/regex level, rather than leaving it looking tested.
101
+
102
+ `pytest -q` stays green after every change.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 András Salamon
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.
@@ -0,0 +1,156 @@
1
+ # Proof-shape metrics — `query shape`
2
+
3
+ A command reference for the `shape` family. Where the other subcommands ask
4
+ *what is declared* and *which facts cite which*, `shape` measures the **shape of
5
+ individual proof steps**: how big a step is, how deeply nested, how many facts
6
+ it holds at once, how much it re-says, and how it is discharged.
7
+
8
+ Everything here is **source-level** — computed by parsing `.thy` text, with no
9
+ Isabelle build and no proof replay. Each value is either *exact* at source level
10
+ or a token-based *estimator*; estimator columns carry an `_est` suffix, so the
11
+ two are never silently conflated.
12
+
13
+ The authoritative definitions — each metric's exact rule, the term-level
14
+ semantics an estimator approximates, and its known approximations — are in
15
+ `src/isabelle_query/shape.py`, stated at the metric. This file decodes the
16
+ identifiers and says how to invoke them; it does not restate the definitions,
17
+ which would drift.
18
+
19
+ ## Views
20
+
21
+ ```sh
22
+ query shape summary # per-theory aggregate table
23
+ query shape steps [THEORY[:A..B]] # per-step records
24
+ query shape lemma <name>... # one proof: every step + M6 curve
25
+ query shape widest [-N n] [PATH...] # the widest steps
26
+ query shape census # stream per-proof JSONL over a corpus
27
+ ```
28
+
29
+ | view | takes | output |
30
+ |---|---|---|
31
+ | `summary` | — | one row per theory |
32
+ | `steps` | an optional `THEORY` or `THEORY:A..B` span | one row per Isar step |
33
+ | `lemma` | one or more entry names | every step, an aggregate footer, and the M6 curve |
34
+ | `widest` | optional trailing `PATH`s | the *n* widest steps by a chosen metric |
35
+ | `census` | — | one JSON object per proof, streamed and resumable |
36
+
37
+ Flags, beyond the global `-R/--root` and `--version`:
38
+
39
+ | flag | views | meaning |
40
+ |---|---|---|
41
+ | `--json` | all but `census` | one JSONL record per line instead of the table |
42
+ | `-a`, `--all` | `steps` | include non-goal steps (context / plumbing / closing); the default shows goal steps only, where the metrics attach |
43
+ | `--scope {proof,entry}` | `summary` | size columns over the proof body (default) or the whole entry including its statement, as `largest` counts |
44
+ | `--content {all,code,prose}` | `summary` | size columns over all lines (default), code only (prose stripped), or prose only |
45
+ | `-N n` | `widest` | how many steps to rank |
46
+ | `--metric {w2,w1,fanin,live}` | `widest` | rank by as-written token width (default), free variables, cited facts, or simultaneously-live facts |
47
+ | `--config TOML`, `--corpus NAME` | `steps`, `lemma` | add the M3 `frame_ratio` columns; see [Corpus configs](#corpus-configs-m3) |
48
+ | `--resume FILE` | `census` | skip records already present in a prior run |
49
+
50
+ ## Metrics
51
+
52
+ | id | column | measures | kind |
53
+ |----|--------|----------|------|
54
+ | M1 | `w1_est` | distinct free variables in the stated proposition | estimator |
55
+ | — | `const_est` | distinct constants in the stated proposition (names + operator notation) | estimator |
56
+ | — | `const_canon_est` | `const_est` with operator glyphs canonicalised to their Isabelle constant (`\<le>`/`\<subseteq>` → one) | estimator |
57
+ | M2 | `w2_src` | as-written proposition width, in tokens | exact |
58
+ | M3 | `frame_ratio` | delta-tracing overhead (components mentioned / changed) | definitional¹ |
59
+ | M4 | `dag_ratio_est` | cross-step redundancy (repeated bracketed subterms / block) | estimator |
60
+ | M5a | `fanin` | distinct facts cited for a step | exact |
61
+ | M5b | `live` | named facts simultaneously live at a step | exact |
62
+ | M5c | `introduce`/`consume` | fact-introducing vs fact-citing lines | exact |
63
+ | M6 | extension curve | width remaining after naming the *k* most-repeated subterms | estimator |
64
+
65
+ ¹ M3 is defined purely syntactically, so it has no estimator/reference split —
66
+ but it needs a per-corpus config.
67
+
68
+ The columns group into axes, which is how the `summary` table is laid out:
69
+
70
+ | axis | asks | columns |
71
+ |------|------|---------|
72
+ | Length | how big is the proof? | `n_steps`, `n_goals`, `proof_lines`, `proof_tokens` (raw + `_code`), `entry_lines` |
73
+ | Depth | how deeply nested? | `depth_max` |
74
+ | Width | how big is one step? | `w1_est`, `const_est`/`const_canon_est`, `w2_src` |
75
+ | Space | how many facts held at once? | `fanin`, `live`, `introduce`/`consume` |
76
+ | Redundancy | how much is re-said? | `dag_ratio_est`, M6 extension curve |
77
+ | Automation | how is it discharged? | `trivial_frac`, `method_kinds`, induction discipline (`n_induct`, `induct_arbitrary_max`, `induct_recursion`) |
78
+ | Framing | how much to say to change a little? | `frame_ratio` |
79
+
80
+ They are separate measurements, not components of a single score: a proof can be
81
+ long and shallow, or short and wide.
82
+
83
+ ## JSONL records
84
+
85
+ `--json` (and `census`) emit one JSON object per line, keyed by a stable
86
+ `(theory, lemma, line)` position so records join across runs without
87
+ re-instrumentation. Two record shapes — per-step (`steps` / `lemma` / `widest`)
88
+ and per-proof aggregate (`summary --json` / `census`). **The full field lists are
89
+ in the `shape_cmds` module docstring.**
90
+
91
+ Per-proof records also carry `session` (`null` when the load had no session
92
+ context), which a corpus run needs: AFP theory names are not unique across
93
+ entries, so `(theory, lemma)` alone cannot identify one.
94
+
95
+ ## Running a census over a corpus
96
+
97
+ ```sh
98
+ query -R AFP/thys shape census > afp.jsonl
99
+ ```
100
+
101
+ One process for the whole corpus, one session at a time — do not loop `query`
102
+ over entries in a shell, which pays interpreter and process startup per entry
103
+ and dominates the run. Memory is bounded by the largest single session rather
104
+ than by the corpus, and a session that fails to parse is reported on stderr and
105
+ skipped rather than aborting the run.
106
+
107
+ Output is flushed per session, so a killed run leaves a valid JSONL prefix, and
108
+ `--resume FILE` skips records already present:
109
+
110
+ ```sh
111
+ query -R AFP/thys shape census --resume afp.jsonl >> afp.jsonl
112
+ ```
113
+
114
+ Exit status follows the usual rule: `2` if no session could be read at all,
115
+ `0` with a stderr summary if some were skipped, `0` in silence for an honest
116
+ zero. A directory with no `ROOT` is still a corpus (the `*.thy` fallback),
117
+ censused as a single unnamed group.
118
+
119
+ ## The method table, and where it limits the numbers
120
+
121
+ The one place Isabelle informs a result is the method/attribute table that tells
122
+ a proof method (`by auto`) from a fact citation.
123
+
124
+ `census` uses a fixed, committed **approximate** table — the union of the
125
+ distribution sessions most AFP entries build on (HOL, HOL-Library, HOL-Analysis,
126
+ HOL-Eisbach, HOL-Decision_Procs) — so it needs no Isabelle and regenerates
127
+ identically anywhere. Being fixed, it is an approximation: methods an entry
128
+ defines itself (an Eisbach `cs_concl`) or that come from a niche logic (Nominal's
129
+ `nominal_induct`) are not in it, so the **Automation** axis under-counts on those
130
+ steps. This affects a few percent of proofs, only in method-defining entries;
131
+ fan-in and width are unaffected.
132
+
133
+ The per-project verbs (`callers` / `callees` / `unused` / `methods` / `shape`)
134
+ instead resolve a **session-exact** table from a loaded Isabelle heap when one is
135
+ built — cached, and never a build — falling back to the committed table with a
136
+ warning. Heaps are looked for where Isabelle looks: `$ISABELLE_HEAPS` first, then
137
+ `$ISABELLE_HEAPS_SYSTEM`, so a stock install with nothing built locally still
138
+ resolves the exact table from the distribution's own `HOL`.
139
+
140
+ A second, smaller committed table backs `const_canon_est`: a notation table
141
+ mapping operator glyphs to their Isabelle constant (`\<le>` → `less_eq`),
142
+ resolved once from a heap by `scripts/extract_notation.py` and checked in, so
143
+ runtime stays pure-Python. A glyph the table does not carry falls back to itself,
144
+ so it only ever dedups, never loses, a constant — and the raw-glyph `const_est`
145
+ beside it stays table-independent.
146
+
147
+ ## Corpus configs (M3)
148
+
149
+ Source parsing cannot resolve types, so M3's "configuration type" degrades to a
150
+ per-corpus list of selector / constructor / relation names. Supply it as TOML —
151
+ one `[corpus]` table per entry — and pass `--config FILE [--corpus NAME]` to
152
+ `steps` / `lemma` to add the `frame_ratio` columns. `configs/m3.toml` ships a
153
+ `Cook_Levin` table. Without a config the rest of the family runs unchanged.
154
+
155
+ When comparing across corpora, normalize per proof or per goal step, never per
156
+ kloc — line counts confound formatting.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: isabelle-query
3
+ Version: 0.6.7
4
+ Summary: Query the live theory index of an Isabelle/Isar project — entries, call graph, dependencies, and dead code — by parsing .thy files on every invocation.
5
+ Project-URL: Homepage, https://github.com/ott2/isabelle-query
6
+ Project-URL: Source, https://github.com/ott2/isabelle-query
7
+ Author-email: András Salamon <Andras.Salamon@st-andrews.ac.uk>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: afp,formal-verification,isabelle,isar,proof-engineering,theorem-proving
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Topic :: Software Development :: Quality Assurance
25
+ Requires-Python: >=3.9
26
+ Requires-Dist: isabelle-layout>=0.2.2
27
+ Description-Content-Type: text/markdown
28
+
29
+ # isabelle-query
30
+
31
+ `query` is a command-line tool for **querying an Isabelle/Isar project** — its
32
+ entries (definitions, lemmas, theorems, datatypes), call graph, theory
33
+ dependencies, outstanding `sorry`s, dead code, and the shape of its proofs.
34
+
35
+ It parses the project's `.thy` sources on every invocation, so results always
36
+ match the current tree: **no Isabelle build, no proof replay**. A large project
37
+ parses in a fraction of a second, and the whole AFP in a couple of minutes. It is
38
+ aimed at projects big enough that grep-and-examine has stopped working — AFP
39
+ entries, the AFP itself, or industrial verification.
40
+
41
+ Pure Python. One runtime dependency,
42
+ [isabelle-layout](https://pypi.org/project/isabelle-layout/) — the ROOT and
43
+ theory-header parser, split out so that reading an Isabelle project's structure
44
+ does not require installing a CLI. `pip` fetches it for you.
45
+
46
+ ## Commands
47
+
48
+ ```sh
49
+ query summary # theory overview table (-S: corpus/session aggregate)
50
+ query theory MyTheory # entries in a theory (-n for terse names)
51
+ query find <regex> # search entry names (--statement: search statements)
52
+ query show <name> # a named entry's declaration + body
53
+ query enclosing FILE:LINE # which entry + proof block owns a line; inverse of outline
54
+ query callers <name> [-r] # who references a name (reverse; -r = transitive)
55
+ query callees <name> [-r] # what a name references (forward)
56
+ query deps <theory> [-r] # what a theory imports (forward; reverse: uses)
57
+ query sorry # outstanding sorry's
58
+ query unused # dead-code / unused-entry analysis
59
+ query shape <view> # proof-shape metrics (summary|steps|lemma|widest|census)
60
+ ```
61
+
62
+ Every subcommand takes `-h`; `query -h` lists all 18.
63
+
64
+ ## Examples
65
+
66
+ Point `query` at any session directory with `-R` (or `--root`):
67
+
68
+ ```sh
69
+ query -R AFP/thys largest # the biggest entries, by line count
70
+ query -R AFP/thys callers metric_domain_tfin_def # every proof step that cites a fact
71
+ query -R AFP/thys find --statement tfin # lemmas *stated about* tfin, whatever their name
72
+ query -R AFP/thys enclosing Tfin.thy:412 # the lemma and nearest proof block a build error sits in
73
+ query -R AFP/thys enclosing Tfin:88..140 # every entry a diff hunk touches
74
+ query -R AFP/thys grep simp Tfin.thy:88..140 # search just a hunk
75
+ ```
76
+
77
+ Locations and spans share one grammar (`theory:line`, `theory:A..B`), so the
78
+ tool's output is valid input: a locus from `callers` / `sorry` pastes into
79
+ `enclosing`, and a span from `outline` / `largest` — or a proof block from
80
+ `enclosing`'s own drill-down (`▸ have key 11..14`) — pastes into `lines`.
81
+
82
+ ## What it reads
83
+
84
+ Only **live Isar text**. A name in a comment, a `\<comment>` note, a `text`
85
+ block or an `ML` body is not a citation, so it never invents a caller or hides a
86
+ dead lemma — and a `definition` left behind in a comment is not an entry.
87
+ Regions are found by a character-level scan, not by line, so
88
+ `by (simp add: foo) (* not bar *)` keeps `foo` and drops `bar`.
89
+
90
+ Layout carries no meaning: Isar is whitespace-insensitive, so a declaration is
91
+ recognised wherever a *command* can start, at any indentation and any block
92
+ depth. Discovery loads what `isabelle build` compiles — each session's declared
93
+ theories plus the closure of their in-entry imports.
94
+
95
+ See **[SCANNING.md](SCANNING.md)** for the details: locale scope, method names
96
+ that collide with fact names, corpus aggregation, and the prose view.
97
+
98
+ ## Proof-shape metrics
99
+
100
+ `query shape` measures the shape of individual proof steps — how big a step is,
101
+ how deeply nested, how many facts it holds at once, how much is re-said, and how
102
+ it is discharged. All source-level, no build.
103
+
104
+ ```sh
105
+ query shape summary # per-theory aggregate table
106
+ query shape lemma <name> # one proof: every step
107
+ query -R AFP/thys shape census # per-proof JSONL over a whole corpus
108
+ ```
109
+
110
+ See **[METRICS.md](METRICS.md)** for the command reference, the metric table, and
111
+ the JSONL record schema.
112
+
113
+ ## Exit status
114
+
115
+ `0` the command ran; `1` the request could not be resolved (unknown theory or
116
+ path, no subcommand); `2` bad usage — an argparse error, or **a root that could
117
+ not be read**; `141` a downstream reader closed the pipe (`query shape census |
118
+ head`), as a shell reports for SIGPIPE.
119
+
120
+ A root that yields no theories is reported on stderr and never as an empty
121
+ success, so a script can tell a broken run from an honestly empty one:
122
+
123
+ ```
124
+ $ query -R /typo/path shape census
125
+ query: /typo/path: no such directory (given to -R/--root)
126
+ $ echo $?
127
+ 2
128
+ ```
129
+
130
+ ## Installation
131
+
132
+ Requires Python 3.9 or greater. Installs a `query` command on your `PATH`, and
133
+ pulls `isabelle-layout` from PyPI.
134
+
135
+ ```sh
136
+ pip install isabelle-query # from PyPI
137
+ pip install . # from a checkout
138
+ ```
139
+
140
+ An editable install, for working on the tool itself:
141
+
142
+ ```sh
143
+ git clone https://github.com/ott2/isabelle-query
144
+ cd isabelle-query
145
+ python -m venv .venv && source .venv/bin/activate # optional but recommended
146
+ pip install -e .
147
+ ```
148
+
149
+ ## Documentation
150
+
151
+ | file | what |
152
+ |---|---|
153
+ | [SCANNING.md](SCANNING.md) | how `query` reads a project — what counts as a declaration, a citation, and a session |
154
+ | [METRICS.md](METRICS.md) | `query shape` command reference and metric definitions |
155
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | the CLI contract and where design decisions are recorded |
156
+
157
+ ## Authors & license
158
+
159
+ By András Salamon, with Claude Opus 4.6, 4.7, 4.8, and 5. [MIT](LICENSE).
@@ -0,0 +1,131 @@
1
+ # isabelle-query
2
+
3
+ `query` is a command-line tool for **querying an Isabelle/Isar project** — its
4
+ entries (definitions, lemmas, theorems, datatypes), call graph, theory
5
+ dependencies, outstanding `sorry`s, dead code, and the shape of its proofs.
6
+
7
+ It parses the project's `.thy` sources on every invocation, so results always
8
+ match the current tree: **no Isabelle build, no proof replay**. A large project
9
+ parses in a fraction of a second, and the whole AFP in a couple of minutes. It is
10
+ aimed at projects big enough that grep-and-examine has stopped working — AFP
11
+ entries, the AFP itself, or industrial verification.
12
+
13
+ Pure Python. One runtime dependency,
14
+ [isabelle-layout](https://pypi.org/project/isabelle-layout/) — the ROOT and
15
+ theory-header parser, split out so that reading an Isabelle project's structure
16
+ does not require installing a CLI. `pip` fetches it for you.
17
+
18
+ ## Commands
19
+
20
+ ```sh
21
+ query summary # theory overview table (-S: corpus/session aggregate)
22
+ query theory MyTheory # entries in a theory (-n for terse names)
23
+ query find <regex> # search entry names (--statement: search statements)
24
+ query show <name> # a named entry's declaration + body
25
+ query enclosing FILE:LINE # which entry + proof block owns a line; inverse of outline
26
+ query callers <name> [-r] # who references a name (reverse; -r = transitive)
27
+ query callees <name> [-r] # what a name references (forward)
28
+ query deps <theory> [-r] # what a theory imports (forward; reverse: uses)
29
+ query sorry # outstanding sorry's
30
+ query unused # dead-code / unused-entry analysis
31
+ query shape <view> # proof-shape metrics (summary|steps|lemma|widest|census)
32
+ ```
33
+
34
+ Every subcommand takes `-h`; `query -h` lists all 18.
35
+
36
+ ## Examples
37
+
38
+ Point `query` at any session directory with `-R` (or `--root`):
39
+
40
+ ```sh
41
+ query -R AFP/thys largest # the biggest entries, by line count
42
+ query -R AFP/thys callers metric_domain_tfin_def # every proof step that cites a fact
43
+ query -R AFP/thys find --statement tfin # lemmas *stated about* tfin, whatever their name
44
+ query -R AFP/thys enclosing Tfin.thy:412 # the lemma and nearest proof block a build error sits in
45
+ query -R AFP/thys enclosing Tfin:88..140 # every entry a diff hunk touches
46
+ query -R AFP/thys grep simp Tfin.thy:88..140 # search just a hunk
47
+ ```
48
+
49
+ Locations and spans share one grammar (`theory:line`, `theory:A..B`), so the
50
+ tool's output is valid input: a locus from `callers` / `sorry` pastes into
51
+ `enclosing`, and a span from `outline` / `largest` — or a proof block from
52
+ `enclosing`'s own drill-down (`▸ have key 11..14`) — pastes into `lines`.
53
+
54
+ ## What it reads
55
+
56
+ Only **live Isar text**. A name in a comment, a `\<comment>` note, a `text`
57
+ block or an `ML` body is not a citation, so it never invents a caller or hides a
58
+ dead lemma — and a `definition` left behind in a comment is not an entry.
59
+ Regions are found by a character-level scan, not by line, so
60
+ `by (simp add: foo) (* not bar *)` keeps `foo` and drops `bar`.
61
+
62
+ Layout carries no meaning: Isar is whitespace-insensitive, so a declaration is
63
+ recognised wherever a *command* can start, at any indentation and any block
64
+ depth. Discovery loads what `isabelle build` compiles — each session's declared
65
+ theories plus the closure of their in-entry imports.
66
+
67
+ See **[SCANNING.md](SCANNING.md)** for the details: locale scope, method names
68
+ that collide with fact names, corpus aggregation, and the prose view.
69
+
70
+ ## Proof-shape metrics
71
+
72
+ `query shape` measures the shape of individual proof steps — how big a step is,
73
+ how deeply nested, how many facts it holds at once, how much is re-said, and how
74
+ it is discharged. All source-level, no build.
75
+
76
+ ```sh
77
+ query shape summary # per-theory aggregate table
78
+ query shape lemma <name> # one proof: every step
79
+ query -R AFP/thys shape census # per-proof JSONL over a whole corpus
80
+ ```
81
+
82
+ See **[METRICS.md](METRICS.md)** for the command reference, the metric table, and
83
+ the JSONL record schema.
84
+
85
+ ## Exit status
86
+
87
+ `0` the command ran; `1` the request could not be resolved (unknown theory or
88
+ path, no subcommand); `2` bad usage — an argparse error, or **a root that could
89
+ not be read**; `141` a downstream reader closed the pipe (`query shape census |
90
+ head`), as a shell reports for SIGPIPE.
91
+
92
+ A root that yields no theories is reported on stderr and never as an empty
93
+ success, so a script can tell a broken run from an honestly empty one:
94
+
95
+ ```
96
+ $ query -R /typo/path shape census
97
+ query: /typo/path: no such directory (given to -R/--root)
98
+ $ echo $?
99
+ 2
100
+ ```
101
+
102
+ ## Installation
103
+
104
+ Requires Python 3.9 or greater. Installs a `query` command on your `PATH`, and
105
+ pulls `isabelle-layout` from PyPI.
106
+
107
+ ```sh
108
+ pip install isabelle-query # from PyPI
109
+ pip install . # from a checkout
110
+ ```
111
+
112
+ An editable install, for working on the tool itself:
113
+
114
+ ```sh
115
+ git clone https://github.com/ott2/isabelle-query
116
+ cd isabelle-query
117
+ python -m venv .venv && source .venv/bin/activate # optional but recommended
118
+ pip install -e .
119
+ ```
120
+
121
+ ## Documentation
122
+
123
+ | file | what |
124
+ |---|---|
125
+ | [SCANNING.md](SCANNING.md) | how `query` reads a project — what counts as a declaration, a citation, and a session |
126
+ | [METRICS.md](METRICS.md) | `query shape` command reference and metric definitions |
127
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | the CLI contract and where design decisions are recorded |
128
+
129
+ ## Authors & license
130
+
131
+ By András Salamon, with Claude Opus 4.6, 4.7, 4.8, and 5. [MIT](LICENSE).