diff --git a/pkgs/website/src/content.config.ts b/pkgs/website/src/content.config.ts index ef8f1e705..daa83b6b8 100644 --- a/pkgs/website/src/content.config.ts +++ b/pkgs/website/src/content.config.ts @@ -1,4 +1,4 @@ -import { defineCollection } from "astro:content"; +import { defineCollection, z } from "astro:content"; import { docsLoader } from "@astrojs/starlight/loaders"; import { docsSchema } from "@astrojs/starlight/schema"; import { topicSchema } from 'starlight-sidebar-topics/schema' @@ -8,7 +8,12 @@ export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema({ - extend: (context) => topicSchema.merge(blogSchema(context)) + extend: (context) => + topicSchema + .merge(blogSchema(context)) + // Task-oriented routing override used by the /docs-index endpoints. + // Add only where `description` is materially ambiguous for routing. + .extend({ agentHint: z.string().optional() }) }) }), }; \ No newline at end of file diff --git a/pkgs/website/src/content/docs/reference/queue-worker/configuration.mdx b/pkgs/website/src/content/docs/reference/queue-worker/configuration.mdx index 02e6b27b5..a372fbf37 100644 --- a/pkgs/website/src/content/docs/reference/queue-worker/configuration.mdx +++ b/pkgs/website/src/content/docs/reference/queue-worker/configuration.mdx @@ -1,6 +1,7 @@ --- title: Configuration description: Learn how to configure Edge Worker with options for configuring Supabase Queues, message processing, polling behavior, and retries. Includes defaults and best practices. +agentHint: Configure the Background Jobs worker for Supabase Queues; for pgflow flow-worker runtime settings use Worker Configuration instead. sidebar: order: 3 banner: diff --git a/pkgs/website/src/pages/docs-index.md.ts b/pkgs/website/src/pages/docs-index.md.ts new file mode 100644 index 000000000..8c36c1c87 --- /dev/null +++ b/pkgs/website/src/pages/docs-index.md.ts @@ -0,0 +1,11 @@ +import type { APIRoute } from 'astro'; +import { getCollection } from 'astro:content'; +import { renderRootIndex } from '../utils/docs-index'; + +export const GET: APIRoute = async ({ site }) => { + const base = (site ?? new URL('https://www.pgflow.dev')).toString().replace(/\/+$/, ''); + const docs = await getCollection('docs'); + return new Response(renderRootIndex(base, docs), { + headers: { 'Content-Type': 'text/markdown; charset=utf-8' }, + }); +}; diff --git a/pkgs/website/src/pages/docs-index/[section].md.ts b/pkgs/website/src/pages/docs-index/[section].md.ts new file mode 100644 index 000000000..01c18e8c5 --- /dev/null +++ b/pkgs/website/src/pages/docs-index/[section].md.ts @@ -0,0 +1,18 @@ +import type { APIRoute } from 'astro'; +import { getCollection } from 'astro:content'; +import { renderSectionIndex, SECTIONS } from '../../utils/docs-index'; + +export function getStaticPaths() { + return SECTIONS.map((section) => ({ params: { section: section.id } })); +} + +export const GET: APIRoute = async ({ site, params }) => { + const section = SECTIONS.find((s) => s.id === params.section); + if (!section) return new Response('Not found', { status: 404 }); + + const base = (site ?? new URL('https://www.pgflow.dev')).toString().replace(/\/+$/, ''); + const docs = await getCollection('docs'); + return new Response(renderSectionIndex(base, section, docs), { + headers: { 'Content-Type': 'text/markdown; charset=utf-8' }, + }); +}; diff --git a/pkgs/website/src/utils/docs-index.ts b/pkgs/website/src/utils/docs-index.ts new file mode 100644 index 000000000..7a2adea69 --- /dev/null +++ b/pkgs/website/src/utils/docs-index.ts @@ -0,0 +1,163 @@ +import type { CollectionEntry } from 'astro:content'; + +type Doc = CollectionEntry<'docs'>; + +export interface Section { + /** Top-level docs collection id prefix. Also the /docs-index/.md file name. */ + id: string; + label: string; + /** One-line, task-oriented routing hint used in /docs-index.md. */ + route: string; +} + +/** + * Section allowlist. Only these top-level sections are indexed, so drafts, + * internal pages (edge-worker, author, demos) and root one-offs never leak + * into the generated indexes. + */ +export const SECTIONS: Section[] = [ + { + id: 'get-started', + label: 'Get Started', + route: 'Install pgflow, run your first flow, background jobs mode, FAQ.', + }, + { + id: 'build', + label: 'Build', + route: 'Author flows: steps, dependencies, conditional execution, retries, starting flows, versioning.', + }, + { + id: 'deploy', + label: 'Deploy', + route: 'Deploy and operate: Supabase or Node/Bun workers, monitoring, troubleshooting, updates.', + }, + { + id: 'concepts', + label: 'Concepts', + route: 'How pgflow works: execution model, data model, compilation, worker lifecycle, architecture.', + }, + { + id: 'reference', + label: 'Reference', + route: 'APIs and configuration: @pgflow/client, context, compile APIs, permissions, worker and step settings.', + }, + { + id: 'tutorials', + label: 'Tutorials', + route: 'Hands-on guides: RAG pipelines, AI web scraper.', + }, + { + id: 'news', + label: 'News', + route: 'Release announcements — what shipped and when.', + }, + { + id: 'comparisons', + label: 'Comparisons', + route: 'pgflow vs DBOS, Inngest, Trigger.dev, Vercel Workflows.', + }, +]; + +/** + * Secondary or niche pages listed under `## Optional` in their section index. + * Matches by exact id or by `prefix/`. + */ +const OPTIONAL_PAGES = [ + 'deploy/connection-string', + 'deploy/prune-records', + 'deploy/troubleshooting-connections', + 'reference/manual-installation', + 'reference/queue-worker/', +]; + +function sectionOf(id: string): Section | undefined { + return SECTIONS.find((s) => id === s.id || id.startsWith(s.id + '/')); +} + +/** Top-level section pages are navigation-only CardGrid hubs — skip them. */ +function isNavigationHub(id: string): boolean { + return SECTIONS.some((s) => id === s.id); +} + +function isOptional(id: string): boolean { + return OPTIONAL_PAGES.some((p) => id === p || id.startsWith(p)); +} + +/** Raw Markdown endpoint (starlight-markdown serves `/index.md`). */ +function markdownUrl(base: string, id: string): string { + const path = id.replace(/^\/+|\/+$/g, ''); + return `${base}/${path}/index.md`; +} + +function routingText(doc: Doc): string { + const text = doc.data.agentHint ?? doc.data.description; + if (!text) { + throw new Error( + `[docs-index] "${doc.id}" has no description or agentHint — every indexed page needs routing text.` + ); + } + return text; +} + +function bySidebarOrderThenTitle(a: Doc, b: Doc): number { + const orderA = a.data.sidebar?.order ?? Number.POSITIVE_INFINITY; + const orderB = b.data.sidebar?.order ?? Number.POSITIVE_INFINITY; + if (orderA !== orderB) return orderA - orderB; + return a.data.title.localeCompare(b.data.title); +} + +function byDateNewestFirst(a: Doc, b: Doc): number { + const dateA = a.data.date?.getTime() ?? 0; + const dateB = b.data.date?.getTime() ?? 0; + return dateB - dateA; +} + +/** Docs belonging to a section, ready for indexing: allowlisted, not draft, not a hub. */ +export function sectionDocs(docs: Doc[], section: Section): Doc[] { + return docs.filter( + (doc) => sectionOf(doc.id) === section && !doc.data.draft && !isNavigationHub(doc.id) + ); +} + +function entryLine(base: string, doc: Doc, withDate: boolean): string { + const date = withDate && doc.data.date ? `${doc.data.date.toISOString().slice(0, 10)} — ` : ''; + return `- [${date}${doc.data.title}](${markdownUrl(base, doc.id)}) — ${routingText(doc)}`; +} + +export function renderSectionIndex(base: string, section: Section, docs: Doc[]): string { + const entries = sectionDocs(docs, section); + const isNews = section.id === 'news'; + const primary = entries.filter((doc) => !isOptional(doc.id)); + const optional = entries.filter((doc) => isOptional(doc.id)); + primary.sort(isNews ? byDateNewestFirst : bySidebarOrderThenTitle); + optional.sort(isNews ? byDateNewestFirst : bySidebarOrderThenTitle); + + const lines = [ + `# pgflow docs — ${section.label}`, + '', + section.route, + '', + ...primary.map((doc) => entryLine(base, doc, isNews)), + ]; + if (optional.length > 0) { + lines.push('', '## Optional', '', ...optional.map((doc) => entryLine(base, doc, isNews))); + } + return lines.join('\n') + '\n'; +} + +export function renderRootIndex(base: string, docs: Doc[]): string { + const lines = [ + '# pgflow docs index', + '', + 'Section indexes for pgflow documentation, served as raw Markdown.', + 'Core docs define current behavior and APIs; news records what shipped and when.', + '', + 'Fetch the one section index matching your task, then only the linked pages you need.', + 'Fetch another section only when the task crosses sections or leaves a gap.', + '', + ...SECTIONS.filter((section) => sectionDocs(docs, section).length > 0).map( + (section) => `- [${section.label}](${base}/docs-index/${section.id}.md) — ${section.route}` + ), + ]; + return lines.join('\n') + '\n'; +} diff --git a/skills/pgflow-docs/SKILL.md b/skills/pgflow-docs/SKILL.md new file mode 100644 index 000000000..966f64897 --- /dev/null +++ b/skills/pgflow-docs/SKILL.md @@ -0,0 +1,41 @@ +--- +name: pgflow-docs +description: Access current pgflow documentation on demand. Use when installing or updating pgflow, authoring or running flows, deploying or monitoring workers, debugging runs, using pgflow APIs or configuration, integrating with Supabase, or checking releases and project news. +--- + +# pgflow Documentation + +Fetch current documentation from https://www.pgflow.dev instead of relying on +training data. Core docs define current behavior and APIs; news records what +shipped and when. + +## Route to the matching section index + +Fetch exactly one section index first: + +| Task | Section index | +|------|---------------| +| Install, quickstart, first flow, background jobs, FAQ | https://www.pgflow.dev/docs-index/get-started.md | +| Author flows: steps, dependencies, conditions, retries, versioning | https://www.pgflow.dev/docs-index/build.md | +| Deploy and operate: Supabase/Node/Bun workers, monitoring, troubleshooting, updates | https://www.pgflow.dev/docs-index/deploy.md | +| How pgflow works: execution model, data model, compilation, architecture | https://www.pgflow.dev/docs-index/concepts.md | +| APIs and configuration: client, context, compile, permissions, settings | https://www.pgflow.dev/docs-index/reference.md | +| Hands-on tutorials: RAG, AI web scraper | https://www.pgflow.dev/docs-index/tutorials.md | +| Release announcements: what shipped and when | https://www.pgflow.dev/docs-index/news.md | +| pgflow vs other workflow engines | https://www.pgflow.dev/docs-index/comparisons.md | + +Unsure which section fits? Start at https://www.pgflow.dev/docs-index.md. + +## Workflow + +1. Fetch the one section index matching the task. +2. Fetch only the linked pages the task needs — each entry names what the page + covers; skip the rest. +3. Fetch another section index only when the task crosses sections or the + first section leaves a gap. + +## Completion criterion + +Every pgflow-specific claim you rely on — API names, configuration options, +CLI commands, versions — is backed by a fetched documentation page, not by +memory. Anything not confirmed from a fetched page is unverified.