ID:WORK-150Status:done
Add content-model renderer for agent-facing rune docs
Priority:highComplexity:moderateAssignee:claudeSource:SPEC-041
No incremental history — criteria tracking started on Apr 20.
Today's describeRune() renders the flat reinterprets map (heading → headline). The declarative content model captures far more — ordering, optionality, repeats, named zones, headingExtract parsing — but no code converts it to readable prose.
Add renderContentModel(serialized) that walks a serialized content model (one of the four patterns: sequence, sections, delimited, custom) and emits agent-readable markdown describing the rune's input shape. This is the single new piece of code SPEC-041 introduces; everything else in the spec is plumbing or migration.
renderContentModel(model: SerializedContentModel): string exported from packages/runes/src/reference.tsmatch predicate (e.g., paragraph, heading, list|fence, any)headingExtract shape when present (e.g., "heading text is parsed as <time> — <location>")--- for hr), fields nested under each zone, distinguishes static zones from dynamicZonesdescription string verbatim under a "Content structure" headingdescribeRune() is updated to call renderContentModel() instead of (or in addition to, see WORK-152) the legacy reinterprets rendering pathpalette (sequence), character (sections), hero (delimited), and one custom runehero matches the example in SPEC-041 (delimited zones with content/media, eyebrow as paragraph in content zone, etc.)ContentModel shapes in packages/types/src/content-model.ts and the existing serializeContentModel from WORK-149 (it strips function fields like processChildren and headingExtract, so the renderer must accept that the function is gone but key metadata it produced — like headingExtract field names — should be retained on the serialized form).serializeContentModel if needed so the serialized form preserves enough metadata for the renderer (e.g., a headingExtract: { fields: ['time', 'location'] } summary instead of dropping the function entirely).renderContentModel with a dispatch over model.type (or pattern, depending on the serialized shape). Each branch emits a structured prose block.describeRune() to call the new renderer. For now, fall back to the legacy reinterprets path when no content model is registered (the fallback is removed in WORK-154 once the migration is complete).packages/runes/test/reference.test.ts.Completed: 2026-04-19
Branch: claude/scaffold-landing-docs-cli-DB31i
renderContentModel(model: SerializedContentModel): string plus four per-pattern renderers (sequence/sections/delimited/custom). Extended SerializedContentModel types (SerializedSequenceModel, SerializedSectionsModel, SerializedDelimitedModel, SerializedCustomModel, SerializedContentField, SerializedDelimitedZone, SerializedHeadingExtractField, SerializedKnownSection). Extended stripContentModel to preserve headingExtract (with regex patterns converted to source strings), knownSections (with hasModel flag), and implicitSection. Added optional contentModel?: SerializedContentModel to RuneInfo. Updated describeRune to render from contentModel when present, falling back to the legacy reinterprets path otherwise.renderContentModel plus all new serialized-model type aliases.any → "any block", heading:2 → "level-2 heading", tag:NAME → "NAME tag", list|fence → "list or fence".headingExtract RegExp patterns are serialized as their .source strings (not the RegExp itself) so the output is JSON-safe.Rune instances without contentModel, so they hit the legacy reinterprets fallback. WORK-155 will populate contentModel explicitly when building refrakt reference output.npm run build succeeds.