@rankcli/agent-runtime 0.0.30 → 0.0.31

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.mjs CHANGED
@@ -94,6 +94,7 @@ var RECIPES = {
94
94
  jekyll: null,
95
95
  eleventy: null,
96
96
  pelican: null,
97
+ sphinx: null,
97
98
  // Unknown
98
99
  unknown: null
99
100
  };
@@ -4210,234 +4211,156 @@ var ISSUE_DEFINITIONS = {
4210
4211
  }
4211
4212
  };
4212
4213
 
4213
- // src/frameworks/detector.ts
4214
- var HTML_SIGNATURES = [
4215
- // ============ JavaScript/TypeScript Frameworks ============
4216
- // Next.js
4217
- {
4218
- framework: "nextjs",
4219
- language: "typescript",
4220
- patterns: [/__next/, /_next\//, /data-nscript/, /__NEXT_DATA__/, /next\/script/],
4221
- confidence: "high"
4222
- },
4223
- // Nuxt
4224
- {
4225
- framework: "nuxt",
4226
- language: "typescript",
4227
- patterns: [/__nuxt/, /_nuxt\//, /data-n-head/, /nuxt-link/, /__NUXT__/],
4228
- confidence: "high"
4229
- },
4230
- // Gatsby
4231
- {
4232
- framework: "gatsby",
4233
- language: "javascript",
4234
- patterns: [/___gatsby/, /gatsby-/, /gatsby-image/, /data-gatsby/],
4235
- confidence: "high"
4236
- },
4237
- // Astro
4238
- {
4239
- framework: "astro",
4240
- language: "typescript",
4241
- patterns: [/astro-/, /data-astro/, /_astro\//],
4242
- confidence: "high"
4243
- },
4244
- // SvelteKit
4245
- {
4246
- framework: "sveltekit",
4247
- language: "typescript",
4248
- patterns: [/__sveltekit/, /_app\/immutable/, /svelte-kit/],
4249
- confidence: "high"
4250
- },
4251
- // Svelte (standalone)
4252
- {
4253
- framework: "svelte",
4254
- language: "javascript",
4255
- patterns: [/svelte-\w+/, /__svelte/],
4256
- confidence: "medium"
4257
- },
4258
- // Remix
4259
- {
4260
- framework: "remix",
4261
- language: "typescript",
4262
- patterns: [/__remix/, /data-remix/],
4263
- confidence: "high"
4264
- },
4265
- // Solid.js
4266
- {
4267
- framework: "solidjs",
4268
- language: "typescript",
4269
- patterns: [/data-solid/, /_solid/],
4270
- confidence: "high"
4271
- },
4272
- // Qwik
4273
- {
4274
- framework: "qwik",
4275
- language: "typescript",
4276
- patterns: [/q:container/, /q:base/, /q:version/, /q:id=/, /qwik/],
4277
- confidence: "high"
4278
- },
4279
- // Fresh (Deno)
4280
- {
4281
- framework: "fresh",
4282
- language: "typescript",
4283
- patterns: [/_frsh\//, /data-fresh/],
4284
- confidence: "high"
4285
- },
4286
- // React (generic - lower priority)
4214
+ // src/frameworks/signals.ts
4215
+ var WEIGHT = { DECISIVE: 100, STRONG: 45, WEAK: 15 };
4216
+ var SCORE_FLOOR = 45;
4217
+ var SIGNALS = [
4218
+ // ---- Meta-frameworks: JavaScript ------------------------------------------------
4219
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-build-dir", html: /\/_next\/(static|image|data)\// },
4220
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-data", html: /__NEXT_DATA__|self\.__next_f/ },
4221
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-mount", html: /<div id=["']__next["']/ },
4222
+ { framework: "nextjs", language: "typescript", weight: WEIGHT.DECISIVE, name: "next-powered-by", header: /^x-powered-by: next\.js/ },
4223
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-build-dir", html: /\/_nuxt\// },
4224
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-payload", html: /__NUXT__|__NUXT_DATA__|<div id="__nuxt"/ },
4225
+ { framework: "nuxt", language: "typescript", weight: WEIGHT.DECISIVE, name: "nuxt-powered-by", header: /^x-powered-by: nuxt/ },
4226
+ { framework: "astro", language: "typescript", weight: WEIGHT.DECISIVE, name: "astro-build-dir", html: /\/_astro\// },
4227
+ { framework: "astro", language: "typescript", weight: WEIGHT.DECISIVE, name: "astro-island", html: /<astro-island|astro-slot|data-astro-(cid|source-file|transition)/ },
4228
+ { framework: "astro", language: "typescript", weight: WEIGHT.STRONG, name: "astro-generator", html: /name=["']?generator["']?[^>]*content=["']?Astro/i },
4229
+ { framework: "sveltekit", language: "typescript", weight: WEIGHT.DECISIVE, name: "sveltekit-immutable", html: /\/_app\/immutable\// },
4230
+ { framework: "sveltekit", language: "typescript", weight: WEIGHT.DECISIVE, name: "sveltekit-runtime", html: /__sveltekit|kit\.start\(/ },
4231
+ { framework: "remix", language: "typescript", weight: WEIGHT.DECISIVE, name: "remix-context", html: /__remixContext|__remixManifest|__remixRouteModules/ },
4232
+ { framework: "remix", language: "typescript", weight: WEIGHT.STRONG, name: "remix-build-dir", html: /\/build\/_shared\/|\/build\/entry\.client-/ },
4233
+ { framework: "gatsby", language: "javascript", weight: WEIGHT.DECISIVE, name: "gatsby-runtime", html: /___gatsby|window\.___chunkMapping/ },
4234
+ { framework: "gatsby", language: "javascript", weight: WEIGHT.STRONG, name: "gatsby-page-data", html: /\/page-data\/(app-data\.json|sq\/)/ },
4235
+ { framework: "qwik", language: "typescript", weight: WEIGHT.DECISIVE, name: "qwik-container", html: /q:container|q:base|q:version|qwikloader/ },
4236
+ { framework: "fresh", language: "typescript", weight: WEIGHT.DECISIVE, name: "fresh-build-dir", html: /\/_frsh\// },
4237
+ { framework: "solidjs", language: "typescript", weight: WEIGHT.STRONG, name: "solid-hydration", html: /_\$HY\b|data-hk=["']/ },
4238
+ // ---- Base UI libraries: weaker by nature, and that is the point -----------------
4239
+ { framework: "angular", language: "typescript", weight: WEIGHT.DECISIVE, name: "angular-version", html: /ng-version=["']/ },
4240
+ { framework: "angular", language: "typescript", weight: WEIGHT.DECISIVE, name: "angular-content-attr", html: /_ngcontent-|_nghost-/ },
4241
+ { framework: "react", language: "javascript", weight: WEIGHT.STRONG, name: "react-root-marker", html: /data-reactroot|__reactContainer\$|__REACT_DEVTOOLS/ },
4242
+ // A Vite-built SPA: an empty mount point plus Vite's hashed entry bundle. Two weak facts
4243
+ // that together mean something; either one alone does not.
4244
+ { framework: "react", language: "javascript", weight: WEIGHT.WEAK, name: "react-empty-root", html: /<div id="root"><\/div>/ },
4245
+ { framework: "react", language: "javascript", weight: WEIGHT.WEAK, name: "react-refresh", html: /RefreshRuntime|react-refresh/ },
4287
4246
  {
4288
4247
  framework: "react",
4289
4248
  language: "javascript",
4290
- // data-reactroot/data-react-helmet are React <=15 hydration markers -
4291
- // React 18's createRoot() never adds them, so every modern CRA/Vite
4292
- // React app (an empty <div id="root">) went undetected from HTML alone.
4293
- patterns: [/data-reactroot/, /data-react-helmet/, /_react/, /react-app/, /<div id="root"><\/div>/],
4294
- confidence: "medium"
4295
- },
4296
- // Vue (generic - lower priority)
4297
- {
4298
- framework: "vue",
4299
- language: "javascript",
4300
- patterns: [/data-v-[a-f0-9]+/, /v-cloak/, /__vue/],
4301
- confidence: "medium"
4302
- },
4303
- // Angular
4304
- {
4305
- framework: "angular",
4306
- language: "typescript",
4307
- patterns: [/ng-version/, /_ngcontent/, /ng-\w+=/, /\[ng/],
4308
- confidence: "high"
4309
- },
4310
- // ============ Hypermedia Frameworks ============
4311
- // HTMX
4312
- {
4313
- framework: "htmx",
4314
- language: "unknown",
4315
- patterns: [/hx-get/, /hx-post/, /hx-trigger/, /hx-swap/, /htmx\.org/],
4316
- confidence: "high"
4317
- },
4318
- // Hotwire/Turbo
4319
- {
4320
- framework: "hotwire",
4321
- language: "ruby",
4322
- patterns: [/turbo-frame/, /turbo-stream/, /data-turbo/],
4323
- confidence: "high"
4324
- },
4325
- // ============ PHP Frameworks ============
4326
- // WordPress
4327
- {
4328
- framework: "wordpress",
4329
- language: "php",
4330
- // Technical signatures only. These patterns run over the whole page, so the old
4331
- // /wordpress/i matched the word anywhere in the copy: rankcli.dev's own "detects
4332
- // Rails, Django, Laravel, and WordPress" line was enough to detect its React SPA as
4333
- // WordPress and then tell it to install an SEO plugin. A generator meta tag is the
4334
- // one place the page says it about itself.
4335
- patterns: [/wp-content/, /wp-includes/, /wp-json/, /name=["']generator["'][^>]*content=["']WordPress/i],
4336
- confidence: "high"
4337
- },
4338
- // Laravel
4339
- {
4340
- framework: "laravel",
4341
- language: "php",
4342
- // /laravel/i had the same problem as WordPress above - it matched the word in prose.
4343
- patterns: [/laravel_session/, /XSRF-TOKEN/, /@csrf/],
4344
- confidence: "medium"
4345
- },
4346
- // Symfony
4347
- {
4348
- framework: "symfony",
4349
- language: "php",
4350
- // Likewise /symfony/, which matched any lowercase mention.
4351
- patterns: [/sf2/, /_sf_/, /profiler\.css/],
4352
- confidence: "medium"
4353
- },
4354
- // ============ Ruby Frameworks ============
4355
- // Rails
4356
- {
4357
- framework: "rails",
4358
- language: "ruby",
4359
- patterns: [/csrf-token/, /turbolinks/, /data-turbo/, /rails-ujs/, /action-cable/],
4360
- confidence: "medium"
4361
- },
4362
- // ============ Python Frameworks ============
4363
- // Django
4364
- {
4365
- framework: "django",
4366
- language: "python",
4367
- patterns: [/csrfmiddlewaretoken/, /django/, /__debug__\//],
4368
- confidence: "medium"
4369
- },
4370
- // Flask
4371
- {
4372
- framework: "flask",
4373
- language: "python",
4374
- patterns: [/flask/, /werkzeug/i],
4375
- confidence: "low"
4376
- },
4377
- // FastAPI
4378
- {
4379
- framework: "fastapi",
4380
- language: "python",
4381
- patterns: [/fastapi/, /openapi\.json/],
4382
- confidence: "low"
4383
- },
4384
- // ============ Java Frameworks ============
4385
- // Spring Boot
4386
- {
4387
- framework: "spring",
4388
- language: "java",
4389
- // /th:/ matched "width:" and "height:" in inline CSS, which made almost every styled
4390
- // page on the web a Spring Boot app - rankcli.dev included, once WordPress stopped
4391
- // matching first. Thymeleaf attributes are what th: actually means.
4392
- patterns: [/\sth:[a-z-]+=/, /thymeleaf/, /_csrf/],
4393
- confidence: "medium"
4394
- },
4395
- // ============ C# Frameworks ============
4396
- // ASP.NET Core
4397
- {
4398
- framework: "aspnet",
4399
- language: "csharp",
4400
- patterns: [/aspnetcore/, /__RequestVerificationToken/, /_framework\/blazor/],
4401
- confidence: "medium"
4402
- },
4403
- // ============ Elixir Frameworks ============
4404
- // Phoenix
4405
- {
4406
- framework: "phoenix",
4407
- language: "elixir",
4408
- patterns: [/phx-/, /live-socket/, /data-phx/],
4409
- confidence: "high"
4410
- },
4411
- // ============ Static Site Generators ============
4412
- // Hugo
4413
- {
4414
- framework: "hugo",
4415
- language: "go",
4416
- patterns: [/name=["']generator["'][^>]*content=["']Hugo/i],
4417
- confidence: "medium"
4249
+ weight: WEIGHT.STRONG,
4250
+ name: "react-vite-spa",
4251
+ // Vite's entry chunk plus React's conventional mount point. Medium confidence by
4252
+ // construction: #app instead of #root stays unknown, because Vue, Solid and others use it.
4253
+ // Not `<div id="root"></div>`: a prerendered SPA has content inside its mount point, which
4254
+ // is what RankCLI's own site does and what the first version of this signal missed.
4255
+ allHtml: [/<script[^>]+src=["'][^"']+\.js["']/, /<div id="root"[\s>]/]
4418
4256
  },
4419
- // Jekyll
4420
- {
4421
- framework: "jekyll",
4422
- language: "ruby",
4423
- patterns: [/name=["']generator["'][^>]*content=["']Jekyll/i],
4424
- confidence: "medium"
4425
- },
4426
- // Eleventy
4427
- {
4428
- framework: "eleventy",
4429
- language: "javascript",
4430
- patterns: [/11ty/, /eleventy/, /generator.*eleventy/i],
4431
- confidence: "medium"
4432
- },
4433
- // Pelican
4434
- {
4435
- framework: "pelican",
4436
- language: "python",
4437
- patterns: [/pelican/, /generator.*pelican/i],
4438
- confidence: "medium"
4439
- }
4257
+ { framework: "vue", language: "javascript", weight: WEIGHT.DECISIVE, name: "vitepress-runtime", html: /__VP_HASH_MAP__|__VP_SITE_DATA__/ },
4258
+ { framework: "vue", language: "javascript", weight: WEIGHT.DECISIVE, name: "vue-scoped-style", html: /data-v-[0-9a-f]{6,10}\b/ },
4259
+ { framework: "vue", language: "javascript", weight: WEIGHT.WEAK, name: "vue-cloak", html: /v-cloak|<div id="app"><\/div>/ },
4260
+ { framework: "svelte", language: "javascript", weight: WEIGHT.WEAK, name: "svelte-class", html: /class=["'][^"']*svelte-[0-9a-z]{6}/ },
4261
+ // ---- Ruby -----------------------------------------------------------------------
4262
+ // csrf-token alone is emitted by Rails, Laravel, Phoenix and Django. csrf-param is Rails.
4263
+ { framework: "rails", language: "ruby", weight: WEIGHT.DECISIVE, name: "rails-csrf-param", html: /name=["']csrf-param["']/ },
4264
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-ujs", html: /rails-ujs|turbolinks|data-remote=["']true["']|action_cable|actioncable/ },
4265
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-session-cookie", header: /^set-cookie: [^;]*_session=/ },
4266
+ { framework: "rails", language: "ruby", weight: WEIGHT.STRONG, name: "rails-app-server", header: /^(server|x-powered-by): (puma|passenger|unicorn|phusion)/ },
4267
+ { framework: "hotwire", language: "ruby", weight: WEIGHT.STRONG, name: "turbo-elements", html: /<turbo-frame|<turbo-stream|data-turbo-track/ },
4268
+ // ---- Python ---------------------------------------------------------------------
4269
+ { framework: "django", language: "python", weight: WEIGHT.DECISIVE, name: "django-csrf-input", html: /name=["']csrfmiddlewaretoken["']/ },
4270
+ { framework: "django", language: "python", weight: WEIGHT.DECISIVE, name: "django-csrf-cookie", header: /^set-cookie: csrftoken=/ },
4271
+ { framework: "django", language: "python", weight: WEIGHT.STRONG, name: "django-admin-static", html: /\/static\/admin\/(css|js)\// },
4272
+ { framework: "django", language: "python", weight: WEIGHT.WEAK, name: "django-wsgi-server", header: /^(server|x-powered-by): (gunicorn|uvicorn|daphne)/ },
4273
+ { framework: "flask", language: "python", weight: WEIGHT.STRONG, name: "flask-server", header: /^server: werkzeug/ },
4274
+ { framework: "fastapi", language: "python", weight: WEIGHT.STRONG, name: "fastapi-docs", html: /swagger-ui-bundle\.js[\s\S]{0,200}openapi\.json/ },
4275
+ // ---- PHP ------------------------------------------------------------------------
4276
+ { framework: "wordpress", language: "php", weight: WEIGHT.DECISIVE, name: "wp-paths", html: /\/wp-content\/|\/wp-includes\/|\/wp-json\// },
4277
+ { framework: "wordpress", language: "php", weight: WEIGHT.DECISIVE, name: "wp-generator", html: /name=["']?generator["']?[^>]*content=["']?WordPress/i },
4278
+ { framework: "wordpress", language: "php", weight: WEIGHT.STRONG, name: "wp-powered-by", header: /^x-powered-by: wordpress/ },
4279
+ { framework: "laravel", language: "php", weight: WEIGHT.DECISIVE, name: "laravel-session-cookie", header: /^set-cookie: laravel_session=/ },
4280
+ { framework: "laravel", language: "php", weight: WEIGHT.STRONG, name: "laravel-xsrf-cookie", header: /^set-cookie: XSRF-TOKEN=/i },
4281
+ { framework: "laravel", language: "php", weight: WEIGHT.STRONG, name: "laravel-livewire", html: /livewire|wire:(click|model|submit)/i },
4282
+ { framework: "laravel", language: "php", weight: WEIGHT.WEAK, name: "laravel-vite-build", html: /\/build\/assets\/app-[A-Za-z0-9_-]{8}\.js/ },
4283
+ { framework: "symfony", language: "php", weight: WEIGHT.DECISIVE, name: "symfony-profiler", html: /\/_profiler\/|sf-toolbar|sf-dump/ },
4284
+ { framework: "symfony", language: "php", weight: WEIGHT.STRONG, name: "symfony-cookie", header: /^set-cookie: (sfsession|symfony)/i },
4285
+ // ---- Elixir ---------------------------------------------------------------------
4286
+ { framework: "phoenix", language: "elixir", weight: WEIGHT.DECISIVE, name: "phoenix-liveview", html: /phx-(click|submit|change|value|hook|socket|main|track-static)/ },
4287
+ { framework: "phoenix", language: "elixir", weight: WEIGHT.STRONG, name: "phoenix-cowboy", header: /^server: cowboy/ },
4288
+ // ---- Java / C# / Go -------------------------------------------------------------
4289
+ { framework: "spring", language: "java", weight: WEIGHT.STRONG, name: "thymeleaf-attr", html: /\sth:[a-z-]+=/ },
4290
+ { framework: "spring", language: "java", weight: WEIGHT.STRONG, name: "spring-server", header: /^server: (apache-coyote|tomcat|jetty)/ },
4291
+ { framework: "aspnet", language: "csharp", weight: WEIGHT.DECISIVE, name: "aspnet-verification-token", html: /__RequestVerificationToken|__VIEWSTATE/ },
4292
+ { framework: "aspnet", language: "csharp", weight: WEIGHT.STRONG, name: "aspnet-server", header: /^(server: kestrel|x-powered-by: asp\.net|x-aspnet-version:)/ },
4293
+ // "gin" used to be tested as a substring of the Server header, which matches nginx - the
4294
+ // single most common web server there is. Anchored to the whole value now.
4295
+ { framework: "go", language: "go", weight: WEIGHT.STRONG, name: "go-server", header: /^server: (gin|echo|fiber|gorilla)(\/|$| )/ },
4296
+ // ---- Hypermedia -----------------------------------------------------------------
4297
+ { framework: "htmx", language: "unknown", weight: WEIGHT.STRONG, name: "htmx-attrs", html: /\shx-(get|post|put|delete|trigger|swap|target)=/ },
4298
+ { framework: "htmx", language: "unknown", weight: WEIGHT.DECISIVE, name: "htmx-script", html: /htmx(\.min)?\.js|unpkg\.com\/htmx/ },
4299
+ // ---- Static site generators: they announce themselves -----------------------------
4300
+ { framework: "hugo", language: "go", weight: WEIGHT.DECISIVE, name: "hugo-generator", html: /name=["']?generator["']?[^>]*content=["']?Hugo/i },
4301
+ { framework: "hugo", language: "go", weight: WEIGHT.STRONG, name: "hugo-generator-header", header: /^x-generator: hugo/ },
4302
+ { framework: "jekyll", language: "ruby", weight: WEIGHT.DECISIVE, name: "jekyll-generator", html: /name=["']?generator["']?[^>]*content=["']?Jekyll/i },
4303
+ { framework: "eleventy", language: "javascript", weight: WEIGHT.DECISIVE, name: "eleventy-generator", html: /name=["']?generator["']?[^>]*content=["']?Eleventy/i },
4304
+ { framework: "pelican", language: "python", weight: WEIGHT.DECISIVE, name: "pelican-generator", html: /name=["']?generator["']?[^>]*content=["']?Pelican/i },
4305
+ // Sphinx: what nearly every Python project's documentation is built with, and the shape
4306
+ // flask.palletsprojects.com turned out to be. Structural markers only - not the word
4307
+ // "sphinx", which appears in plenty of prose.
4308
+ { framework: "sphinx", language: "python", weight: WEIGHT.DECISIVE, name: "sphinx-doc-options", html: /_static\/documentation_options\.js/ },
4309
+ { framework: "sphinx", language: "python", weight: WEIGHT.STRONG, name: "sphinx-theme-markup", html: /class=["'][^"']*sphinxsidebar|sphinx-highlight|_static\/searchtools\.js/ },
4310
+ { framework: "sphinx", language: "python", weight: WEIGHT.STRONG, name: "docutils-generator", html: /name=["']?generator["']?[^>]*content=["']?Docutils/i }
4440
4311
  ];
4312
+ function headerLines(headers) {
4313
+ if (!headers) return "";
4314
+ return Object.entries(headers).map(([k, v]) => `${k.toLowerCase()}: ${String(v).toLowerCase()}`).join("\n");
4315
+ }
4316
+ function detectFromEvidence(input) {
4317
+ const html = input.html ?? "";
4318
+ const headers = headerLines(input.headers);
4319
+ const byFramework = /* @__PURE__ */ new Map();
4320
+ for (const signal of SIGNALS) {
4321
+ const headerHit = signal.header && headers && new RegExp(
4322
+ signal.header.source,
4323
+ signal.header.flags.includes("m") ? signal.header.flags : `${signal.header.flags}m`
4324
+ ).test(headers);
4325
+ const allHit = signal.allHtml && html && signal.allHtml.every((re) => re.test(html));
4326
+ const hit = signal.html && html && signal.html.test(html) || headerHit || allHit;
4327
+ if (!hit) continue;
4328
+ const existing = byFramework.get(signal.framework);
4329
+ if (existing) {
4330
+ existing.score += signal.weight;
4331
+ existing.matched.push(signal.name);
4332
+ } else {
4333
+ byFramework.set(signal.framework, {
4334
+ framework: signal.framework,
4335
+ language: signal.language,
4336
+ score: signal.weight,
4337
+ matched: [signal.name]
4338
+ });
4339
+ }
4340
+ }
4341
+ const ranked = [...byFramework.values()].sort((a, b) => b.score - a.score || a.framework.localeCompare(b.framework));
4342
+ const best = ranked[0];
4343
+ if (!best || best.score < SCORE_FLOOR) {
4344
+ return {
4345
+ framework: "unknown",
4346
+ language: "unknown",
4347
+ confidence: "low",
4348
+ score: best?.score ?? 0,
4349
+ matched: best?.matched ?? [],
4350
+ alternatives: ranked
4351
+ };
4352
+ }
4353
+ return {
4354
+ framework: best.framework,
4355
+ language: best.language,
4356
+ confidence: best.score >= WEIGHT.DECISIVE ? "high" : "medium",
4357
+ score: best.score,
4358
+ matched: best.matched,
4359
+ alternatives: ranked.slice(1)
4360
+ };
4361
+ }
4362
+
4363
+ // src/frameworks/detector.ts
4441
4364
  var PACKAGE_DEPENDENCIES = [
4442
4365
  // Meta-frameworks (check first - more specific)
4443
4366
  { framework: "nextjs", language: "typescript", packages: ["next"], meta_framework: "react" },
@@ -4487,19 +4410,14 @@ var ELIXIR_DEPENDENCIES = [
4487
4410
  { framework: "phoenix", language: "elixir", packages: [":phoenix", "phoenix"] }
4488
4411
  ];
4489
4412
  function detectFromHtml(html) {
4490
- for (const signature of HTML_SIGNATURES) {
4491
- for (const pattern of signature.patterns) {
4492
- if (pattern.test(html)) {
4493
- return {
4494
- framework: signature.framework,
4495
- language: signature.language,
4496
- confidence: signature.confidence,
4497
- detected_from: "html"
4498
- };
4499
- }
4500
- }
4501
- }
4502
- return null;
4413
+ const result = detectFromEvidence({ html });
4414
+ if (result.framework === "unknown") return null;
4415
+ return {
4416
+ framework: result.framework,
4417
+ language: result.language,
4418
+ confidence: result.confidence,
4419
+ detected_from: "html"
4420
+ };
4503
4421
  }
4504
4422
  function detectFromPackageJson(packageJson) {
4505
4423
  const allDeps = {
@@ -4641,52 +4559,14 @@ function detectFromMixExs(mixContent) {
4641
4559
  return null;
4642
4560
  }
4643
4561
  function detectFromHeaders(headers) {
4644
- const poweredBy = headers["x-powered-by"]?.toLowerCase() || "";
4645
- const server = headers["server"]?.toLowerCase() || "";
4646
- const generator = headers["x-generator"]?.toLowerCase() || "";
4647
- if (poweredBy.includes("next.js") || poweredBy.includes("next")) {
4648
- return { framework: "nextjs", language: "typescript", confidence: "high", detected_from: "headers" };
4649
- }
4650
- if (poweredBy.includes("nuxt")) {
4651
- return { framework: "nuxt", language: "typescript", confidence: "high", detected_from: "headers" };
4652
- }
4653
- if (poweredBy.includes("remix")) {
4654
- return { framework: "remix", language: "typescript", confidence: "high", detected_from: "headers" };
4655
- }
4656
- if (poweredBy.includes("phusion") || poweredBy.includes("passenger") || poweredBy.includes("puma") || poweredBy.includes("unicorn")) {
4657
- return { framework: "rails", language: "ruby", confidence: "medium", detected_from: "headers" };
4658
- }
4659
- if (poweredBy.includes("gunicorn") || poweredBy.includes("uvicorn") || poweredBy.includes("daphne")) {
4660
- return null;
4661
- }
4662
- if (poweredBy.includes("php")) {
4663
- return null;
4664
- }
4665
- if (server.includes("tomcat") || server.includes("jetty") || poweredBy.includes("spring")) {
4666
- return { framework: "spring", language: "java", confidence: "medium", detected_from: "headers" };
4667
- }
4668
- if (poweredBy.includes("asp.net") || server.includes("kestrel")) {
4669
- return { framework: "aspnet", language: "csharp", confidence: "high", detected_from: "headers" };
4670
- }
4671
- if (poweredBy.includes("phoenix") || poweredBy.includes("cowboy")) {
4672
- return { framework: "phoenix", language: "elixir", confidence: "medium", detected_from: "headers" };
4673
- }
4674
- if (server.includes("gin") || server.includes("echo") || server.includes("fiber")) {
4675
- return { framework: "go", language: "go", confidence: "medium", detected_from: "headers" };
4676
- }
4677
- if (generator.includes("hugo")) {
4678
- return { framework: "hugo", language: "go", confidence: "high", detected_from: "headers" };
4679
- }
4680
- if (generator.includes("jekyll")) {
4681
- return { framework: "jekyll", language: "ruby", confidence: "high", detected_from: "headers" };
4682
- }
4683
- if (generator.includes("eleventy") || generator.includes("11ty")) {
4684
- return { framework: "eleventy", language: "javascript", confidence: "high", detected_from: "headers" };
4685
- }
4686
- if (generator.includes("pelican")) {
4687
- return { framework: "pelican", language: "python", confidence: "high", detected_from: "headers" };
4688
- }
4689
- return null;
4562
+ const result = detectFromEvidence({ headers });
4563
+ if (result.framework === "unknown") return null;
4564
+ return {
4565
+ framework: result.framework,
4566
+ language: result.language,
4567
+ confidence: result.confidence,
4568
+ detected_from: "headers"
4569
+ };
4690
4570
  }
4691
4571
  function detectFramework(options) {
4692
4572
  if (options.packageJson) {
@@ -4721,13 +4601,16 @@ function detectFramework(options) {
4721
4601
  const result = detectFromMixExs(options.mixExsContent);
4722
4602
  if (result) return result;
4723
4603
  }
4724
- if (options.headers) {
4725
- const result = detectFromHeaders(options.headers);
4726
- if (result) return result;
4727
- }
4728
- if (options.html) {
4729
- const result = detectFromHtml(options.html);
4730
- if (result) return result;
4604
+ if (options.html || options.headers) {
4605
+ const result = detectFromEvidence({ html: options.html, headers: options.headers });
4606
+ if (result.framework !== "unknown") {
4607
+ return {
4608
+ framework: result.framework,
4609
+ language: result.language,
4610
+ confidence: result.confidence,
4611
+ detected_from: options.html ? "html" : "headers"
4612
+ };
4613
+ }
4731
4614
  }
4732
4615
  if (options.files) {
4733
4616
  if (options.files.some((f) => f.includes("next.config"))) {
@@ -4834,6 +4717,7 @@ function getFrameworkDisplayName(framework) {
4834
4717
  jekyll: "Jekyll",
4835
4718
  eleventy: "Eleventy (11ty)",
4836
4719
  pelican: "Pelican",
4720
+ sphinx: "Sphinx",
4837
4721
  unknown: "Unknown"
4838
4722
  };
4839
4723
  return names[framework];
@@ -32162,8 +32046,12 @@ async function analyzeComprehensive(html, url, options = {}) {
32162
32046
  // src/frameworks/index.ts
32163
32047
  var frameworks_exports = {};
32164
32048
  __export(frameworks_exports, {
32049
+ SCORE_FLOOR: () => SCORE_FLOOR,
32050
+ SIGNALS: () => SIGNALS,
32165
32051
  SUPPORTED_FRAMEWORKS: () => SUPPORTED_FRAMEWORKS,
32052
+ WEIGHT: () => WEIGHT,
32166
32053
  detectFramework: () => detectFramework,
32054
+ detectFromEvidence: () => detectFromEvidence,
32167
32055
  detectFromHeaders: () => detectFromHeaders,
32168
32056
  detectFromHtml: () => detectFromHtml,
32169
32057
  detectFromPackageJson: () => detectFromPackageJson,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/agent-runtime",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "RankCLI agent runtime - executes SEO audits and fixes with AI",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,8 @@
27
27
  "build:wasm": "tsup --config tsup.wasm.config.ts",
28
28
  "test": "vitest run",
29
29
  "test:watch": "vitest",
30
- "test:coverage": "vitest run --coverage"
30
+ "test:coverage": "vitest run --coverage",
31
+ "benchmark:detection": "node scripts/benchmark-detection.mjs"
31
32
  },
32
33
  "dependencies": {
33
34
  "@anthropic-ai/sdk": "^0.52.0",