Commands

discover.py

Infers a project's conventions from its code and tooling config, with the evidence behind each one. Deterministic: no model, no network, nothing sent anywhere.

Agent or script? The agent reaches the same engine through discover_conventions, then verifies and rewrites what it finds. Run this script yourself when you want the raw candidates saved as pending rules without an agent's judgement.

Synopsis

python3 discover.py --name myapp                  # preview candidates, saves nothing
python3 discover.py --name myapp --save           # save as PENDING rules
python3 discover.py --name myapp --save --activate
python3 discover.py --name myapp --min-ratio 0.8 --min-files 3
python3 discover.py --name myapp --markers extra.json

Options

OptionDefaultMeaning
--name NAME requiredThe project.
--saveoffStore the candidates. Without it nothing is saved.
--activateoffWith --save: make the rules active at once instead of pending.
--min-ratio R0.9How lopsided the evidence must be (the share of files or occurrences that follow the convention).
--min-files N5Minimum number of files of evidence.
--markers FILEA JSON file with extra pattern markers; see your own markers.

What it looks at

Four kinds of evidence, each reported with its numbers:

config
Tooling config. .editorconfig; manifests for PHP, JS/TS, Python, Go, Rust, JVM, .NET, Ruby, Dart, Elixir, Scala, C/C++; Makefile and justfile targets; about 20 linter and formatter configs (ESLint, Prettier, Biome, ruff, golangci-lint, rustfmt, RuboCop, phpcs, PHPStan and more); and about 90 well-known frameworks and tools recognised from dependency names. Runtime targets (PHP, Node, Python, Go, Rust, Java, .NET, Ruby versions) become "target X" rules.
stats
Code statistics. Indentation, quote style, semicolons, line length and file-name casing per folder.
marker
Language markers. 120 framework-independent conventions across 19 languages (PHP, JavaScript, TypeScript, Vue, Svelte, Python, Go, Ruby, Java, Kotlin, C#, Rust, Scala, Dart, Elixir, Lua, Swift, C, C++): conventions of omission ("no console.log", "no .unwrap()", "no bare except:") and style choices where one side dominates ("f-strings 95% against .format()").
framework
Framework markers. 81 markers for 19 frameworks, evaluated only when the project demonstrably uses the framework (a dependency in a manifest, or a signature in the source): Laravel, React, Express, Vue, SvelteKit, Angular, Django, Gin, Spring Boot, Ktor, ASP.NET Core, Axum, Rails, Phoenix, Play, Flutter, LÖVE, SwiftUI and Qt.

When something is reported

A convention becomes a candidate only when the evidence is lopsided:

Marker kindReports a rule whenEnough evidence means
FileMarkerat least min-ratio of the applicable files contain a patternat least min-files applicable files
OccurrenceMarkerat least min-ratio of the occurrences of X are also Yat least 15 occurrences
AbsentMarkerat least 95% of non-test files do not contain a patternat least 8 files (or min-files if larger)
ChoiceMarkerone of several styles has at least min-ratio of the occurrencesat least 15 occurrences

Only the team's own code counts. Vendored, built, minified and generated files are ignored (vendor, node_modules, dist, build, public/js, *.min.*, lock files), as are files over 300 KB, and at most 3,000 files per language are read. Markers that ask for non-test code skip folders such as tests, migrations, config and scripts.

Example

$ python3 discover.py --name lore
== stats (3) ==
[glob:**/*.py] Indent python code with 4 spaces.
    evidence: 93/95 (98%) of python files
[glob:**/*.py] Use double quotes for strings in python code.
    evidence: 7641/7811 (98%) of string literals
[glob:tests/*.py] Name .py files in snake_case (tests).
    evidence: tests: 12/12 (100%)

== marker (8) ==
[glob:**/*.py] Never use a bare `except:`; catch specific exceptions.
    evidence: 98/98 (100%) of non-test files contain none [py-bare-except]
…

11 candidate rule(s). Re-run with --save to store them as pending.

Notes

  • It reports what the code does, not what is right. A habit can be universal and still bad. Read the pending list and reject what you do not want enforced.
  • Every rule carries its evidence (for example 93/95 (98%) of python files) and, for markers, the marker's id in brackets so you can find the definition.
  • Nothing is found? Fewer than min-files files of evidence, or no convention reaches the ratio. Try --min-ratio 0.8.
  • Saved rules start pending (the script has no agent to judge them). Near-identical rules are not duplicated; rules that contradict an existing one are reported.

Your own markers

For a one-off, pass a JSON list. Each object has a kind (file, occ, absent or choice), a glob, an id, and the fields of that kind:

[
  {"kind": "absent", "id": "my-todo", "glob": "**/*.py",
   "bad": "TODO\\(", "rule": "Do not leave TODO markers in application code."},
  {"kind": "choice", "id": "my-quotes", "glob": "**/*.js",
   "options": [
     {"label": "single", "pattern": "'[^']*'", "rule": "Use single quotes."},
     {"label": "double", "pattern": "\"[^\"]*\"", "rule": "Use double quotes."}]}
]

To make a convention part of Lore itself (so everyone gets it), add a marker to a language or framework module: see Extending Lore.