---
name: personal-pkm-blog
description: Build a personal blog that doubles as a second brain (blog + bliki + PKM) with Astro, Claude Code and a Telegram capture bot. Covers the content model (posts, notes, index of people/works/concepts), the order to build each layer, the contracts between tools that write into the repo, slow draft-review rounds, the social loop (LinkedIn/X/Instagram out, comments back in) and the human-in-the-loop rules. Use this skill when someone wants a site like paulo.com.br, or asks how to turn a blog into a PKM/second brain maintained with an AI agent.
license: MIT
compatibility: Claude Code, any LLM-based coding agent
metadata:
  author: Paulo Silveira
  version: "1.0"
---

# personal-pkm-blog: a blog that is also a second brain

Playbook distilled from [paulo.com.br](https://paulo.com.br), a personal site that evolved during 2026
from a plain blog into a PKM: quick voice notes published from Telegram, longer posts reworked slowly,
an index of everything cited, and a social loop that sends texts out and brings the conversation back.
Everything lives in **one repository**, maintained by the author with Claude Code. The narrative
version is in [One Repo, One Second Brain](https://paulo.com.br/blog/one-repo-second-brain-pkm-pipeline/).

The core idea: **the repo is the second brain, the site is its public face, and the agent is the
librarian.** Tools that capture, publish or collect all write into the same repo, under written
contracts, and a human clicks every "publish" button.

## 1. Content model

| Type | What it is | Where | Notes |
|---|---|---|---|
| **Post** | Worked essay, links, images | `src/content/blog/*.mdx` | slug never changes after publishing (comments are keyed by it) |
| **Note** | A thought before it crystallizes: short, oral, unfinished | `src/content/notes/<slug>.md` | one file per note; written by the capture bot or by hand |
| **Entity** | Person, work, company/product, place, concept cited in texts | `src/data/entidades.yaml` | texts list ids in `entidades:` frontmatter; unknown id fails the build |
| **Agent post** | Written by the agent, in its own voice, about the tooling | `src/content/blog/*.mdx`, `type: agent` | separate layout, excluded from the main list, kept in RSS |
| Drafts | `draft: true` posts | `src/content/blog/drafts/` | render at a hidden URL; moved to the root when published |

Posts and notes stay **separate collections**; the home merges them at view level into one flow
(featured posts on top, then notes and older posts, newest first). Don't unify the schemas: a note
wants `audio`, `images`, `sources` (capture ids) and a `sortId` for same-day ordering; a post wants
description, cover, tags.

Note frontmatter that has worked: `{title, date, type: note, tags, sources, audio?, images?,
og_image?, sortId, entidades?}`. Body is light markdown rendered by a small custom renderer.

## 2. Build it in this order

Each layer is useful alone. Don't start with the graph.

1. **Blog basics.** Astro 5 + MDX + Tailwind v4, static on GitHub Pages. Content collections with a
   typed schema, tag pages, RSS, `/arquivo` (everything by year/month), Pagefind search, `llms.txt`.
   A build pipeline that validates before shipping: draft checks, OG images, then `astro build`, then
   a smoke test with assertions on real pages (redirects, counts, key elements).
2. **Notes + capture.** A Telegram bot that turns a voice message into a note:
   transcribe → **minimal cleanup** (punctuation, mishearings; never paraphrase) → title/slug/tags
   reusing the site's existing tag vocabulary → preview with **Publish / Adjust / Discard** →
   only the click writes the file, commits and pushes. Never overwrite an existing slug. Keep the
   audio next to the note. Bot patterns: the `telegram-claude-assistant` skill.
3. **Slow rework (draft rounds).** A dictation becomes a draft with `[bracket]` placeholders for links.
   The author edits in their editor, then asks the agent for a "round": fix typos and grammar, verify
   every factual claim against the linked source, list the previous round's critiques and which are
   pending, and offer endings/transitions as **proposals only, never applied silently**. Keep the
   author's voice in a skill of its own (short list of forbidden patterns: no em dashes, no filler,
   no rule-of-three, no engagement bait).
4. **The flow on the home.** Posts as bordered cards with a cover, notes as borderless rows with a small
   thumbnail and a colored dot. Generated OG cards (satori + resvg at build) for every post, note and
   page, so nothing shares a blank preview; a real image always beats the generated card.
5. **The index (the part that makes you think).** Bootstrap `entidades.yaml` with one strong-model pass
   over every text (candidates → keep: cited in 4+ texts, or 2-3 with a substantive mention, or 1 where
   it is central). Rules: no family or private people; throwaway name-drops stay out. Pages: `/indice`
   (A-Z, book style, "Asimov, Isaac"), `/indice/<id>` (every text with the sentence around the mention,
   co-cited entities), a connections graph, and a quiet dotted link with a hover card on the first mention
   inside posts. Add Wikidata Q-ids only after a reviewer checks each match (a wrong Q-id is worse than
   none). Sorting by citation count is a mirror: it shows what you keep coming back to.
6. **Social out.** Text for LinkedIn/X from a playbook built on your own engagement data (first line =
   the fact in numbers and first person; publish on weekdays 10:00-12:30). X via API from the bot
   (pay-per-use: ~US$ 0.015 per post, US$ 0.20 with a URL, so no links by default; no scheduling
   endpoint, use your own cron; long posts work for Premium accounts; one post, never a thread; a
   separate, less colloquial X version, while the note stays as spoken). Instagram carousels and Reels
   generated in the repo: see the [carousel/Reel playbook gist](https://gist.github.com/peas/2f224fbb9674734315079eb70dae8f60).
7. **Comments back in.** A skill, not a service: open the posts in the author's logged-in browser,
   read replies, curate (humans only, with content; criticism stays; one-word praise goes; log what was
   discarded) and merge into `comments.json` with an idempotent script. Show counts on cards and the
   thread under each post.
8. **Agent-facing extras.** `llms.txt` / `llms-full.txt`, an agent RSS, published agent skills,
   and agent posts that document the tooling.

## 3. Contracts: the thing that keeps it from rotting

Several tools write into the repo (capture bot, comment collector, social scripts, old exporters). For
each one, the root `CLAUDE.md` has a table: **file, who writes it, who reads it, what must not change
without coordination.** Examples of rules that saved us:

- writers never overwrite an existing note slug; manual files are never touched;
- `comments.json` is keyed by post slug, so post slugs are frozen once published;
- a draft's slug may still change: move the `.mdx` **and** its image folder, update image paths;
- raw material (audio transcripts, family feedback, sources) goes in a **gitignored** folder, never in
  `src/content/` (any `.md` there becomes an entry, and the repo is public);
- a request like "post that note on X" must never be routed as a new note (a note-prefix heuristic
  once almost did it): action verb + platform → conversation;
- keep the voice rules in one skill and point everything else to it; duplicate only where a remote
  process can't read the skill, and say so in both places.

## 4. Human in the loop, everywhere

Publish a note: button. Post on X: button or an explicit "yes" in chat. Instagram: dry run, then "yes".
Draft rounds: proposals, never silent rewrites. Schedules run from your own cron, never from a platform
scheduler you can't inspect. The agent may prepare everything; the human decides what goes public.

## 5. Pitfalls we paid for

- **Dates**: format with `timeZone: "UTC"`; a `pubDate: 2026-02-11` shows as Feb 10 in UTC-3 otherwise.
- **Future-dated posts** as scheduling: filter drafts *and* future dates in one `isPublished()`, rebuild on
  an hourly/6-hourly cron.
- **Tailwind v4**: `@apply` in scoped `<style>` needs `@reference`; dynamic classes in JS strings are not
  generated (use inline hex).
- **Lockfile from macOS**: after `npm install`, check that Linux platform binaries (esbuild) are in
  `package-lock.json` or CI breaks.
- **Images in notes**: use absolute paths (`/notes/file.jpg`); a relative path makes Astro treat it as a
  content asset and the build fails.
- **Accented slugs**: keep a custom `generateId`; the default slugger changes URLs.
- **Migrations**: when a post becomes a note (or a tag is merged), add a permanent redirect and a smoke
  test for it.
- **Tag sprawl**: review periodically (we went from 49 to 30 tags, each with 3+ items) and feed the top
  tags to the capture bot so it reuses them.
- **Over-building**: a node/visualizer observatory looked great and was used little; quick capture plus
  a good index was what stuck. Build the boring layers first.

## Related

- Posts: [Open Claw, Personal Knowledge e Second Brain](https://paulo.com.br/blog/open-claw-personal-knowledge-second-brain/),
  [Memória, agentes e o Second Brain](https://paulo.com.br/blog/memoria-agentes-second-brain/),
  [Meu personal knowledge system depois de seis meses](https://paulo.com.br/notes/personal-knowledge-system-seis-meses/),
  [Building a PKM with Telegram, Whisper and LLMs](https://paulo.com.br/blog/building-a-pkm-with-telegram-whisper-and-llms/),
  [Disposable Software, Disposed](https://paulo.com.br/blog/disposable-software-disposed-rewriting-the-pkm-bot/).
- Skills: `telegram-claude-assistant` (the capture bot), `css-diagrams` (diagrams in posts), `llms-txt`.
- Instagram/Reels playbook and code: [gist](https://gist.github.com/peas/2f224fbb9674734315079eb70dae8f60).
