Freshness
Every page can declare who owns it, when it was last updated, how often it should be reviewed, and where the sources of truth live. kazam turns that into a banner — yellow if a review is due within a week, red if it's already overdue — and a build-time report of everything that needs attention.
One block of YAML per page
Add a freshness: block to any page's YAML. Every field is optional — a page with only updated: and review_every: will still compute status correctly; fields you don't set just aren't shown in the banner.
title: Onboarding guide
shell: standard
freshness:
updated: 2026-01-15 # ISO date of the last content change
review_every: 90d # Nd | Nw | Nm | Ny | weekly | monthly | quarterly | yearly
owner: owner@example.com # free-form — email, Slack handle, team name
sources_of_truth: # bare URL or { label, href }
- https://notion.so/abc123
- label: "#ts-hub"
href: https://company.slack.com/archives/C012345
- label: "Linear: TSH project"
href: https://linear.app/co/project/tsh
components:
- type: header
title: Onboarding guide
...
Yellow, then red
kazam injects a banner at the top of the rendered page based on how close it is to the review deadline. No runtime JavaScript — the check runs at kazam build time and the banner is baked into the HTML. To keep the banner accurate between rebuilds, run a scheduled daily build (a GitHub Action on schedule: works).
Review is due in 5 days. Last updated Jan 15, 2026 (85 days ago). Review cadence: every 90d. Site last built: Apr 21, 2026. Owner: owner@example.com.
Review is 20 days overdue. Last updated Jan 15, 2026 (110 days ago). Review cadence: every 90d. Site last built: Apr 21, 2026. Owner: owner@example.com.
When sources_of_truth: is set, the real banner also renders a list of links underneath — the reviewer clicks straight through to the doc / Slack channel / Linear project to do the refresh. (The two callouts above show the banner body; the real banner adds a sources-of-truth row beneath.)
Every build tells you what's stale
kazam build always prints a grouped summary of every page past (or nearly past) its review window. Overdue items sort first, most-overdue at the top. Silent when nothing is stale — no noise on healthy builds.
$ kazam build .
_site/guide.html
_site/onboarding.html
_site/api/reference.html
✓ 3 page(s) → _site
⚠ 2 overdue page(s):
onboarding.html 20 day(s) overdue (cadence: every 90d) — owner owner@example.com
api/reference.html 5 day(s) overdue (cadence: every 180d) — owner eng@example.com
⏳ 1 page(s) due for review soon:
guide.html due in 3 day(s) (cadence: every 30d)
Run kazam build . on every PR and capture the log; the report becomes a free review surface. Or on a nightly cron: a weekly gh action that emails the owner field for anything overdue costs nothing and keeps the KB honest.
kazam doesn't fetch — your agent does
The sources_of_truth: list is deliberately just labeled URLs. kazam never reaches out to Notion, Linear, Slack, or anything else at build time — that preserves the zero-supply-chain, no-network-at-build-time guarantee.
Where this gets interesting: an agent refreshing the page can see the sources, fetch what it's able to (URLs via WebFetch, Linear via its MCP, Slack via its MCP), and ask the user to paste anything it can't reach. User owns the fetcher; agent owns the integration; kazam stays narrow.
Shorthand — just a URL. The label becomes the URL itself.
sources_of_truth:
- https://notion.so/abc
Labeled — any link (Slack channel, Linear project, HubSpot dashboard) gets a readable label.
sources_of_truth:
- label: "#ts-hub"
href: https://company.slack.com/archives/C01
- label: "Linear: TSH"
href: https://linear.app/co/project/tsh
`updated + review_every` vs today
The check is a pure date comparison — updated + review_every vs today (from KAZAM_TODAY when set, else the system clock). Three states:
Pages missing either updated: or review_every: are always Fresh — there's nothing to compare against. The yellow-window size (currently 7 days) will become configurable per site in a future release if demand surfaces.
Point your agent at `_site/stale.md`
Every build that finds any stale pages also writes a markdown report to _site/stale.md. Overdue pages first, due-soon below, each with path, cadence, and owner. Perfect for handing an agent one path and saying "fix these."
# Build, then hand the stale report straight to Claude Code.
# Claude reads _site/stale.md, then opens each page's .yaml and
# its sources_of_truth to propose updates.
kazam build .
claude -p "Read _site/stale.md. For each stale page, open its source YAML in the repo + the sources_of_truth links, then propose concrete updates."
The file is rewritten on every build and deleted when no pages are stale, so "does _site/stale.md exist?" is a cheap CI check: any PR that introduces stale pages will produce the file; a clean build leaves it absent.
The link graph
Same shape, different signal. Every kazam build also walks the page graph from index.html + the site nav: and reports two classes of problem:
- Orphan pages — built pages not reachable from
index.htmlvia nav or any in-pagehref:. Either link them, delete them, or setunlisted: trueon the page (same flag that excludes drafts fromllms.txt). - Broken internal links —
.htmlhrefs that don't match any built page. Conservative: assets, externals, anchors,mailto:, andtel:are skipped.
Silent on clean builds. When anything surfaces, the build prints a grouped summary and writes _site/links.md — same agent-consumable pattern as stale.md.
$ kazam build .
_site/index.html
_site/guide.html
_site/examples/landing.html
✓ 3 page(s) → _site
⚠ 1 orphan page(s) (not linked from nav or any page):
examples/landing.html
⚠ 1 broken internal link(s):
guide.html → /missing.html
kazam dev and kazam build --allow-orphans silence orphan detection — useful for work-in-progress pages you haven't wired into nav yet. Broken links always surface; there's no legitimate reason to tolerate a dead internal href.