DOC-6939 Improve the AI-facing JSON and Markdown feed output - #3754
Conversation
The page claimed both feed files contain ~4,100 documents; the live feed has 2,643 (2,611 content + 32 index). The applied AI team's source assessment flagged it because a consumer taking the figure at face value concludes a third of the corpus is missing. Replaced it with a growth-safe phrasing instead of the true figure on purpose — the count climbs by roughly one a day (2,637 on their 31 July run, 2,643 five days later), so a hardcoded exact number is precisely what rotted last time. While verifying the feed I also refuted their observation 04, which claimed content_hash coverage is undocumented and might exclude code. Both halves are wrong, and the evidence is on this same page. The algorithm is published a few sections further down, and it hashes the summary plus every section text plus every example code block, reproducing the published hash for all 2,611 content pages including the 1,211 that carry examples. So a code-only edit does change the hash, and consumers can safely gate refresh on it alone rather than re-fetching to confirm. Their other four measurable claims reproduced exactly against today's feed (2,119 distinct ids for 2,643 URLs, 15 role values, 103 placeholder mismatches, 6 sections with residual shortcodes), so the rest of the assessment is trustworthy where it is measurable. Learned: content_hash provably covers examples[].code, so the feed hash is safe to gate a refresh on Constraint: the hash algorithm published on this page must track computeContentHash in build/transform_json_sections.ts Directive: do not replace "more than 2,600" with an exact count — an exact figure is what went stale here Rejected: emit the live count from build/generate_ndjson.py into a Hugo data file | needs a build change, disproportionate to a one-line docs fix Ticket: DOC-6939 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 8323ac4 |
Adds a markdown-table-children partial so the AI-facing outputs render the
same table the HTML shortcode does, and fixes the shortcode-strip regex that
let six of them leak raw into the feed.
The reported symptom was six release-notes pages carrying unexpanded
shortcodes, which looked like a shortcode missing from the pipeline's
allowlist. It isn't. 101 pages use table-children, and the six that leak are
exactly the six whose attributes contain a literal ">" -- their columnNames
use <br/> to wrap header text. The strip pattern was a [^>]* class, which
cannot cross that ">", so the match fails and the whole shortcode survives
verbatim. Made it non-greedy to the first ">}}" instead.
Fixing the strip alone would have been the wrong fix, and finding out why is
the useful part. The other 95 pages were being stripped "successfully" and
silently losing the entire table -- and on the release-notes index pages the
table IS the body, so those pages reached the feed with either nothing or a
dangling colon ("Here are the most recent changes for Redis Insight:" followed
by no changes). The table's version columns are worse: children[] carries only
id, summary, title and url, so the minimum Redis and cluster versions existed
nowhere else in the feed. The leak was the visible 6% of a defect affecting all
101 pages, and only the leak was reported because a shortcode-shaped regex is
what anyone checks for.
The leading newline in the replacement is deliberate and load-bearing. Without
it, on pages whose whole body is the shortcode, the table opens on the same
line as the closing fence of the Markdown metadata block, which stops that
being a valid closing fence and swallows the table into the code block. That
pre-existing fusing affects 1,383 pages site-wide and is not fixed here; the
newline just keeps this change from adding to it, and happens to clear 7.
Verified by building the site twice, before and after, and diffing every
page's JSON content: 5,631 byte-identical, 101 changed, 0 changed that don't
use the shortcode. Markdown row counts match the HTML table row counts on all
101, including both limitTags filters and the one from= indirection.
Learned: the six leaking pages were the visible edge of a defect dropping the table from all 101, so the reported symptom understated it by 16x
Constraint: never match shortcodes with a [^>] class -- attribute values legitimately contain > (columnNames uses <br/>)
Directive: keep the leading newline in the table replacement or the table gets swallowed by the metadata block's closing fence
Rejected: fix only the strip regex | makes the 6 consistent with the other 95 but leaves all 101 tables missing from AI output
Gaps: the 1,383-page fused closing fence in .md output is pre-existing, unfixed, and needs its own change
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 9d1b68d |
…tput
The closing fence of the metadata block was landing on the same line as the
first line of page content, on 1,223 of 5,732 pages. A closing fence may be
followed only by whitespace, so it stopped closing anything: 860 pages went out
with an unterminated code block, and on the rest a later bare fence closed it
but inverted the open/close role of every fence after it, so real code examples
and prose swapped places.
The cause is whitespace trimming, not the content. Both single.md and section.md
had the closing fence, a literal blank line, and then a template comment opening
with a left-trim marker. That trim eats all preceding whitespace, so it removed
the blank line and the fence's own newline, leaving the fence flush against the
content. The two now emit the separator explicitly instead of relying on literal
blank lines that a trim marker can reach.
Two dead ends worth recording. Simply dropping the dash to stop the trim does
not compile: a Go template comment must open as "{{/*" or "{{- /*", and "{{ /*"
is a parse error. And even with the valid no-trim form, the following line's own
left-trim reaches back through the blank line and re-fuses the fence, so no
arrangement of literal whitespace is safe here.
I also had the cause wrong at first, in a way worth flagging: the injected code
examples legend looked like the trigger, since it is what appears fused on the
client pages. It accounts for only 120 of the 1,223. The trailing text is
whatever each page's content happens to begin with, so this was never specific
to pages carrying code examples.
Verified by diffing the Markdown output of a full build before and after, with
the edited region normalised on both sides: 5,732 files identical outside it,
zero collateral change. Fused fences 1,223 to 0, unterminated blocks 860 to 10.
Note that Python-Markdown is a misleading oracle here because it is not
CommonMark-compliant and reported the fix as making things worse; the number
above comes from applying the fence rules directly.
Learned: a Go template comment must open as {{/* or {{- /*, and "{{ /*" is a parse error, so a trim marker cannot be removed from a comment without restructuring
Constraint: never separate the metadata block from page content with literal blank lines -- a following trim marker will reach back and eat them, so emit the newlines explicitly
Directive: verify Hugo output against a clean build, as hugo does not empty public/ and stale alias files survive to produce phantom findings
Rejected: dropping the left-trim marker from the comment | "{{ /*" does not parse, and the valid form still leaves the next line's trim to re-fuse the fence
Gaps: 10 pages still emit an unterminated fence from source authoring errors (7 kubernetes logs pages and rdi installation/ha-test fuse a command onto its opening fence; 2 search-and-query pages have an odd fence count)
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 60847d4 |
Correction to the
|
Two separate defects sat behind the reported placeholder/examples mismatch, and
neither is the cause the assessment guessed (a placeholder emitted for non-code
blocks such as command output or tables).
The first is small. extractCodeBlocks returned the [code example] placeholder
unconditionally but only recorded an example when the block had content, so an
empty fenced block left a placeholder with nothing in examples[] to resolve it.
16 sections. It now drops the placeholder along with the empty block.
The second is the interesting one and explains the mismatches that ran the other
way, which the assessment could not account for. Section ids were not unique
within a page: 440 pages carried at least one collision. slugify discards
punctuation, so headings that differ only in punctuation reduce to the same
string -- commands/xread has "The special `$` ID." and "The special `+` ID", both
becoming the-special-id. Because examples key off the section id, the two
sections' examples merged into one bucket of 4 while each section's own text held
only 2 placeholders, which is what surfaced as more examples than placeholders.
The knock-on effects were worse than the count mismatch. examples[].id is built
from the section id, so it was not unique either: 131 pages had colliding example
ids, up to 5 examples sharing examples-ex0. Deep links of the form
url#section-id were ambiguous, and any consumer keying sections by id silently
dropped one of a colliding pair. Ids are now disambiguated with Hugo's own -1,
-2 suffix convention.
One divergence from Hugo is deliberate: where a page has intro prose and also an
"Overview" heading, the synthetic intro section keeps the overview id and the
real heading becomes overview-1, whereas Hugo would give the heading the plain
anchor. Keeping the established id for the intro section matters more to existing
consumers than matching the anchor in that narrow case.
Mismatches counted per section object drop from 739 to 1, duplicate section ids
from 440 to 0, duplicate example ids from 131 to 0. The single remaining
mismatch is not a defect: ai-agent-resources documents the feed format and so
writes the literal token in prose, which any placeholder count will pick up.
Confirmed content_hash still reproduces from the published algorithm for all
5,687 content pages.
Learned: transform_json_sections.ts is not idempotent -- it strips the content field it reads, so a second run silently turns every content page into an empty index page with no error
Constraint: section ids must be unique within a page -- they join examples[].section_id, build examples[].id, and are the target of url#section-id deep links
Rejected: aligning slugify with Hugo's anchor algorithm outright | changes ids on far more than the colliding pages, so it belongs with the anchor-parity work, not here
Gaps: section ids still do not match rendered page anchors in general -- 79.6% on a 400-page sample -- and the {#explicit-anchor} heading defect leaking into 4,363 section titles is untouched
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 3e64858 |
Headings using Hugo's explicit anchor syntax were not parsed, so the literal
brace expression was treated as part of the heading text. For "## Specify socket
file location during install {#during-install}" the feed published the syntax
inside the user-visible title, an id of
specify-socket-file-location-during-install-during-install, and therefore a dead
deep link, while the anchor Hugo actually rendered was during-install. 4,363
sections across 773 source files. The title is now the heading without the
anchor, and the id is the anchor itself.
The explicit anchor is used verbatim rather than slugified. Hugo takes it as the
rendered id unchanged, so passing it through slugify would produce a different
string from the anchor on the page and defeat the point of the change.
The match is deliberately strict, and this is the part worth knowing before
touching it. Many headings end in a brace that is not an anchor: 725 Python
signatures ending in an empty dict, 280 dict defaults such as extra ignore, and
145 or more relref shortcodes. A loose trailing-brace match would silently eat
all of them from the titles. Measured across all 40,951 level 2 and 3 headings,
the strict form and a loose form happen to agree today at 4,368 matches with no
corrupting cases, and no match sits inside a code span -- but the strict form is
what keeps that true when someone writes a heading ending in a dict literal.
Anchor parity on the same seeded 400-page sample used to measure the problem
rises from 79.6% to 91.9%. The residual is a separate, well-understood
difference between slugify and Goldmark rather than anything left of this defect:
Goldmark keeps dots and underscores where slugify replaces them, so
application.properties becomes application-properties, and it drops apostrophes
where slugify hyphenates them, so "What You'll Learn" becomes what-you-ll-learn
against Goldmark's what-youll-learn. Closing that means aligning slugify with
Goldmark, which moves ids on far more pages than this and is tracked as the
remainder of C7 on the ticket.
Verified the A3 invariants still hold: duplicate section ids and duplicate
example ids both remain at zero, and content_hash still reproduces from the
published algorithm for all 5,687 content pages.
Learned: most braces in a heading are not anchors, so the anchor pattern has to be shape-checked rather than matched as a trailing brace, or 1,000-plus Python signatures and dict defaults lose text from their titles
Constraint: an explicit heading anchor must be used verbatim as the section id, never slugified, or it stops matching the anchor Hugo renders
Gaps: 8.1% of section ids still differ from the rendered anchor because slugify and Goldmark disagree on dots, underscores and apostrophes
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 95bcfb1 |
Three fixes from the Bugbot review of this branch. The third is the substantive
one and it was worse than reported.
Bugbot flagged that the metadata tableOfContents ids no longer agree with the
JSON sections[] ids now that sections carry explicit anchors and dedup suffixes.
True, but on reading toc-from-markdown.html the TOC turned out to still have both
of the defects just fixed on the JSON side: the literal brace expression sitting
in its user-visible titles, and the anchor concatenated onto the slug instead of
replacing it. So change-location-socket-files was publishing a TOC entry titled
"Specify socket file location during install {#during-install}" with the id
specify-socket-file-location-during-install-during-install. The same strict
anchor pattern and the same suffix convention now apply there, taking brace
leakage in TOC titles to zero and duplicate TOC ids to zero.
That leaves a divergence worth knowing about before touching any of this: there
are now three separate slug implementations in play, and they disagree. The TOC
strips inline formatting then deletes anything outside a-z0-9 and hyphen, so
Pub/Sub becomes pubsub; slugify in the transform replaces punctuation runs with a
hyphen, so the same heading becomes pub-sub; Goldmark does a third thing.
About 9.2% of TOC ids still do not appear in sections[] for that reason alone.
Consolidating the three is the same work as the anchor-parity item on the ticket.
The table-children partial no longer falls back to the current page's children
when a from= target fails to resolve, which would publish a plausible-looking
table of the wrong data. Bugbot's stated reason for this one does not hold up --
it says the HTML shortcode renders an empty table in that case, whereas the HTML
dereferences .Pages on a nil page and should error instead. I did not run it to
confirm, and the fix does not depend on which is right, since silently emitting
another page's rows is wrong either way.
The partial also now emits the header row when every row is filtered out,
matching the HTML shortcode, which always renders thead. Unreachable today, as
all 101 pages using the shortcode have rows, but a table that filtered down to
nothing would otherwise blank the entire body of the release-notes index pages,
which is the defect this branch started from.
One process note, because it nearly went into a report as a regression: the
row-count parity check reads the page's content field, and the transform strips
that field. Run after the transform it silently compares against an empty string
and reports all 101 pages as broken. Re-pointed at sections[] text it is 101 of
101 matching, with residual shortcodes still at zero.
Learned: three slug implementations now exist across toc-from-markdown.html, transform_json_sections.ts and Goldmark, and they disagree on punctuation, which caps TOC-to-sections id agreement at about 91%
Constraint: the explicit-anchor parsing and the id dedup convention are implemented twice, in toc-from-markdown.html and in build/transform_json_sections.ts, and must be changed together or the metadata TOC and the JSON sections drift apart again
Directive: when verifying this pipeline, read the artifact for the stage you are at -- the transform deletes the content field it consumes, so a post-transform check of content compares against empty and reports false failures
Rejected: Bugbot's premise that the HTML shortcode renders an empty table for an unresolvable from= | it dereferences .Pages on nil and should error, so the fix rests on avoiding silent wrong data instead
Gaps: about 9.2% of TOC ids still differ from sections[] ids purely from the competing slug implementations, unfixed here
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at e974a94 |
The script consumes the content field and does not write it back, so running it twice over the same output was silently destructive: the second pass found no content and rewrote every content page as an empty index page, with no error and no warning. Normal builds are safe because the Makefile always chains it after a fresh hugo, but nothing stopped a re-run, and the failure mode is total loss of feed content. It cost two rebuilds during this branch. A file that has a page_type but no content has already been transformed, so it is now skipped, and the run reports how many it skipped rather than staying quiet about it. The obvious alternative -- writing content back so the script really is idempotent -- was rejected. Replacing content with sections and examples is the whole purpose of the transform, and keeping the original alongside them would roughly double the size of every page record and of the feed. Verified by building once and transforming twice. The second run reports 5,732 skipped and 0 transformed, and the output still holds 5,687 content pages, 46,348 sections and 29,319 examples, with content_hash verifying on all 5,687. Before this change the second run left zero content pages. Learned: the failure was silent because an already-transformed page is structurally valid input, just with no content, so the index-page branch accepted it happily Rejected: writing the content field back to make the script truly idempotent | replacing content with sections is the point of the transform, and keeping both would roughly double the feed size Ticket: DOC-6939 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 501e77f |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 501e77f. Configure here.
Reverses a decision made deliberately in 3e64858, which reasoned that where a page has intro prose and also an "## Overview" heading, the synthetic intro should keep the plain overview id and the real heading should take overview-1, because the established id mattered more to existing consumers than matching the anchor in that narrow case. That reasoning was wrong and the commit message for it should be read with this one. What it missed is that the mismatch is not only against Hugo. On the 166 affected pages Hugo renders the anchor overview for the real heading, and the metadata tableOfContents also gives overview to the real heading, so sections[] was the only one of the three naming the intro prose. A consumer following a url#overview citation landed on the intro instead of the section it asked for. That is a wrong answer rather than an id-stability preference, so it wins. Real heading ids are now reserved before the synthetic intro is named, so the heading keeps overview and the intro takes overview-1. Same id vocabulary, no new values, and the suffix now falls on the section that has no anchor on the page at all. All 166 pages check out: the intro holds the suffixed id and the plain overview is a real heading that Hugo has anchored. Also adds the + unescape that the post-table-children block was missing relative to the block above it. Unreachable today -- no built page contains that entity, no table-children column name or row contains a plus -- so this is closing an inconsistency, not fixing an observed defect. A version column reading "6.0+" would have exposed it. Duplicate section ids, duplicate example ids and brace leakage into titles all remain at zero, and content_hash still verifies for all 5,687 content pages. Worth knowing for the next verification: the check I first wrote for this used the section role to tell the synthetic intro from a real heading, which is meaningless, because assignRole gives a heading titled "Overview" the role overview too. It reported 165 of 166 still broken when the fix was already correct. Inspecting one page's section list in order settled it in seconds. Learned: the intro section holding the plain overview id mis-resolved url#overview citations on 166 pages, because Hugo's anchor and the metadata TOC both give that id to the real heading Constraint: reserve the ids of real headings before naming synthetic sections like the intro, or the intro takes an id that belongs to a heading and deep links resolve to the wrong section Rejected: keeping the plain overview id on the synthetic intro for consumer stability, as 3e64858 did | it breaks the real heading's deep link, and the intro has no page anchor to protect Ticket: DOC-6939 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 317acdc |
…hat hid three sections Two Bugbot findings on the TOC partial, plus a content bug that following the second one uncovered. The TOC was built from every ##/### line in the raw markdown, including lines inside fenced code blocks, while splitContentIntoSections skips those. A YAML comment reading "## the defaults are commented" was therefore published as a navigation entry on the context-retriever install page. The TOC now drops fenced blocks before matching headings, using the same pairing rule as the transform so the two agree on what counts as code. The id dedup loop was also bounded at an arbitrary 50, where the TypeScript side is unbounded. Not reachable today -- the largest same-base-id group on any page is 19 -- but an arbitrary cap fails by silently leaving duplicates. It is now bounded by the heading count, which a page can never exceed. The content bug is the more valuable find and it runs the opposite way to what Bugbot described. On integrate/redis-data-integration/installation/upgrade a fenced block inside a warning shortcode was never closed. Hugo does not mind, because the shortcode body ends and the HTML renders correctly with every heading present, so nothing looked wrong on the site. The transform pairs ``` marks naively, so it paired that opener with the next block's opener and treated two large spans as code, silently dropping three real sections from the feed: Upgrading a Kubernetes installation, Upgrading to RDI 1.8.0 or later from an earlier version, and Verifying the upgrade. That page went from 5 sections to its full 8 once the fence was closed. Order mattered here. Aligning the TOC with the transform's code-block rule while the source was still broken would have made the TOC drop those three headings too, so the two artifacts would have agreed by being equally wrong. The source is fixed first for that reason. TOC entries with no counterpart in sections[] fall from 60 to 53, duplicate TOC ids stay at zero, and the section-id and example-id invariants and content_hash verification are unchanged. My fence detection was wrong for the fourth time in this branch on the way here: of four headings it reported as sitting inside code blocks, three were not, all on this same RDI page, because an indented fence it cannot model flipped its state. Comparing the built TOC against the built sections[] -- artifact against artifact -- found the real one and the dropped sections that a source-parsing heuristic had missed entirely. Learned: a source fence that Hugo renders correctly can still break the feed, because Hugo closes it at the shortcode boundary while the transform pairs backtick marks naively -- so "the page looks right" is not evidence the feed is right Constraint: the TOC partial and splitContentIntoSections must use the same rule for what counts as a fenced code block, or their ids diverge again Directive: never bound the id dedup loop by a round number -- bound it by the heading count, since an arbitrary cap fails by leaving silent duplicates Gaps: 53 TOC entries still have no counterpart in sections[], now from a different defect -- the TOC's emphasis-stripping regexes mangle code-like headings, turning a Python signature into "class AsyncSearchIndex(schema, , redisurl=None)" Ticket: DOC-6939 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🧠 Redis MemoryFound 5 related items from repository history:
Memory updated at 33353bf |
|
Thanks @dwdougherty ! |

Fixes content loss and id defects in the AI-facing JSON and Markdown output, from DOC-6939.
Why
The applied AI team assessed our three machine-readable docs sources as ingestion candidates for the redis.io MCP server (
redis_docs_source_assessment.pdf, measured 31 July 2026 againstredis/docs@4d51073). This PR addresses the defects that landed on us as publisher, plus several bigger ones found while diagnosing them.What changed
Content that was missing from the feed entirely
table-childrentable from AI outputtable-childrenshortcode had no handler in the AI output pipeline, so it was stripped. On the release-notes index pages the table is the body, so those pages reached the feed empty or with a dangling colon, and the table's version columns (min-version-db,min-version-rs) appear nowhere else in the feed. Newmarkdown-table-children.htmlpartial renders it as a Markdown table.[^>]*class, which can't cross the literal>incolumnNames="…<br/>…". Now non-greedy.single.md/section.md, a trim marker was eating the newline after the metadata block's closing fence, so content started on the fence line. A closing fence may be followed only by whitespace, so it stopped closing anything.integrate/redis-data-integration/installation/upgradehad a fenced block inside awarningshortcode that was never closed. The HTML renders fine (Hugo closes it at the shortcode boundary), but the transform's naive backtick pairing treated two large spans as code and silently dropped three real sections.Section and example ids
{#anchor}leaking into the titleslugifydiscards punctuation, so headings differing only in punctuation collided —commands/xreadhasThe special `$` IDandThe special `+` ID, both becomingthe-special-id, merging their examples into one bucket. Ids are now disambiguated with Hugo's-1/-2convention.## Title {#during-install}published the literal{#…}in the user-visible title and an id oftitle-during-install, breaking the deep link. The anchor is now used verbatim as the id.[code example]placeholder with no corresponding example.ai-agent-resourcesdocuments the feed format and writes the literal token in prose.Metadata table of contents
The TOC in the Markdown metadata block had the same anchor and duplicate-id defects as
sections[], and additionally built entries from##lines inside fenced code blocks — so a YAML comment reading## the defaults are commentedwas published as a navigation entry. Both are fixed, and the dedup bound is now the heading count rather than an arbitrary 50.Build robustness
transform_json_sections.tsconsumes thecontentfield and doesn't write it back, so a second run rewrote every content page as an empty index page, silently. It now skips already-transformed files and reports the count.What a reviewer should focus on
The line count is modest but the output blast radius isn't:
sections[].idvalues change on 440 pages (dedup) and 4,363 sections (explicit anchors). Ids are a consumer-visible contract and are used forurl#section-iddeep links..mdoutput gains a separator after the metadata block (5,732 files).## Overviewheading, the real heading keepsoverviewand the synthetic intro takesoverview-1. Hugo's anchor and the TOC both giveoverviewto the real heading, so doing it the other way mis-resolved#overviewcitations on 166 pages.Verification
Full
hugobuild plus transform, before and after, diffed per page:table-childrenchange; the 101 that changed are exactly the ones using the shortcode, 0 collateraltable-childrenMarkdown row counts match the HTML table row counts on 101/101 pages, including bothlimitTagsfilters and the onefrom=indirection.mdoutputcontent_hashstill reproduces from the published algorithm for all 5,687 content pagesOne assessment finding is refuted
Their observation 04 says
content_hashcoverage is undocumented and may exclude code, so a code-only edit wouldn't change the hash. Both halves are wrong: the algorithm is published onai-agent-resources.md, and it hashes the summary plus every section text plus every example code block, reproducing the published hash for all 5,687 content pages including the 1,211 carrying examples. Consumers can gate refresh oncontent_hashalone. That item has been withdrawn on the ticket rather than worked.Not in this PR
slugifywith Hugo's anchor algorithm, which would close the remaining 8.1% anchor gap but moves ids well beyond the pages touched here (ticket item C7)**kwargsbecoming*kwargsandredis_urlbecomingredisurl(ticket item D3, filed from this work)rolevocabulary), which change no outputFour rounds of Cursor Bugbot review are addressed. Two of its findings were valid and one changed a decision I'd made deliberately; the reasoning is in the individual commit messages.
🤖 Generated with Claude Code