Repository navigation
feat: the vocabulary, M19a–M19c (#27–#40) - #46
Merged
Merged
Conversation
New package @codegraph/vocabulary (PLAN.md §20.1). splitIdentifier(name, kind, lexicon) breaks on every character that is not a letter, a mark or a digit, then on camel-case boundaries read from Unicode categories: lower→Upper, digit→Upper, and an Upper run before Upper+lower. A digit run stays with its word (utf8, md5, v2); a piece of digits only is dropped and counted. The lexicon adds the conventional names skipped whole, the file kinds whose extension is no word, and C#'s I prefix. Hand tables per language, read through each shipped lexicon, the unicode fixture, and fast-check properties: words are lowercase slices of the name, a separator splits a name into its parts' words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
createClassifier(lexicon, technical) tags each word, in this order: keyword (the lexicon reserves it), short (one letter or digit, marks excluded), technical (the shared list ∪ the profile's), else domain. A keyword the splitter never yields whole (`defined?`, `__FILE__`) classifies nothing: the `file` of `upload_file` is not `__FILE__`. DEFAULT_TECHNICAL is the plumbing every language shares. Words two or more lexicons listed (args, opts, ctx, err, callback, json, str, mutex, mixin, async, repo) move into it and out of the profiles, as METAMODEL §5.3 already says a profile's list is what lies beyond the shared one; a test keeps the two disjoint. The website profiles page is regenerated from the CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roleOf(entity, slots) reads traits, never the kind: TModule → module, TType → type, TInvocable → operation, TStructural → parameter or local when an entity lists it in parameters[] / localVariables[], else attribute. A named entity with none of these (a TS namespace, a PHP code file, a Python decorator) is `other`: principle 1 says its words still count. DEFAULT_WEIGHTS: module and type 8, attribute 4, operation 3, parameter, local and other 1. On the fixtures, every listed parameter and local resolves; the C# indexer's parameter is listed nowhere (a property has no TWithParameters in that profile), weighs as an attribute, and is pinned by id until the model lists it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§20.1 states the digit and caseless-letter rules of the split, the properties as tested, the keyword spellings that classify nothing, the shared technical list (125 words, disjoint from every profile's), the `other` role and the C# indexer parameter the model lists nowhere. §20.4 ticks the three modules and keeps aggregate, artifact and drift open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A profile's lexicon gains `qualified`: the kinds a language names by a qualified path, each with the separator that ends the qualifier. Java's package by `.`, C#'s namespace, Elixir's module and protocol, Clojure's namespace and Python's module by `.`, Ruby's class, module and constant by `::`, PHP's namespace by `\`, and every file kind by `/`. Go's package and Rust's module are named by one identifier, so neither is listed. The vocabulary will weigh the words before the last separator as incidental ones: on the fixtures, full names made `acme` weigh 392 in Elixir and 468 in Ruby against about 50 for the next domain word. validateProfile reports a qualified kind the profile does not define (`lexicon-unknown-kind`) and a separator that is empty or holds a letter, a mark or a digit (`lexicon-not-a-separator`). `profiles` prints the row and carries the key in --json; METAMODEL §5.3, the CLI reference and both website pages follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`splitIdentifier` now reports, beside the tokens, how many of them spell the name's qualifier: what precedes the last separator of a kind the lexicon declares qualified (`AcmeOrder::Order::Line` → 3 of acme, order, order, line; `packages/order/src/basket.ts` → 3). The tokens themselves never change, a property pins it; only the boundary is new. The interface prefix now reads the own name after the qualifier. The weights gain a `qualifier` key, 1 by default: a container's words, spoken again by every name it qualifies, weigh as incidental ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`buildVocabulary(graph, options)` counts the words of every kept entity's name into the scopes the fold puts it in: its type (containingType), its module (containingModule) and the corpus. A stub never speaks, whatever the view: its name was chosen elsewhere. An entity's language is its model's, read from the union, never its id. A word weighs its carrier's role, or `qualifier` (1) before the last separator. Each scope keeps `[term, weight, occurrences, carriers]` rows by weight then term, its entities by role, what it skipped (unnamed, conventional, numeric) and its distinct and distinctDomain counts. Each term carries its spread: the module and type scopes that speak it, and the one module when only one does. A word two languages class apart is two terms. Kept entities no module contains count at the corpus alone and are listed. The artifact, `codegraph.vocabulary/1`, records its view, weights, classes and the technical list's size and sha256. It is compact by default; `pretty` writes one term and one scope per line. Tested on a hand-counted corpus and by properties over generated ones: closure, conservation (every kept entity in one module or unplaced), and the same bytes whatever the order entities arrive in. The package now depends on @codegraph/analyzer, as PLAN.md planned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each fixture's model now has its words committed beside it: `fixtures/<name>/expected/vocabulary.json` for java, csharp, typescript, elixir, ruby and unicode. They are pretty-printed, one term and one scope per line, so a change to the split or the weights reads in review as the words it moved. The vocabulary's tests rebuild them byte for byte, check their closure, and rebuild them over a fresh graph; CODEGRAPH_UPDATE_SNAPSHOTS=1 rewrites them. Each README counts one word by hand, name by name: Java's and C#'s `order` (40 and 42, through the package and namespace qualifiers), C#'s `priceable` (no word `i`), TypeScript's `legacy` and `packages` (path qualifiers), Elixir's and Ruby's `pricing` (dots, `::` and slashes). A test pins those counts, so a snapshot rewritten in one command still has to agree with them. The C# fixture gains a README for its count, pointing at docs/csharp-extractor.md for the rest. The unicode README notes that terms sort by code unit too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLAN §20.1 now has the lexicon's `qualified` key and the measurement that called for it. It also covers the splitter's `qualifier` count and its properties, the `qualifier` weight row, and the aggregation as built: stubs never speak, nearest-fold scopes, per-scope counts, one term per (word, class), unplaced and withoutLexicon. The artifact section adds the technical list's sha256 and the pretty form. In §20.4 the qualifier, aggregate.ts, artifact.ts and the snapshots (the five fixtures plus unicode, hand counts pinned by a test) are checked. drift.ts moves to the command's item, the next PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`parseBusinessTerms(text)` reads a plain glossary, one term per line,
each split with no lexicon so `Order Line` meets `order` and `line`.
Blank lines and `#` comments are skipped; a line naming no word (`2024`)
is reported by its number; a term whose words repeat an earlier one's
is that term.
`vocabularyDrift(artifact, terms, {level})` says, per business term,
which scopes of the level speak every one of its words (whatever class
the code gives them) and which words no scope of the level says. The
other half is the code-only words: `domain` terms some scope of the
level speaks that no business term contains, summed over those scopes,
by weight then term. Nothing is stemmed: `lines` is not `line`.
Tested on the hand-counted corpus at the three levels, and by
properties over the java and ruby fixtures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`operationScope(graph, artifact, id, options)` counts one operation's words as the artifact counts a scope: the operation and every kept entity written inside it, lambdas and their parameters included, short of a type or module, which are scopes of their own. Its rows index the artifact's terms and its parent is the nearest stored scope, so a reader shows it beside the stored ones. PLAN.md §20.5 keeps these out of the artifact; `codegraph vocabulary --scope ID` will ask for one. The counting rule moves into one function both paths call, so the fixture snapshots are unchanged. An artifact built under other options is refused rather than mis-indexed. A C# delegate, invocable but a type, stays a type scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`parseTechnicalWords(text)` reads the list `--technical FILE` will replace the shared one with: one word per line, lowercased as the splitter lowers a token, blank lines and `#` comments skipped. A line that is not one word (`HTTPServer`, `value object`, `2024`) could never match a token, so it is refused by its line number instead of kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`analyze` printed its view as `name (filters)`, or `all (nothing filtered …)`, through a private helper. It moves into view.ts beside `resolveView`, so the next report that states its view prints it the same way. analyze's output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`codegraph vocabulary [model.jsonl...]` reports the words the model's names are made of, as `@codegraph/vocabulary` counts them (PLAN.md §20.1). Every report states its view, weights and technical list. - No flag: the corpus's 40 heaviest domain words. Each row gives weight, occurrences, carriers, and how many modules and types say it. - `--level module|type`: one block per scope, ranked by distinct terms (the cognitive-load report), 10 words each by default. - `--scope ID`: one stored module or type, or an operation counted on demand. An unknown id, a stub, or a member that is no operation is a usage error that says what the id is. - `--class domain|technical|keyword|short|all` and `--top N` pick the rows. - `--weights KEY=N,…` overrides weights over the defaults. An unknown key, a repeated key or a negative weight is a usage error. - `--technical FILE` replaces the shared list whole; a line that is not one word is refused by its number. - `--drift FILE` compares with a glossary at `--level`. It lists the business terms no scope says, with their missing words, the ones it says, and the domain words no term contains. It exits 3 when a term goes unsaid, as `analyze --report cycles` does for a cycle. - `--out FILE` writes vocabulary.json beside the report. - `--json`, `--internal-only`, `--declared-only` and `--no-cache` work as on the other commands. The cache path counts the same words. Tests cover each flag, text/JSON parity, determinism, stream purity and the exit codes end to end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command gets its section and synopsis in docs/cli.md and a website page: the flags, what a scope is, how drift matches a glossary, the exit codes, and two examples taken from the java fixture's real output. The artifact gets a website page: every field of `codegraph.vocabulary/1` and the weights table. The CLI index, the artifacts index and the exit-codes table list both, and `vocabulary` joins the commands that build and reuse a sibling model.db. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§20.1's command paragraph now describes what was built: - the header's view, weights and technical list; - the 10-word blocks under --level; - an operation's scope on demand, and the usage errors that say what an id is; - the --weights and --technical rules, and --out beside the report; - the glossary's comments, merges and wordless lines; - drift's two halves and its exit 3; - the flags a report would not read, refused; - the JSON kind. §20.4 checks the drift and command item. Left in M19a: the coverage table on the real corpora. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on BroadleafCommerce, Mastodon, Plausible and codegraph itself (PLAN.md §20.1), each word read off the names that carry it: 13 words named the program's machinery, not the business, in a real corpus's top ten, and join the shared technical list (125 → 138). - name type value key map property field: the data vocabulary of every language (getName, propertyName, cacheKey, parsedValue, itemMap). - extension extensions: Broadleaf's …ExtensionManager pattern. Moved from the C# profile, since a word two lists hold is the shared list's. - read: the R of CRUD, missing from a list that claimed the CRUD verbs. - serializer worker index: Mastodon's app/serializers/, app/workers/ and search indexes; index is machinery in all four corpora. Broadleaf's top ten now reads order item customer offer product admin group fulfillment category payment. In the fixtures only value, name and index change class; no weight moves. Snapshots, the C# profile page and the CLI page's examples follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The §20.1 table filled on the five fixtures, BroadleafCommerce, codegraph, Mastodon and Plausible (commits, machine and method stated), what their unnamed entities are, the Broadleaf top ten reviewed and the list correction argued word by word. The last M19a item is checked and the milestone row marked done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
layoutCloud (PLAN.md §20.2): pure geometry, no randomness. Words by weight, ties by text then class, so any order of the same words lays out the same; the font size on the square root of the weight (area reads as magnitude); each padded box at the first point of a 2:1 Archimedean spiral where it overlaps nothing placed. The measurer is injected: a monospace default that counts a mark as no cell and a wide character as two, or a page's measureText. Type imports only, so the navigator's tab can run the same function. Properties: no overlap, every box inside the canvas, order-independent, weight order with sizes never inverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vocabularyCloudSvg draws one scope's words: size by weight, colour by class and nothing else (a stylesheet keyed by class, light and dark; no inline fill), a key when more than one class is drawn, and a legend stating the scope, counts, weights, view and technical list. The font is monospace, so the bytes are the same on every run: fixtures/java/expected/vocabulary.svg is a snapshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--svg FILE draws the cloud of the one scope reported, the corpus or the --scope one (an operation counted on demand too), from the words --class and --top pick, and confirms on stderr. A cloud is one scope's, so --svg with --level module|type or --drift is a usage error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/cli.md and the website's vocabulary page describe --svg: what size, colour and the legend say, and when the flag is refused. docs/vocabulary-broadleaf.svg is BroadleafCommerce's corpus cloud, drawn by the command with --top 80. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§20.1's command paragraph states when --svg is refused; §20.2 records the layout's settings, the measurer's cells and the SVG's palette as built, and the Broadleaf demo. The first M19b item is checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The artifact folds what a name says into every scope the entity belongs to; the carriers lookup will keep it per entity. Both now read through reading.ts (the split, the class, the role's weight, the qualifier), so a carrier's share is exactly what the row counted. Snapshots unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vocabularyCarriers(graph, artifact, options) reads every kept entity once and answers which names of a stored scope carry a term, each with what it added to the row. A row is the sum of its carriers: a property over every term of every scope of the six fixtures. A type or module carries its id, a member the owner a page reveals it in; an artifact counted under other weights, list or view is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codegraph/vocabulary/cloud is cloud.ts alone, built as its own entry: a page can run the layout as a value without the analyzer, core, zod and the SQLite loader behind the package's index. A test keeps cloud.ts's imports type-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codegraph serve builds vocabulary.json from the same graph, view and name as the navigator and the city, and serves it at /vocabulary.json. /carriers.json?term=…[&scope=…][&class=…][&limit=…] answers which names of a stored scope carry a word, from the model the server holds: 404 for an unknown scope or term, 400 for a request it cannot ask or a text the scope speaks as two terms. The index is built at the first answerable request, and the graph is let go then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The daemon's job writes vocabulary.json beside navigator.json and city.json and serves it under the token. A project directory without one, built before it existed, is rebuilt rather than reused. The daemon holds no model between jobs, so /carriers.json is a 404 there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sixth tab: the words of the selected scope's names, the corpus until a module or a type is selected (a member shows its type), as a cloud and a table. The cloud is @codegraph/vocabulary/cloud's layout measured with the page's monospace face; size is weight, colour the class alone, and a legend states what was counted. Class chips, a top-N slider, the scope chain to step up. A word asks carriers.json for its names, and each reveals its node in Navigate; with no such route the tab says so. The artifact kind, the carriers kind and the class list are literals pinned to the package's; the package is imported for types only, its ./cloud entry as the one value. The Vite dev server serves VOCABULARY_JSON as it does NAVIGATOR_JSON. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md's package map, docs/cli.md's serve section and the website's serve, vocabulary, vocabulary.json and environment pages: the sixth tab, the third artifact, the carriers answer and its statuses, the daemon without a carriers route, VOCABULARY_JSON for the dev server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19a-vocabulary-command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19a-vocabulary-coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19b-vocabulary-cloud. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19b-vocabulary-tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19b-vocabulary-metric. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration 1, the first rung of the ladder to run on a real file, creates insight_word: one row per entry of a block's `words`, the entry as JSON text less its `meaning`, with word, concern, sense and confidence generated over it, indexed on word and id. A record's rows are written with it in the unit's transaction (the replaced record's deleted first), an import rebuilds the table, and nothing is exported from it. The migration projects the words a version-1 block already holds: a build asking prompt 2 on a version-1 store wrote some. A reader still refuses an older store; its message now says any `explain` on it migrates it, --dry-run included, which calls no model. Tests: a property that the table is every record's words in block order for any side-car, rebuilt by an import; a put replacing words, and a refused unit writing none; and, from fixtures/store-v1.sql (the .dump of a store the version-1 build wrote, with its export), the migration keeping records, failures, ledger and stamp, exporting the same bytes, the reader's refusal, and a failed migration left at 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`codegraph insights` on a store the version-1 build wrote exits 2, names the migration that calls no model, and leaves the file's bytes alone; `explain --dry-run` on it migrates it to version 2, with its six records, asks for no client and writes no side-car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/insights.md describes insight_word and the migration, and that a reader refuses an older store with the way to migrate it; docs/ cli.md says so for `codegraph insights`. The `--insights` help (and the website's serve page, which restates it) and the route's comment say the panel shows nothing for a store no explain has migrated yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§20.3 records the store as built: JSON entries with generated columns, no `meaning`, the backfill §20.3 said was not needed, the reader rule kept, the real-file fixture, and the scale measured on a synthetic side-car at Broadleaf's counts. §20.4 checks the item, the real Broadleaf side-car owed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Layer 3 of PLAN §20.3, as pure functions over the store port. The port gains four projections: wordHits(word) through the insight_word index, glossaryEntries(word?) over the module blocks, concernRows() and withoutWords(); stats() counts the records a model wrote without words (prompt 1). glossary.ts rolls them up: wordReport, polysemyReport, concernsReport, wordsAbsence (prompt 1's or a template's) and moduleOf. A record is placed in its module by its natural key, rendered by core and grouped on naturalKeyIndex; the id is never parsed. Lookups are exact on any string: a word with a lone surrogate is not what the binding sends for the index's TEXT, so it is found by a scan, and JS equality decides either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codegraph insights --word WORD, --glossary MODULE_ID, --polysemy and --concerns: tab-separated like the command's other lists (the module first, so the grouping survives sort), each with a --json kind. A record explained under prompt 1 is answered as such: --id says so, --glossary on such a module says why it has none, and the summary and every word answer count what they cannot see. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/cli.md: the four flags, their JSON kinds and the prompt-1 answers. docs/insights.md: the readers over the port. PLAN §20.3: as built; §20.4 notes the readers and the prompt-1 answer as in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wordHits() with no word returns every entry, in side-car order then block order, as glossaryEntries() does: what vocabulary --drift-insights compares. The adapter already had the statement, for the lone-surrogate scan. The projection property now checks it for any side-car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
concernDrift sets each word a model defined beside the class the list gives it in that entity's language: the technical words judged domain somewhere, the domain words judged technical, with the judgements against, surest first. businessJudged sets a glossary's terms against both. The judgements come in this package's own shape; it imports nothing from @codegraph/insights. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
Reads the insights store beside the first model (--insights FILE names another) and prints the two lists, five judgements per word (--json: all); with --drift, the business terms against both. A judgement never sets the exit code; --drift keeps its exit 3. The store opener moves to insights-paths.ts, shared with the insights command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
cli.md, insights.md (a third reader, beside the model), and the website's vocabulary and exit-code pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
PLAN 20.3: the decisions and the Broadleaf-scale measurement; 20.4: the reader is in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
answerGlossary(store, { term, scope, limit }, placement): the entries
of the entities under a module or a type, and the modules'
reconciliations, for serve's /glossary.json. Which entity is under a
scope is the model's, so the placement is injected: this package still
never opens a model. GLOSSARY_ANSWER_KIND is the page's pin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
The third lookup of codegraph serve: what the explained entities under a scope said a word means. It reads the store /insight.json reads (one reader, opened on first request, now shared) and places entities by the fold the carriers use, built at the first request a store can answer. No store is a quiet 404, as for /insight.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
The insight and carriers loaders each carried the same "ask once per URL, stop asking a server with no such route" logic; lookup.ts holds it once, before a third lookup needs it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
The Vocabulary tab lists, under a clicked word's carriers, what the explained entities of its scope said the word means: the modules' reconciliations, then each entity's entry, in the explanation panel's inference colour and badge. An entry reveals its entity in Navigate (a member, its type). No store, no route: nothing is shown. The Navigate panel reads a block's words and glossary by name, as "order (domain) — sense. Meaning. In Order · 0.90": generically the identifiers stood where a definition reads, and the types that disagree on a word were dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
serve's third lookup, /glossary.json, in docs/cli.md and the website's serve page (and the daemon's lack of it); the Vocabulary tab's glossary on the vocabulary page; answerGlossary and its placement in docs/insights.md. The --insights help now names both panels it feeds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
The test runs `explain` through the real seam, which reads the process environment. With OPENROUTER_API_KEY exported in the developer's shell the run found a key, stopped at the no-terminal confirmation instead of the missing-key error, and the test failed. It now clears the four provider variables for its own duration (vi.stubEnv), as childEnv already strips NO_COLOR from a spawned CLI. No call could have gone out: without a terminal and without --yes the run stops before the first call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
The per-word and per-entry figures were PLAN §20.3's guesses (30 and 40) and the block figures came from prompt 1. A live prompt-2 run of gpt-5.6-luna over Spring PetClinic (106 calls, 96 356 completion tokens, reasoning included) measured 31% more output than --estimate said. Each call's prompt was rebuilt with the planner (contextPackFor + renderPrompts over the run's records; the rebuilt estimates sum to the run's, token for token) and its completion tokens fitted on what it asked: 466 / 534 / 527 per operation / type / module block, 121 per word, 123 per glossary entry (r² 0.81 per call, against 0.13). Rounded: 470 / 530 / 530, 120 and 125. Fitted on half the calls, the figures predict the other half's total within -7% and +15%; the old ones missed by -29% and -33%. The glossary figure rests on five module calls. The estimate line now says the figures were measured on a live run, for which model, with reasoning counted. The tests that multiplied 450/650/ 900 by hand use the exported constants, so they keep checking the formula, not restating the measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
docs/insights.md (IN-6) and the explanation page state the refitted figures and where they were measured. The how-to, the tutorial and the discover page reprint the Java fixture's --estimate as it now reads: 52 900 output tokens (was 41 670), $0.0432 at $0.10/$0.60 (was $0.0365). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
§20.3's Cost paragraph keeps its estimate and gains the measurement that replaces it: the live PetClinic run (Broadleaf's place as the test corpus, by the user's decision), the fit, its out-of-sample check, the caveat on the glossary figure, and the input gap left as it is (7.9% from explanations a cold estimate cannot quote, 6.3% from a tokenizer at 3.76 characters per token). §20.4's estimate item: the measurement is in; the live run on the Java fixture is still owed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKCVgyop39vhNa45r7zM8w
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bottom of the M19 stack (issue #6, PLAN §20), brought to
mainas one PR so the stack can land in order: this, then #42, #43, #44 and #45 as GitHub retargets each one.It carries fourteen PRs that were each reviewed and merged inside the stack, one onto the previous branch, on top of #26 (the profile lexicons, already on main):
The diff against
mainis the stack's whole content up to #40: the@codegraph/vocabularypackage,codegraph vocabulary, the Vocabulary tab and thevocabulary:<word>city metric, the wordsexplainasks a model to define, the version-2 insights store and its readers, the glossary in the page. A localgit merge-treeofmainand the stack tip reports no conflict; #40's CI was green on every job.🤖 Generated with Claude Code
https://claude.ai/code/session_01PKCVh2RjqQgcaeCBoVEntz