Maintain the kpow.xyz build log — log a day's work on a build, start a new build, audit a build against its source repo, fix wrong content, and publish. Use when the user says "/buildlog", "log today's work", "add an entry", "new build", "the build log is wrong", "audit <build>", "fix <build>", "publish the build log", or drops photos in inbox/.
Install
npx skillscat add kpow/kpow-buildlog/buildlog Install via the SkillsCat registry.
buildlog
This repo is the content behind kpow.xyz/builds: markdown + photos, no database.
kpow_v3 clones it at build time and renders builds.json. Work in this repo only.
Modes
| Command | Does |
|---|---|
/buildlog |
Status: every build, days since last entry, unlogged commits, inbox, unpushed work |
/buildlog log <slug> [what you did] |
Add entries for recent work — the everyday one |
/buildlog new <slug> |
Start a new build |
/buildlog audit <slug> |
Check a build against its source repo and photos; list what's off |
/buildlog fix <slug> <what's wrong> |
Correct existing content |
/buildlog publish |
Regenerate, commit, push, optionally redeploy the site |
No slug on log/audit/fix: run status and ask which build — one question.
Tools (all zero-dependency, run from repo root)
node scripts/status.mjs # dashboard
node scripts/commits.mjs <slug> # each entry + the source commits behind it; UNLOGGED at the end
node scripts/commits.mjs <slug> --since D # commits after date D
node scripts/photo.mjs info <files...> # when each photo was taken, orientation, GPS?
node scripts/photo.mjs ingest <in> <out> # resize 1600px, upright, ALL metadata stripped
node scripts/video.mjs info <files...> # when each clip was shot, length, GPS?
node scripts/video.mjs ingest <in> <out.mp4> # H.264 1280px, no audio, ALL metadata stripped, poster
node scripts/lint.mjs [slug] # mechanical checks; must pass before publishing
node scripts/build-buildlog.mjs ./builds ./builds.json # regenerate the site data
node scripts/redeploy.mjs # make kpow.xyz rebuild (asks first — see Publish)Source repos are found automatically: build.md's repo: is matched against checkouts
under ~/projects. The photo archive is ../source-media/ — iPhone exports namedYYYY-MM-DD HH.MM.SS.jpg, so ls ../source-media | grep '^2026-06-29' finds a day's
photos. IMG_*.HEIC files aren't dated by name; use photo.mjs info.
The one rule: never invent
Every claim in an entry must trace to one of: a commit (subject or diff), a photo, or
the user's own words this session. Commit messages say what changed; the user knows
why and what it was like. If a draft needs a detail you don't have — a number, a
reason, whether something worked — ask, or leave it out. A shorter true entry beats a
fuller made-up one. This is how the log got out of whack in the first place.
log
node scripts/commits.mjs <slug> --since <last entry date>and listinbox/.- Group the commits into chunks of work, not one entry per commit. A chunk is what
you'd tell a friend you did that day. Date each entry the day that work landed.
Several days of small commits on one thing = one entry, dated its last day. - Photos:
photo.mjs info inbox/*. Match each to an entry by the date it was taken.
Look at every photo (Read it) before deciding whether it needs a caption at
all — see "Captions are optional". Photos with no matching work day: ask. - Draft all entries at once and show them. If the user gave a sentence, it leads.
Ask one question at a time about anything you can't source. - On approval: ingest photos (below), write the files, then Publish.
No commits and no photos? It's a hands-on day — ask what they did.
new
- Find the source: ask for the repo, confirm the checkout exists under ~/projects.
- Read its README and
git log --reverse --format='%cs %s' | head -60for the story. - Ask the user, in one message: one-line summary · status · hardware, software or hybrid ·
when it started. Offer your own guesses from the repo to confirm or correct. - Draft
build.md(template below). The body is the evergreen description:
what it is, how it works, why it's built that way.##headings are fine. - Hero: best whole-project photo from inbox or
../source-media→media/hero.jpg. - Backfill entries from history like
log, then Publish.
audit
The correction pass. Goal: a short list the user can say yes/no to.
node scripts/lint.mjs <slug>. Mechanical errors are findings.node scripts/commits.mjs <slug>. For each entry, compare its claims to the commits
in its window.git -C <source> show --stat <hash>when a subject is too vague.⚠ no commits behind this entryis not automatically wrong — soldering, cases,
and assembly don't make commits. Check../source-mediafor photos that day. No
commits and no photos → ask the user whether it happened.- Check
build.md:statusstill right?facts:numbers still true (grep the code —
e.g. count the effects)?summarystill accurate?stackcomplete? - Report at most 5 findings per message, worst first:
date · entry · what's off · evidence · proposed fix. Then ask for go/no-go. - Apply what's approved via fix. Offer the next 5 if there are more.
fix
Entries are append-only for new work — never rewrite an old entry to change what
happened. Fixing a mistake is the exception: wrong date, a claim that isn't true, a
bad caption, a broken format. Git history keeps the old version.
- Wrong date:
git mvthe file to the right date AND change itsdate:. - Show a before/after for any change to entry text, then Publish.
- Something that never happened: delete the entry (ask first, name the file).
Format — the parser is tiny, so be exact
build.md:
---
slug: radar
title: ESP32 Plane Radar
status: shipped # active | shipped | live | shelved (live = deployed software)
kind: hardware # hardware | software | hybrid
summary: A desktop ADS-B aircraft scope on a round LCD.
stack: [ESP32-C3, LovyanGFX, ADS-B, PlatformIO]
tags: [display, adsb, firmware]
rung: solder # leave existing values alone; don't invent one
repo: kpow/ESP32-Plane-Radar
facts:
aircraft: 64 # 0-4 stat chips; must be true and checkable
hero: media/hero.jpg
links:
- { label: repo, url: https://github.com/kpow/ESP32-Plane-Radar }
started: 2026-06-13
---
Evergreen markdown body.log/YYYY-MM-DD.md (a second entry the same day: YYYY-MM-DD-<word>.md):
---
date: 2026-05-27
title: "Disco-ball satellite — and a nasty ground-noise lesson"
tags: [satellites, disco, hardware-lesson]
media:
- { src: media/disco-skull.jpg }
---
Body.Text-only entry: media: [].
Video entry — src: the ingested .mp4, poster: a .jpg shown before it plays:
- { src: media/matrix-yellow-cross.mp4, poster: media/matrix-yellow-cross.jpg }A still that isn't a photo says so with kind: — render, screenshot ordiagram. Video needs no flag; the site badges it off the .mp4.
- { src: media/mobile-version-cad.jpg, kind: render, caption: "The mobile version" }Captions are optional, and usually wrong
A caption carries something the photo can't: a number, a part name, a
failure. "44 icons cached, last synced 52 seconds ago" earns its place.
"Three printed case bodies on the mat" does not — the reader has eyes.
Nothing to add? Omit the
caption:key. Don't writecaption: "".Never describe the frame, and never restate a sentence from the body. That
duplication was half of what made the log read like filler.Never write "(video)" or "(render)" into caption text. The site draws a badge.
Keep one under 90 characters; lint warns past that.
---on line 1, LF endings.date:must equal the filename's date.Quote titles and captions with
"…". Never\"inside — it shows up literally.
If the text contains", wrap it in single quotes:title: 'The 7" touch build'.[a, b]lists split on commas: no commas or quotes inside items.Captions can't contain
}.src:andposter:are relative to the build folder:media/<name>.jpg/.mp4.
Body markdown
CommonMark plus GFM — tables, strikethrough, task lists, autolinks all parse, andbuilds.json carries the body through untouched.
- Blank line before every list, heading, table, callout and fence. A
-line
can otherwise interrupt a paragraph and swallow it. ***for a horizontal rule, never---. Directly under a text line,---
is a setext H2.- No raw HTML; it is dropped, not rendered.
- Tables need a header row. Three columns maximum, short cells — an entry body is
about 300px wide on a phone. - Links open in a new tab on their own. Don't write the target.
Voice and shape
Shape follows the content. Most entries are 2–4 short paragraphs (40–140 words),
and the structure varies because the days varied. A one-paragraph entry is still
right when the day was one thing.
- A list when there are 3+ parallel items: things fixed, parts swapped,
options weighed. Lead with a bolded noun label when the items have names —- **FC16 module:** …— the renderer styles that first bold run. - A callout, at most one per entry, for the thing worth shouting at your past
self:> **Trap:**,> **Lesson:**,> **Dead end:**,> **Note:**,> **Next:**. Colon inside the bold. No hard-won lesson, no callout. - A table only for genuinely tabular facts: settings vs results, before and
after, board vs board. Four columns of prose is a design failure, not a table. - Headings belong in
build.mdbodies and long entries. A 60-word entry with
a heading looks like a memo. - First person is earned. Agentless past tense for plain work ("Swapped in an
FC16 module"). "I" for mistakes, guesses, reversals and opinions ("I never
actually use it", "until I noticed the tint was still on"). If the commit log
knows it, drop the subject. If only you know it, say I. - Name the real parts, chips and numbers. No marketing, no "excited to", no emoji.
- Title: short and specific, joins with
—,+,,or:. - Tags: 1–3, lowercase, kebab-case.
- Media says what it is through
kind:and the badge, never in caption text.
Banned tics — these are what made the log read like a machine
- Comma-chained dumps. Three or more clauses welded into one sentence with
commas. Break them into a list or into sentences. - "Also …" / "Additionally …" tails bolted on to cram in leftovers. If it
matters, give it its own sentence; if it doesn't, cut it. - Stock editorialising closers: "This is the backbone everything else hangs
off", "Lesson learned", "A good reminder that…". Put the lesson in a callout in
your own words or leave it out. - Em dashes: at most one per entry, zero preferred. They were the loudest
tell in the old log — 71 of them across 94 entries. Use a period or a colon. - Hedges: "a bit", "somewhat", "fairly", "pretty much".
- Repeating build.md in an entry. If the evergreen body says it, link the
reader's attention elsewhere.
Photos and video
Anything off the phone is welcome in inbox/ — .HEIC, .JPG, .PNG, .MOV, .MP4.
Always through photo.mjs ingest / video.mjs ingest, never copy a raw file intomedia/. Raw phone photos and clips carry GPS and the creation date; the scripts
strip it and refuse to finish if any survives.
node scripts/photo.mjs ingest inbox/IMG_1234.HEIC builds/<slug>/media/<name>.jpg
node scripts/video.mjs ingest inbox/IMG_4180.MOV builds/<slug>/media/<name>.mp4
mkdir -p inbox/done && mv inbox/IMG_1234.HEIC inbox/IMG_4180.MOV inbox/done/video.mjs also writes media/<name>.jpg from ~40% in as the poster, unless that
.jpg already exists (then it's kept). A still of the same clip already in media/
makes a better poster than a second copy — point poster: at it and delete an
unused auto-poster so lint has no orphans. Watch clips (pull frames with
ffmpeg and Read them) before captioning, same as photos.<name>: short kebab-case description of what's in frame — disco-skull,hex-orb — unique in that media/. Never delete originals; move them to inbox/done/.
Safety
- Never publish credentials, WiFi names/passwords, IPs, MAC addresses, API keys or
coordinates. ESP32-Plane-Radar and vizpow have hardcoded WiFi creds, home lat/lon
and a fleet secret in their source — read their history for what changed, never
quote config values. - "Home" is fine; where home is, isn't.
Publish
node scripts/lint.mjs— fix errors before going on.node scripts/build-buildlog.mjs ./builds ./builds.jsongit addthe specific build folder(s) +builds.json; commit<slug>: <what changed>with the attribution line from the system prompt.- Ask before pushing: pushing makes it public on GitHub.
git push.- Pushing alone doesn't update kpow.xyz. Ask: "Redeploy the site now?" On yes:
node scripts/redeploy.mjs(pushes an empty commit to kpow_v3 main, which
triggers DigitalOcean). Batch it — once after a session of edits, not per entry. - Say what's now live in one line, e.g. "2 noodle entries + 3 photos are in; site
rebuilds in ~5 min at kpow.xyz/builds/noodle".