Skip to content
internal/blog/posts
On this page

What makes a Markdown file a blog post: the required frontmatter, the directive declaration, the derived slug, and that slug's immutability once published.

#internal/blog/posts

#internal/blog/posts

Package posts discovers and validates a project's blog posts.

A post is a dated Markdown file with frontmatter, sitting under the posts directory. This package is the whole of what makes one a post: the required fields, the required directive declaration, the derived slug and its immutability once published, and the type and version keys a post carries without declaring them.

#Two callers, one meaning

[Discover] reads the files on disk; [Parse] takes one post's source as a string. The editor's render path calls Parse on a buffer that may never be saved, so both agree on what a post's source means -- there is one definition of a valid post rather than one per entry point.

#The refusals carry coordinates

Every refusal is a [PostError] naming the post's path relative to the posts directory, and the line inside the post file when the defect sits at one. The check surface turns one of these into a POST diagnostic, and a diagnostic's file and line are read by editors, CI annotations and the JSON output -- none of which parse prose.

#PostError

Go go
type PostError struct

PostError reports an invalid post, with the coordinates of where it is invalid.

Path is relative to the posts directory, as a Post's own path is. Line is the post file's own line number, or nil for a defect that sits at no particular line (a missing frontmatter field).

Code is the POST lint code the refusal is reported under when the refusal came from the frontmatter schema -- the missing title, the missing or misspelled date, the missing or non-boolean directive declaration. It is empty for the refusals this package decides on its own (the slug rules and the directive-marker scan), which the check surface codes by their message.

#Post

Go go
type Post struct

Post is one post's metadata, in the shape the build, the listing pages and the editor read it.

#ManifestPosts

Go go
func ManifestPosts(all []Post) []manifest.Post

ManifestPosts converts a whole discovery result for the manifest writer, keeping the order it came in.

#Parse

Go go
func Parse(raw, relPath, publishedSlug string) (Post, error)

Parse parses and validates one post's Markdown source.

relPath is the post's path relative to the posts directory; it is named in every refusal and carried on the result. publishedSlug is the slug this post was published under, when it has one -- a different derived slug is a slug immutability violation. Pass "" when the post has never been published.

#Discover

Go go
func Discover(postsDir, projectRoot string, handle *effects.Handle) ([]Post, error)

Discover discovers, validates and returns the posts under postsDir, sorted newest-first and then by slug.

A postsDir that is not a directory holds no posts, which is an answer rather than a failure.

projectRoot optionally names the repository the posts belong to. When it is given, slug immutability is enforced against the COMMITTED manifest read out of git HEAD rather than the copy on disk, because gen may already have rewritten that copy with the new slug by the time this runs. A directory that is not a repository, a repository with no commits, and a manifest that was never committed each leave the check with nothing to compare against, and it is skipped.

Files are read in sorted order, so a duplicate-slug refusal always names the same pair in the same direction. The Python walked in directory-listing order and could name either post as the second one.

#PostError.Error

Go go
func (e *PostError) Error() string { return e.Message }

Error renders the refusal.

#Post.ManifestPost

Go go
func (p Post) ManifestPost() manifest.Post

ManifestPost narrows p to the slice a project's manifest records.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search