feat(entrypoints): framework-tier call rules; shipped non-web ruleset (electron, commander, worker_threads, process.on) - #168
Merged
Conversation
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
force-pushed
the
feat/issue-167-nonweb-entrypoints
branch
from
September 6, 2026 12:59
bc6e9ad to
571cb3b
Compare
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.
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 shipsclick/celeryso 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:frameworks.<x>.calls: gated ondetect:, matched on the import-table-resolved callee (app.on→electron.app.on;ipc.handleviaimport { ipcMain as ipc }→electron.ipcMain.handle), defaultcertain, 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, runs last.Shipped rules
electronelectronapp.{on,once},ipcMain.{on,once,handle,handleOnce}route= event/channelcommandercommanderprogram.{*,*.*,*.*.*}.action— a chained receiver resolves through its headworker_threadsworker_threads/node:worker_threadsparentPort.{on,once}process.{on,once}— a Node global, nothing to gate onVerification
app/ipcMainand noelectrondependency registers nothing from these rulesnode:specifier,process.onatheuristic, and never-doubles (a framework call claim blocks a heuristicapp.getrecord on the same handler) each have a testbun testgreen; no schema, Neo4j orSCHEMA_VERSIONchangeFound along the way
The per-framework loop did
continueon a framework with nofiles:rules — harmless until something was placed after the files stage. Fixed here.Cross-language
frameworks.<x>.callsis 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.