π A self-maintaining docs pipeline: source β knowledge base β guides [NT-3625]#369
π A self-maintaining docs pipeline: source β knowledge base β guides [NT-3625]#369Tim Beyer (TimBeyer) wants to merge 43 commits into
Conversation
β¦rve) proseWrap: always reflowed every paragraph at printWidth 100, which split source pointers and sentences across lines mid-token. preserve leaves authored line breaks untouched β zero diff on existing files (all lines already β€100), and keeps a one-line source pointer on one line going forward. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦check validator
The internal SDK knowledge base recorded facts with free-text source pointers
(e.g. "source: accepted App Router guide" β circular; "OptimizedEntryResolver.ts:148-209"
β already drifted). Nothing verified them, so the base rots silently and guides
re-derive facts from scratch instead of relying on it.
Define a strict, machine-checked pointer grammar in the base README and enforce it
with scripts/validate-sdk-knowledge.ts (pnpm knowledge:check):
- Grammar tokens: <sdk>#<relpath>[#<symbol>], impl:<name>#<relpath>, concept:<slug>,
kb:<relpath>, extern:<free text>. Semicolon-separated. NO line numbers β symbols
are the anchor; line ranges drift on every edit.
- <sdk> keys are workspace package directory basenames, auto-discovered from
packages/**/package.json, so every SDK family is covered with no hardcoded map.
- Symbol resolution uses the TypeScript compiler API (already a dep) to confirm the
named identifier is actually declared β top-level decls, interface/type members,
and class members/methods. No new dependency, no build, no tsconfig graph.
- Also enforces: no pointerless facts, no circular guide pointers, and per-SDK files
match _template.md heading-for-heading.
- Split into scripts/sdk-knowledge/{markdown,source-symbols}.ts for readability.
Migrate the shared/ knowledge files (vocabulary, concepts, consistency-notes) onto
the grammar as the reference migration; they now pass knowledge:check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a knowledge-check job gated on a new `knowledge` paths-filter that fires when the SDK knowledge base OR any packages/**/src changes β deliberately NOT markdown-excluded, since the KB is markdown and a source refactor can invalidate a symbol pointer without touching a fact. This is the change-triggered propagation edge: a rename that orphans a recorded pointer fails on the same PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦owledge:check gate Update the sdk-knowledge-maintenance skill so its source-pointer rule references the machine-checked grammar (symbol-anchored, never line numbers, never a guide), and make `pnpm knowledge:check` the explicit finish gate. Note that CI runs it on packages/**/src changes, so a rename that orphans a pointer must be fixed on the same PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦se drift Team-shared .claude/settings.json Stop hook runs .claude/hooks/knowledge-check.sh: when the working tree touched the KB or packages/**/src this session and knowledge:check is failing, it feeds the (capped) report back as additionalContext so drift gets fixed in the same turn. Advisory only β always exits 0, never traps the agent in a stop loop. CI remains the hard gate; this is the in-session nudge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guide-authoring step 5 (verify APIs against source) now tells authors to check the internal knowledge base FIRST β reusing facts already verified against source instead of re-grepping β and to record newly verified facts back into it as a byproduct. This is the propagation edge that lets each guide rely on the KB rather than re-deriving everything from scratch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship the docs writer β newcomer reviewer β technical-foundation reviewer loop as
portable, standards-based artifacts (plain SKILL.md; no MCP/skill-kit dependency):
- skills/guide-newcomer-review β read a guide cold as an average developer with no
personalization background; report undefined jargon, skim-mode, unperformable steps,
dishonest labels. Reader-experience only.
- skills/guide-source-verification β prove every load-bearing SDK claim against
packages/**/src with file:symbol evidence, reuse KB facts already verified, and
record newly verified facts back into the knowledge base. This closes the capture
edge: the verifier's output IS the KB delta.
Add thin .claude/agents/{guide-writer,guide-newcomer-reviewer,guide-source-verifier}.md
wrappers (each points at its skill β the portable source of truth) and a
.claude/commands/review-guide.md that chains the roles and funnels durable learnings
back into the authoring skill (principles) or the knowledge base (facts).
Skills are discoverable via the existing skills/ β .claude/skills and .agents/skills
directory symlinks, so they work in Claude Code and any agentskills.io-compatible tool.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite every source pointer in web/nextjs-app-router.md to the machine-checked grammar, replacing the circular "source: accepted App Router guide" references (~6) with real symbols verified against packages/web/frameworks/nextjs-sdk/src (createNextjsAppRouterOptimization, OptimizedEntry, NextAppAutoPageTracker, the bound-component-types, request-handler, cookies helpers) and cross-SDK symbols in core-sdk / react-web-sdk / api-schemas. Drops all line-number anchors and the stray </content> artifact. pnpm knowledge:check passes for this file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prose `source:` matcher only reads tokens on the same physical line, so a pointer
wrapped onto a continuation line silently skipped validation (a fake symbol on a
wrapped line was not flagged). Add checkDanglingPointerLines: a line consisting only
of grammar tokens (each segment carries a `#` or known prefix) is flagged as a wrapped
pointer, enforcing the grammar's "one line per source pointer" rule. Prose
continuations ("The React Web guidesβ¦") have plain words and are not flagged.
Surfaced by the App Router KB migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦to single lines Follow-up to the App Router migration: put every prose `source:` pointer on its own single line (safe under proseWrap preserve) so all tokens are validated, and reword two extern: texts that contained the literal token separator. knowledge:check passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite all ~90 pointer tokens in web/web.md to the symbol-anchored grammar (pnpm knowledge:check passes). Beyond format, the migration re-verified every fact against source and corrected drift the old line/path pointers had hidden: - Moved-file paths: state/EventEmissionResult β events/, state/Consent β consent/, state/StatefulDefaults β package root, handlers/AcceptedCurrentStateTracker β tracking/; a referenced RootElement.ts that no longer exists β the real web-components/ContentfulOptimizationRootElement.ts. - Non-declared anchors re-pointed to their true declaring symbols (Zod object consts, ResolvedData in the resolver, LogLevels in api-client). - Circular "guide" pointers replaced with concrete symbols and concept: links. Multi-line behavioral facts keep their full assertions, now anchored to the enclosing symbol instead of drift-prone line ranges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite all ~90 pointer tokens in web/react-web.md to the symbol-anchored grammar (pnpm knowledge:check passes). Re-verification against source corrected drift the old pointers hid: - Re-export barrels (index.ts, api-schemas.ts, server-optimized-entries.ts) carry no declarations; repointed to the real declaring files (OptimizationRoot.tsx, contentful/typeGuards.ts, OptimizedEntrySourceController.ts, etc.). - Logger misattributed to core-sdk; createScopedLogger is declared in api-client. - Consent action drift: setConsent/identifyUser/resetUser are React hook names on UseOptimizationActionsResult; the SDK methods are consent/identify/reset β pointers now cite both layers. - Renamed constants (DEFAULT_WEB_ALLOWED_EVENT_TYPES, ANONYMOUS_ID_COOKIE) anchored correctly; circular "guide" pointers replaced with concept: links and real symbols. - Fixed escaped-pipe table cells that desynced the source column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite every source pointer in web/nextjs-pages-router.md to the symbol-anchored grammar (pnpm knowledge:check passes). ref-impl pointers β impl:nextjs-sdk_pages-router#β¦, cross-doc references β kb:shared/β¦, framework conventions β extern:, circular guide pointers and line ranges β verified file#symbol anchors in packages/**/src. With this the entire SDK knowledge base passes knowledge:check: every source pointer resolves to a real symbol and all per-SDK files conform to _template.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README.md and _template.md carried a leftover </content> tag at EOF (an artifact from the #358 knowledge-base scaffolding). Remove them so the meta files are clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The table-row parser split on every `|`, so a `Returns` cell like `ReactElement \| null` became two cells and its `null` could be misread as the source column. Split on unescaped pipes only and unescape `\|` within each cell (correct GFM semantics), so a union-type cell is one value. Both Next.js KB migrations had to work around this by rewording cells; now the parser handles them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore and expand the explanatory comments lost when the header was condensed to fit the line cap (the module split fixed the line count; this restores the docs). The validator does non-obvious work and was under-commented. - Header: the four checks (grammar/resolution/coverage/template), the two pointer carriers, per-file check scope, and why symbol anchors beat line numbers. - extractPointers/handleTableRow: the stateful source-column tracking across a table's rows and why it is cleared when the table ends. - checkSourceToken: the three resolution gates and which one catches a moved/renamed export. matchProseSource: why a wrapped pointer tail would be dropped. - coverage heuristics (itemHasPointerNearby, isCrossReference): what each rejects and why. - discoverSdkSrcRoots: why keys are derived not hardcoded, and the uniqueness invariant. - source-symbols.ts: parser-only rationale, declarations-not-re-exports (the barrel-file drift the migration caught), and why variable declarations are handled separately. No logic changes; knowledge:check still passes, lint and format clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦n't log it consistency-notes.md was a persisted work-log β "Open items to reconcile when guides 2 & 3 land", "Resolved when guides 2 & 3 closed", "Backport candidate" β the in-progress scratchpad from authoring the web guides in #358, committed as if it were knowledge. A file that catalogs known-broken/divergent things instead of fixing them is an anti-pattern, and it duplicated wording already owned by vocabulary.md/concepts.md. - Delete the file. Its one durable fact (control-variant selectedOptimization nuance) is already recorded in web/web.md; the "preview-panel" entry was never real drift (correct per-framework prefixes), now stated as a plain fact in the pages-router KB row. - Remove the "log cross-guide drift here" instruction from the sdk-knowledge-maintenance skill β the policy that produced the file β and replace it with "fix drift when you find it; shared wording lives once in shared/ and guides reuse it." Update the finish-checklist item to match. - Drop the file from the README org tree and the dead kb: pointer in the pages-router KB. knowledge:check passes; no dangling references remain. Follow-up (noted for the docs workflow): enforce cross-guide consistency with a deterministic check that greps the canonical shared phrases across a guide family, rather than any prose record. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦oints The docs-generation workflow, driven by a slash command chaining the three role agents (lowest machinery, human-in-the-loop). One command, two entry points: - /author-guide classifies the target: an existing guide (or an SDK that already has one) β refresh; an SDK with no guide β new. Both paths converge on the same review loop, so quality is identical either way. - Draft (guide-writer) β review loop (delegates to /review-guide: newcomer + source verification, concurrent) β gate (blockers fixed, claims corrected, knowledge:check + format pass) β funnel learnings back into the skill (principles) or KB (facts), fixing cross-guide issues rather than logging them. Back the new capabilities in the agents: - guide-writer now documents the refresh path (diff against the current skill; the tells of a pre-current guide β no Quick start / Before you start, monolithic flow, numbered headings, setup table, missing copy/adapt labels β and restructure preserving content). - guide-source-verifier now creates a KB family file (node/, native/) from _template.md when none exists, and flags that #symbol resolution is TS-only (Swift/Kotlin deferred). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The graph had only the sourceβKB direction (a fact's source: pointer). For incremental docs updates we also need the downstream direction: given a changed KB fact, which guide must be recomposed. Each per-SDK file now declares it with a `<!-- feeds-guides: documentation/guides/<guide>.md -->` marker under the title, and knowledge:check requires the marker and that its targets resolve. This is the missing edge that lets an incremental update scope a KB change to the exact guide(s) that consume it, instead of recomposing the whole family. Added markers to the four web KB files and the template; documented in the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦sion role The pipeline was missing the role that derives facts FROM source. Verification was guide-triggered (read a guide, re-derive its claims from source), which re-comprehends code per guide β the expensive path on every change. This skill makes the knowledge base a first-class derivation target from source, updated independently of any guide: - BOOTSTRAP: read an SDK's exported surface once into a new KB file. Expensive, once per SDK. - INCREMENTAL: a source change scopes the work β re-verify only facts whose source: pointer hits a changed file (knowledge:check finds broken pointers), capture new exports in that area, leave untouched facts alone. This is the cheap common case. The KB memoizes comprehension so guides and reviews read facts, not code. Ships the skill + sdk-knowledge-author agent. It reports which guides each change affects (via feeds-guides) so the composition step can scope the guide update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦e re-derivation guide-source-verification re-read packages/**/src to prove each guide claim, which re-comprehends code per guide β the expensive path the knowledge base exists to avoid. Reshape it into a consistency check against the KB: - A claim is confirmed when it matches a KB fact and knowledge:check passes (fast path). - A claim that contradicts a fact is a guide bug β correction to the writer. - A claim with no backing fact is ESCALATED to sdk-knowledge-author (which owns reading source) β the base gains the fact or the claim leaves the guide. The verifier no longer reads source itself (Edit dropped from its tools; it reports, it does not mutate). Bootstrap (SDK with no KB file) still runs after sdk-knowledge-authoring has created the file, so this review is always guide-against-KB, never guide-against-source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ssing facts The writer's step 5 and checklist told authors to verify every API "against the SDK source" β re-grepping packages/**/src per guide. Reshape composition to read the knowledge base as the source of truth: - Every SDK claim in a guide must trace to a verified fact in the KB; compose from facts, do not re-grep source. The reference impl is for shape/"adapt" starting points, not truth. - A missing fact is escalated to sdk-knowledge-authoring (which owns reading source), then composed from the fact it adds β comprehension stays in one place. - Updated the authoring skill step 5, the authoring-checklist source-verification item, and the guide-writer agent to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the two lifecycle modes into distinct commands so the cheap path is the default: - /author-guide is now the BOOTSTRAP path only β comprehend source into a new KB file (sdk-knowledge-author) FIRST, then compose the guide from those facts (guide-writer), then review. Knowledge first, guide second. Expensive, once per SDK. - /refresh-docs is the new INCREMENTAL path β the steady-state default. A source diff scopes the work: deterministically find affected KB facts (source: pointers into changed files) and the guides that consume them (feeds-guides), reconcile only those facts (sdk-knowledge-author incremental), recompose only the dependent guide passages, review only what changed. Never re-comprehends an SDK or rewrites whole guides. - /review-guide updated: the verifier does a KB lookup (confirmed/contradicts-KB/ no-backing-fact) and escalates fact gaps to sdk-knowledge-author instead of re-deriving from source or recording facts itself. This makes the knowledge base a compiler IR: comprehension is memoized once, and small source changes cost a scoped reconcile + recompose, not an 800k-token rebuild. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦e/ KB First run of the docs workflow (/author-guide) on a real target. Refreshes the Node integration guide from the pre-current structure (a `## Required setup` inventory table, change-narration prose) to the current archetype: plain-language five-sentence intro defining variant/experience/Experience API/resolving/baseline fallback/profile, two-milestone framing, `## Before you start` prerequisites with the authored-variant gotcha, a performable verify step, and a source-grounded `## Troubleshooting`. Bootstraps the first Node knowledge-base file (documentation/internal/sdk-knowledge/node/node.md) from _template.md: the stateless ContentfulOptimization + forRequest()βCoreStatelessRequest model, default ['identify','page'] allow-list, consent/canPersistProfile axes, trackView profile contract, locale precedence, request-scoped options, and the reader-owned ctfl-opt-aid cookie β each with a resolvable source pointer, feeds-guides set to this guide. Review: all 41 load-bearing claims traced to source; 2 newcomer blockers + 15 friction items fixed. knowledge:check and format clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦uthoring checklist The Node guide review surfaced durable reader-experience rules; encode them as mechanical checklist assertions (principles, not SDK facts) so future guides catch them upfront: result-envelope fields defined at first use; near-identical/shorthand identifiers disambiguated; SDK-constantβliteral-value mapping stated once; helpers defined or forward-pointed; no orphan cross-SDK API in warnings/tables; source-language switches announced with run instructions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦eserve Running proseWrap: preserve, the one file still carrying proseWrap: always drift (wrapped link-reference definitions) is normalized to single-line link defs. Content-free reflow β done once here deliberately so it cannot resurface piecemeal in unrelated diffs (it briefly rode along in the Node docs run before being reverted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Node-run feedback: subagents ran bare `pnpm format:fix`, which reformats the whole tree and pulled an unrelated concept doc into the diff (later reverted). The command gates said bare `format:fix`; make them explicitly pass the touched paths and warn against the bare form. The skills already used the `<file>` form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ap native/ KB Second workflow run, and the first exercising the RESHAPED bootstrap (knowledge-first) order: sdk-knowledge-author ran before guide-writer, and the writer escalated a missing fact instead of re-grepping source β proof the read-facts-not-code discipline holds. Bootstraps the first native/ family file (documentation/internal/sdk-knowledge/native/ react-native.md): async ContentfulOptimization.create() factory + OptimizationRoot/Provider, managed-vs-manual entry union, AsyncStorage identifier ownership, screen/viewport/tap tracking with verified defaults (minVisibleRatio 0.8, dwellTimeMs 2000, tap threshold 10), the RN ['identify','screen'] pre-consent allow-list, NetInfo/AppState/Expo quirks, and the SDK-fixed content-model fields (nt_experiencesβnt_experienceβnt_variants/nt_audience) β each with a resolvable source pointer, feeds-guides set to this guide. Guide: `## Required setup` table β `## Before you start`, teach-first intro, section order fixed to the archetype (22 TOC anchors resolve). Review: 0 contradicts-KB; 7 no-backing-fact claims all resolved by adding the backing fact from source (none unfounded); ~18 newcomer findings fixed. The onOfflineDrop callback shape differs from the iOS/Android sibling guides β left as a legitimate per-SDK difference (separate Swift/Kotlin source, unverified here). Also funnels the RN review's durable reader-experience rules into the authoring checklist (principles, not SDK facts): prose state-nouns defined at first use across sections; SDK-fixed content-model identifiers glossed with ownership; orphan rule extended to event names; render prop defined in prose; every promised proof verified; runtime root component as a diff not a paste-over; native build step inline in the quick start; Core sections open with a bridge not a recap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦vention, review overlap The React Native run surfaced real workflow-spec gaps; fix them: - Make "refresh an existing guide" a first-class sub-case of /author-guide. The command's premise is "the KB has no file yet" β but the guide often already exists (Node and RN were both 900+ lines). Step 2 now branches new-vs-refresh explicitly so an operator doesn't expect a blank compose. - Formalize the escalation handoff. The writer had invented an ad-hoc <!-- ESCALATE(sdk-knowledge-author): β¦ --> marker; define it in the author-guide/guide-writer skills and enforce that none survives to a finished guide β the validator now scans both the KB and documentation/guides/** and fails on any ESCALATE marker (and the CI knowledge filter now includes guides so a guide-only change triggers it). - Resolve the review-loop overlap. /review-guide is the self-contained review/fix/funnel/validate loop; /author-guide and /refresh-docs now delegate to it in ONE pass and only add their mode-specific gate checks, instead of describing the same loop twice. Also clarified it is a skill/command, not a stray file to hunt for. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ope beyond web Two RN-run hygiene findings: - The template's "Last verified against packages/**/src during the guide rewrite" boilerplate is untrue for a bootstrap (no rewrite happened). Reword mode-neutrally to "each with a source pointer verified against packages/**/src" across the template and all six KB files. - shared/concepts.md and shared/vocabulary.md were titled "web family", but a native/ family now consumes them and the concepts are genuinely SDK-neutral (they live in core-sdk). Widen the stated scope to the families that actually consume them, and drop the prose workaround the RN file had added to explain the mismatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # documentation/guides/integrating-the-node-sdk-in-a-node-app.md # documentation/internal/sdk-knowledge/shared/concepts.md # documentation/internal/sdk-knowledge/shared/vocabulary.md # documentation/internal/sdk-knowledge/web/nextjs-app-router.md # documentation/internal/sdk-knowledge/web/nextjs-pages-router.md # documentation/internal/sdk-knowledge/web/react-web.md # documentation/internal/sdk-knowledge/web/web.md
|
It might be worth having Alex take a look at this process. |
β¦s) vs behavior (from KB) The blanket "compose from the KB, never read source" rule conflated two different comprehension costs. Reading an INTERFACE β a symbol's existence, signature, prop/config names & types, optionality, union shape, return type, import path β is cheap, deterministic, and self-verifying: the type system is its always-current store, and copying it into the KB just duplicates what the compiler already guarantees. Tracing BEHAVIOR β fallback contracts, dynamic-render forcing, batching/chunking, defaults, identifier ownership, cross-SDK semantics β is the expensive, judgment-heavy work the KB exists to memoize. Refine the rule to: read interfaces directly from source/types; compose behavior from the KB; re-trace behavior never; escalate only a missing BEHAVIORAL fact. - optimization-guide-authoring: step 5 + checklist now source claims by kind, with the interface/behavior boundary spelled out (and the "don't re-trace under cover of a lookup" trap). - guide-writer: reads interface from source, composes behavior from KB, escalates behavioral gaps. - sdk-knowledge-authoring / sdk-knowledge-author: scope narrowed to behavior/ownership/defaults/ cross-SDK semantics β anchored to the interface symbol, but content is behavior, not signatures. - guide-source-verification / guide-source-verifier: two verification paths β interface checked against the types, behavior against the KB; escalate only behavioral no-backing-fact. - review-guide / author-guide + sdk-knowledge-maintenance: aligned wording. No validator change and no KB content change; knowledge:check still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce documentation/authoring/ as the structural source of truth for guides, separate from the authoring skill (voice) and the knowledge base (facts). A recipe per archetype (integration, decision, supplemental) holds the section spine; two fragments (personalization explainer, authored-variant gotcha) hold reusable reader-facing prose with knowledge-base-filled slots. Every recipe/fragment splits into ## Context (agent-only rationale + fill rules, never rendered) and ## Template (the prose that becomes guide output). Fragments are context-free definitions; the recipe is the caller that names which fragments it composes and where. An LLM (guide-writer) instantiates them β verbatim spine for fixed sentences, composed-around-a-fixed-anchor for SDK-shaped bullets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ture out of the skill Move guide structure out of the optimization-guide-authoring skill and into the new documentation/authoring/ recipes, resolving the two-sources-of-truth problem (skill refs vs. sibling guides). The skill now owns voice, the copy-vs-adapt honesty principle, and workflow only. - guide-writer agent: compose structure from the archetype recipe (authoritative over sibling guides); instantiate fragments verbatim, filling slots from the KB. - author-guide / review-guide commands: read structure from recipes; funnel durable structure/shared-prose findings to recipes/fragments, voice to the skill, facts to the KB. - guides/AGENTS.md: point at recipes for structure, the skill for voice. - Delete the three migrated reference templates (integration-guide-template, decision-and-recipe-templates, before-you-start); repoint authoring-checklist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ared/
A guide review escalated the "Experience API returns a profile" claim as
behavioral-no-backing-fact: it lived only in node/node.md, so the RN guide's
explainer had no backing fact in the base that feeds it, violating the
"capture each shared fact once in shared/" rule.
Add shared/concepts.md#experience-response-payload: an accepted Experience call
returns OptimizationData { profile, selectedOptimizations, changes }, and a
stateful SDK applies it to its signals. Reference it from native/react-native.md;
dedupe node/node.md to link the shared shape while keeping Node-specific
request-client behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦ment + checklist First full pipeline run on the new recipe/fragment system (RN guide). The writer realigned two intro bullets to the personalization-explainer fragment spine β reconciling real drift between two pipelined guides (RN "builds/keeps consistent" vs the canonical "returns/uses to keep consistent") β proving the primitive. Fix defects review surfaced in the new artifacts: - Fragment opener said "N sentences" but bullets are multi-sentence β "N points", with the four/five count tied to profile-bullet inclusion. - Composed entry-source bullet oversold freedom (a reorder license that invited the drift the writer then undid) β near-verbatim, fixed order. - Two new authoring-checklist rules: stated-count-matches-content, and a verify step's inspection mechanism must be present in the pasted quick-start code. Apply the RN reader-experience fixes: profile inbound/outbound consistency, performable screen-event verify (logLevel in the quick start), nt_experiences vs nt_experience gloss, changes/flag/merge-tag gloss, consent placement, include: 10 why. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦e-derivation Give technical writers a snappy inner loop: tune phrasing, tone, or sequence β or edit a recipe/fragment and re-render every guide that instantiates it β without running the whole pipeline. It recomposes only the affected prose from the EXISTING knowledge base; no source read, no fact re-verified, because a pure-prose change moves no fact. Hard-stop guardrail: the moment an edit would change what a guide asserts about the SDK, it stops and hands off to /refresh-docs (source changed) or /review-guide (verify vs the base) rather than silently drifting a fact. /review-guide remains the final gate. Wire it into the command family: cross-reference from /refresh-docs and surface it in the authoring README's Iterating section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦nd writers Add discoverable entry points to the agent-driven docs system without duplicating the self-documenting artifacts: - CONTRIBUTING.md gains a "Guides and the knowledge base" section under Documentation β the three-layer pipeline, the four slash commands and when to use each, and knowledge:check β pointing into the authoring and sdk-knowledge READMEs for depth (dev/contributor signpost). - documentation/authoring/README.md gains a "Start here (technical writers)" opening: edit a recipe/fragment, re-render with /iterate-guide, gate with /review-guide (writer front door). No new standalone doc; both are thin pointers, one source of truth per fact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦tch, coverage gap Three fmamud review findings on PR #369, all verified against the code: - Stop hook: `packages/**/src` matched zero files in `git status --porcelain` (empirically confirmed); use `packages/**/src/**` so a source-only change actually triggers the in-session knowledge:check. - Validator meta-file exemption used `path.basename`, which would also exempt a future nested `native/README.md` from all checks. Match by path relative to the knowledge dir via a new `isMetaFile()`, applied at both call sites. - Pointer coverage keyed off `line.endsWith('.')`, silently exempting any single-line fact not ending in a period (e.g. one ending in a backticked symbol) β narrowing the silent-rot guard. Replace with a fact-unit-end test (`isFactUnitEnd`) plus a label-lead-in skip; verified against the live KB (~122 wrapped facts still pass β no false positives) and that a period-less unsourced fact is now caught. Extracted the coverage shape-helpers (isFactUnitEnd, isLabelLeadIn, looksLikeFact, isCrossReference) into sdk-knowledge/markdown.ts to stay under the module line-cap β splitting into modules, per the repo rule, not disabling the lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
β¦ystem
Conformance pass (guide-writer per guide, then a cross-guide consistency review)
bringing Node, Web, React Web, Next.js App Router, and Next.js Pages Router into
verbatim instantiation of the personalization-explainer and authored-variant-gotcha
fragments. Narrow edits only β structure already conformed.
- Intro opener "N sentences" β "N points" across all five (five for Node, which
includes the profile bullet; four for the web family).
- Entry-source hand-off bullet normalized to the fragment: the fixed
baseline-fallback anchor made exact ("β¦the **baseline fallback**", not "which is
the β¦"), and the managed-fetch clause added where the KB confirms managed
fetching (React Web, App Router, Pages Router β the earlier intros omitted it).
- Fixed a parallel-run divergence: Pages Router kept the old "the SDK's only job
isβ¦" lead while gaining the managed-fetch clause (self-contradictory); aligned it
to the fragment's "β¦and the SDK sits at that hand-off:" lead, matching the other four.
knowledge:check green; format:fix per file; all TOC anchors resolve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦antifier The knowledge filter listed six mutually-exclusive positive globs while the paths-filter step runs with `predicate-quantifier: every`. Under `every` a changed file must match every pattern in a filter, but no path lives in two of these directories at once, so the filter was unsatisfiable and always emitted `false` β the `knowledge-check` job (guarded by `knowledge == 'true'`) could never run, even for a direct edit to the knowledge base. A local Stop hook running `pnpm knowledge:check` masked this by keeping the check green on dev machines. Collapse the six globs into one brace-alternation, matching the idiom of the other filters, so `every` is trivially satisfied and the job fires whenever a file touches any listed area. Preserves the deliberate no-markdown-exclude intent (the KB is markdown). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
195150c
β¦erbose The validator printed an identical β whether it checked the whole base or nothing, so a degenerate run (broken glob, misrooted scan) was indistinguishable from a real pass β the same class of silent-nothing failure that hid the dead CI filter. Always tally what was inspected into the success line (source pointers, fact files, ESCALATE-scanned files); "0 pointers" now stands out. Add opt-in `--verbose`/`-v` (or KNOWLEDGE_CHECK_VERBOSE) that logs every file and pointer as it is checked, and enable it in the CI job so the pipeline log shows the base was actually traversed. Local/default runs stay quiet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| @@ -0,0 +1,720 @@ | |||
| /* eslint-disable no-console -- This CLI prints a machine-readable validation report. */ | |||
There was a problem hiding this comment.
Repo instructions explicitly say not to add eslint-disable, eslint-disable-next-line, or eslint-disable-line unless requested. This adds a file-wide no-console disable. Please restructure CLI reporting to satisfy lint without the disable, or get explicit maintainer approval for the exception.
There was a problem hiding this comment.
Fair flag β AGENTS.md does forbid adding eslint-disable without explicit sign-off. Signing off explicitly here, for two reasons:
Precedent. Every CLI script in the repo opens with this same file-wide disable and a descriptive reason:
scripts/list-npm-package-targets.tsβ/* eslint-disable no-console -- This CLI prints machine-readable records and errors. */lib/mocks/scripts/generateTypeDefinition.ts,fetchEntries.ts,copyGeneratedTypeDefinition.tsβ/* eslint-disable no-console -- CLI */
This file follows that pattern exactly (line 1, with a reason). Restructuring this one alone would make it the odd one out among its siblings.
Reasoning. no-console guards against stray debug logging leaking into library/runtime code. For a CLI whose entire job is to print a validation report, console output is the intended behavior β so a file-wide disable is the honest scope: the whole file is a reporter, not a module with an incidental log. It's categorically unlike a rule such as no-explicit-any, where a disable would mask a real type-safety hazard; there's no accidental side effect being suppressed here, just an intentional output channel.
Leaving as-is on that basis.
Address PR review from Lotfi-Arif. - Coverage gap (validator): per-SDK files state facts as table rows, but checkPointerCoverage graded only list items, and pointer extraction skips a row whose source cell is empty or a β/none placeholder. So a table row could state an SDK fact with no pointer and pass. Add checkTableSourceCoverage: every non-divider body row under a `source` column must carry a real pointer. Lift the table-source walk both consumers share into markdown.ts (eachTableSourceCell) rather than duplicating it; extractPointers now reads cells through it and handleTableRow is gone. Behavior-preserving for extraction (still 670 pointers); verified an emptied and a β-placeholder cell now fail. - Stop hook: the validator scans documentation/guides/** for stray ESCALATE markers and CI triggers knowledge-check on guide changes, but the hook early-exited unless the KB or package source changed, so a guide-only session could leave an ESCALATE marker with no advisory. Add documentation/guides to the changed-path check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The problem we were actually solving
The previous docs PR (#358) produced good web-family guides and introduced three artifacts β public
guides, an internal knowledge base of verified SDK facts, and authoring skills. But they
were not tied into a cycle. Each was produced in a single agent context, and there was nothing to
stop them drifting apart from each other or from the SDK source. Two failure modes followed directly:
nothing noticed. We found real examples β a pointer to
OptimizedEntryResolver.ts:148-209that nolonger meant anything,
source: accepted App Router guidepointers that were circular (a "fact"whose evidence was another doc), and stale directory paths from files that had since moved.
scratch. That is fine once; as a steady-state practice it means a small SDK change triggers a
full, expensive re-comprehension of the codebase to rebuild docs β re-reading and re-reasoning
about the whole SDK because someone renamed a prop.
The ask was to look at this as a whole SDLC β not just the from-scratch authoring path β and make
the pieces flow into each other, mostly automatically, with as little bespoke machinery as possible.
The core idea: the knowledge base is a compiler IR
The design that fell out of the exploration treats the three artifacts as a compilation pipeline:
machine-checkable pointer back to the exact source symbol that proves it.
The expensive step is comprehension β reading source and understanding what is true. The knowledge
base memoizes that step. Everything downstream (writing guides, reviewing them) reads facts, not
code. This is the hinge the whole design turns on, and it is what makes the steady state cheap.
Why this resolves the chicken-and-egg
There is a real tension: to know what to document you must first understand the code, so you cannot
"just read the knowledge base." The resolution is that comprehension is bounded differently in each
mode, and only one mode is expensive:
/iterate-guideThe comprehension cost β reading and reasoning about the whole SDK β is paid at bootstrap and
amortized. A one-line SDK change touches a handful of facts and a handful of guide passages, because
the dependency graph tells us exactly which ones, instead of re-reasoning about everything.
The dependency graph is bidirectional and machine-checked
This is the piece that makes incremental scoping deterministic rather than guesswork:
source:pointer (e.g.core-sdk#CoreBase.ts#ContentfulConfig).Given a changed source file, we know exactly which facts are at risk.
<!-- feeds-guides: β¦ -->. Given a changedfact, we know exactly which guide must be recomposed.
Both directions are validated in CI (
pnpm knowledge:check): a pointer must resolve to a symbol thatactually exists in source (checked via the TypeScript compiler, not a regex), and a
feeds-guidestarget must be a real guide.
Design decisions, and the reasoning behind them
These were the forks in the road. They are where review attention is best spent.
Symbol-anchored pointers, never line numbers. Line ranges drift on every edit; a symbol name
survives refactors. The stale
:148-209pointer was the evidence. Pointers name a symbol(
file#Symbol) and the validator confirms it is actually declared β so a rename fails the buildinstead of rotting silently. Line numbers are gone from the grammar entirely.
A strict grammar, defined first, then enforced. Rather than write a parser to tolerate the
messy free-text pointers that existed, we defined a clean grammar (the KB's README owns it) and
migrated the base onto it. The migration was where the circular and stale pointers died. The
grammar is the contract; the validator checks it.
Deterministic where possible, agentic only where judgment is required. The dependency graph,
pointer resolution, template conformance, and drift detection are plain deterministic checks (a
~700-line TypeScript validator using the compiler API β no new dependencies). Only the genuinely
judgment-bound work β comprehending source into facts, composing teachable prose, reading a guide
as a newcomer β is done by agents. This keeps CI honest and cheap and reserves model cost for what
actually needs a model.
Standards-compliant over Claude-exclusive. We researched the landscape first. AGENTS.md, Agent
Skills (SKILL.md), and MCP are open, multi-vendor standards; subagent definitions and docs-drift
tooling are not. So the load-bearing parts are portable β skills live in a tool-neutral top-level
skills/dir (already symlinked into.claude/and.agents/), the validator is a normalpnpmscript, CI is plain GitHub Actions. The Claude-specific pieces (agent wrappers, a Stop hook) are a
thin convenience layer over that portable core, not the substrate. We deliberately did not
reach for MCP/skill-kit here: those add install friction and suit a distributed, customer-facing
product, not an internal CI loop.
Fix drift, do not log it. An earlier artifact (
consistency-notes.md) was a persisted work-logof "things to reconcile later." A file that catalogs known-broken things instead of fixing them is
an anti-pattern; it was deleted and the skill instruction that produced it was replaced with "fix
it now; shared wording lives once." Transient authoring state belongs to a workflow run, not a
committed file.
Do not compromise readability to satisfy tooling. When the validator hit a lint line-cap, the
fix was to split it into modules and keep every explanatory comment β not to strip the docs. The
validator is thoroughly commented precisely because it does subtle work.
Formalizing structure: recipes and fragments (the missing third artifact)
The original design closed the loop on facts β the KB is the machine-checked source of truth for
what the SDK does β but left structure unformalized. A guide's section spine, sequence, and shared
prose lived in two competing places: long-form rules inside the authoring skill, and the shipped
sibling guides themselves. An agent writing a new guide could anchor on either, and copying a sibling
was the easy pull β so "shared" wording survived only by imitation and drifted a little each time.
Meanwhile the KB was the fact authority but had no owner for editorial shape, and the natural person
to own that β a technical writer β had nowhere to work that wasn't either skill internals or a
single guide surgically.
This adds a fourth, writer-owned artifact under
documentation/authoring/, splitting structureout of the skill the same way the KB earlier split facts out of the guides:
recipes/, one per archetype) β the editorial spine: section order, which fragments aguide composes, and the rationale. The structural source of truth, authoritative over any sibling
guide.
fragments/) β reusable reader-facing prose (the "New to personalization?" introexplainer, the mandatory authored-variant gotcha) with a fixed spine and knowledge-base-filled
slots. This is the reader-facing mirror of the KB's
shared/β canonical wording captured once,instantiated per guide, rather than re-imitated.
Two design points worth scrutiny, because they mirror decisions already made for facts:
frontmatter with tokens like
opens_with: introβ which read as code but had no interpreter and noregistry the tokens resolved to. The consumer of this layer is the
guide-writeragent, whosenative interface is natural language; a fake-precision schema would be strictly worse than prose.
So recipes/fragments are prose, and each file splits into
## Context(agent-only rationale andfill rules, never rendered) and
## Template(the prose that becomes guide output). That splitis the interface/behavior distinction one layer up: Context is judgment, Template is the artifact.
slots filled), which is what keeps a guide family consistent β and makes a future check able to
confirm instantiation by matching the fixed sentence. But not every bullet is verbatim: where the
content is genuinely SDK-shaped (the entry-source hand-off differs by manual/managed/both), the
fragment marks the bullet composed β free prose around a fixed anchor (the baseline-fallback
clause) that must survive intact. This distinction was discovered empirically by running the
pipeline (see below), not designed up front.
A third slash command drives the recompose mode this enables:
/iterate-guideβ editorial-only: recompose affected guides from the existing KB and recipes,reading no source and re-verifying no fact, because a pure-prose change moves no fact. Its guardrail
is a hard stop: the moment an edit would change what a guide asserts about the SDK, it hands
off to
/refresh-docs(source changed) or/review-guide(verify vs. the base) rather thansilently drifting a fact. This is the technical writer's tight inner loop β tune wording/tone/
sequence, re-render, repeat β with
/review-guideas the final gate.Encoding the authoring process itself
The web-family guides in #358 were authored by agents, not by hand, and that work followed an
implicit division of labor: a writer, a reviewer reading as an average developer with no prior
knowledge, and a technical-foundation reviewer verifying every claim is true β with adjustments
funneled back into the skills. That division was never named or made repeatable; it lived only in how
the work happened to run. This PR makes it explicit as four roles, split so comprehension happens
exactly once:
sdk-knowledge-authorguide-writerguide-source-verifierguide-newcomer-reviewerThe important correction during design: verification is a consistency check against the knowledge
base, not a re-derivation from source. A guide claim is trustworthy when it matches a verified
fact; a claim with no backing fact is escalated to the knowledge author, not re-verified inline.
That is what keeps comprehension in one place.
Three slash commands drive the lifecycle modes:
/author-guideβ bootstrap: comprehend source into a new KB file first, then compose the guidefrom it, then review.
/refresh-docsβ incremental (the default): a source diff scopes a KB reconcile and aguide recompose, reviewing only what changed.
/iterate-guideβ recompose: editorial-only tuning from the existing KB and recipes, no sourceread (the technical writer's inner loop; see the recipes/fragments section above).
The loop is self-improving: durable review findings are funneled back into the right artifact β
reader/voice rules into the skill, structure and shared prose into the recipe or fragment, facts
into the knowledge base β never left as a TODO, so the next guide starts smarter.
Does it actually work? (three proofs)
We ran the workflow on the Node guide as the first real target. It:
node/knowledge-base file (the statelessContentfulOptimizationmodel,consent axes, cookie ownership, locale precedence β each with a resolvable source pointer);
## Required setuptable, change-narrationprose) to the current archetype (plain-language intro,
## Before you start, performable verifystep, source-grounded troubleshooting);
items;
format:fix), which are fixed here.The run passing
knowledge:checkand producing a genuinely better guide β while teaching us wherethe workflow itself was underspecified β is the evidence the loop is real, not aspirational.
React Native was then run as a second target, specifically to exercise the reshaped bootstrap
(knowledge-first) order. It held: the knowledge author ran before the guide writer, and the writer
escalated a missing fact rather than re-reading source β direct proof of the read-facts-not-code
discipline. It bootstrapped the first
native/family file, refreshed the guide to the archetype,resolved 7 no-backing-fact claims by adding each fact from source (none turned out unfounded), and
correctly declined to propagate an
onOfflineDropshape to the iOS/Android sibling guides, sincetheir Swift/Kotlin source is not yet verifiable here β refusing to compose an unfounded claim is
exactly the discipline working.
The RN guide was then re-run through the full pipeline again β this time against the new
recipe/fragment layer β and that run is the sharpest proof the composition primitive works. The
guide-writerrealigned two intro bullets to thepersonalization-explainerfragment spine, whichreconciled real drift between two already-pipelined guides (RN said the SDK "builds/keeps
consistent" a profile; Node's canonical wording, now the fragment, says "returns/uses to keep
consistent") β the fragment forcing a family back into one voice is precisely what it exists to do.
The same run surfaced three distinct classes of issue, each routed to the correct layer: a defect in
the new artifact (the explainer opener promised "five sentences" but its bullets are multi-sentence
β fixed to "points," and the empirical discovery that some bullets must be composed-around-an-anchor
rather than verbatim), a genuine KB gap (the "Experience API returns a profile" fact lived only in
node/node.md, leaving the RN claim unbacked inshared/β promoted toshared/concepts.mdandreferenced), and ordinary reader-experience fixes (a non-performable verify step, undefined
identifiers). Facts to the KB, structure to the fragment, reader rules to the checklist β the
funnel-back working as designed, on live output.
Then
mainmoved under us β and that became the best proof of all. While this branch was open,mainshipped a managed-CDA rework (#364) that renamed an entire API family:prefetchOptimizedEntriesβprefetchManagedEntries,OptimizedEntryPrefetchDescriptor/ServerOptimizedEntryHandoffβManagedEntryDescriptor/ManagedEntryHandoff,serverOptimizedEntriesβprefetchedManagedEntries, a movedgetOptimizedEntrySourceKey, plus newgetEntries()batching. Merging it produced conflicts in the knowledge base β and this is exactly thescenario the whole design exists for.
pnpm knowledge:checkpinpointed every source pointer therename had invalidated, so each stale fact was re-anchored to the current declaring symbol against
the merged source, and main's genuinely-new facts were folded in β rather than a human eyeballing a
2,400-line diff and hoping to catch every renamed symbol. The merge lands with the full base green.
This is the steady-state value proposition demonstrated involuntarily: source changed, the graph
found the affected facts, and drift could not slip through silently.
What is deliberately not done yet
Swift/Kotlin symbols. Those guides are not refreshed in this PR; the decision on a native
symbol-check (vs. file-level pointers) is deferred and called out in the skills.
greps canonical shared phrases across a guide family is noted as a follow-up.
guides:check(structural enforcement). The recipe/fragment layer is enforced today by theguide-writerand the review roles following it, not by a validator. Aguides:checkthat verifiesa guide instantiated its recipe's fragments (grepping the verbatim spines) and holds the fixed frame
order is deferred β the verbatim-spine and fixed-anchor decisions were made precisely to keep that
future check cheap. Structure is currently agent-enforced; facts remain machine-enforced.
recipe/fragment layer so far. The other pipelined guides (Node, Web, React Web, Next.js App/Pages)
already match the extracted fragment spines β since the fragments were extracted from them β but a
confirming
/iterate-guidepass per guide is a follow-up.decision guide (
choosing-the-right-sdk.md) are not refreshed here β the native four are blocked onthe symbol-check decision above; both are follow-ups that run through the same workflow.
How to review this PR
(symbol anchors, deterministic-vs-agentic split, standards posture), and the role decomposition.
documentation/internal/sdk-knowledge/README.md, therecipe/fragment model in
documentation/authoring/README.md, the validator inscripts/validate-sdk-knowledge.ts, and the skills/commands underskills/and.claude/. The"Guides and the knowledge base" section of
CONTRIBUTING.mdis the one-screen orientation.pnpm knowledge:checkproves every fact resolves to real source. If you want to spot-check, pick one fact in
node/node.mdornative/react-native.md, follow its pointer intopackages/, and confirm it istrue β that is the loop working.
Draft so the design can be discussed before the remaining guides are run through the same workflow.
π€ Generated with Claude Code