SEO/GEO audit, Literata fonts, layout and footer fixes#13
Conversation
Comprehensive SEO and GEO pass across all 17 posts: internal linking gaps closed, titles and summaries tightened, llms.txt updated with missing posts, llms-full.txt generation automated via build script. GEO improvements: lastModified frontmatter on all posts powering dateModified in Article schema, code block language tags, question-framed H3 subheadings for content chunking, new GEO optimization skill. Fonts switched from Fraunces + Instrument Serif to Literata for both display and serif roles. Homepage "hard parts" layout fixed (stacked number above heading). Footer now links to open source repo. Editorial rules updated: "Why does X matter?" banned as AI tell across CLAUDE.md, essay-authoring, khaliq-voice, and content-system skills. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR establishes a content framework for AI discoverability and cross-linked essay folio. It adds GEO optimization standards, enforces anti-slop writing patterns, builds an LLM-indexable full-text pipeline with generated header/footer context, extends post metadata with lastModified dates, updates ~20 posts with revised titles and internal cross-references, replaces typography from Fraunces/Instrument to Literata, and refreshes page layouts. ChangesWriting Standards & AI Discoverability Framework
LLM Full-Text Generation & Discoverability Infrastructure
Post Metadata & SEO Schema
Post Content & Cross-References
Typography & Layout Updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
content-system/llms-full-footer.md (1)
15-15: 💤 Low valueConsider restructuring for clarity.
The sentence "Before this, three years as the first hire at Nango..." is a fragment following a period. While comprehensible, it reads more naturally as a continuation.
✏️ Suggested revision
-Written by Khaliq Gant, co-founder of AgentWorkforce. Before this, three years as the first hire at Nango — the market leader in third-party integrations — where most of the work was getting the right context to AI: webhooks, normalized payloads, the long tail of provider quirks. +Written by Khaliq Gant, co-founder of AgentWorkforce. Previously spent three years as the first hire at Nango — the market leader in third-party integrations — where most of the work was getting the right context to AI: webhooks, normalized payloads, the long tail of provider quirks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@content-system/llms-full-footer.md` at line 15, Merge the fragment sentence "Before this, three years as the first hire at Nango ..." into the preceding sentence so it reads as a single, grammatically complete sentence; replace the period after "AgentWorkforce." with a comma and continue with the clause (or rephrase to "Before that, he spent three years as the first hire at Nango — the market leader in third-party integrations — where...") ensuring punctuation around the parenthetical dash/commas remains correct and the sentence flows naturally.scripts/generate-llms-full.mjs (2)
30-38: ⚡ Quick winAdd validation for empty posts array.
If no
.mdxfiles are found incontent/posts, the script will generate an empty essays section inllms-full.txtwithout warning. This could silently break LLM discoverability.🛡️ Proposed validation
const files = fs.readdirSync(POSTS_DIR).filter((f) => f.endsWith(".mdx")); +if (files.length === 0) { + console.error("Error: No .mdx files found in content/posts"); + process.exit(1); +} const posts = files🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/generate-llms-full.mjs` around lines 30 - 38, The script reads POSTS_DIR into files and builds posts (const files, const posts) but doesn't validate when no .mdx files are found; add a guard after computing files/posts to detect an empty array and either throw an error or log an explicit warning and exit so llms-full.txt isn't generated empty. Locate the block that defines files and posts in generate-llms-full.mjs (references: POSTS_DIR, files, posts) and implement a check like if (posts.length === 0) { processLogger.error(...) or throw new Error(...) / process.exit(1) } with a clear message about missing .mdx files so downstream generation fails fast.
72-75: ⚡ Quick winAdd error handling for file I/O operations.
The script will crash the entire dev/build pipeline if
writeFileSyncfails (e.g., disk full, permission denied). Since this script now runs before every Next.js build, consider wrapping critical operations in try-catch.🛡️ Proposed error handling
-fs.writeFileSync(OUT, output); -console.log( - `Generated llms-full.txt (${posts.length} essays, ${output.length} chars)` -); +try { + fs.writeFileSync(OUT, output); + console.log( + `Generated llms-full.txt (${posts.length} essays, ${output.length} chars)` + ); +} catch (err) { + console.error(`Failed to write llms-full.txt: ${err.message}`); + process.exit(1); +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/generate-llms-full.mjs` around lines 72 - 75, The file write and logging around fs.writeFileSync(OUT, output) run without error handling and can crash the build; wrap the write (and the subsequent console.log that reads posts.length/output.length) in a try-catch block, call fs.writeFileSync(OUT, output) inside the try, and in the catch log the error with console.error including the error.message and context (e.g., OUT) and exit with process.exit(1) (or rethrow) so the failure is surfaced cleanly; reference the OUT, output, and posts variables and the fs.writeFileSync call when applying the change.lib/posts.ts (1)
40-40: ⚡ Quick winNormalize
lastModifiedat runtime instead of asserting typeLines 40 and 59 use
as string, which only silences TypeScript and does not enforce runtime types. Sincegray-matterreturns untyped data,lastModifiedcould be a Date object (if YAML parses an unquoted date), an empty string, or another type. The current?? undefinedcheck only catches null/undefined, allowing invalid values into the JSON-LD schema.Current frontmatter data is well-formed, but defensive validation prevents future data quality issues:
Suggested patch
+function normalizeFrontmatterDate(value: unknown): string | undefined { + if (typeof value === "string") { + const v = value.trim(); + return v.length > 0 ? v : undefined; + } + if (value instanceof Date && !Number.isNaN(value.getTime())) { + return value.toISOString().slice(0, 10); + } + return undefined; +} + export async function getAllPosts(): Promise<PostMeta[]> { @@ - lastModified: (data.lastModified as string) ?? undefined, + lastModified: normalizeFrontmatterDate(data.lastModified), @@ export async function getPost(slug: string): Promise<Post | null> { @@ - lastModified: (data.lastModified as string) ?? undefined, + lastModified: normalizeFrontmatterDate(data.lastModified),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/posts.ts` at line 40, Replace the unsafe type assertions for lastModified in lib/posts.ts with runtime normalization: locate the places setting lastModified (the object field at the JSON-LD construction and the second occurrence around the metadata handling) and convert potential Date or string values into a validated ISO 8601 string or undefined; for example, if data.lastModified is a Date use toISOString(), if it's a non-empty string try new Date(value) and check !isNaN(date.getTime()) before using toISOString(), otherwise set undefined. Ensure both occurrences (the line building lastModified and the other metadata assignment) use this same normalization helper logic so only valid ISO strings or undefined are emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agentworkforce/workforce/skills/geo-optimization.md:
- Line 32: Replace the raw fenced markers in the sentence that currently reads
"typescript, ```markdown, ```json, etc.). Untagged code blocks..." with inline
code tokens wrapped in backticks so markdownlint MD038 is satisfied;
specifically locate the fragment containing the raw fenced markers (the sequence
including "```markdown" and "```json") and change them to inline literals like
`typescript`, `markdown`, `json` (keep the rest of the sentence intact),
ensuring the sentence begins with "Always tag code blocks with a language
identifier (`typescript`, `markdown`, `json`, etc.)."
In `@app/globals.css`:
- Around line 6-7: The CSS custom properties --font-serif and --font-display
contain the font fallback keyword "Georgia" which violates the
value-keyword-case rule; update the fallback keyword to lowercase ("georgia") in
both declarations so the values read like --font-serif: var(--font-literata),
ui-serif, georgia, serif; and --font-display: var(--font-literata), ui-serif,
georgia, serif; ensuring the value-keyword-case lint rule is satisfied.
---
Nitpick comments:
In `@content-system/llms-full-footer.md`:
- Line 15: Merge the fragment sentence "Before this, three years as the first
hire at Nango ..." into the preceding sentence so it reads as a single,
grammatically complete sentence; replace the period after "AgentWorkforce." with
a comma and continue with the clause (or rephrase to "Before that, he spent
three years as the first hire at Nango — the market leader in third-party
integrations — where...") ensuring punctuation around the parenthetical
dash/commas remains correct and the sentence flows naturally.
In `@lib/posts.ts`:
- Line 40: Replace the unsafe type assertions for lastModified in lib/posts.ts
with runtime normalization: locate the places setting lastModified (the object
field at the JSON-LD construction and the second occurrence around the metadata
handling) and convert potential Date or string values into a validated ISO 8601
string or undefined; for example, if data.lastModified is a Date use
toISOString(), if it's a non-empty string try new Date(value) and check
!isNaN(date.getTime()) before using toISOString(), otherwise set undefined.
Ensure both occurrences (the line building lastModified and the other metadata
assignment) use this same normalization helper logic so only valid ISO strings
or undefined are emitted.
In `@scripts/generate-llms-full.mjs`:
- Around line 30-38: The script reads POSTS_DIR into files and builds posts
(const files, const posts) but doesn't validate when no .mdx files are found;
add a guard after computing files/posts to detect an empty array and either
throw an error or log an explicit warning and exit so llms-full.txt isn't
generated empty. Locate the block that defines files and posts in
generate-llms-full.mjs (references: POSTS_DIR, files, posts) and implement a
check like if (posts.length === 0) { processLogger.error(...) or throw new
Error(...) / process.exit(1) } with a clear message about missing .mdx files so
downstream generation fails fast.
- Around line 72-75: The file write and logging around fs.writeFileSync(OUT,
output) run without error handling and can crash the build; wrap the write (and
the subsequent console.log that reads posts.length/output.length) in a try-catch
block, call fs.writeFileSync(OUT, output) inside the try, and in the catch log
the error with console.error including the error.message and context (e.g., OUT)
and exit with process.exit(1) (or rethrow) so the failure is surfaced cleanly;
reference the OUT, output, and posts variables and the fs.writeFileSync call
when applying the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b9e0252c-a2e7-499a-a996-01cea2861208
📒 Files selected for processing (35)
.agentworkforce/workforce/personas/operator.json.agentworkforce/workforce/skills/content-system.md.agentworkforce/workforce/skills/essay-authoring.md.agentworkforce/workforce/skills/geo-optimization.md.agentworkforce/workforce/skills/khaliq-voice.mdCLAUDE.mdapp/globals.cssapp/layout.tsxapp/page.tsxcomponents/site-footer.tsxcontent-system/llms-full-footer.mdcontent-system/llms-full-header.mdcontent/posts/agent-moves-first.mdxcontent/posts/building-weekly-digest.mdxcontent/posts/chatgpt-pulse.mdxcontent/posts/forty-two-percent.mdxcontent/posts/magical-agents.mdxcontent/posts/notion-ships-the-primitives.mdxcontent/posts/posthog-code.mdxcontent/posts/push-breaks-too.mdxcontent/posts/reactive-vs-proactive.mdxcontent/posts/review-agent-three-acts.mdxcontent/posts/the-genesis.mdxcontent/posts/the-prompt-cant-save-you.mdxcontent/posts/the-webhook-tax.mdxcontent/posts/the-wish-list.mdxcontent/posts/three-primitives.mdxcontent/posts/what-proactive-agents-cost.mdxcontent/posts/why-proactive-is-hard.mdxlib/posts.tslib/seo.tspackage.jsonpublic/llms-full.txtpublic/llms.txtscripts/generate-llms-full.mjs
There was a problem hiding this comment.
1 issue found across 35 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/generate-llms-full.mjs">
<violation number="1" location="scripts/generate-llms-full.mjs:24">
P2: The `import`/`export` stripping regexes remove lines inside fenced code blocks, corrupting code examples in the output. Protect code blocks before stripping MDX-level imports.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| md = md.replace(/^import\s+.*$/gm, ""); | ||
| md = md.replace(/^export\s+.*$/gm, ""); |
There was a problem hiding this comment.
🟡 stripMdxComponents strips import/export lines from inside fenced code blocks
The stripMdxComponents function uses /^import\s+.*$/gm and /^export\s+.*$/gm to remove MDX-level import/export statements. However, the m flag causes ^ to match at the start of any line, including lines inside fenced code blocks. This corrupts code examples in the generated llms-full.txt.
Confirmed impact: content/posts/reactive-vs-proactive.mdx:50 contains import { agent } from "@agent-relay/agent"; inside a ```typescript code block. In the generated public/llms-full.txt, this line is stripped, leaving the proactive agent code example starting with a bare agent({ call and no import — incomplete and misleading for any AI system citing it.
The same bug exists in the pre-existing scripts/generate-markdown.mjs:32 but since generate-llms-full.mjs is new code in this PR, it introduces the issue into the new llms-full output.
Prompt for agents
The stripMdxComponents function in scripts/generate-llms-full.mjs (and the pre-existing scripts/generate-markdown.mjs) uses regexes /^import\s+.*$/gm and /^export\s+.*$/gm to remove MDX-level import/export statements. These regexes also match import/export lines inside fenced code blocks (triple backticks), corrupting code examples.
The fix should make the stripping context-aware: only remove import/export lines that appear outside of fenced code blocks. One approach is to split the content by code block boundaries (``` delimiters), apply the import/export stripping only to non-code segments, and then reassemble. Another approach is to first extract and preserve code blocks (replacing them with placeholders), run the stripping, then restore the code blocks.
Affected files: scripts/generate-llms-full.mjs (lines 24-25) and scripts/generate-markdown.mjs (lines 32-33). The fix should be applied to both scripts since they share the same logic.
Was this helpful? React with 👍 or 👎 to provide feedback.
- Fix Georgia → georgia casing in CSS font fallbacks (stylelint) - Fix raw fenced markers in geo-optimization.md (markdownlint MD038) - Protect fenced code blocks from import/export stripping in generate-llms-full.mjs so code examples aren't corrupted - Add empty-posts guard and try-catch error handling to generation script - Add normalizeFrontmatterDate helper in lib/posts.ts for runtime safety on lastModified values - Fix fragment sentence in llms-full-footer.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
llms.txtwith 4 missing posts, automatedllms-full.txtgeneration via new build script (scripts/generate-llms-full.mjs)lastModifiedfrontmatter to all posts (poweringdateModifiedin Article JSON-LD schema), tagged untagged code blocks, added question-framed H3 subheadings for content chunking in oversized sections, created new GEO optimization skill--font-displayand--font-serifroles (Inter body + JetBrains Mono code unchanged)Test plan
lastModifiedin the page source JSON-LD (dateModifiedshould be2026-05-15)/llms-full.txtcontains full text of all 17 essays/llms.txtlists all 17 posts with correct titles🤖 Generated with Claude Code