[GH-ISSUE #6516] [Bug] analyze-javascript-application assigns Next App Router support to non-router paths #3908

Closed
opened 2026-10-31 00:45:08 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6516

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze-javascript-application on current main classifies ordinary app/.../page.js suffixes as Next App Router page support even inside Pages Router files, unrelated components, dependency files and import/require strings. Routes have classification: "page", classification_basis: "next-app-page", confidence: "medium" and mechanism next-app-chunk. In a mixed source layout, genuine app/page.js and unrelated pages/app/page.js both support /, giving it two occurrences and two sources. This concerns static structural attribution, not runtime reachability.

Reproduction

Run this block from a source checkout at the main revision below with its compiled runtime, Python 3 and the stated Node runtime on PATH. Each Next source fixture declares dependencies/scripts; App Router cases include a root HTML/body layout. No dependency installation, Next build or Next server is required or claimed. The mixed fixture avoids pages/index.js conflicting with app/page.js. Every JavaScript file passes node --check; only the harmless CommonJS control is executed to establish its module result. All six main CLI calls exited 0. The block cleans its isolated fixtures and HOME/XDG directories.

python3 - <<'PYTHON'
import json, os, pathlib, subprocess, tempfile, shutil
repo = pathlib.Path.cwd()
assert (repo / 'scripts/rea.mjs').is_file()
node = shutil.which('node')
assert node
manifest = {'name':'route-fixture','version':'1.0.0','private':True,'type':'module','scripts':{'dev':'next dev','build':'next build','start':'next start'},'dependencies':{'next':'16.4.2','react':'19.2.0','react-dom':'19.2.0'}}
component = 'export default function Page() { return null; }\n'
layout = "import { createElement } from 'react';\nexport default function Layout({children}) { return createElement('html', null, createElement('body', null, children)); }\n"
cases = {
 'pages': {'pages/index.js':component,'pages/app/index.js':component,'pages/app/page.js':component,'pages/app/settings/page.js':component},
 'mixed': {'app/layout.js':layout,'app/page.js':component,'pages/app/index.js':component,'pages/app/page.js':component,'pages/app/settings/page.js':component},
 'components': {'app/layout.js':layout,'app/page.js':component,'components/app/not-a-route/page.js':component,'node_modules/fixture-module/package.json':'{"name":"fixture-module","version":"1.0.0","type":"module"}','node_modules/fixture-module/app/dependency-only/page.js':component},
 'specifier': {'pages/index.js':component,'main.js':"import Component from './components/app/string-only/page.js';\nexport default Component;\n",'components/app/string-only/page.js':component},
 'src-control': {'src/app/layout.js':layout,'src/app/control/page.js':component},
 'plain': {'package.json':'{"name":"plain-fixture","version":"1.0.0","main":"main.cjs"}','main.cjs':"module.exports = require('./components/app/module-only/page.js');\n",'components/app/module-only/page.js':"module.exports = {kind:'ordinary-module'};\n"}
}
root = pathlib.Path(tempfile.mkdtemp(prefix='route-verifier-'))
try:
 for name,files in cases.items():
  target=root/name; target.mkdir(); (target/'package.json').write_text(json.dumps(manifest))
  for path,text in files.items():
   f=target/path; f.parent.mkdir(parents=True,exist_ok=True); f.write_text(text)
  for f in target.rglob('*'):
   if f.suffix in ('.js','.cjs'):
    checked=subprocess.run([node,'--check',str(f)],capture_output=True,text=True)
    assert checked.returncode==0, checked.stderr
  pages={}
  for p in target.glob('pages/**/*.js'):
   parts=list(p.relative_to(target/'pages').with_suffix('').parts)
   if parts[-1]=='index': parts.pop()
   pages[str(p.relative_to(target))]='/'+ '/'.join(parts)
  home=root/('home-'+name); home.mkdir()
  env={'PATH':os.path.dirname(node)+':/usr/bin:/bin:/usr/sbin:/sbin','HOME':str(home),'XDG_CONFIG_HOME':str(home/'config'),'XDG_DATA_HOME':str(home/'data'),'XDG_CACHE_HOME':str(home/'cache'),'XDG_STATE_HOME':str(home/'state'),'TMPDIR':str(home),'NO_COLOR':'1'}
  run=subprocess.run([node,str(repo/'scripts/rea.mjs'),'analyze-javascript-application',str(target),'--format','json'],cwd=home,env=env,capture_output=True,text=True,timeout=90)
  assert run.returncode == 0, (name, run.returncode, run.stderr)
  out=json.loads(run.stdout); inv=out['normalized_result']['route_inventory']
  sources={s['id']:s['key'] for s in inv['sources']}; paths={r['id']:r['path'] for r in inv['routes']}
  summary={'case':name,'exit':run.returncode,'pages_oracle':pages,'coverage':inv['coverage'],'routes':[{k:r[k] for k in ('path','classification','classification_basis','confidence','occurrence_count','source_count')} for r in inv['routes']],'occurrences':[{'path':paths[o['route_id']],'source':sources[o['source_id']],'origin':o['origin'],'source_range':o['source_range']} for o in inv['occurrences']]}
  if name=='plain':
   runtime=subprocess.run([node,'-e',"console.log(JSON.stringify(require(process.argv[1])))",str(target/'main.cjs')],capture_output=True,text=True)
   assert runtime.returncode==0
   summary['node_runtime']=runtime.stdout.strip()
  assert inv['coverage']['source_parsing'] == 'complete'
  print(json.dumps(summary))
finally:
 shutil.rmtree(root)
PYTHON

Expected behavior

Known Pages Router files, components outside the router root, dependency modules and their ordinary import specifiers must not establish App Router page support solely through that suffix. Reject that attribution or retain an explicit applicability gap. For example, Pages Router maps pages/app/page.js to /app/page, pages/app/settings/page.js to /app/settings/page, and pages/app/index.js to /app. Discovering those Pages Router routes is outside this report; the defect is asserting unrelated Next App support. Genuine app/page.js and src/app/control/page.js should retain their page candidates.

Impact and scope

Observed consequences are wrong structural page provenance, derived candidate paths and supporting-source counts across the six small source fixtures. Main reports complete source parsing and candidate enumeration while leaving application_routes unknown. Medium confidence and unknown application coverage appropriately limit reachability/completeness claims but do not establish the asserted router provenance. NOT_ENOUGH_INFO: prevalence in real applications, downstream decisions affected, or a practical result-preserving workaround. No severe workflow outage, target mutation or security consequence was observed.

REA version

Source main 28a33cae8f8d4de8e11f7a266ff0d756432ab092, freshly fetched and compiled; --version reports 6.7.0. Separately tested latest published npm package rea-agents@6.7.0 (GitHub tag rea-agents-6.7.0): all six fixture analyses exit 0, but normalized_result.route_inventory is absent. The reported inventory defect is observed on main; it is not reproduced on the published release. The block above targets main's inventory and cannot summarize the release's output.

Runtime and platform

Main: Node v24.22.1; published release comparison: Node v24.18.0; macOS 27.0.1 arm64. No native analysis provider or browser was started.

Target and artifact context

Self-authored JavaScript directories: Pages Router, mixed-router source layout, App Router with unrelated components/dependency files, ESM import, plain CommonJS require, and a separate src/app control. No captured application or private sample.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed main rows (path, classification, basis, occurrence_count/source_count):

pages:       /                page next-app-page 1/1
             /settings        page next-app-page 1/1
mixed:       /                page next-app-page 2/2
             /settings        page next-app-page 1/1
components:  /                page next-app-page 1/1
             /dependency-only page next-app-page 1/1
             /not-a-route     page next-app-page 1/1
specifier:   /string-only     page next-app-page 2/2
src-control: /control         page next-app-page 1/1
plain:       /module-only     page next-app-page 2/2

All rows have medium confidence. The specifier case includes a source-syntax occurrence from main.js at line 1, columns 22–60, plus the component's source-path occurrence. The plain control returns {"kind":"ordinary-module"} in Node.

  • Expected source: The public directory workflow admits these inputs. The route inventory contract supports Next App page paths and page-file/structured classification, while disclaiming reachability and application completeness. Official Pages Router mappings and App Router project structure distinguish the source conventions. An independently executed, unchanged Next v16.4.2 root-selection helper and path-format helper confirmed the three Pages mappings and exclusion of component/dependency directories as router roots. This oracle is not a Next server test.
  • Failure source: The executed public CLI block and main output above. Full output retains the file/specifier provenance and counts; the release comparison is described in REA version.
  • Evidence provenance: observed CLI results and an independent path/root oracle; the detector explanation below is source-derived.
  • Local verification: reproduced. The observation is on main; the release lacks this inventory.
  • Reproduction completeness: complete. The block targets main.

Typed outcome or recovery expected

Do not attribute known non-App-Router occurrences as supporting next-app-page. An explicit interpretation gap is acceptable when context cannot establish the router root. Keep application_routes unknown; no runtime probing or additional Pages Router discovery is requested.

Analysis or suspected cause

Source-derived: classifyNextAppRoutePath accepts app after arbitrary prefixes. Source metadata and string inspection use that classifier to supply next-app-chunk occurrences and page classification. This explains the shared filename/specifier symptom without prescribing a patch.

Additional context

Restoration check: Proposed; rerun the block. Known Pages Router/component/dependency/specifier occurrences must no longer assert the false App Router support above; an explicit applicability gap is acceptable. The mixed root should retain the genuine app/page.js with one occurrence/source, and the separate src/app control should retain /control. Preserve coverage dimensions; newly discovering Pages Router routes is not required. No source fix was attempted.

Open/closed issue and PR searches found no exact duplicate. Related: #4772 introduced the inventory via #6019; #6349 concerns exact legacy object-property endpoints, a different detector and restoration signal; #3608 proposes broader React/Pages Router discovery. NOT_ENOUGH_INFO: last known good main revision and first affected release.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Executed static directory analysis on main gives bounded wrong page provenance and path/source aggregation for supported inputs. No severe workflow outage or security effect was observed; the published release lacks this inventory.
  • Workaround status: unverified
  • Workaround basis: Caller-side source filtering and aggregate recomputation were not executed or shown to preserve the supported source/chunk profile; no practical result-preserving workaround is established.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6516 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `analyze-javascript-application` on current main classifies ordinary `app/.../page.js` suffixes as Next App Router page support even inside Pages Router files, unrelated components, dependency files and import/require strings. Routes have `classification: "page"`, `classification_basis: "next-app-page"`, `confidence: "medium"` and mechanism `next-app-chunk`. In a mixed source layout, genuine `app/page.js` and unrelated `pages/app/page.js` both support `/`, giving it two occurrences and two sources. This concerns static structural attribution, not runtime reachability. ### Reproduction Run this block from a source checkout at the main revision below with its compiled runtime, Python 3 and the stated Node runtime on PATH. Each Next source fixture declares dependencies/scripts; App Router cases include a root HTML/body layout. No dependency installation, Next build or Next server is required or claimed. The mixed fixture avoids `pages/index.js` conflicting with `app/page.js`. Every JavaScript file passes `node --check`; only the harmless CommonJS control is executed to establish its module result. All six main CLI calls exited 0. The block cleans its isolated fixtures and HOME/XDG directories. ```sh python3 - <<'PYTHON' import json, os, pathlib, subprocess, tempfile, shutil repo = pathlib.Path.cwd() assert (repo / 'scripts/rea.mjs').is_file() node = shutil.which('node') assert node manifest = {'name':'route-fixture','version':'1.0.0','private':True,'type':'module','scripts':{'dev':'next dev','build':'next build','start':'next start'},'dependencies':{'next':'16.4.2','react':'19.2.0','react-dom':'19.2.0'}} component = 'export default function Page() { return null; }\n' layout = "import { createElement } from 'react';\nexport default function Layout({children}) { return createElement('html', null, createElement('body', null, children)); }\n" cases = { 'pages': {'pages/index.js':component,'pages/app/index.js':component,'pages/app/page.js':component,'pages/app/settings/page.js':component}, 'mixed': {'app/layout.js':layout,'app/page.js':component,'pages/app/index.js':component,'pages/app/page.js':component,'pages/app/settings/page.js':component}, 'components': {'app/layout.js':layout,'app/page.js':component,'components/app/not-a-route/page.js':component,'node_modules/fixture-module/package.json':'{"name":"fixture-module","version":"1.0.0","type":"module"}','node_modules/fixture-module/app/dependency-only/page.js':component}, 'specifier': {'pages/index.js':component,'main.js':"import Component from './components/app/string-only/page.js';\nexport default Component;\n",'components/app/string-only/page.js':component}, 'src-control': {'src/app/layout.js':layout,'src/app/control/page.js':component}, 'plain': {'package.json':'{"name":"plain-fixture","version":"1.0.0","main":"main.cjs"}','main.cjs':"module.exports = require('./components/app/module-only/page.js');\n",'components/app/module-only/page.js':"module.exports = {kind:'ordinary-module'};\n"} } root = pathlib.Path(tempfile.mkdtemp(prefix='route-verifier-')) try: for name,files in cases.items(): target=root/name; target.mkdir(); (target/'package.json').write_text(json.dumps(manifest)) for path,text in files.items(): f=target/path; f.parent.mkdir(parents=True,exist_ok=True); f.write_text(text) for f in target.rglob('*'): if f.suffix in ('.js','.cjs'): checked=subprocess.run([node,'--check',str(f)],capture_output=True,text=True) assert checked.returncode==0, checked.stderr pages={} for p in target.glob('pages/**/*.js'): parts=list(p.relative_to(target/'pages').with_suffix('').parts) if parts[-1]=='index': parts.pop() pages[str(p.relative_to(target))]='/'+ '/'.join(parts) home=root/('home-'+name); home.mkdir() env={'PATH':os.path.dirname(node)+':/usr/bin:/bin:/usr/sbin:/sbin','HOME':str(home),'XDG_CONFIG_HOME':str(home/'config'),'XDG_DATA_HOME':str(home/'data'),'XDG_CACHE_HOME':str(home/'cache'),'XDG_STATE_HOME':str(home/'state'),'TMPDIR':str(home),'NO_COLOR':'1'} run=subprocess.run([node,str(repo/'scripts/rea.mjs'),'analyze-javascript-application',str(target),'--format','json'],cwd=home,env=env,capture_output=True,text=True,timeout=90) assert run.returncode == 0, (name, run.returncode, run.stderr) out=json.loads(run.stdout); inv=out['normalized_result']['route_inventory'] sources={s['id']:s['key'] for s in inv['sources']}; paths={r['id']:r['path'] for r in inv['routes']} summary={'case':name,'exit':run.returncode,'pages_oracle':pages,'coverage':inv['coverage'],'routes':[{k:r[k] for k in ('path','classification','classification_basis','confidence','occurrence_count','source_count')} for r in inv['routes']],'occurrences':[{'path':paths[o['route_id']],'source':sources[o['source_id']],'origin':o['origin'],'source_range':o['source_range']} for o in inv['occurrences']]} if name=='plain': runtime=subprocess.run([node,'-e',"console.log(JSON.stringify(require(process.argv[1])))",str(target/'main.cjs')],capture_output=True,text=True) assert runtime.returncode==0 summary['node_runtime']=runtime.stdout.strip() assert inv['coverage']['source_parsing'] == 'complete' print(json.dumps(summary)) finally: shutil.rmtree(root) PYTHON ``` ### Expected behavior Known Pages Router files, components outside the router root, dependency modules and their ordinary import specifiers must not establish App Router page support solely through that suffix. Reject that attribution or retain an explicit applicability gap. For example, Pages Router maps `pages/app/page.js` to `/app/page`, `pages/app/settings/page.js` to `/app/settings/page`, and `pages/app/index.js` to `/app`. Discovering those Pages Router routes is outside this report; the defect is asserting unrelated Next App support. Genuine `app/page.js` and `src/app/control/page.js` should retain their page candidates. ### Impact and scope Observed consequences are wrong structural page provenance, derived candidate paths and supporting-source counts across the six small source fixtures. Main reports complete source parsing and candidate enumeration while leaving `application_routes` unknown. Medium confidence and unknown application coverage appropriately limit reachability/completeness claims but do not establish the asserted router provenance. NOT_ENOUGH_INFO: prevalence in real applications, downstream decisions affected, or a practical result-preserving workaround. No severe workflow outage, target mutation or security consequence was observed. ### REA version Source main `28a33cae8f8d4de8e11f7a266ff0d756432ab092`, freshly fetched and compiled; `--version` reports `6.7.0`. Separately tested latest published npm package `rea-agents@6.7.0` (GitHub tag `rea-agents-6.7.0`): all six fixture analyses exit 0, but `normalized_result.route_inventory` is absent. The reported inventory defect is observed on main; it is not reproduced on the published release. The block above targets main's inventory and cannot summarize the release's output. ### Runtime and platform Main: Node v24.22.1; published release comparison: Node v24.18.0; macOS 27.0.1 arm64. No native analysis provider or browser was started. ### Target and artifact context Self-authored JavaScript directories: Pages Router, mixed-router source layout, App Router with unrelated components/dependency files, ESM import, plain CommonJS require, and a separate `src/app` control. No captured application or private sample. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed main rows (`path`, classification, basis, occurrence_count/source_count): ```text pages: / page next-app-page 1/1 /settings page next-app-page 1/1 mixed: / page next-app-page 2/2 /settings page next-app-page 1/1 components: / page next-app-page 1/1 /dependency-only page next-app-page 1/1 /not-a-route page next-app-page 1/1 specifier: /string-only page next-app-page 2/2 src-control: /control page next-app-page 1/1 plain: /module-only page next-app-page 2/2 ``` All rows have medium confidence. The specifier case includes a `source-syntax` occurrence from `main.js` at line 1, columns 22–60, plus the component's source-path occurrence. The plain control returns `{"kind":"ordinary-module"}` in Node. - Expected source: The [public directory workflow](https://github.com/morluto/rea/blob/28a33cae8f8d4de8e11f7a266ff0d756432ab092/docs/javascript-artifact-reconstruction.md#L14-L32) admits these inputs. The [route inventory contract](https://github.com/morluto/rea/blob/28a33cae8f8d4de8e11f7a266ff0d756432ab092/docs/javascript-route-inventory.md#L60-L90) supports Next App page paths and page-file/structured classification, while disclaiming reachability and application completeness. Official [Pages Router mappings](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts) and [App Router project structure](https://nextjs.org/docs/app/getting-started/project-structure) distinguish the source conventions. An independently executed, unchanged Next v16.4.2 [root-selection helper](https://github.com/vercel/next.js/blob/2864b1772c37242a9b40ab4d366eb7d368c2a070/packages/next/src/lib/find-pages-dir.ts) and [path-format helper](https://github.com/vercel/next.js/blob/2864b1772c37242a9b40ab4d366eb7d368c2a070/packages/next/src/shared/lib/page-path/remove-page-path-tail.ts) confirmed the three Pages mappings and exclusion of component/dependency directories as router roots. This oracle is not a Next server test. - Failure source: The executed public CLI block and main output above. Full output retains the file/specifier provenance and counts; the release comparison is described in REA version. - Evidence provenance: observed CLI results and an independent path/root oracle; the detector explanation below is source-derived. - Local verification: reproduced. The observation is on main; the release lacks this inventory. - Reproduction completeness: complete. The block targets main. ### Typed outcome or recovery expected Do not attribute known non-App-Router occurrences as supporting `next-app-page`. An explicit interpretation gap is acceptable when context cannot establish the router root. Keep `application_routes` unknown; no runtime probing or additional Pages Router discovery is requested. ### Analysis or suspected cause Source-derived: [classifyNextAppRoutePath](https://github.com/morluto/rea/blob/28a33cae8f8d4de8e11f7a266ff0d756432ab092/src/domain/javascript/javascriptRoutePaths.ts#L19-L42) accepts `app` after arbitrary prefixes. Source metadata and string inspection use that classifier to supply `next-app-chunk` occurrences and page classification. This explains the shared filename/specifier symptom without prescribing a patch. ### Additional context Restoration check: Proposed; rerun the block. Known Pages Router/component/dependency/specifier occurrences must no longer assert the false App Router support above; an explicit applicability gap is acceptable. The mixed root should retain the genuine `app/page.js` with one occurrence/source, and the separate `src/app` control should retain `/control`. Preserve coverage dimensions; newly discovering Pages Router routes is not required. No source fix was attempted. Open/closed issue and PR searches found no exact duplicate. Related: [#4772](https://github.com/morluto/rea/issues/4772) introduced the inventory via [#6019](https://github.com/morluto/rea/pull/6019); [#6349](https://github.com/morluto/rea/issues/6349) concerns exact legacy object-property endpoints, a different detector and restoration signal; [#3608](https://github.com/morluto/rea/issues/3608) proposes broader React/Pages Router discovery. NOT_ENOUGH_INFO: last known good main revision and first affected release. ### Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Executed static directory analysis on main gives bounded wrong page provenance and path/source aggregation for supported inputs. No severe workflow outage or security effect was observed; the published release lacks this inventory. - Workaround status: unverified - Workaround basis: Caller-side source filtering and aggregate recomputation were not executed or shown to preserve the supported source/chunk profile; no practical result-preserving workaround is established.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

Source router applicability is now owned by the nearest package and inventoried app/src/app root. Outside-app sources are rejected, ordinary source references never establish routes, and unknown applicability stays explicit. The CLI/stdio MCP regression includes pages/app paths, unrelated components, a dependency package, an import specifier, a plain CommonJS package, and a src/app workspace control; it requires exactly / and /control, each with one supporting occurrence/source.

Implementation: JavaScriptNextAppContext.ts, javascriptRoutePaths.ts.

The relevant regression checks in javascriptNextRouteContext.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380764063 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Source router applicability is now owned by the nearest package and inventoried app/src/app root. Outside-app sources are rejected, ordinary source references never establish routes, and unknown applicability stays explicit. The CLI/stdio MCP regression includes pages/app paths, unrelated components, a dependency package, an import specifier, a plain CommonJS package, and a src/app workspace control; it requires exactly / and /control, each with one supporting occurrence/source. Implementation: [JavaScriptNextAppContext.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/javascript/JavaScriptNextAppContext.ts#L9), [javascriptRoutePaths.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptRoutePaths.ts#L63). The relevant regression checks in [javascriptNextRouteContext.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/acceptance/applications/javascriptNextRouteContext.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3908
No description provided.