Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cloudflare_site/public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Sitemap: https://fullsend.sh/docs/sitemap.xml

# Training crawlers — blocked site-wide
User-agent: ClaudeBot
Disallow: /
Expand Down
15 changes: 15 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineConfig } from "vitepress";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DOCS_URL_BASE, globalSeoHead, isIndexablePage, pageSeoHead } from "./seo";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const docsDir = path.resolve(__dirname, "..");
Expand Down Expand Up @@ -158,8 +159,22 @@ export default defineConfig({
href: "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap",
},
],
// Site-wide SEO metadata (Open Graph type/site name/image + Twitter card).
...globalSeoHead,
],

// Emit sitemap.xml for the docs. The hostname carries the /docs/ base (and a
// trailing slash) because VitePress resolves base-less page paths against it.
sitemap: {
Comment thread
shairevivo marked this conversation as resolved.
Resolved
hostname: DOCS_URL_BASE,
},
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved

// Per-page canonical + Open Graph tags derived from the resolved page data.
transformHead({ page, title, description, siteConfig }) {
if (!isIndexablePage(page)) return [];
return pageSeoHead({ page, title, description, cleanUrls: siteConfig.cleanUrls });
Comment thread
shairevivo marked this conversation as resolved.
Resolved
},

srcExclude: ["**/agents/icons/**", "**/testing/**"],

ignoreDeadLinks: true,
Expand Down
107 changes: 107 additions & 0 deletions docs/.vitepress/seo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, expect, it } from "vitest";
import {
DOCS_URL_BASE,
canonicalUrl,
globalSeoHead,
isIndexablePage,
pageOutputPath,
pageSeoHead,
} from "./seo";

describe("pageOutputPath", () => {
it("maps the root index to the empty (directory) path", () => {
expect(pageOutputPath("index.md")).toBe("");
expect(pageOutputPath("README.md")).toBe("");
});

it("maps a nested index/README to its directory", () => {
expect(pageOutputPath("agents/index.md")).toBe("agents/");
expect(pageOutputPath("guides/getting-started/README.md")).toBe("guides/getting-started/");
});

it("maps a content page to an .html path when cleanUrls is off", () => {
expect(pageOutputPath("agents/triage.md")).toBe("agents/triage.html");
expect(pageOutputPath("guides/user/jira-integration.md")).toBe(
"guides/user/jira-integration.html",
);
});

it("drops the extension entirely when cleanUrls is on", () => {
expect(pageOutputPath("agents/triage.md", true)).toBe("agents/triage");
expect(pageOutputPath("index.md", true)).toBe("");
});
});

describe("canonicalUrl", () => {
it("always includes the /docs/ base", () => {
// Regression guard: the docs are served under /docs/, so a base-less
// hostname (as in the original issue template) would drop that segment.
for (const page of ["index.md", "agents/triage.md", "guides/getting-started/README.md"]) {
expect(canonicalUrl(page).startsWith(DOCS_URL_BASE)).toBe(true);
}
});

it("resolves the root to the docs base URL", () => {
expect(canonicalUrl("index.md")).toBe("https://fullsend.sh/docs/");
});

it("resolves a directory index to a trailing-slash URL", () => {
expect(canonicalUrl("agents/index.md")).toBe("https://fullsend.sh/docs/agents/");
});

it("resolves a content page to its .html URL", () => {
expect(canonicalUrl("guides/user/jira-integration.md")).toBe(
"https://fullsend.sh/docs/guides/user/jira-integration.html",
);
});
});

describe("pageSeoHead", () => {
const head = pageSeoHead({
page: "agents/triage.md",
title: "Triage Agent | Fullsend",
description: "How the triage agent works",
});

it("emits a canonical link and og:url with the full docs URL", () => {
const url = "https://fullsend.sh/docs/agents/triage.html";
expect(head).toContainEqual(["link", { rel: "canonical", href: url }]);
expect(head).toContainEqual(["meta", { property: "og:url", content: url }]);
});

it("emits page-specific og:title and og:description", () => {
expect(head).toContainEqual([
"meta",
{ property: "og:title", content: "Triage Agent | Fullsend" },
]);
expect(head).toContainEqual([
"meta",
{ property: "og:description", content: "How the triage agent works" },
]);
});
});

describe("isIndexablePage", () => {
it("excludes the 404 page from self-canonical / OG tags", () => {
expect(isIndexablePage("404.md")).toBe(false);
});

it("treats content pages as indexable", () => {
expect(isIndexablePage("index.md")).toBe(true);
expect(isIndexablePage("agents/triage.md")).toBe(true);
});
});

describe("globalSeoHead", () => {
it("declares site-wide Open Graph and Twitter card metadata", () => {
expect(globalSeoHead).toContainEqual(["meta", { property: "og:type", content: "website" }]);
expect(globalSeoHead).toContainEqual([
"meta",
{ property: "og:site_name", content: "Fullsend" },
]);
expect(globalSeoHead).toContainEqual([
"meta",
{ name: "twitter:card", content: "summary_large_image" },
]);
});
});
97 changes: 97 additions & 0 deletions docs/.vitepress/seo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { HeadConfig } from "vitepress";

// SEO metadata helpers for the VitePress docs site.
//
// The docs are served under a base path — https://fullsend.sh/docs/ — so every
// absolute URL we emit (canonical, og:url, sitemap entries) must include that
// `/docs/` segment. VitePress 1.6.x builds sitemap entries as base-less relative
// paths and resolves them against `sitemap.hostname`, so the hostname itself must
// carry the base and a trailing slash. The same rule applies to URLs we resolve
// here, which is why DOCS_URL_BASE ends in a slash.

/** Public origin of the site. */
export const SITE_ORIGIN = "https://fullsend.sh";

/** Base path the docs are served under (matches `base` in config.ts). */
export const DOCS_BASE = "/docs/";

/**
* Absolute base for canonical/OG URLs and the sitemap `hostname`.
* The trailing slash is required for correct `new URL(relative, base)` resolution.
*/
export const DOCS_URL_BASE = `${SITE_ORIGIN}${DOCS_BASE}`;

/**
* Default social-share image (absolute URL). This is the square brand logo; a
* dedicated 1200×630 asset would render better on cards and is a good follow-up.
*/
export const OG_IMAGE = `${DOCS_URL_BASE}img/logo.png`;

/**
* Convert a VitePress page path (the post-rewrite source path, e.g. "index.md",
* "agents/index.md", "guides/user/jira-integration.md") into the site-relative
* output path VitePress actually serves, mirroring its sitemap/link logic:
* - `index.md` / `README.md` → the containing directory ("" for the root)
* - other `*.md` → `*.html` (or the bare path when `cleanUrls` is enabled)
*
* The result is a base-less relative path, suitable for resolving against
* {@link DOCS_URL_BASE}.
*/
export function pageOutputPath(page: string, cleanUrls = false): string {
const asDir = page.replace(/(^|\/)(index|README)\.md$/, "$1");
if (asDir !== page) return asDir;
return page.replace(/\.md$/, cleanUrls ? "" : ".html");
}

/** Absolute canonical URL for a page, including the `/docs/` base. */
export function canonicalUrl(page: string, cleanUrls = false): string {
return new URL(pageOutputPath(page, cleanUrls), DOCS_URL_BASE).href;
}

export interface PageSeoInput {
/** Post-rewrite source path of the page (VitePress `TransformContext.page`). */
page: string;
/** Resolved page title (VitePress `TransformContext.title`). */
title: string;
/** Resolved page description (VitePress `TransformContext.description`). */
description: string;
cleanUrls?: boolean;
}

/**
* Per-page SEO head tags: a canonical link plus page-specific Open Graph tags.
* Twitter cards fall back to the `og:*` values, so no `twitter:title` /
* `twitter:description` duplication is needed.
*/
export function pageSeoHead({
page,
title,
description,
cleanUrls = false,
}: PageSeoInput): HeadConfig[] {
const url = canonicalUrl(page, cleanUrls);
return [
["link", { rel: "canonical", href: url }],
["meta", { property: "og:url", content: url }],
["meta", { property: "og:title", content: title }],
["meta", { property: "og:description", content: description }],
];
}

/**
* Whether a page should advertise a self-canonical / OG URL to crawlers.
* The 404 page is served for unknown paths and must not declare itself
Comment thread
shairevivo marked this conversation as resolved.
Resolved
* canonical, or crawlers would treat the not-found page as indexable.
*/
export function isIndexablePage(page: string): boolean {
return page !== "404.md";
}

/** Site-wide SEO head tags that are identical on every page. */
export const globalSeoHead: HeadConfig[] = [
["meta", { property: "og:type", content: "website" }],
["meta", { property: "og:site_name", content: "Fullsend" }],
["meta", { property: "og:image", content: OG_IMAGE }],
["meta", { name: "twitter:card", content: "summary_large_image" }],
["meta", { name: "twitter:image", content: OG_IMAGE }],
];
4 changes: 4 additions & 0 deletions docs/agents/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: Reference for the agents fullsend ships — triage, code, review, fix, retro, and prioritize — plus how to register custom agents via config.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved
---

# Agents

Reference documentation for the agents shipped by fullsend.
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/code.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend code agent implements triaged GitHub issues following repository conventions, runs tests and linters, and commits to a feature branch.
---

# Code Agent

![Code agent icon](icons/coder.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/fix.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend fix agent reads PR review feedback, implements targeted fixes, runs tests and linters, and commits the result.
---

# Fix Agent

![Fix agent icon](icons/coder.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/prioritize.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend prioritize agent scores GitHub issues with the RICE framework and produces ranked, reasoned priority scores.
---

# Prioritize Agent

![Prioritize agent icon](icons/prioritize.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/retro.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend retro agent runs retrospectives on agent workflows and proposes improvements as structured GitHub issues.
---

# Retro Agent

![Retro agent icon](icons/retro.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/review.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend review agent evaluates pull requests for correctness, security, intent alignment, style, and documentation currency.
---

# Review Agent

![Review agent icon](icons/review.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/agents/triage.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: How the fullsend triage agent inspects new GitHub issues, assesses information sufficiency, asks clarifying questions, and produces a structured triage decision.
---

# Triage Agent

![Triage agent icon](icons/triage.png)
Expand Down
4 changes: 4 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: The components of the fullsend agent execution stack and how they fit together — the current architectural truth, kept in sync with accepted ADRs.
---

# Architecture

What are the components of the agent execution stack?
Expand Down
1 change: 1 addition & 0 deletions docs/guides/getting-started/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
description: Set up fullsend step by step — get inference, choose an agent runtime, and configure GitHub to start running autonomous SDLC agents on your repos.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved
---

# Getting Started
Expand Down
4 changes: 4 additions & 0 deletions docs/vision.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: The fullsend vision — fully autonomous agentic software development across a GitHub organization, with humans setting strategic intent and guardrails.
---

# Vision

## The goal
Expand Down
Loading