Skip to content

feat(core): language lexicons on profiles (M19a) - #26

Open
jgrodziski wants to merge 4 commits into
mainfrom
feat/m19a-profile-lexicon
Open

jgrodziski wants to merge 4 commits into
mainfrom
feat/m19a-profile-lexicon

Conversation

@jgrodziski

Copy link
Copy Markdown
Contributor

First step of the word cloud / glossary (#6), milestone M19a in PLAN.md §20. Each language profile now carries a lexicon: the words the vocabulary splitter has to know about that language. Like the rest of a profile, it is data in @codegraph/core. Nothing reads it yet. PR 2 adds the splitter that does.

What a lexicon holds

key what it is example
keywords the language's reserved words, from its spec Java new, Go type
conventional names the language or runtime dictates, so they say nothing about the domain toString, __init__, Ruby <=>, C# op_Addition
technical plumbing words that are not reserved ctx, opts, cb
fileKinds the kinds whose name is a file path, so the extension gets stripped TS/JS module, Ruby/Elixir file, PHP codeFile
interfacePrefix strip the leading I of an interface name C# only

All eleven shipped profiles have one. TypeScript and JavaScript share one ECMAScript list, because TypeScript reserves exactly ECMAScript's words.

Only reserved words count as keywords. A contextual keyword (record, var, event, TS type) is a legal identifier and often a domain word. Listing it would hide it from the default cloud. A test pins this per language.

Changes from the plan

PLAN §20.1 sketched separators, prefixes and suffixes as profile data. The splitter has to break on every non-letter, non-digit character anyway, or names like <=> and this[] would become words. Once it does, those fields change nothing, so they are gone. The plan's single profile-wide file flag became fileKinds, declared per kind: Elixir's module is dotted (AcmeOrder.Registry) while its file is a path, so a profile-wide flag would cut .Registry off as an extension. PLAN.md is updated to match.

Validation

validateProfile gains four codes:

  • lexicon-duplicate
  • lexicon-not-a-word (an entry that can never match a split word)
  • lexicon-keyword-overlap (a conventional or technical entry that is already a keyword)
  • lexicon-unknown-kind (a file kind the profile does not declare)

Every shipped profile passes them.

Surfaces

  • codegraph profiles --lang X prints the lexicon between the kinds and the notes. --json carries it in a fixed key order.
  • METAMODEL §5.3, docs/cli.md, and both website pages. The website profiles page is regenerated from the CLI.
  • No change to the wire: the lexicon is not in model.jsonl, and pnpm run gen:schemas leaves schemas/ unchanged.

Verification

  • pnpm -r typecheck green. Lint clean on touched code.
  • core 539 tests, cli 907 tests, and every other package pass. Two failures come from the local machine, not this change:
    • Analyzer. One test (is not vacuous: a static import of the same builtin does warn) expects Node to warn on node:sqlite. Node 26 no longer warns.
    • CLI. The explain test "without an API key" fails when OPENROUTER_API_KEY is set in the shell. It passes with the key removed.
  • The website builds with Hugo after git submodule update --init (docs: initialise the Hextra submodule first #24). I read the rendered lexicon section and its link anchor in the built page.

Part of #6.

🤖 Generated with Claude Code

Jérémie Grodziski and others added 4 commits October 23, 2026 13:35
Profile.lexicon is what the vocabulary splitter (M19, #6) must know
about a language and cannot derive: the specification's reserved
words (contextual keywords are identifiers, often domain words), the
whole names the language or its runtime dictates, its plumbing words,
the kinds whose name is a file path, and C#'s interface marker.
Word boundaries are universal, so separators and sigils carry no data.

The eleven shipped profiles carry one; TypeScript and JavaScript read
one ECMASCRIPT_LEXICON. validateProfile gains four codes for entries
that could never do what their list says. `codegraph profiles` prints
the lexicon between the kinds and the notes, and --json carries it in
a fixed key order. schemas/ is unchanged: a profile is not on the wire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lexicon drops the draft's separators, prefixes and suffixes, which
could change no output once the splitter breaks on every non-word
character, and turns the per-profile fileModules flag into per-kind
fileKinds: Elixir's `module` is a dotted name, not a file. The split
steps and principle 2 follow; the M19a profile item is ticked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
profiles-page.mjs stops the kinds at `lexicon:` and renders each
profile's reserved words, conventional names, technical words and file
kinds as a list (Ruby's `|` operator rules out a table cell); the page
is regenerated from the CLI. The metamodel page gains the lexicon row
and section, and now says eleven shipped profiles, not nine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page generator matched the CLI's indentation with runs of literal
spaces, which eslint's no-regex-spaces rejects; the lexicon row added
one more. Written as {2} and {4}, the script passes lint and writes
the same page byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

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 51ed180 Commit Preview URL

Branch Preview URL
Oct 23 2026, 03:14 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