seo-gravity-mcp 1.0.2 → 1.1.0

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.
Files changed (49) hide show
  1. package/README.md +72 -105
  2. package/dist/adapters/adapterRegistry.d.ts +7 -0
  3. package/dist/adapters/adapterRegistry.js +30 -0
  4. package/dist/adapters/astroAdapter.d.ts +17 -0
  5. package/dist/adapters/astroAdapter.js +191 -0
  6. package/dist/adapters/nextAppAdapter.d.ts +17 -0
  7. package/dist/adapters/nextAppAdapter.js +265 -0
  8. package/dist/adapters/nextPagesAdapter.d.ts +17 -0
  9. package/dist/adapters/nextPagesAdapter.js +190 -0
  10. package/dist/adapters/remixAdapter.d.ts +16 -0
  11. package/dist/adapters/remixAdapter.js +118 -0
  12. package/dist/adapters/staticAdapter.d.ts +16 -0
  13. package/dist/adapters/staticAdapter.js +117 -0
  14. package/dist/adapters/svelteKitAdapter.d.ts +16 -0
  15. package/dist/adapters/svelteKitAdapter.js +122 -0
  16. package/dist/adapters/types.d.ts +33 -0
  17. package/dist/adapters/types.js +1 -0
  18. package/dist/adapters/viteReactAdapter.d.ts +16 -0
  19. package/dist/adapters/viteReactAdapter.js +172 -0
  20. package/dist/cli.d.ts +2 -0
  21. package/dist/cli.js +97 -0
  22. package/dist/providers/providerRegistry.d.ts +65 -0
  23. package/dist/providers/providerRegistry.js +64 -0
  24. package/dist/providers/types.d.ts +22 -0
  25. package/dist/providers/types.js +1 -0
  26. package/dist/test.js +103 -132
  27. package/dist/tools/orchestration.js +23 -21
  28. package/dist/types/canonical.d.ts +64 -0
  29. package/dist/types/canonical.js +1 -0
  30. package/dist/types/findings.d.ts +22 -0
  31. package/dist/types/findings.js +1 -1
  32. package/dist/types/seo.d.ts +24 -0
  33. package/dist/utils/astLocator.d.ts +14 -0
  34. package/dist/utils/astLocator.js +137 -0
  35. package/dist/utils/cacheManager.d.ts +15 -0
  36. package/dist/utils/cacheManager.js +37 -0
  37. package/dist/utils/experimentEngine.d.ts +26 -0
  38. package/dist/utils/experimentEngine.js +41 -0
  39. package/dist/utils/findingEngine.d.ts +2 -1
  40. package/dist/utils/findingEngine.js +1 -0
  41. package/dist/utils/gitDiffEngine.d.ts +19 -0
  42. package/dist/utils/gitDiffEngine.js +72 -0
  43. package/dist/utils/opportunityEngine.d.ts +19 -0
  44. package/dist/utils/opportunityEngine.js +61 -0
  45. package/dist/utils/projectScanner.d.ts +4 -2
  46. package/dist/utils/projectScanner.js +14 -346
  47. package/dist/utils/snapshotEngine.d.ts +3 -1
  48. package/dist/utils/snapshotEngine.js +213 -93
  49. package/package.json +6 -4
package/README.md CHANGED
@@ -1,18 +1,17 @@
1
- # 🚀 SEO Gravity MCP (v1.0.2)
1
+ # 🚀 SEO Gravity (v1.1.0)
2
2
 
3
3
  <div align="center">
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/seo-gravity-mcp?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/seo-gravity-mcp)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/seo-gravity-mcp?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/seo-gravity-mcp)
7
7
  ![MCP Protocol](https://img.shields.io/badge/MCP-Protocol-blue?style=for-the-badge&logo=anthropic)
8
+ ![CLI Ready](https://img.shields.io/badge/CLI-CI%2FCD_Ready-success?style=for-the-badge)
8
9
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-3178C6?style=for-the-badge&logo=typescript&logoColor=white)
9
- ![Node.js](https://img.shields.io/badge/Node.js-18+-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)
10
- ![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)
11
10
  ![Zero API Cost](https://img.shields.io/badge/Zero_API_Cost-100%25_Free-orange?style=for-the-badge)
12
11
 
13
- **The Universal SEO Intelligence, Source-Code Correlation & Remediation Engine for AI Coding Agents.**
12
+ **The Enterprise SEO Engineering Layer for AI Coding Agents & CI/CD Pipelines.**
14
13
 
15
- [⚡ Instant Start](#-instant-start) • [🤖 Client Setup](#-ai-ide--mcp-client-setup) • [🧠 5-Stage Autonomous Loop](#-the-5-stage-autonomous-remediation-loop) • [🛠 Tool Catalog (35 Tools)](#-complete-tool-catalog-35-tools) • [📄 License](#-license)
14
+ [⚡ Instant Start](#-instant-start) • [💻 CLI & CI/CD](#-command-line--cicd-runner) • [🏛 5-Layer Architecture](#-5-layer-system-architecture) • [🧩 Framework Adapters](#-modular-framework-adapters) • [🛠 Tool Catalog (35 Tools)](#-complete-tool-catalog) • [📄 License](#-license)
16
15
 
17
16
  </div>
18
17
 
@@ -20,67 +19,87 @@
20
19
 
21
20
  ## 🌟 What is SEO Gravity?
22
21
 
23
- **SEO Gravity** gives AI coding agents (Antigravity, Cursor, Claude Code, Windsurf, Cline, Roo Code, etc.) the autonomous ability to **understand, audit, fix, and continuously verify the SEO of the websites they build.**
22
+ **SEO Gravity** is an **SEO engineering infrastructure layer** designed specifically for AI coding agents (Antigravity, Cursor, Claude Code, Windsurf, Cline, Roo Code, etc.) and automated CI/CD pipelines.
24
23
 
25
- Instead of acting as a passive "SEO metrics dump", SEO Gravity connects **rendered web signals** directly to your **source code components** (Next.js App Router, Astro, Vite/React, Remix, SvelteKit), ranks issues by impact-to-effort priority, generates code fix blueprints, and verifies that commits introduce **zero regressions**.
24
+ Instead of operating as a passive SEO reporting tool, SEO Gravity connects **rendered web observations** directly to **source code AST nodes and line numbers** across modern web frameworks (Next.js App/Pages Router, Astro, Vite/React, Remix, SvelteKit), generates actionable code fix blueprints, and enforces invariant-based regression checks.
26
25
 
27
26
  ```text
28
27
  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
29
28
  │ 1. DISCOVER │ ──▶ │ 2. OBSERVE │ ──▶ │ 3. DIAGNOSE │ ──▶ │ 4. FIX │ ──▶ │ 5. VERIFY │
30
- │ Framework │ │ Live/Dev SSR │ │ Root Causes │ │ AI Modifies │ │ Re-crawl &
31
- Routes │ │ SERP & Comp. │ │ Prioritized │ │ Source Code │ │ Regression │
32
- │ Source Map │ │ Crawl Graph │ │ Action Plan │ │ Components │ │ Diff Check
29
+ │ Framework │ │ Live/Dev SSR │ │ AST Line Nos │ │ AI Modifies │ │ Invariant
30
+ Adapters │ │ Crawl Graph │ │ Prioritized │ │ Components │ │ Regression │
31
+ │ Source Map │ │ Provenance │ │ Action Plan │ │ Snippets │ │ Git Stamping
33
32
  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
34
33
  ```
35
34
 
36
35
  ---
37
36
 
38
- ## 💡 Key Architectural Pillars
37
+ ## 🏛 5-Layer System Architecture
39
38
 
40
- 1. **Source-to-URL Correlation**: Directly maps audited URLs (`/blog/my-post`) to source files (`app/blog/[slug]/page.tsx`), identifying missing metadata exports or canonical tags.
41
- 2. **Canonical Finding Model**: Every audit issue produces a normalized finding with `id`, `severity`, `confidence` ($0.0-1.0$), `evidenceType` (*Observed*, *Derived*, *Heuristic*, *Predictive*), `effort`, `priorityScore`, and verification steps.
42
- 3. **Crawl Graph & Site Architecture**: Computes BFS click depths, orphan pages, hub pages, circular link loops, and internal link equity (PageRank heuristic).
43
- 4. **Multidimensional Health Scoring**: Breaks scores into 7 discrete dimensions (Technical, Content, Discoverability, Authority, Entity, Performance, AI Readiness) with confidence levels.
44
- 5. **SEO Regression & Snapshot System**: Creates snapshots (`seo.gravity/v1`) before commits/PRs and validates that changes introduced 0 regressions.
45
- 6. **Zero Paid API Requirement**: 100% functional out-of-the-box using local AST parsing, cheerio, JSDOM, Google Autocomplete, and standard web extraction.
39
+ ```text
40
+ ┌─────────────────────────────────────────────────────────────┐
41
+ │ 1. AGENT & CLI INTERFACE │
42
+ │ MCP Server (35 tools) + Standalone CLI (`seo-gravity`)
43
+ ├─────────────────────────────────────────────────────────────┤
44
+ │ 2. INTELLIGENCE & REASONING LAYER │
45
+ │ Root-cause diagnosis, deterministic priority, fix plans │
46
+ ├─────────────────────────────────────────────────────────────┤
47
+ │ 3. ANALYSIS & INVARIANT LAYER │
48
+ │ SEO Invariants (HTTP 200, canonical, schema, noindex) │
49
+ ├─────────────────────────────────────────────────────────────┤
50
+ │ 4. OBSERVATION LAYER (Factual Raw State) │
51
+ │ Observations ≠ Findings; AST inspection, headers, DOM │
52
+ ├─────────────────────────────────────────────────────────────┤
53
+ │ 5. PROVIDER & ADAPTER LAYER │
54
+ │ Framework Adapters (Next, Astro, Vite), Providers, Cache │
55
+ └─────────────────────────────────────────────────────────────┘
56
+ ```
46
57
 
47
58
  ---
48
59
 
49
- ## Instant Start
60
+ ## 🧩 Modular Framework Adapters
50
61
 
51
- Run instantly with `npx`:
62
+ | Framework Adapter | Detection | Route Discovery | AST Inspection |
63
+ | :--- | :--- | :--- | :--- |
64
+ | **`NextAppAdapter`** | `next` + `app/` | App Router hierarchy, route groups `(group)`, dynamic `[slug]` | Pinpoints `metadata` export & `generateMetadata()` lines |
65
+ | **`NextPagesAdapter`** | `next` + `pages/` | Pages router hierarchy, dynamic `[id]` | Scans `<Head>` & `<NextSeo>` JSX components |
66
+ | **`AstroAdapter`** | `astro` + `src/pages` | `.astro`, `.md`, `.mdx` pages | Scans Astro frontmatter & `<title>` tags |
67
+ | **`ViteReactAdapter`** | `vite` / `react` | `index.html` + React Router routes | Scans `<Helmet>` & route definition trees |
68
+ | **`RemixAdapter`** | `@remix-run/react` | `app/routes/` | Inspects `meta` and `links` export functions |
69
+ | **`SvelteKitAdapter`** | `@sveltejs/kit` | `src/routes/` (`+page.svelte`) | Scans `<svelte:head>` components |
70
+ | **`StaticAdapter`** | Static HTML files | Recursive HTML directory traversal | Direct DOM / meta element inspection |
52
71
 
53
- ```bash
54
- npx -y seo-gravity-mcp
55
- ```
72
+ ---
56
73
 
57
- Or install globally:
74
+ ## 💻 Command-Line & CI/CD Runner
75
+
76
+ SEO Gravity includes a standalone CLI binary (`seo-gravity`) for direct terminal use and GitHub Actions / CI/CD quality gates:
58
77
 
59
78
  ```bash
60
- npm install -g seo-gravity-mcp
79
+ # 1. Audit project
80
+ npx seo-gravity-mcp audit --project ./my-app
81
+
82
+ # 2. Create baseline snapshot
83
+ npx seo-gravity-mcp snapshot --project ./my-app --output baseline.json
84
+
85
+ # 3. Check for SEO regressions in CI/CD PR gate (Exits with 0 or 1)
86
+ npx seo-gravity-mcp check --project ./my-app --baseline baseline.json
87
+
88
+ # 4. Targeted Git differential audit on modified routes only
89
+ npx seo-gravity-mcp diff --project ./my-app --base-ref origin/main
61
90
  ```
62
91
 
63
92
  ---
64
93
 
65
- ## 🤖 AI IDE & MCP Client Setup
66
-
67
- ### 1. 🟣 Claude Desktop
68
- Add to `claude_desktop_config.json`:
94
+ ## Instant Start (MCP Server)
69
95
 
70
- ```json
71
- {
72
- "mcpServers": {
73
- "seo-gravity": {
74
- "command": "npx",
75
- "args": ["-y", "seo-gravity-mcp"]
76
- }
77
- }
78
- }
96
+ ```bash
97
+ npx -y seo-gravity-mcp
79
98
  ```
80
99
 
81
- ### 2. Cursor
82
- Add to `.cursor/mcp.json`:
100
+ ### AI Client Configurations
83
101
 
102
+ #### Cursor (`.cursor/mcp.json`)
84
103
  ```json
85
104
  {
86
105
  "mcpServers": {
@@ -92,9 +111,7 @@ Add to `.cursor/mcp.json`:
92
111
  }
93
112
  ```
94
113
 
95
- ### 3. 🌊 Antigravity / Windsurf / VS Code Cline
96
- Add to `mcp_config.json`:
97
-
114
+ #### Claude Desktop (`claude_desktop_config.json`)
98
115
  ```json
99
116
  {
100
117
  "mcpServers": {
@@ -108,78 +125,28 @@ Add to `mcp_config.json`:
108
125
 
109
126
  ---
110
127
 
111
- ## 🛠 Complete Tool Catalog (35 Tools)
128
+ ## 🛠 Complete Tool Catalog
112
129
 
113
130
  ### Layer 0: Agent Orchestration & Remediation (Flagship)
114
131
  | Tool Name | Operational Class | Description |
115
132
  | :--- | :--- | :--- |
116
133
  | `seo_project_audit` | **Planning / Audit** | Scans workspace framework, discovers routes, correlates source files, builds crawl graph, and computes multidimensional SEO health. |
117
- | `seo_diagnose` | **Planning / Diagnose** | Deep root-cause inspection linking an observed issue/URL back to specific source code files with ready-to-use fix blueprints. |
134
+ | `seo_diagnose` | **Planning / Diagnose** | Deep root-cause AST inspection linking an observed issue/URL back to specific source code line ranges with ready-to-use fix blueprints. |
118
135
  | `seo_prioritize` | **Planning / Strategy** | Ranks findings into Quick Wins, Critical Blockers, and Architectural Improvements using `(Impact × Confidence × Reach) / Effort`. |
119
136
  | `seo_fix_plan` | **Planning / Execution** | Generates an end-to-end code modification and verification plan for AI coding agents. |
120
- | `seo_snapshot_create` | **Verification** | Creates and saves a project SEO baseline snapshot adhering to canonical schema `seo.gravity/v1`. |
121
- | `seo_snapshot_compare` | **Verification** | Diffs two snapshots to compute resolved findings, new regressions, and multidimensional score deltas. |
137
+ | `seo_snapshot_create` | **Verification** | Creates and saves a project SEO baseline snapshot with Git commit stamping adhering to schema `seo.gravity/v1`. |
138
+ | `seo_snapshot_compare` | **Verification** | Invariant-based diffing of two snapshots to isolate resolved findings, new regressions, and multidimensional score deltas. |
122
139
  | `seo_regression_check` | **Verification / CI** | Automated pass/fail regression check with alert messages for CI/CD gates and PR bots. |
123
140
 
124
- ### Layer 1: SERP & Competitor Intelligence
125
- | Tool Name | Description |
126
- | :--- | :--- |
127
- | `seo_serp_analyze` | Live Google SERP scraper returning rankings, snippets, People Also Ask (PAA), and SERP features. |
128
- | `seo_competitor_content_gap` | Compares target page against top 3-5 competitors for missing TF-IDF entities, subtopics, and word count deltas. |
129
- | `seo_competitor_profile` | Deep extraction of competitor heading tree (H1-H4), schema types, reading grade, and link ratios. |
130
- | `seo_competitor_diff` | 25-factor side-by-side scorecard comparing your page vs #1 competitor. |
131
- | `seo_forum_discussions_pulse` | Scrapes Reddit & Quora threads ranking for a query to extract user pain points and consensus. |
132
-
133
- ### Layer 2: Generative Engine Optimization (GEO & AEO)
134
- | Tool Name | Description |
135
- | :--- | :--- |
136
- | `seo_geo_ai_readiness_audit` | Audits content for AI Overviews & Perplexity citation readiness with structured retrieval signals and evidence tiers. |
137
- | `seo_llms_txt_generate` | Generates standard `/llms.txt` and `/llms-full.txt` markdown context files for AI search bots. |
138
- | `seo_ai_bots_robots_audit` | Audits robots.txt permissions specifically for AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended). |
139
-
140
- ### Layer 3: Information Gain & E-E-A-T
141
- | Tool Name | Description |
142
- | :--- | :--- |
143
- | `seo_information_gain_score` | Quantifies content novelty vs top 10 SERPs to prevent generic AI fluff penalties. |
144
- | `seo_eeat_audit` | Audits Google E-E-A-T trust signals (Person schema, author bylines, sameAs Wikidata/LinkedIn, publish/modified dates). |
145
-
146
- ### Layer 4: On-Page & Content Strategy
147
- | Tool Name | Description |
148
- | :--- | :--- |
149
- | `seo_onpage_audit` | Deep on-page audit of pixel width, meta CTR, heading hierarchy, image alt, and slug optimization. |
150
- | `seo_content_brief_generate` | Generates data-backed Content Outline & Brief with target word count, H1/H2/H3 structure, and FAQs. |
151
- | `seo_readability_score` | Computes Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog index. |
152
-
153
- ### Layer 5: Technical SEO & JavaScript Hydration
154
- | Tool Name | Description |
155
- | :--- | :--- |
156
- | `seo_technical_audit` | Inspects HTTP status codes, redirect chains, canonical consistency, meta robots (`noindex` leaks), and SSL. |
157
- | `seo_js_rendering_diff` | Compares raw server HTML vs hydrated client DOM (JavaScript SEO) to detect client-only content or broken tags. |
158
- | `seo_robots_txt_validate` | Validates bot crawlability against specific paths and User-Agents. |
159
- | `seo_sitemap_inspect` | Validates XML sitemaps, sitemap indexes, lastmod timestamps, and URL limit constraints. |
160
- | `seo_internal_links_analyze` | Analyzes internal link equity distribution, anchor texts, generic anchors, and nofollow flags. |
161
-
162
- ### Layer 6: Keyword Research & Intent
163
- | Tool Name | Description |
164
- | :--- | :--- |
165
- | `seo_keyword_suggestions` | Generates keyword suggestions using Google Autocomplete and Alphabet Soup expansion. |
166
- | `seo_questions_find` | Finds question queries asked by users across Google (Who, What, Where, When, Why, How, Can). |
167
- | `seo_keyword_cluster` | Clusters keyword lists into Topic Pillars and Supporting Articles using semantic similarity. |
168
- | `seo_search_intent_classify` | Classifies keywords into Informational, Navigational, Commercial, or Transactional intent. |
169
-
170
- ### Layer 7: Schema & Entity Graph
171
- | Tool Name | Description |
172
- | :--- | :--- |
173
- | `seo_entity_salience_map` | Extracts named entities, computes salience scores, and generates SPO relationship triples. |
174
- | `seo_schema_generate` | Generates validated Schema.org JSON-LD scripts (Article, FAQPage, Product, LocalBusiness, Organization). |
175
- | `seo_schema_validate` | Validates structured data against Schema.org and Google Rich Result criteria. |
176
-
177
- ### Layer 8: Performance & Maintenance
178
- | Tool Name | Description |
179
- | :--- | :--- |
180
- | `seo_pagespeed_audit` | Checks Core Web Vitals (LCP, FCP, CLS, TTFB) with performance optimization fixes. |
181
- | `seo_indexnow_submit` | Submits newly created or updated URLs directly to Bing & Yandex via the IndexNow API protocol. |
182
- | `seo_content_decay_audit` | Scans content for freshness decay (stale year references, outdated stats, broken external links). |
141
+ ### Layers 1–8: Specialized SEO & GEO Engines
142
+ - **Layer 1 (SERP & Competitors)**: `seo_serp_analyze`, `seo_competitor_content_gap`, `seo_competitor_profile`, `seo_competitor_diff`, `seo_forum_discussions_pulse`
143
+ - **Layer 2 (GEO & AI Search)**: `seo_geo_ai_readiness_audit`, `seo_llms_txt_generate`, `seo_ai_bots_robots_audit`
144
+ - **Layer 3 (Info Gain & E-E-A-T)**: `seo_information_gain_score`, `seo_eeat_audit`
145
+ - **Layer 4 (On-Page)**: `seo_onpage_audit`, `seo_content_brief_generate`, `seo_readability_score`
146
+ - **Layer 5 (Technical SEO)**: `seo_technical_audit`, `seo_js_rendering_diff`, `seo_robots_txt_validate`, `seo_sitemap_inspect`, `seo_internal_links_analyze`
147
+ - **Layer 6 (Keywords & Intent)**: `seo_keyword_suggestions`, `seo_questions_find`, `seo_keyword_cluster`, `seo_search_intent_classify`
148
+ - **Layer 7 (Schema & Entity)**: `seo_entity_salience_map`, `seo_schema_generate`, `seo_schema_validate`
149
+ - **Layer 8 (Speed & Maintenance)**: `seo_pagespeed_audit`, `seo_indexnow_submit`, `seo_content_decay_audit`
183
150
 
184
151
  ---
185
152
 
@@ -0,0 +1,7 @@
1
+ import { FrameworkAdapter } from './types.js';
2
+ export declare class AdapterRegistry {
3
+ private adapters;
4
+ getAdapterForProject(projectDir: string): FrameworkAdapter;
5
+ getAdapterById(id: string): FrameworkAdapter | undefined;
6
+ }
7
+ export declare const defaultAdapterRegistry: AdapterRegistry;
@@ -0,0 +1,30 @@
1
+ import { NextAppAdapter } from './nextAppAdapter.js';
2
+ import { NextPagesAdapter } from './nextPagesAdapter.js';
3
+ import { AstroAdapter } from './astroAdapter.js';
4
+ import { ViteReactAdapter } from './viteReactAdapter.js';
5
+ import { RemixAdapter } from './remixAdapter.js';
6
+ import { SvelteKitAdapter } from './svelteKitAdapter.js';
7
+ import { StaticAdapter } from './staticAdapter.js';
8
+ export class AdapterRegistry {
9
+ adapters = [
10
+ new NextAppAdapter(),
11
+ new NextPagesAdapter(),
12
+ new AstroAdapter(),
13
+ new RemixAdapter(),
14
+ new SvelteKitAdapter(),
15
+ new ViteReactAdapter(),
16
+ new StaticAdapter() // fallback
17
+ ];
18
+ getAdapterForProject(projectDir) {
19
+ for (const adapter of this.adapters) {
20
+ if (adapter.detect(projectDir)) {
21
+ return adapter;
22
+ }
23
+ }
24
+ return this.adapters[this.adapters.length - 1]; // fallback static
25
+ }
26
+ getAdapterById(id) {
27
+ return this.adapters.find(a => a.id === id);
28
+ }
29
+ }
30
+ export const defaultAdapterRegistry = new AdapterRegistry();
@@ -0,0 +1,17 @@
1
+ import { FrameworkAdapter, MetadataLocationInfo, CanonicalLocationInfo, SchemaLocationInfo } from './types.js';
2
+ import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../types/findings.js';
3
+ export declare class AstroAdapter implements FrameworkAdapter {
4
+ id: string;
5
+ name: string;
6
+ detect(projectDir: string): boolean;
7
+ getProjectInfo(projectDir: string): ProjectFrameworkInfo;
8
+ discoverRoutes(projectDir: string): DiscoveredRoute[];
9
+ mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
10
+ findMetadataImplementation(projectDir: string, route: DiscoveredRoute): Promise<MetadataLocationInfo>;
11
+ findCanonicalDeclaration(projectDir: string, route: DiscoveredRoute): Promise<CanonicalLocationInfo>;
12
+ findSchemaDeclaration(projectDir: string, route: DiscoveredRoute): Promise<SchemaLocationInfo>;
13
+ findRobotsConfig(projectDir: string): string | null;
14
+ findSitemapConfig(projectDir: string): string | null;
15
+ findLlmsTxt(projectDir: string): string | null;
16
+ private extractDynamicParams;
17
+ }
@@ -0,0 +1,191 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ export class AstroAdapter {
4
+ id = 'astro';
5
+ name = 'Astro Framework';
6
+ detect(projectDir) {
7
+ const hasConfig = fs.existsSync(path.join(projectDir, 'astro.config.mjs')) ||
8
+ fs.existsSync(path.join(projectDir, 'astro.config.ts'));
9
+ if (hasConfig)
10
+ return true;
11
+ const pkgPath = path.join(projectDir, 'package.json');
12
+ if (fs.existsSync(pkgPath)) {
13
+ try {
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
15
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
16
+ return Boolean(deps['astro']);
17
+ }
18
+ catch { }
19
+ }
20
+ return false;
21
+ }
22
+ getProjectInfo(projectDir) {
23
+ const pkgPath = path.join(projectDir, 'package.json');
24
+ let pkg = {};
25
+ if (fs.existsSync(pkgPath)) {
26
+ try {
27
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
28
+ }
29
+ catch { }
30
+ }
31
+ const hasSrcPages = fs.existsSync(path.join(projectDir, 'src/pages'));
32
+ const routesDir = hasSrcPages ? 'src/pages' : 'pages';
33
+ return {
34
+ framework: 'astro',
35
+ name: pkg.name || path.basename(projectDir),
36
+ version: pkg.dependencies?.astro || pkg.devDependencies?.astro,
37
+ packageManager: fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml'))
38
+ ? 'pnpm'
39
+ : fs.existsSync(path.join(projectDir, 'yarn.lock'))
40
+ ? 'yarn'
41
+ : 'npm',
42
+ hasTypeScript: fs.existsSync(path.join(projectDir, 'tsconfig.json')),
43
+ hasSitemapConfig: Boolean(this.findSitemapConfig(projectDir)),
44
+ hasRobotsConfig: Boolean(this.findRobotsConfig(projectDir)),
45
+ hasLlmsTxt: Boolean(this.findLlmsTxt(projectDir)),
46
+ rootDir: path.resolve(projectDir),
47
+ routesDir,
48
+ devCommand: 'npm run dev',
49
+ defaultDevPort: 4321
50
+ };
51
+ }
52
+ discoverRoutes(projectDir) {
53
+ const info = this.getProjectInfo(projectDir);
54
+ const fullRoutesDir = path.join(projectDir, info.routesDir || 'src/pages');
55
+ const discovered = [];
56
+ if (!fs.existsSync(fullRoutesDir))
57
+ return discovered;
58
+ const scanDir = (currentDir, routePrefix) => {
59
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
60
+ for (const entry of entries) {
61
+ const entryPath = path.join(currentDir, entry.name);
62
+ if (entry.isDirectory()) {
63
+ const nextPrefix = routePrefix === '/' ? `/${entry.name}` : `${routePrefix}/${entry.name}`;
64
+ scanDir(entryPath, nextPrefix);
65
+ }
66
+ else if (entry.isFile() && /\.(astro|md|mdx)$/.test(entry.name)) {
67
+ const baseName = entry.name.replace(/\.(astro|md|mdx)$/, '');
68
+ let routePath = routePrefix;
69
+ if (baseName !== 'index') {
70
+ routePath = routePrefix === '/' ? `/${baseName}` : `${routePrefix}/${baseName}`;
71
+ }
72
+ if (routePath === '')
73
+ routePath = '/';
74
+ const relFile = path.relative(projectDir, entryPath).replace(/\\/g, '/');
75
+ const content = fs.readFileSync(entryPath, 'utf-8');
76
+ const dynamicParams = this.extractDynamicParams(routePath);
77
+ discovered.push({
78
+ routePath,
79
+ sourceFilePath: relFile,
80
+ isDynamic: dynamicParams.length > 0,
81
+ dynamicParams,
82
+ routeType: 'page',
83
+ hasMetadataExport: /title\s*=|description\s*=|astro:head|<title/.test(content),
84
+ hasGenerateMetadata: false,
85
+ hasSchemaMarkup: /application\/ld\+json/.test(content),
86
+ hasHeadComponent: /<head|astro:head|<title/.test(content),
87
+ isClientComponent: false
88
+ });
89
+ }
90
+ }
91
+ };
92
+ scanDir(fullRoutesDir, '/');
93
+ return discovered;
94
+ }
95
+ mapRouteToSource(targetUrl, routes) {
96
+ let urlPath = targetUrl;
97
+ try {
98
+ if (targetUrl.startsWith('http')) {
99
+ urlPath = new URL(targetUrl).pathname;
100
+ }
101
+ }
102
+ catch {
103
+ urlPath = targetUrl;
104
+ }
105
+ urlPath = urlPath.replace(/\/$/, '') || '/';
106
+ for (const r of routes) {
107
+ const clean = r.routePath.replace(/\/$/, '') || '/';
108
+ if (clean === urlPath) {
109
+ return {
110
+ urlPath,
111
+ matchedRoute: r,
112
+ sourceFilePath: r.sourceFilePath,
113
+ confidence: 1.0,
114
+ resolutionMethod: 'exact_match'
115
+ };
116
+ }
117
+ }
118
+ return {
119
+ urlPath,
120
+ confidence: 0.0,
121
+ resolutionMethod: 'unmapped'
122
+ };
123
+ }
124
+ async findMetadataImplementation(projectDir, route) {
125
+ const fullPath = path.join(projectDir, route.sourceFilePath);
126
+ if (!fs.existsSync(fullPath))
127
+ return { hasMetadata: false, type: 'none' };
128
+ const content = fs.readFileSync(fullPath, 'utf-8');
129
+ const hasMeta = /<title\b|<meta\s+name=["']description["']/i.test(content) || /title\s*:/i.test(content);
130
+ return {
131
+ hasMetadata: hasMeta,
132
+ type: hasMeta ? 'jsx_head' : 'none'
133
+ };
134
+ }
135
+ async findCanonicalDeclaration(projectDir, route) {
136
+ const fullPath = path.join(projectDir, route.sourceFilePath);
137
+ if (!fs.existsSync(fullPath))
138
+ return { hasCanonical: false, type: 'none' };
139
+ const content = fs.readFileSync(fullPath, 'utf-8');
140
+ const hasCanonical = /rel=["']canonical["']/i.test(content);
141
+ return {
142
+ hasCanonical,
143
+ type: hasCanonical ? 'link_jsx_tag' : 'none'
144
+ };
145
+ }
146
+ async findSchemaDeclaration(projectDir, route) {
147
+ const fullPath = path.join(projectDir, route.sourceFilePath);
148
+ if (!fs.existsSync(fullPath))
149
+ return { hasSchema: false, typesFound: [] };
150
+ const content = fs.readFileSync(fullPath, 'utf-8');
151
+ const hasSchema = /application\/ld\+json/i.test(content);
152
+ return {
153
+ hasSchema,
154
+ typesFound: hasSchema ? ['JSON-LD'] : []
155
+ };
156
+ }
157
+ findRobotsConfig(projectDir) {
158
+ const paths = ['public/robots.txt', 'robots.txt'];
159
+ for (const p of paths) {
160
+ if (fs.existsSync(path.join(projectDir, p)))
161
+ return p;
162
+ }
163
+ return null;
164
+ }
165
+ findSitemapConfig(projectDir) {
166
+ const paths = ['public/sitemap.xml', 'sitemap.xml', 'src/pages/sitemap.xml.ts'];
167
+ for (const p of paths) {
168
+ if (fs.existsSync(path.join(projectDir, p)))
169
+ return p;
170
+ }
171
+ return null;
172
+ }
173
+ findLlmsTxt(projectDir) {
174
+ const paths = ['public/llms.txt', 'llms.txt'];
175
+ for (const p of paths) {
176
+ if (fs.existsSync(path.join(projectDir, p)))
177
+ return p;
178
+ }
179
+ return null;
180
+ }
181
+ extractDynamicParams(routePath) {
182
+ const params = [];
183
+ const matches = routePath.match(/\[\.{0,3}([a-zA-Z0-9_-]+)\]/g);
184
+ if (matches) {
185
+ for (const m of matches) {
186
+ params.push(m.replace(/[\[\].]/g, ''));
187
+ }
188
+ }
189
+ return params;
190
+ }
191
+ }
@@ -0,0 +1,17 @@
1
+ import { FrameworkAdapter, MetadataLocationInfo, CanonicalLocationInfo, SchemaLocationInfo } from './types.js';
2
+ import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../types/findings.js';
3
+ export declare class NextAppAdapter implements FrameworkAdapter {
4
+ id: string;
5
+ name: string;
6
+ detect(projectDir: string): boolean;
7
+ getProjectInfo(projectDir: string): ProjectFrameworkInfo;
8
+ discoverRoutes(projectDir: string): DiscoveredRoute[];
9
+ mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
10
+ findMetadataImplementation(projectDir: string, route: DiscoveredRoute): Promise<MetadataLocationInfo>;
11
+ findCanonicalDeclaration(projectDir: string, route: DiscoveredRoute): Promise<CanonicalLocationInfo>;
12
+ findSchemaDeclaration(projectDir: string, route: DiscoveredRoute): Promise<SchemaLocationInfo>;
13
+ findRobotsConfig(projectDir: string): string | null;
14
+ findSitemapConfig(projectDir: string): string | null;
15
+ findLlmsTxt(projectDir: string): string | null;
16
+ private extractDynamicParams;
17
+ }