You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The entrypoint pass (#72, #159, #161, #167) ships rules for nestjs, angular, nextjs, sveltekit,
electron, commander and worker_threads. Several JS/TS-only frameworks with no Java or Python
counterpart are missing, and measuring against a fixture turned up three resolver gaps that block
them independently of any rule row:
Remix / React Router v7 — app/routes/** modules exporting loader / action / default.
These land in the tree as exported, module-level, correctly-named callables, so the existing
file tier (entrypointsFromFiles, src/entrypoints/matching.ts:335) resolves them with no code
change. Nothing detects them today.
Astro API routes — src/pages/**/*.{ts,js} exporting GET/POST/…, same shape.
Next.js src/ layout — nextjs.app-route and nextjs.pages-api anchor their globs at the
repo root (pages/api/**), so the officially-supported src/pages/api/** and src/app/**/route.ts
layouts match nothing. Asserted as current behaviour at test/entrypoints-files.test.ts:26.
Named function expression handlers — app.get("/x", function named(req, res) {}) is counted
in entrypoint_report.unresolved instead of resolving. resolveHandler
(src/entrypoints/matching.ts:192-197) accepts an inline callable only when its name is "(anonymous)"; a named function expression passes the INLINE regex at :132 and then matches
nothing. Measured: of four Express handler shapes, the identifier, arrow and async-arrow forms
resolve and this one does not.
Nitro / Nuxt — export default defineEventHandler((event) => {}). The wrapped arrow is (anonymous) with is_exported: false, and resolveDefaultExport (matching.ts:309-324)
requires the anonymous callable's span to begin exactly after the export default token, which
a wrapper call displaces.
Scope boundary
Rules for the frameworks whose shape the current machinery can already resolve, plus the four
narrow resolver fixes that unblock them. Specifically NOT in this issue:
No promotion of Express / Fastify / Koa / Hapi / Hono to declared frameworks: rules.
Framework-tier calls: rules match the import-table-resolved callee and bail on an unresolvable
one (matching.ts:172). Their receiver is a factory result held in a local (const app = express()), which resolveWritten (src/syntactic_analysis/importResolver.ts:29-35) cannot
name — it resolves only spellings whose head is an imported binding, which is why electron.app-on works and app.get cannot. These frameworks keep working via the heuristic
tier and keep reporting framework: "heuristic". Promoting them needs a local-to-factory-origin
link; separate issue.
No Cloudflare Workers export default { async fetch() {} }. Object-literal methods yield
zero module-level callables today, so there is nothing for any rule to target. Symbol-table
scope; separate issue. Bun.serve({ fetch }) misses for the same reason.
No .astro / .vue / .svelte parsing — only the .ts/.js files beside them.
No change to TSEntrypoint, the Neo4j schema, or SCHEMA_VERSION.
astro block — detect: [astro]; astro.api-route over src/pages/**/*.{ts,js}, exports: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, ALL], methods: {from: export_name}
nextjs — add nextjs.app-route-src (src/app/**/route.{ts,tsx,js,mjs}) and nextjs.pages-api-src (src/pages/api/**/*.{ts,tsx,js,mjs})
Resolver fixes:
resolveHandler (matching.ts:192-197) — in the INLINE branch, select the earliest callable whose span lies inside the call site regardless of its name, so a named function expression resolves
resolveDefaultExport (matching.ts:309-324) — accept export default <wrapper>(<handler>), bounding the gap between the export default token and the callable to wrapper-call text only (/^(?:[A-Za-z_$][\w$.]*\s*\(\s*)+$/) so an unrelated later callable cannot be claimed
routeFromFileKey (matching.ts:291) — strip a leading src/ from the glob's literal prefix, so src/app/users/route.ts yields /users and src/pages/api/hello.ts yields /api/hello
methodsOfexport_name branch (matching.ts:101) — filter through HTTP_VERBS as the match_suffix branch already does at :92, so Astro's ALL yields [] instead of the non-method ["ALL"]
Caveats and known risks
framework values shift for existing projects. A Remix or Astro project whose handlers the
heuristic tier previously claimed will now report framework: "remix" / "astro", because the
file tier runs before the heuristic tiers (src/entrypoints/pipeline.ts:69-92) and the heuristic
calls tier skips framework-claimed nodes (pipeline.ts:113). This is an output-content change,
not a schema change. Mitigation: pinned by a test asserting the exact new framework/rule
values, and called out here.
remix detects on react-router because v7 absorbed Remix. A React Router SPA that happens to
have app/routes/ and an export named loader will be flagged. The alternative — omitting it —
misses every React Router v7 app. Accepted at confidence: certain, not declared.
Astro's ALL export produces http_methods: []. Deliberate: ALL is not an HTTP method, and is_entrypoint plus route still carry the finding.
Remix loader / action carry http_methods: []. Mapping loader to GET and action to the
mutating verbs needs a per-export method source the rule grammar does not have; the rule id
(remix.loader vs remix.action) carries the distinction instead. Not attempted here.
resolveDefaultExport's wrapper shape still misses a handler that is not the first callable in
the wrapper call — defineEventHandler({ onRequest: fn }) resolves nothing. Counted in unresolved rather than silently dropped.
The src/ strip in routeFromFileKey applies to the glob's literal prefix only, so the existing routeFromFileKey("src/routes/x/+server.ts", "**/+server.{ts,js}") → /src/routes/x assertion
(test/entrypoints-files.test.ts:35) is unaffected.
Definition of done
One fixture carrying every shape named above; the entrypoint record set equals a
hand-written expected set — exact framework, rule, confidence, route and http_methods per target, not a non-empty check
entrypoint_report.frameworks_detected equals exactly ["astro", "nextjs", "remix"] on that
fixture
A test per resolver fix that fails on main and passes after: named function expression
handler resolves and no longer appears in unresolved; export default defineEventHandler(...) resolves; src/app/** and src/pages/api/** produce /users and /api/hello; Astro ALL yields http_methods: []
The four globToRegExp / routeFromFileKey assertions at test/entrypoints-files.test.ts:24-36 pass unchanged
bun test green, bun run typecheck clean
bun run gen:schema leaves schema.neo4j.json byte-identical, demonstrating no contract move
Problem
The entrypoint pass (#72, #159, #161, #167) ships rules for nestjs, angular, nextjs, sveltekit,
electron, commander and worker_threads. Several JS/TS-only frameworks with no Java or Python
counterpart are missing, and measuring against a fixture turned up three resolver gaps that block
them independently of any rule row:
app/routes/**modules exportingloader/action/default.These land in the tree as exported, module-level, correctly-named callables, so the existing
file tier (
entrypointsFromFiles,src/entrypoints/matching.ts:335) resolves them with no codechange. Nothing detects them today.
src/pages/**/*.{ts,js}exportingGET/POST/…, same shape.src/layout —nextjs.app-routeandnextjs.pages-apianchor their globs at therepo root (
pages/api/**), so the officially-supportedsrc/pages/api/**andsrc/app/**/route.tslayouts match nothing. Asserted as current behaviour at
test/entrypoints-files.test.ts:26.app.get("/x", function named(req, res) {})is countedin
entrypoint_report.unresolvedinstead of resolving.resolveHandler(
src/entrypoints/matching.ts:192-197) accepts an inline callable only when its name is"(anonymous)"; a named function expression passes theINLINEregex at:132and then matchesnothing. Measured: of four Express handler shapes, the identifier, arrow and async-arrow forms
resolve and this one does not.
export default defineEventHandler((event) => {}). The wrapped arrow is(anonymous)withis_exported: false, andresolveDefaultExport(matching.ts:309-324)requires the anonymous callable's span to begin exactly after the
export defaulttoken, whicha wrapper call displaces.
Scope boundary
Rules for the frameworks whose shape the current machinery can already resolve, plus the four
narrow resolver fixes that unblock them. Specifically NOT in this issue:
frameworks:rules.Framework-tier
calls:rules match the import-table-resolved callee and bail on an unresolvableone (
matching.ts:172). Their receiver is a factory result held in a local (const app = express()), whichresolveWritten(src/syntactic_analysis/importResolver.ts:29-35) cannotname — it resolves only spellings whose head is an imported binding, which is why
electron.app-onworks andapp.getcannot. These frameworks keep working via the heuristictier and keep reporting
framework: "heuristic". Promoting them needs a local-to-factory-originlink; separate issue.
export default { async fetch() {} }. Object-literal methods yieldzero module-level callables today, so there is nothing for any rule to target. Symbol-table
scope; separate issue.
Bun.serve({ fetch })misses for the same reason..astro/.vue/.svelteparsing — only the.ts/.jsfiles beside them.TSEntrypoint, the Neo4j schema, orSCHEMA_VERSION.Goals
Rules (
src/entrypoints/rules.yml):remixblock —detect: [@remix-run/node, @remix-run/react, @remix-run/server-runtime, @remix-run/cloudflare, react-router]; three file rules overapp/routes/**/*.{ts,tsx,js,jsx}:remix.loader(exports: [loader]),remix.action(exports: [action]),remix.route-component(exports: [default])astroblock —detect: [astro];astro.api-routeoversrc/pages/**/*.{ts,js},exports: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, ALL],methods: {from: export_name}nextjs— addnextjs.app-route-src(src/app/**/route.{ts,tsx,js,mjs}) andnextjs.pages-api-src(src/pages/api/**/*.{ts,tsx,js,mjs})Resolver fixes:
resolveHandler(matching.ts:192-197) — in theINLINEbranch, select the earliest callable whose span lies inside the call site regardless of its name, so a named function expression resolvesresolveDefaultExport(matching.ts:309-324) — acceptexport default <wrapper>(<handler>), bounding the gap between theexport defaulttoken and the callable to wrapper-call text only (/^(?:[A-Za-z_$][\w$.]*\s*\(\s*)+$/) so an unrelated later callable cannot be claimedrouteFromFileKey(matching.ts:291) — strip a leadingsrc/from the glob's literal prefix, sosrc/app/users/route.tsyields/usersandsrc/pages/api/hello.tsyields/api/hellomethodsOfexport_namebranch (matching.ts:101) — filter throughHTTP_VERBSas thematch_suffixbranch already does at:92, so Astro'sALLyields[]instead of the non-method["ALL"]Caveats and known risks
frameworkvalues shift for existing projects. A Remix or Astro project whose handlers theheuristic tier previously claimed will now report
framework: "remix"/"astro", because thefile tier runs before the heuristic tiers (
src/entrypoints/pipeline.ts:69-92) and the heuristiccalls tier skips framework-claimed nodes (
pipeline.ts:113). This is an output-content change,not a schema change. Mitigation: pinned by a test asserting the exact new
framework/rulevalues, and called out here.
remixdetects onreact-routerbecause v7 absorbed Remix. A React Router SPA that happens tohave
app/routes/and an export namedloaderwill be flagged. The alternative — omitting it —misses every React Router v7 app. Accepted at
confidence: certain, notdeclared.ALLexport produceshttp_methods: []. Deliberate:ALLis not an HTTP method, andis_entrypointplusroutestill carry the finding.loader/actioncarryhttp_methods: []. MappingloadertoGETandactionto themutating verbs needs a per-export method source the rule grammar does not have; the
ruleid(
remix.loadervsremix.action) carries the distinction instead. Not attempted here.resolveDefaultExport's wrapper shape still misses a handler that is not the first callable inthe wrapper call —
defineEventHandler({ onRequest: fn })resolves nothing. Counted inunresolvedrather than silently dropped.src/strip inrouteFromFileKeyapplies to the glob's literal prefix only, so the existingrouteFromFileKey("src/routes/x/+server.ts", "**/+server.{ts,js}")→/src/routes/xassertion(
test/entrypoints-files.test.ts:35) is unaffected.Definition of done
hand-written expected set — exact
framework,rule,confidence,routeandhttp_methodsper target, not a non-empty checkentrypoint_report.frameworks_detectedequals exactly["astro", "nextjs", "remix"]on thatfixture
mainand passes after: named function expressionhandler resolves and no longer appears in
unresolved;export default defineEventHandler(...)resolves;src/app/**andsrc/pages/api/**produce/usersand/api/hello; AstroALLyieldshttp_methods: []globToRegExp/routeFromFileKeyassertions attest/entrypoints-files.test.ts:24-36pass unchangedbun testgreen,bun run typecheckcleanbun run gen:schemaleavesschema.neo4j.jsonbyte-identical, demonstrating no contract move