Skip to content

feat(entrypoints): framework-tier call rules; shipped non-web ruleset (electron, commander, worker_threads, process.on) - #168

Merged
rahlk merged 1 commit into
mainfrom
feat/issue-167-nonweb-entrypoints
Sep 6, 2026
Merged

feat(entrypoints): framework-tier call rules; shipped non-web ruleset (electron, commander, worker_threads, process.on)#168
rahlk merged 1 commit into
mainfrom
feat/issue-167-nonweb-entrypoints

Conversation

@rahlk

@rahlk rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Closes #167. Stacked on #163 (unit 5) — targets that branch.

Measured on an Electron-plus-CLI fixture with the shipped rules: the manifest tier finds main/bin, and nothing else. app.on("ready", onReady), ipcMain.handle("ping", …) and commander's .command("start").action(run) all went unclaimed — and an unclaimed call leaves no trace in the report. Python ships click/celery so its non-web roots are covered out of the box; ours was web-only.

Why not just more heuristic rules

app.on(...) exists on every EventEmitter and in every Express app. An ungated written-spelling rule would tag every one of them as an Electron entrypoint, and the spec's rule is that false positives are worse than misses. So call rules get the split decorators already have:

  • Framework tierframeworks.<x>.calls: gated on detect:, matched on the import-table-resolved callee (app.onelectron.app.on; ipc.handle via import { ipcMain as ipc }electron.ipcMain.handle), default certain, run inside the per-framework loop so its claim is framework-claimed for never-doubles. A callee the resolver can't name is skipped — never matched on its written form.
  • Heuristic tier — unchanged: written spelling, heuristic, runs last.

Shipped rules

framework gate matches attaches to
electron electron app.{on,once}, ipcMain.{on,once,handle,handleOnce} last positional (the handler); route = event/channel
commander commander program.{*,*.*,*.*.*}.action — a chained receiver resolves through its head last positional
worker_threads worker_threads / node:worker_threads parentPort.{on,once} last positional
(heuristic) process.{on,once} — a Node global, nothing to gate on last positional

Verification

  • The negative test is the centrepiece: the identical source with a local app/ipcMain and no electron dependency registers nothing from these rules
  • Alias resolution, the chained commander receiver, the node: specifier, process.on at heuristic, and never-doubles (a framework call claim blocks a heuristic app.get record on the same handler) each have a test
  • bun test green; no schema, Neo4j or SCHEMA_VERSION change

Found along the way

The per-framework loop did continue on a framework with no files: rules — harmless until something was placed after the files stage. Fixed here.

Cross-language

frameworks.<x>.calls is a second rules-format key python must accept-but-ignore; noted on python #187. Yargs/oclif rules can follow once this lands; a "calls matched by no rule" report counter is a visibility decision for the spec, deliberately not slipped in.

@rahlk
rahlk changed the base branch from feat/issue-161-entrypoint-files to main September 6, 2026 12:54
Measured on an Electron-plus-CLI fixture with the shipped rules, only the manifest
tier fired: `app.on("ready", onReady)`, `ipcMain.handle("ping", ...)` and commander's
`.command("start").action(run)` went unclaimed, and an unclaimed call leaves no trace
in the report. Python ships click and celery for exactly this; ours was web-only.

They cannot ship as `heuristics.calls`: `app.on(...)` is on every EventEmitter and
in every Express app, and false positives are worse than misses. So call rules get
the split decorators already have -- a FRAMEWORK tier, `frameworks.<x>.calls`, gated
on `detect:` and matched on the import-table-RESOLVED callee (`app.on` ->
`electron.app.on`; `ipc.handle` via `import { ipcMain as ipc }` ->
`electron.ipcMain.handle`), default `certain`, inside the per-framework loop so its
claim is framework-claimed for never-doubles; and the HEURISTIC tier, unchanged, on
the written spelling. A callee the resolver cannot name is skipped by the framework
tier, never matched on its written form.

Shipped: electron (`app.{on,once}`, `ipcMain.{on,once,handle,handleOnce}`),
commander (`program.{*,*.*,*.*.*}.action` -- a chained receiver resolves through its
head), worker_threads (`parentPort.{on,once}`, `node:` specifier included), and
`process.{on,once}` as a heuristic (a Node global; nothing to gate on). `route`
carries the event/channel name for on/handle rules -- the nearest field.

The negative test is the point: the identical source with a local `app`/`ipcMain`
and no electron dependency registers nothing from these rules.

Also fixes a latent bug this exposed: the per-framework loop `continue`d on a
framework with no `files:` rules, which would have skipped anything placed after
the files stage.
@rahlk
rahlk force-pushed the feat/issue-167-nonweb-entrypoints branch from bc6e9ad to 571cb3b Compare September 6, 2026 12:59
@rahlk
rahlk merged commit 3c72d68 into main Sep 6, 2026
@rahlk
rahlk deleted the feat/issue-167-nonweb-entrypoints branch September 6, 2026 12:59
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.

feat(entrypoints): framework-tier call rules and a shipped non-web ruleset (electron, commander, worker_threads, process.on)

1 participant