# Reasoning Modules — Reference **Version:** alpha · API 1. This is the per-module lookup: the kinds each published module declares, the fields you write, the fields it derives for you, and the queries you can call. The [Reasoning Modules Tutorial](/tutorial/dke/reasoning-modules/) teaches how to use one; this page is what you consult once you are using it. **This document as Markdown:** [dke-reasoning-modules-ref.md](/ref/dke/reasoning-modules/dke-reasoning-modules-ref.md) — the same text this page is rendered from, for readers and tools that would rather have the source than the page. Module semantics in general — how `import` resolves, how namespacing works, what re-submitting does — are in the [DKE Python Reference §9](/ref/dke/python/#modules). This page covers what the individual libraries contain. --- ## 1. What a module declares Four kinds of declaration, all of which become part of your store the moment you import it: | Declaration | What it is | |---|---| | **Classes** | the kinds of thing the domain has, and the fields they carry | | **Derived fields** | values the engine works out from the ones you wrote, and keeps current as those change — you never write one | | **Standing rules** | conclusions that hold exactly while their condition holds over your data, and withdraw themselves when it stops | | **Queries** | `def`s you call by name, which answer over whatever facts you have loaded | Names are scoped to the module: `code.Function` and `finance.Function` are different kinds and never read each other's data. Derived fields and rule conclusions carry a **`derived` provenance** rather than a source, so you can always tell a conclusion from something you were told. --- ## 2. `code` — reasoning about a codebase `code` reasons about a codebase: import it, load facts about your own source, and ask. **Version 1.12.0.** Download it at [dke.langsyn.com/source/code/latest/code.dpy](https://dke.langsyn.com/source/code/latest/code.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/code/1.12.0/code.dpy`. A versioned URL says exactly which bytes you read and serves them until the next release, which replaces the previous version rather than keeping it — so `latest` is the link that goes on lasting, and the copy you have already downloaded is yours either way. A version never changes without its content changing, so the version you hold answers whether you are current without your having to diff anything. ### 2.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `code.Function` | `name`, `module`, `lines`, `branches`, `loops`, `deprecated`, `test`, `called`, `returns_ignored`, `language`, `callers`, `callees`, `explained` | | `code.Class` | `name`, `module`, `methods`, `language` | | `code.Call` | `caller`, `callee`, `site` | | `code.UnresolvedCall` | `caller`, `site`, `text` | | `code.Rationale` | `subject`, `marker`, `text`, `site` | | `code.Import` | `importer`, `imported`, `root` | | `code.Dependency` | `name`, `version`, `direct` (absent when transitive), `pinned` | | `code.Counterpart` | `name`, `left`, `right`, `relation`, `left_covers`, `right_covers` | **Every query that answers "which functions" returns subjects**, never display names — the same identity `code.Call.caller` holds and `callers_of(fn)` takes. So an answer from one query feeds straight into another, and `name` stays what it is: an optional label for reading, not an identity. Three fields decide whether the queries work at all: - **`code.Call.caller` and `code.Call.callee` hold the subject you recorded a `Function` under**, not its display name. The queries compare them directly, so display names make every function look uncalled, with nothing to tell you why. - **`code.Function.test`** names a test that reaches the function, and `untested` reads its **absence**. Write it only where a test really does reach, or the rule falls silent everywhere instead of where it should be. - **`code.Function.called`** says something in this codebase calls this function, and `uncalled` reads its **absence** — the same shape as `test`. Record it on the CALLEE, next to the `Call` you record for the same edge. That looks redundant and is not: a rule ranges over one kind, so "does any `Call` name me?" is a question nothing can conclude from. Recorded here, the absence is a fact about this function and `uncalled` is an ordinary rule. **Recording the `Call` alone is not enough** — write both, or every function you recorded comes back `uncalled` however many callers the store holds. **`code.Function.callers` and `code.callees`** are degree — how many **distinct** functions call this one, and how many it calls. Distinct neighbours, not call sites: a caller that calls you three times is one dependency, not three, and counting sites would rank a function by how chatty its callers happen to be. They are recorded rather than counted for you, for the same reason `called` is — and unlike `called`, write them for **every** function, zero included. Those above are read by their absence, so a value everywhere silences the rule. `callers` is read by comparison, and a function nothing calls has a fan-in of zero, which is a fact rather than a gap. Optional and absent-tolerant: record neither and everything else answers as before, with `hub` simply never concluding. **`code.UnresolvedCall`** is the opposite record: a call you saw and could *not* name a target for. It asserts no edge, deliberately — a missing edge is a known gap, and a guessed one is a wrong answer no query could tell from a fact. Write `caller` as the subject of the enclosing function (as on `Call`), `site` as the location to look at, and `text` as the callee exactly as it appears in the source. `site` is what makes an entry worth having; a subject without one is skipped. This kind is optional and absent-tolerant — see §2.5 for what it buys you. **`code.Rationale`** is a reason the author wrote down, attached to what it explains. Every other kind here is structure — something a reader could recompute from your source. This is the part that cannot be recomputed: *why* the structure is the way it is. It is **stated, never inferred**, for the reason `Counterpart` is: intent is a claim only a reader can make, and an inferred one produces facts you cannot tell from the real ones. Write `subject` as the subject of the `Function` or `Class` it explains, `marker` as which convention it was, `text` as what the author wrote, and `site` as where to read it in full. `text` is what makes an entry worth having; a subject without one is skipped. **Which comments count is your producer's decision**, exactly as what counts as a `branch` is — and the same warning applies in the same direction. A convention wide enough to sweep ordinary comments turns every line in your tree into a fact and `rationale_for` into noise. Choose something an author had to type on purpose. Ours records `WHY:` and `NOTE:` at the start of a comment, uppercase, and nothing else. **`code.Function.explained`** says a reason exists, and `unexplained_critical` reads its **absence** — the same shape as `test` and `called`. Write it only where a reason really was recorded, or the rule falls silent everywhere instead of firing. **`language`** says what a fact is *about* — `python`, `cpp`, `rust`, whatever you call it. It is optional, and a store that never records it works exactly as before. Record it once your store holds more than one language: `module` is a path, so it hints at the language at best and no query can rely on it, and without `language` there is no way to ask about one half of a repository. It also tells two producers apart, which matters because `branches` is whatever *your* analysis counted, and two producers can both be right on different scales while `complexity` compares them against one threshold either way. **`code.Counterpart`** records two functions that have to agree — an encoder and its decoder, a writer and its reader, two siblings that ought to guard their inputs alike. Nothing in a call graph says two functions are halves of one contract; that is a claim about intent, and only you know it. State the pair, name in `relation` what is being counted, and give the count each side handles. `left_covers` and `right_covers` count members of that one named set, which makes them comparable to each other and to nothing else. ### 2.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `code.Function.complexity` | one path, plus one per branch and per loop — see §2.3 | | `code.Function.hotspot` | complexity above 10 | | `code.Function.oversized` | more than 200 lines | | `code.Function.untested` | has lines recorded, and no test was seen to reach it | | `code.Function.uncalled` | has lines recorded, and nothing was seen to call it | | `code.Function.hub` | more than 5 distinct functions call it | | `code.Function.critical_untested` | complexity above 10, and no test was seen to reach it | | `code.Function.unexplained_critical` | the same, and no reason was recorded either | | `code.Function.discards_result` | throws away at least one result it was handed | | `code.Class.sprawling` | more than 20 methods | | `code.Counterpart.gap` / `reverse_gap` | how far apart the two sides are, each direction as a positive number | | `code.Counterpart.left_stricter` | the left side handles more — the right accepts what the left refuses | | `code.Counterpart.right_stricter` | the same, the other way round | ### 2.3 How to count `branches` and `loops` `hotspot` fires at a complexity above 10. A fixed threshold over a number you compute yourself only means something if we agree on how it is computed, so here is the rule. Follow it and your numbers are comparable — to your own past numbers, to another producer's, and to the threshold. `complexity` is **one, plus one for each decision the function makes**: | Count one for | Notes | |---------------|-------| | each `if` (and each `else if` / `elif`) | `else` counts nothing | | each loop | `for`, `while`, `do`, and each clause of a comprehension | | each `case` arm that tests | a catch-all `default` / `case _` counts nothing | | each exception handler | one per `except` / `catch` clause | | each short-circuit boolean | `and` / `or`, `&&` / `\|\|` | | each conditional expression | the `a ? b : c` / `b if a else c` form | `else`, `default` and a bare `case _` are where control lands when none of the decisions above them fired, so they add nothing — three arms and a catch-all is three, not four. Two scope rules decide what belongs to which function: - a **named** nested function is its own `Function`, with its own counts; its decisions are not charged to the function that contains it; - an **anonymous** inline function — a lambda, a closure, a block — counts toward the function containing it. It is part of that function's text and cannot be called from anywhere else, so there is no other function for its decisions to belong to. **The list is closed.** Anything not on it counts nothing, including constructs with a fair claim to being a decision. An `assert` is the clearest one: it is a conditional failure, and most complexity tools count it — but in some languages it is a macro or a compiled-out call that a reader working from source cannot reliably see, and a rule that some producers can follow and others cannot is the problem this rule exists to solve. A number everyone computes the same way is worth more than a number that is arguably more faithful. If you want a different scale — a stricter threshold, a metric of your own — define your own module named `code`. ### 2.4 Queries | Call | Answers | |------|---------| | `code.callers_of(fn)` | everything that calls `fn`, directly | | `code.callees_of(fn)` | everything `fn` calls, directly | | `code.impact_of(fn)` | what breaks if `fn` changes — callers, and theirs, to three hops | | `code.importers_of(module)` | every module importing `module` | | `code.uncalled()` | functions with no recorded caller | | `code.unused_dependencies()` | dependencies you declared that nothing imports | | `code.unpinned_dependencies()` | dependencies declared as a range, not an exact version | | `code.asymmetries()` | pairs whose two sides do not handle the same amount, and which way | | `code.discarded_results()` | functions that throw a result away, so a failure cannot reach the caller | | `code.critical()` | complicated and unreached — where a defect is likeliest to survive | | `code.unexplained()` | `critical()` with everything already accounted for taken out | | `code.rationale_for(fn)` | why `fn` is the way it is, in the author's own words | | `code.functions_in(language)` | every function recorded as belonging to one language | | `code.hotspots_in(language)` | the complicated ones in one language | | `code.hubs()` | the most depended-on functions, most first — what breaks most if you touch it | | `code.dispatch_sites()` | every call whose target was not determined — where reading stopped | ### 2.5 What the answers can and cannot mean The queries are only as good as the data, and for a codebase that has consequences worth stating plainly. **A call graph derived from source is a lower bound.** Python decides calls as it runs, so anything reading source sees the calls the source makes plain and no others — a call through a variable, a handler table or `getattr` is not visible. Every edge you record that way is real; edges are missing. That matters most for `uncalled()`: a function reached only dynamically appears there. Read it as candidates to look at, never a list to delete. **How loose that bound is, is measurable.** Record an `UnresolvedCall` wherever your producer sees a call and cannot name the target, and `dispatch_sites()` lists them: the location, and the callee as written. Read it beside `uncalled()` — any of those sites could be the call that a function on that list is reached by. A codebase with two dispatch sites and one with two hundred deserve very different trust in the same `uncalled()` answer, and this is what tells them apart. Nothing on the list is a defect by itself: a plugin table, a callback and a decorator all work exactly this way. It is a measure of how much of your call graph is decidable by reading. Recording none of them is fine. The kind is absent-tolerant — everything else answers as before, and `dispatch_sites()` returns nothing. **`untested` inherits that bound**, since reaching a function is worked out over the same graph — and reaching is not executing, because a branch may never be taken. Read it the same way. **`critical()` cannot tell deliberate from overlooked, and `unexplained()` can.** Two functions can be identical in every structural respect — same complexity, neither reached by a test — and be entirely different objects: one whose author wrote down why it is shaped that way and why it is not tested, and one that says nothing. `critical()` returns both, because everything it reads is structure. Record `Rationale` and `explained` and `unexplained()` is the same list with the accounted-for taken out. A short `unexplained()` under a long `critical()` is a good result: the risk is known and written down rather than absent. Record none of it and `unexplained()` is simply `critical()` again, which is where you were. **There is no dependency age**, and that is the schema being honest rather than an omission. Python's package metadata carries no release date, so an age could only come from asking an index — which means sending a dependency list to a third party. A field nothing can honestly fill is worse than no field, because it reads as a capability. **`unused_dependencies()` matches by name**, so a distribution whose import name differs from its package name (Pillow is imported as `PIL`) will look unused. **A subject missing the field a query reads is skipped, not fatal.** You are never obliged to fill every field of a kind, and one half-written subject does not take out the answer for the complete ones beside it — a `Dependency` with no `name`, a `Counterpart` with no `relation`, an `UnresolvedCall` with no `site` and a `Rationale` with no `text` are each passed over, and everything else answers as it would have. So a partial record costs you that record and nothing more. --- ## 3. How you get one, and what holds once you have it You **download the `.dpy` file and compile it into your own store**, once. From then on `import ` resolves it there like any other module, and nothing in your store distinguishes it from one you wrote. Modules are served from `dke.langsyn.com`, under `/source///.dpy`, with `/source//latest/` forwarding to the current release, which replaces the previous version at that path rather than joining it. They are MIT, they are plain text, and you can read one in a browser before deciding to compile it. 1. **You can read it before you run it.** It is a file in the published language. Every class, rule and query this page documents is a line you can see, and the tables below are a lookup, not a substitute for the source. 2. **It is yours once compiled.** `forget_rule` removes one of its rules, `forget_module` removes the whole module, and nothing puts either back — your store holds what you compiled into it. 3. **Editing it is expected.** If a rule does not suit your data, change the line and compile again. What you get is then genuinely yours, and this page describes the version you started from rather than the one you are running. 4. **Its interface is enumerable in the store.** `list_modules` reports it, `info_module` reports the scripts it defines and the classes it declares, `list` reports its scripts with their signatures, and `list rules` its standing rules by name. A module's SOURCE is not stored — the store keeps the compiled form — so the file you downloaded is the copy to keep. 5. **The version tells you whether you are current.** The version stated for each module below never changes without its content changing, so the version you hold answers the question directly. A published module's name is a **default, not a reserved word**: compile your own module named `code` and yours is what `import code` resolves to. --- ## 4. What it costs Nothing beyond what you already pay. Reasoning modules are not a separate subscription: importing one, and everything it then does, is billed by the same per-operation meter as any other work you send. A module that reasons more costs more, in proportion, and one you never call costs nothing. --- *Companion documents: the [Reasoning Modules Tutorial](/tutorial/dke/reasoning-modules/), and the [DKE Python Reference](/ref/dke/python/) for the language these modules are written in.*