Skip to content

feat(vocabulary): split, classify, weight (M19a) - #27

Open
jgrodziski wants to merge 4 commits into
feat/m19a-profile-lexiconfrom
feat/m19a-vocabulary-split
Open

jgrodziski wants to merge 4 commits into
feat/m19a-profile-lexiconfrom
feat/m19a-vocabulary-split

Conversation

@jgrodziski

Copy link
Copy Markdown
Contributor

Second step of the word cloud / glossary (#6), milestone M19a in PLAN.md §20. Stacked on #26: review that one first; this PR's diff is the four commits after it.

It adds a new package, @codegraph/vocabulary, that turns one name into the words it is made of, tags each word, and says how much a word weighs given what carries the name. Pure computation over the model's names (never source), depending on @codegraph/core alone. Nothing calls it yet: PR 3 aggregates over a model with it, PR 4 adds the CLI command.

Splitting a name

splitIdentifier(name, kind, lexicon) → {tokens, numeric, conventional}.

  • A name the language dictates (toString, __init__, handle_call) gives no word, and says so.
  • A kind in the lexicon's fileKinds loses its extension (order_service.rb → order_service); a leading dot (.eslintrc) is not one.
  • C#'s interfacePrefix drops the I of IOrderService, never of IO or IOError.
  • Every character that is not a letter, mark or digit breaks a word, so <=>, this[], :: and / need no per-language separator list.
  • Camel-case boundaries on Unicode categories: lower → Upper, digit → Upper, and HTTPServer → http, server. Digits stay with their word (utf8, i18n); a piece of digits only is dropped and counted in numeric. A caseless letter (注, 𠀀) counts as lower; a combining mark never splits.

Tests: a hand table per language (all eleven), the fixtures/unicode corpus (no U+FFFD, no lone surrogate), and fast-check properties (words only, lowercase, slices of the lowered name, deterministic, a + sep + b splits as a then b, conventional names give nothing). Removing the acronym rule fails 8 tests, removing the interface prefix fails 10.

Classifying a word

createClassifier(lexicon, technical?) tags each word, in this order:

  1. keyword: the lexicon reserves it. A keyword that is not a word (Ruby __FILE__) can never match, so the file of upload_file stays a domain word.
  2. short: one letter or digit, counted in code points. id, io, db are words.
  3. technical: the shared DEFAULT_TECHNICAL list (125 words, the PLAN seed included) ∪ the profile's own technical.
  4. domain: everything else.

Nothing is filtered: a consumer decides what to show (PLAN §20 principle 3). The shared list is overridable whole; the profile's own words still apply.

Shared words moved out of the profiles. Eleven words some profiles listed are plumbing in every language: args, async, callback, ctx, err, json, mixin, mutex, opts, repo, str. They now live once in DEFAULT_TECHNICAL, and a test keeps it disjoint from every profile's own list. Without the move, a Java corpus would count args and json as domain words. (repo was Elixir's alone; it moved because repository is already shared. The commit body of 60c3148 says otherwise, wrongly.) The website profiles page is regenerated.

Roles and weights

roleOf(entity, slots) reads the role from traits, never the kind, first match wins: TModule → module, TType → type, TInvocable → operation, TStructural → parameter / local / attribute (by whether some entity's parameters or localVariables lists it), else other. A property test pins that the kind is never read.

role module type attribute operation parameter local other
weight 8 8 4 3 1 1 1
  • A seventh role, other. Five shipped kinds have no role trait: clj:implBlock, php:codeFile, python:decorator, rust:impl, ts:namespace. Three of them are named, and every name's words count, so they weigh 1. One consequence: a PHP code file weighs 1, while Ruby and Elixir files weigh 8 as modules, because that is what their traits say.
  • A gap in the C# model. The parameter of the indexer this[int index] is a child of its property, and the C# profile gives a property no TWithParameters, so no list names it and it weighs as an attribute. The fixture test pins that one id rather than guessing from the kind; the fix belongs in the C# profile and extractor.

Also

  • eslint.config.js: the package is inside the no-DOM boundary; CLAUDE.md lists it with core, analyzer, city, navigator.
  • PLAN §20.1 and §20.4 describe what was built; the M19a checklist splits off aggregate.ts, artifact.ts and drift.ts as still to do.

Verification

  • pnpm -r build and pnpm -r typecheck green; lint clean on touched files; pnpm run gen:schemas leaves schemas/ unchanged.
  • vocabulary 183 tests, core 539, cli 907, every other package passes, except the two local-machine failures noted on feat(core): language lexicons on profiles (M19a) #26 (Node 26 no longer warns on node:sqlite; the explain no-key test with OPENROUTER_API_KEY set).

Part of #6.

🤖 Generated with Claude Code

Jérémie Grodziski and others added 4 commits October 23, 2026 17:13
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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
codegraph c5bfa7a Commit Preview URL

Branch Preview URL
Oct 23 2026, 10:54 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant