@rankcli/agent-runtime 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3562,6 +3562,7 @@ __export(index_exports, {
3562
3562
  sendAlerts: () => sendAlerts,
3563
3563
  sendDiscordAlert: () => sendDiscordAlert,
3564
3564
  sendSlackAlert: () => sendSlackAlert,
3565
+ servedDir: () => servedDir,
3565
3566
  shouldRunAudit: () => shouldRunAudit,
3566
3567
  shouldSendAlert: () => shouldSendAlert,
3567
3568
  startWizardSession: () => startWizardSession,
@@ -3622,6 +3623,8 @@ var RECIPES = {
3622
3623
  jekyll: null,
3623
3624
  eleventy: null,
3624
3625
  pelican: null,
3626
+ sphinx: null,
3627
+ docusaurus: null,
3625
3628
  // Unknown
3626
3629
  unknown: null
3627
3630
  };
@@ -7741,234 +7744,161 @@ var ISSUE_DEFINITIONS = {
7741
7744
  // src/audit/engine.ts
7742
7745
  init_http();
7743
7746
 
7744
- // src/frameworks/detector.ts
7745
- var HTML_SIGNATURES = [
7746
- // ============ JavaScript/TypeScript Frameworks ============
7747
- // Next.js
7748
- {
7749
- framework: "nextjs",
7750
- language: "typescript",
7751
- patterns: [/__next/, /_next\//, /data-nscript/, /__NEXT_DATA__/, /next\/script/],
7752
- confidence: "high"
7753
- },
7754
- // Nuxt
7755
- {
7756
- framework: "nuxt",
7757
- language: "typescript",
7758
- patterns: [/__nuxt/, /_nuxt\//, /data-n-head/, /nuxt-link/, /__NUXT__/],
7759
- confidence: "high"
7760
- },
7761
- // Gatsby
7762
- {
7763
- framework: "gatsby",
7764
- language: "javascript",
7765
- patterns: [/___gatsby/, /gatsby-/, /gatsby-image/, /data-gatsby/],
7766
- confidence: "high"
7767
- },
7768
- // Astro
7769
- {
7770
- framework: "astro",
7771
- language: "typescript",
7772
- patterns: [/astro-/, /data-astro/, /_astro\//],
7773
- confidence: "high"
7774
- },
7775
- // SvelteKit
7776
- {
7777
- framework: "sveltekit",
7778
- language: "typescript",
7779
- patterns: [/__sveltekit/, /_app\/immutable/, /svelte-kit/],
7780
- confidence: "high"
7781
- },
7782
- // Svelte (standalone)
7783
- {
7784
- framework: "svelte",
7785
- language: "javascript",
7786
- patterns: [/svelte-\w+/, /__svelte/],
7787
- confidence: "medium"
7788
- },
7789
- // Remix
7790
- {
7791
- framework: "remix",
7792
- language: "typescript",
7793
- patterns: [/__remix/, /data-remix/],
7794
- confidence: "high"
7795
- },
7796
- // Solid.js
7797
- {
7798
- framework: "solidjs",
7799
- language: "typescript",
7800
- patterns: [/data-solid/, /_solid/],
7801
- confidence: "high"
7802
- },
7803
- // Qwik
7804
- {
7805
- framework: "qwik",
7806
- language: "typescript",
7807
- patterns: [/q:container/, /q:base/, /q:version/, /q:id=/, /qwik/],
7808
- confidence: "high"
7809
- },
7810
- // Fresh (Deno)
7811
- {
7812
- framework: "fresh",
7813
- language: "typescript",
7814
- patterns: [/_frsh\//, /data-fresh/],
7815
- confidence: "high"
7816
- },
7817
- // React (generic - lower priority)
7747
+ // src/frameworks/signals.ts
7748
+ var WEIGHT = { DECISIVE: 100, STRONG: 45, WEAK: 15 };
7749
+ var SCORE_FLOOR = 45;
7750
+ var SIGNALS = [
7751
+ // ---- Meta-frameworks: JavaScript ------------------------------------------------
7752
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-build-dir", html: /\/_next\/(static|image|data)\// },
7753
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-data", html: /__NEXT_DATA__|self\.__next_f/ },
7754
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-mount", html: /<div id=["']__next["']/ },
7755
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-powered-by", header: /^x-powered-by: next\.js/ },
7756
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-build-dir", html: /\/_nuxt\// },
7757
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-payload", html: /__NUXT__|__NUXT_DATA__|<div id="__nuxt"/ },
7758
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-powered-by", header: /^x-powered-by: nuxt/ },
7759
+ { framework: "astro", language: "typescript", weight: WEIGHT.DECISIVE, name: "astro-build-dir", html: /\/_astro\// },
7760
+ { framework: "astro", language: "typescript", weight: WEIGHT.DECISIVE, name: "astro-island", html: /<astro-island|astro-slot|data-astro-(cid|source-file|transition)/ },
7761
+ { framework: "astro", language: "typescript", weight: WEIGHT.STRONG, name: "astro-generator", html: /name=["']?generator["']?[^>]*content=["']?Astro/i },
7762
+ { framework: "sveltekit", language: "typescript", weight: WEIGHT.DECISIVE, name: "sveltekit-immutable", html: /\/_app\/immutable\// },
7763
+ { framework: "sveltekit", language: "typescript", weight: WEIGHT.DECISIVE, name: "sveltekit-runtime", html: /__sveltekit|kit\.start\(/ },
7764
+ { framework: "remix", language: "typescript", weight: WEIGHT.DECISIVE, name: "remix-context", html: /__remixContext|__remixManifest|__remixRouteModules/ },
7765
+ { framework: "remix", language: "typescript", weight: WEIGHT.STRONG, name: "remix-build-dir", html: /\/build\/_shared\/|\/build\/entry\.client-/ },
7766
+ { framework: "gatsby", language: "javascript", weight: WEIGHT.DECISIVE, name: "gatsby-runtime", html: /___gatsby|window\.___chunkMapping/ },
7767
+ { framework: "gatsby", language: "javascript", weight: WEIGHT.STRONG, name: "gatsby-page-data", html: /\/page-data\/(app-data\.json|sq\/)/ },
7768
+ { framework: "qwik", language: "typescript", weight: WEIGHT.DECISIVE, name: "qwik-container", html: /q:container|q:base|q:version|qwikloader/ },
7769
+ { framework: "fresh", language: "typescript", weight: WEIGHT.DECISIVE, name: "fresh-build-dir", html: /\/_frsh\// },
7770
+ { framework: "solidjs", language: "typescript", weight: WEIGHT.STRONG, name: "solid-hydration", html: /_\$HY\b|data-hk=["']/ },
7771
+ // ---- Base UI libraries: weaker by nature, and that is the point -----------------
7772
+ { framework: "angular", language: "typescript", weight: WEIGHT.DECISIVE, name: "angular-version", html: /ng-version=["']/ },
7773
+ { framework: "angular", language: "typescript", weight: WEIGHT.DECISIVE, name: "angular-content-attr", html: /_ngcontent-|_nghost-/ },
7774
+ { framework: "react", language: "javascript", weight: WEIGHT.STRONG, name: "react-root-marker", html: /data-reactroot|__reactContainer\$|__REACT_DEVTOOLS/ },
7775
+ // A Vite-built SPA: an empty mount point plus Vite's hashed entry bundle. Two weak facts
7776
+ // that together mean something; either one alone does not.
7777
+ { framework: "react", language: "javascript", weight: WEIGHT.WEAK, name: "react-empty-root", html: /<div id="root"><\/div>/ },
7778
+ { framework: "react", language: "javascript", weight: WEIGHT.WEAK, name: "react-refresh", html: /RefreshRuntime|react-refresh/ },
7818
7779
  {
7819
7780
  framework: "react",
7820
7781
  language: "javascript",
7821
- // data-reactroot/data-react-helmet are React <=15 hydration markers -
7822
- // React 18's createRoot() never adds them, so every modern CRA/Vite
7823
- // React app (an empty <div id="root">) went undetected from HTML alone.
7824
- patterns: [/data-reactroot/, /data-react-helmet/, /_react/, /react-app/, /<div id="root"><\/div>/],
7825
- confidence: "medium"
7826
- },
7827
- // Vue (generic - lower priority)
7828
- {
7829
- framework: "vue",
7830
- language: "javascript",
7831
- patterns: [/data-v-[a-f0-9]+/, /v-cloak/, /__vue/],
7832
- confidence: "medium"
7833
- },
7834
- // Angular
7835
- {
7836
- framework: "angular",
7837
- language: "typescript",
7838
- patterns: [/ng-version/, /_ngcontent/, /ng-\w+=/, /\[ng/],
7839
- confidence: "high"
7840
- },
7841
- // ============ Hypermedia Frameworks ============
7842
- // HTMX
7843
- {
7844
- framework: "htmx",
7845
- language: "unknown",
7846
- patterns: [/hx-get/, /hx-post/, /hx-trigger/, /hx-swap/, /htmx\.org/],
7847
- confidence: "high"
7848
- },
7849
- // Hotwire/Turbo
7850
- {
7851
- framework: "hotwire",
7852
- language: "ruby",
7853
- patterns: [/turbo-frame/, /turbo-stream/, /data-turbo/],
7854
- confidence: "high"
7855
- },
7856
- // ============ PHP Frameworks ============
7857
- // WordPress
7858
- {
7859
- framework: "wordpress",
7860
- language: "php",
7861
- // Technical signatures only. These patterns run over the whole page, so the old
7862
- // /wordpress/i matched the word anywhere in the copy: rankcli.dev's own "detects
7863
- // Rails, Django, Laravel, and WordPress" line was enough to detect its React SPA as
7864
- // WordPress and then tell it to install an SEO plugin. A generator meta tag is the
7865
- // one place the page says it about itself.
7866
- patterns: [/wp-content/, /wp-includes/, /wp-json/, /name=["']generator["'][^>]*content=["']WordPress/i],
7867
- confidence: "high"
7868
- },
7869
- // Laravel
7870
- {
7871
- framework: "laravel",
7872
- language: "php",
7873
- // /laravel/i had the same problem as WordPress above - it matched the word in prose.
7874
- patterns: [/laravel_session/, /XSRF-TOKEN/, /@csrf/],
7875
- confidence: "medium"
7876
- },
7877
- // Symfony
7878
- {
7879
- framework: "symfony",
7880
- language: "php",
7881
- // Likewise /symfony/, which matched any lowercase mention.
7882
- patterns: [/sf2/, /_sf_/, /profiler\.css/],
7883
- confidence: "medium"
7884
- },
7885
- // ============ Ruby Frameworks ============
7886
- // Rails
7887
- {
7888
- framework: "rails",
7889
- language: "ruby",
7890
- patterns: [/csrf-token/, /turbolinks/, /data-turbo/, /rails-ujs/, /action-cable/],
7891
- confidence: "medium"
7892
- },
7893
- // ============ Python Frameworks ============
7894
- // Django
7895
- {
7896
- framework: "django",
7897
- language: "python",
7898
- patterns: [/csrfmiddlewaretoken/, /django/, /__debug__\//],
7899
- confidence: "medium"
7900
- },
7901
- // Flask
7902
- {
7903
- framework: "flask",
7904
- language: "python",
7905
- patterns: [/flask/, /werkzeug/i],
7906
- confidence: "low"
7907
- },
7908
- // FastAPI
7909
- {
7910
- framework: "fastapi",
7911
- language: "python",
7912
- patterns: [/fastapi/, /openapi\.json/],
7913
- confidence: "low"
7914
- },
7915
- // ============ Java Frameworks ============
7916
- // Spring Boot
7917
- {
7918
- framework: "spring",
7919
- language: "java",
7920
- // /th:/ matched "width:" and "height:" in inline CSS, which made almost every styled
7921
- // page on the web a Spring Boot app - rankcli.dev included, once WordPress stopped
7922
- // matching first. Thymeleaf attributes are what th: actually means.
7923
- patterns: [/\sth:[a-z-]+=/, /thymeleaf/, /_csrf/],
7924
- confidence: "medium"
7925
- },
7926
- // ============ C# Frameworks ============
7927
- // ASP.NET Core
7928
- {
7929
- framework: "aspnet",
7930
- language: "csharp",
7931
- patterns: [/aspnetcore/, /__RequestVerificationToken/, /_framework\/blazor/],
7932
- confidence: "medium"
7933
- },
7934
- // ============ Elixir Frameworks ============
7935
- // Phoenix
7936
- {
7937
- framework: "phoenix",
7938
- language: "elixir",
7939
- patterns: [/phx-/, /live-socket/, /data-phx/],
7940
- confidence: "high"
7941
- },
7942
- // ============ Static Site Generators ============
7943
- // Hugo
7944
- {
7945
- framework: "hugo",
7946
- language: "go",
7947
- patterns: [/name=["']generator["'][^>]*content=["']Hugo/i],
7948
- confidence: "medium"
7949
- },
7950
- // Jekyll
7951
- {
7952
- framework: "jekyll",
7953
- language: "ruby",
7954
- patterns: [/name=["']generator["'][^>]*content=["']Jekyll/i],
7955
- confidence: "medium"
7782
+ weight: WEIGHT.STRONG,
7783
+ name: "react-vite-spa",
7784
+ // Vite's entry chunk plus React's conventional mount point. Medium confidence by
7785
+ // construction: #app instead of #root stays unknown, because Vue, Solid and others use it.
7786
+ // Not `<div id="root"></div>`: a prerendered SPA has content inside its mount point, which
7787
+ // is what RankCLI's own site does and what the first version of this signal missed.
7788
+ allHtml: [/<script[^>]+src=["'][^"']+\.js["']/, /<div id="root"[\s>]/]
7956
7789
  },
7957
- // Eleventy
7958
- {
7959
- framework: "eleventy",
7960
- language: "javascript",
7961
- patterns: [/11ty/, /eleventy/, /generator.*eleventy/i],
7962
- confidence: "medium"
7963
- },
7964
- // Pelican
7965
- {
7966
- framework: "pelican",
7967
- language: "python",
7968
- patterns: [/pelican/, /generator.*pelican/i],
7969
- confidence: "medium"
7970
- }
7790
+ { framework: "vue", language: "javascript", weight: WEIGHT.DECISIVE, name: "vitepress-runtime", html: /__VP_HASH_MAP__|__VP_SITE_DATA__/ },
7791
+ { framework: "vue", language: "javascript", weight: WEIGHT.DECISIVE, name: "vue-scoped-style", html: /data-v-[0-9a-f]{6,10}\b/ },
7792
+ { framework: "vue", language: "javascript", weight: WEIGHT.WEAK, name: "vue-cloak", html: /v-cloak|<div id="app"><\/div>/ },
7793
+ { framework: "svelte", language: "javascript", weight: WEIGHT.WEAK, name: "svelte-class", html: /class=["'][^"']*svelte-[0-9a-z]{6}/ },
7794
+ // ---- Ruby -----------------------------------------------------------------------
7795
+ // csrf-token alone is emitted by Rails, Laravel, Phoenix and Django. csrf-param is Rails.
7796
+ { framework: "rails", language: "ruby", weight: WEIGHT.DECISIVE, name: "rails-csrf-param", html: /name=["']csrf-param["']/ },
7797
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-ujs", html: /rails-ujs|turbolinks|data-remote=["']true["']|action_cable|actioncable/ },
7798
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-session-cookie", header: /^set-cookie: [^;]*_session=/ },
7799
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-app-server", header: /^(server|x-powered-by): (puma|passenger|unicorn|phusion)/ },
7800
+ { framework: "hotwire", language: "ruby", weight: WEIGHT.STRONG, name: "turbo-elements", html: /<turbo-frame|<turbo-stream|data-turbo-track/ },
7801
+ // ---- Python ---------------------------------------------------------------------
7802
+ { framework: "django", language: "python", weight: WEIGHT.DECISIVE, name: "django-csrf-input", html: /name=["']csrfmiddlewaretoken["']/ },
7803
+ { framework: "django", language: "python", weight: WEIGHT.DECISIVE, name: "django-csrf-cookie", header: /^set-cookie: csrftoken=/ },
7804
+ { framework: "django", language: "python", weight: WEIGHT.STRONG, name: "django-admin-static", html: /\/static\/admin\/(css|js)\// },
7805
+ { framework: "django", language: "python", weight: WEIGHT.WEAK, name: "django-wsgi-server", header: /^(server|x-powered-by): (gunicorn|uvicorn|daphne)/ },
7806
+ { framework: "flask", language: "python", weight: WEIGHT.STRONG, name: "flask-server", header: /^server: werkzeug/ },
7807
+ { framework: "fastapi", language: "python", weight: WEIGHT.STRONG, name: "fastapi-docs", html: /swagger-ui-bundle\.js[\s\S]{0,200}openapi\.json/ },
7808
+ // ---- PHP ------------------------------------------------------------------------
7809
+ { framework: "wordpress", language: "php", weight: WEIGHT.DECISIVE, name: "wp-paths", html: /\/wp-content\/|\/wp-includes\/|\/wp-json\// },
7810
+ { framework: "wordpress", language: "php", weight: WEIGHT.DECISIVE, name: "wp-generator", html: /name=["']?generator["']?[^>]*content=["']?WordPress/i },
7811
+ { framework: "wordpress", language: "php", weight: WEIGHT.STRONG, name: "wp-powered-by", header: /^x-powered-by: wordpress/ },
7812
+ { framework: "laravel", language: "php", weight: WEIGHT.DECISIVE, name: "laravel-session-cookie", header: /^set-cookie: laravel_session=/ },
7813
+ { framework: "laravel", language: "php", weight: WEIGHT.STRONG, name: "laravel-xsrf-cookie", header: /^set-cookie: XSRF-TOKEN=/i },
7814
+ { framework: "laravel", language: "php", weight: WEIGHT.STRONG, name: "laravel-livewire", html: /livewire|wire:(click|model|submit)/i },
7815
+ { framework: "laravel", language: "php", weight: WEIGHT.WEAK, name: "laravel-vite-build", html: /\/build\/assets\/app-[A-Za-z0-9_-]{8}\.js/ },
7816
+ { framework: "symfony", language: "php", weight: WEIGHT.DECISIVE, name: "symfony-profiler", html: /\/_profiler\/|sf-toolbar|sf-dump/ },
7817
+ { framework: "symfony", language: "php", weight: WEIGHT.STRONG, name: "symfony-cookie", header: /^set-cookie: (sfsession|symfony)/i },
7818
+ // ---- Elixir ---------------------------------------------------------------------
7819
+ { framework: "phoenix", language: "elixir", weight: WEIGHT.DECISIVE, name: "phoenix-liveview", html: /phx-(click|submit|change|value|hook|socket|main|track-static)/ },
7820
+ { framework: "phoenix", language: "elixir", weight: WEIGHT.STRONG, name: "phoenix-cowboy", header: /^server: cowboy/ },
7821
+ // ---- Java / C# / Go -------------------------------------------------------------
7822
+ { framework: "spring", language: "java", weight: WEIGHT.STRONG, name: "thymeleaf-attr", html: /\sth:[a-z-]+=/ },
7823
+ { framework: "spring", language: "java", weight: WEIGHT.STRONG, name: "spring-server", header: /^server: (apache-coyote|tomcat|jetty)/ },
7824
+ { framework: "aspnet", language: "csharp", weight: WEIGHT.DECISIVE, name: "aspnet-verification-token", html: /__RequestVerificationToken|__VIEWSTATE/ },
7825
+ { framework: "aspnet", language: "csharp", weight: WEIGHT.STRONG, name: "aspnet-server", header: /^(server: kestrel|x-powered-by: asp\.net|x-aspnet-version:)/ },
7826
+ // "gin" used to be tested as a substring of the Server header, which matches nginx - the
7827
+ // single most common web server there is. Anchored to the whole value now.
7828
+ { framework: "go", language: "go", weight: WEIGHT.STRONG, name: "go-server", header: /^server: (gin|echo|fiber|gorilla)(\/|$| )/ },
7829
+ // ---- Hypermedia -----------------------------------------------------------------
7830
+ { framework: "htmx", language: "unknown", weight: WEIGHT.STRONG, name: "htmx-attrs", html: /\shx-(get|post|put|delete|trigger|swap|target)=/ },
7831
+ { framework: "htmx", language: "unknown", weight: WEIGHT.DECISIVE, name: "htmx-script", html: /htmx(\.min)?\.js|unpkg\.com\/htmx/ },
7832
+ // ---- Static site generators: they announce themselves -----------------------------
7833
+ { framework: "hugo", language: "go", weight: WEIGHT.DECISIVE, name: "hugo-generator", html: /name=["']?generator["']?[^>]*content=["']?Hugo/i },
7834
+ { framework: "hugo", language: "go", weight: WEIGHT.STRONG, name: "hugo-generator-header", header: /^x-generator: hugo/ },
7835
+ { framework: "jekyll", language: "ruby", weight: WEIGHT.DECISIVE, name: "jekyll-generator", html: /name=["']?generator["']?[^>]*content=["']?Jekyll/i },
7836
+ { framework: "eleventy", language: "javascript", weight: WEIGHT.DECISIVE, name: "eleventy-generator", html: /name=["']?generator["']?[^>]*content=["']?Eleventy/i },
7837
+ { framework: "pelican", language: "python", weight: WEIGHT.DECISIVE, name: "pelican-generator", html: /name=["']?generator["']?[^>]*content=["']?Pelican/i },
7838
+ // Sphinx: what nearly every Python project's documentation is built with, and the shape
7839
+ // flask.palletsprojects.com turned out to be. Structural markers only - not the word
7840
+ // "sphinx", which appears in plenty of prose.
7841
+ // Validated against docusaurus.io, jestjs.io and redux.js.org - all three carry the mount
7842
+ // point and the skip-to-content id. Deliberately not the word "docusaurus", which appears on
7843
+ // deno.com purely as a link in a logo list; that near-miss is why this needed a real marker.
7844
+ { framework: "docusaurus", language: "typescript", weight: WEIGHT.DECISIVE, name: "docusaurus-root", html: /id=["']__docusaurus/ },
7845
+ { framework: "docusaurus", language: "typescript", weight: WEIGHT.STRONG, name: "docusaurus-skip-link", html: /__docusaurus_skipToContent/ },
7846
+ { framework: "sphinx", language: "python", weight: WEIGHT.DECISIVE, name: "sphinx-doc-options", html: /_static\/documentation_options\.js/ },
7847
+ { framework: "sphinx", language: "python", weight: WEIGHT.STRONG, name: "sphinx-theme-markup", html: /class=["'][^"']*sphinxsidebar|sphinx-highlight|_static\/searchtools\.js/ },
7848
+ { framework: "sphinx", language: "python", weight: WEIGHT.STRONG, name: "docutils-generator", html: /name=["']?generator["']?[^>]*content=["']?Docutils/i }
7971
7849
  ];
7850
+ function headerLines(headers) {
7851
+ if (!headers) return "";
7852
+ return Object.entries(headers).map(([k, v]) => `${k.toLowerCase()}: ${String(v).toLowerCase()}`).join("\n");
7853
+ }
7854
+ function detectFromEvidence(input) {
7855
+ const html = input.html ?? "";
7856
+ const headers = headerLines(input.headers);
7857
+ const byFramework = /* @__PURE__ */ new Map();
7858
+ for (const signal of SIGNALS) {
7859
+ const headerHit = signal.header && headers && new RegExp(
7860
+ signal.header.source,
7861
+ signal.header.flags.includes("m") ? signal.header.flags : `${signal.header.flags}m`
7862
+ ).test(headers);
7863
+ const allHit = signal.allHtml && html && signal.allHtml.every((re) => re.test(html));
7864
+ const hit = signal.html && html && signal.html.test(html) || headerHit || allHit;
7865
+ if (!hit) continue;
7866
+ const existing = byFramework.get(signal.framework);
7867
+ if (existing) {
7868
+ existing.score += signal.weight;
7869
+ existing.matched.push(signal.name);
7870
+ } else {
7871
+ byFramework.set(signal.framework, {
7872
+ framework: signal.framework,
7873
+ language: signal.language,
7874
+ score: signal.weight,
7875
+ matched: [signal.name]
7876
+ });
7877
+ }
7878
+ }
7879
+ const ranked = [...byFramework.values()].sort((a, b) => b.score - a.score || a.framework.localeCompare(b.framework));
7880
+ const best = ranked[0];
7881
+ if (!best || best.score < SCORE_FLOOR) {
7882
+ return {
7883
+ framework: "unknown",
7884
+ language: "unknown",
7885
+ confidence: "low",
7886
+ score: best?.score ?? 0,
7887
+ matched: best?.matched ?? [],
7888
+ alternatives: ranked
7889
+ };
7890
+ }
7891
+ return {
7892
+ framework: best.framework,
7893
+ language: best.language,
7894
+ confidence: best.score >= WEIGHT.DECISIVE ? "high" : "medium",
7895
+ score: best.score,
7896
+ matched: best.matched,
7897
+ alternatives: ranked.slice(1)
7898
+ };
7899
+ }
7900
+
7901
+ // src/frameworks/detector.ts
7972
7902
  var PACKAGE_DEPENDENCIES = [
7973
7903
  // Meta-frameworks (check first - more specific)
7974
7904
  { framework: "nextjs", language: "typescript", packages: ["next"], meta_framework: "react" },
@@ -8018,19 +7948,14 @@ var ELIXIR_DEPENDENCIES = [
8018
7948
  { framework: "phoenix", language: "elixir", packages: [":phoenix", "phoenix"] }
8019
7949
  ];
8020
7950
  function detectFromHtml(html) {
8021
- for (const signature of HTML_SIGNATURES) {
8022
- for (const pattern of signature.patterns) {
8023
- if (pattern.test(html)) {
8024
- return {
8025
- framework: signature.framework,
8026
- language: signature.language,
8027
- confidence: signature.confidence,
8028
- detected_from: "html"
8029
- };
8030
- }
8031
- }
8032
- }
8033
- return null;
7951
+ const result = detectFromEvidence({ html });
7952
+ if (result.framework === "unknown") return null;
7953
+ return {
7954
+ framework: result.framework,
7955
+ language: result.language,
7956
+ confidence: result.confidence,
7957
+ detected_from: "html"
7958
+ };
8034
7959
  }
8035
7960
  function detectFromPackageJson(packageJson) {
8036
7961
  const allDeps = {
@@ -8172,52 +8097,14 @@ function detectFromMixExs(mixContent) {
8172
8097
  return null;
8173
8098
  }
8174
8099
  function detectFromHeaders(headers) {
8175
- const poweredBy = headers["x-powered-by"]?.toLowerCase() || "";
8176
- const server = headers["server"]?.toLowerCase() || "";
8177
- const generator = headers["x-generator"]?.toLowerCase() || "";
8178
- if (poweredBy.includes("next.js") || poweredBy.includes("next")) {
8179
- return { framework: "nextjs", language: "typescript", confidence: "high", detected_from: "headers" };
8180
- }
8181
- if (poweredBy.includes("nuxt")) {
8182
- return { framework: "nuxt", language: "typescript", confidence: "high", detected_from: "headers" };
8183
- }
8184
- if (poweredBy.includes("remix")) {
8185
- return { framework: "remix", language: "typescript", confidence: "high", detected_from: "headers" };
8186
- }
8187
- if (poweredBy.includes("phusion") || poweredBy.includes("passenger") || poweredBy.includes("puma") || poweredBy.includes("unicorn")) {
8188
- return { framework: "rails", language: "ruby", confidence: "medium", detected_from: "headers" };
8189
- }
8190
- if (poweredBy.includes("gunicorn") || poweredBy.includes("uvicorn") || poweredBy.includes("daphne")) {
8191
- return null;
8192
- }
8193
- if (poweredBy.includes("php")) {
8194
- return null;
8195
- }
8196
- if (server.includes("tomcat") || server.includes("jetty") || poweredBy.includes("spring")) {
8197
- return { framework: "spring", language: "java", confidence: "medium", detected_from: "headers" };
8198
- }
8199
- if (poweredBy.includes("asp.net") || server.includes("kestrel")) {
8200
- return { framework: "aspnet", language: "csharp", confidence: "high", detected_from: "headers" };
8201
- }
8202
- if (poweredBy.includes("phoenix") || poweredBy.includes("cowboy")) {
8203
- return { framework: "phoenix", language: "elixir", confidence: "medium", detected_from: "headers" };
8204
- }
8205
- if (server.includes("gin") || server.includes("echo") || server.includes("fiber")) {
8206
- return { framework: "go", language: "go", confidence: "medium", detected_from: "headers" };
8207
- }
8208
- if (generator.includes("hugo")) {
8209
- return { framework: "hugo", language: "go", confidence: "high", detected_from: "headers" };
8210
- }
8211
- if (generator.includes("jekyll")) {
8212
- return { framework: "jekyll", language: "ruby", confidence: "high", detected_from: "headers" };
8213
- }
8214
- if (generator.includes("eleventy") || generator.includes("11ty")) {
8215
- return { framework: "eleventy", language: "javascript", confidence: "high", detected_from: "headers" };
8216
- }
8217
- if (generator.includes("pelican")) {
8218
- return { framework: "pelican", language: "python", confidence: "high", detected_from: "headers" };
8219
- }
8220
- return null;
8100
+ const result = detectFromEvidence({ headers });
8101
+ if (result.framework === "unknown") return null;
8102
+ return {
8103
+ framework: result.framework,
8104
+ language: result.language,
8105
+ confidence: result.confidence,
8106
+ detected_from: "headers"
8107
+ };
8221
8108
  }
8222
8109
  function detectFramework(options) {
8223
8110
  if (options.packageJson) {
@@ -8252,13 +8139,16 @@ function detectFramework(options) {
8252
8139
  const result = detectFromMixExs(options.mixExsContent);
8253
8140
  if (result) return result;
8254
8141
  }
8255
- if (options.headers) {
8256
- const result = detectFromHeaders(options.headers);
8257
- if (result) return result;
8258
- }
8259
- if (options.html) {
8260
- const result = detectFromHtml(options.html);
8261
- if (result) return result;
8142
+ if (options.html || options.headers) {
8143
+ const result = detectFromEvidence({ html: options.html, headers: options.headers });
8144
+ if (result.framework !== "unknown") {
8145
+ return {
8146
+ framework: result.framework,
8147
+ language: result.language,
8148
+ confidence: result.confidence,
8149
+ detected_from: options.html ? "html" : "headers"
8150
+ };
8151
+ }
8262
8152
  }
8263
8153
  if (options.files) {
8264
8154
  if (options.files.some((f) => f.includes("next.config"))) {
@@ -8365,6 +8255,8 @@ function getFrameworkDisplayName(framework) {
8365
8255
  jekyll: "Jekyll",
8366
8256
  eleventy: "Eleventy (11ty)",
8367
8257
  pelican: "Pelican",
8258
+ sphinx: "Sphinx",
8259
+ docusaurus: "Docusaurus",
8368
8260
  unknown: "Unknown"
8369
8261
  };
8370
8262
  return names[framework];
@@ -33052,6 +32944,48 @@ function findAppRoot(files) {
33052
32944
  return `${nonRoot[0]}/`;
33053
32945
  }
33054
32946
 
32947
+ // src/fixer/served-dir.ts
32948
+ var SERVED_DIR = {
32949
+ // Bundlers and meta-frameworks that copy a public/ directory verbatim.
32950
+ react: "public/",
32951
+ vue: "public/",
32952
+ svelte: "public/",
32953
+ angular: "public/",
32954
+ solidjs: "public/",
32955
+ qwik: "public/",
32956
+ nextjs: "public/",
32957
+ nuxt: "public/",
32958
+ astro: "public/",
32959
+ sveltekit: "static/",
32960
+ // SvelteKit's is static/, not public/
32961
+ remix: "public/",
32962
+ fresh: "static/",
32963
+ // Gatsby, Hugo, Eleventy and Docusaurus all name theirs static/.
32964
+ gatsby: "static/",
32965
+ hugo: "static/",
32966
+ eleventy: "public/",
32967
+ docusaurus: "static/",
32968
+ // Server frameworks with a public document root.
32969
+ rails: "public/",
32970
+ laravel: "public/",
32971
+ symfony: "public/",
32972
+ phoenix: "priv/static/",
32973
+ spring: "src/main/resources/static/",
32974
+ aspnet: "wwwroot/",
32975
+ // Jekyll and Pelican copy their source tree, so the app root is the served root.
32976
+ jekyll: "",
32977
+ pelican: ""
32978
+ // Python web frameworks have no single convention; the tree decides below.
32979
+ };
32980
+ function servedDir(framework, files, appRoot = "") {
32981
+ const known = framework ? SERVED_DIR[framework] : void 0;
32982
+ if (known !== void 0) return known;
32983
+ for (const candidate of ["public/", "static/", "assets/"]) {
32984
+ if (files.some((f) => f.startsWith(`${appRoot}${candidate}`))) return candidate;
32985
+ }
32986
+ return "";
32987
+ }
32988
+
33055
32989
  // src/fixer/safety.ts
33056
32990
  var HTML_EXT = /\.html?$/i;
33057
32991
  var SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|astro)$/i;
@@ -35740,8 +35674,12 @@ async function analyzeComprehensive(html, url, options = {}) {
35740
35674
  // src/frameworks/index.ts
35741
35675
  var frameworks_exports = {};
35742
35676
  __export(frameworks_exports, {
35677
+ SCORE_FLOOR: () => SCORE_FLOOR,
35678
+ SIGNALS: () => SIGNALS,
35743
35679
  SUPPORTED_FRAMEWORKS: () => SUPPORTED_FRAMEWORKS,
35680
+ WEIGHT: () => WEIGHT,
35744
35681
  detectFramework: () => detectFramework,
35682
+ detectFromEvidence: () => detectFromEvidence,
35745
35683
  detectFromHeaders: () => detectFromHeaders,
35746
35684
  detectFromHtml: () => detectFromHtml,
35747
35685
  detectFromPackageJson: () => detectFromPackageJson,
@@ -36027,6 +35965,7 @@ var SUPPORTED_FRAMEWORKS = [
36027
35965
  sendAlerts,
36028
35966
  sendDiscordAlert,
36029
35967
  sendSlackAlert,
35968
+ servedDir,
36030
35969
  shouldRunAudit,
36031
35970
  shouldSendAlert,
36032
35971
  startWizardSession,