diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml
new file mode 100644
index 00000000..bf912b57
--- /dev/null
+++ b/.github/workflows/pr-checks.yml
@@ -0,0 +1,47 @@
+name: PR Checks
+
+on:
+ pull_request:
+ branches: [dev, main]
+
+jobs:
+ link-audit:
+ name: Link audit & redirect enforcement
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # full history needed for git diff
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Fetch @futureagi/chat-widget
+ run: |
+ git clone --depth 1 \
+ https://x-access-token:${{ secrets.GH_PAT }}@github.com/future-agi/landing-page.git .landing-tmp
+ cp -r .landing-tmp/docs-agent/packages/chat-widget ./chat-widget
+ rm -rf .landing-tmp
+
+ - name: Patch chat-widget dependency
+ run: |
+ sed -i 's|"@futureagi/chat-widget": "workspace:\*"|"@futureagi/chat-widget": "file:./chat-widget"|' package.json
+
+ - name: Cache npm dependencies
+ uses: actions/cache@v4
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json', 'package.json') }}
+ restore-keys: ${{ runner.os }}-npm-
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Check broken nav & content links
+ run: node scripts/audit-links.mjs
+
+ - name: Check deleted pages have redirects
+ run: node scripts/check-deleted-pages.mjs ${{ github.base_ref }}
diff --git a/scripts/check-deleted-pages.mjs b/scripts/check-deleted-pages.mjs
new file mode 100644
index 00000000..5361750d
--- /dev/null
+++ b/scripts/check-deleted-pages.mjs
@@ -0,0 +1,79 @@
+/**
+ * Checks that every MDX page deleted in this branch has a corresponding
+ * entry in src/lib/redirects.ts. Fails with exit code 1 if any are missing.
+ *
+ * Usage: node scripts/check-deleted-pages.mjs [base-branch]
+ * Default base branch: dev
+ */
+import { execSync } from 'child_process';
+import { readFileSync } from 'fs';
+
+const baseBranch = process.argv[2] || 'dev';
+
+// Get deleted MDX files compared to base branch
+let deletedFiles;
+try {
+ const output = execSync(
+ `git diff origin/${baseBranch}...HEAD --name-only --diff-filter=D`,
+ { encoding: 'utf-8' }
+ );
+ deletedFiles = output.trim().split('\n').filter(Boolean);
+} catch {
+ // If origin/base doesn't exist, try without origin/
+ try {
+ const output = execSync(
+ `git diff ${baseBranch}...HEAD --name-only --diff-filter=D`,
+ { encoding: 'utf-8' }
+ );
+ deletedFiles = output.trim().split('\n').filter(Boolean);
+ } catch {
+ console.error('Could not determine deleted files — failing to surface the error.');
+ process.exit(1);
+ }
+}
+
+// Filter to only MDX pages under src/pages/
+const deletedPages = deletedFiles.filter(f => f.startsWith('src/pages/') && f.endsWith('.mdx'));
+
+if (deletedPages.length === 0) {
+ console.log('No MDX pages deleted in this branch. ✓');
+ process.exit(0);
+}
+
+// Convert file path to URL path
+function fileToPath(file) {
+ return file
+ .replace(/^src\/pages/, '')
+ .replace(/\.mdx$/, '')
+ .replace(/\/index$/, '') || '/';
+}
+
+// Load redirects map
+const redirectsRaw = readFileSync('src/lib/redirects.ts', 'utf-8');
+const redirectEntries = [...redirectsRaw.matchAll(/["']([^"']+)["']:\s*["']([^"']+)["']/g)];
+const redirectMap = new Set(redirectEntries.map(([, from]) => from));
+
+// Check each deleted page
+const missing = [];
+for (const file of deletedPages) {
+ const urlPath = fileToPath(file);
+ if (!redirectMap.has(urlPath)) {
+ missing.push({ file, urlPath });
+ }
+}
+
+if (missing.length === 0) {
+ console.log(`All ${deletedPages.length} deleted page(s) have redirects. ✓`);
+ process.exit(0);
+}
+
+console.error(`\n✗ ${missing.length} deleted page(s) have no redirect in src/lib/redirects.ts:\n`);
+for (const { file, urlPath } of missing) {
+ console.error(` ${urlPath}`);
+ console.error(` (deleted file: ${file})`);
+}
+console.error(`
+To fix: add an entry to src/lib/redirects.ts for each path above, pointing to the closest current page.
+Example: '${missing[0].urlPath}': '/docs/some-current-page',
+`);
+process.exit(1);
diff --git a/src/components/AiChatWidget.astro b/src/components/AiChatWidget.astro
index 544b518a..0ca8ec5c 100644
--- a/src/components/AiChatWidget.astro
+++ b/src/components/AiChatWidget.astro
@@ -88,7 +88,7 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY || '';
-
FutureAGI AI Assistant
+ FutureAGI AI Assistant
Ask me anything about the FutureAGI platform — I can search across all docs instantly.
diff --git a/src/components/GiscusComments.tsx b/src/components/GiscusComments.tsx
index 2057f50a..20ec904c 100644
--- a/src/components/GiscusComments.tsx
+++ b/src/components/GiscusComments.tsx
@@ -28,7 +28,7 @@ export default function GiscusComments({ pagePath }: { pagePath: string }) {
return (
-
Questions & Discussion
+
Questions & Discussion
);
diff --git a/src/components/TableOfContents.astro b/src/components/TableOfContents.astro
index 1c1cbfad..21780783 100644
--- a/src/components/TableOfContents.astro
+++ b/src/components/TableOfContents.astro
@@ -22,9 +22,9 @@ const feedbackUrl = `https://github.com/${GITHUB_REPO}/issues/new?title=${encode
{toc.length > 0 && (
-
+
On this page
-
+