Skip to content
Back to skills

Hud

ASecurity

Capture a note, playbook, peer, or project-context update into the user's personal work HUD — a local markdown tree at $HUD_ROOT. Triggers on "jot this down", "remember this for <project>", "add a playbook for X", "note this peer/colleague", "update the context for <project>", or "lint the hud". The consolidation rule: this is the only place standalone notes go — never write a note file anywhere else.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 19, 2026
ai-agentsgobashdebugginggit

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add jamestexas/agents --skill hud --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hud?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hud
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jamestexas-hud/badge)](https://www.skillsdirectory.com/skills/jamestexas-hud)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hud
description: >
  Capture a note, playbook, peer, or project-context update into the user's
  personal work HUD — a local markdown tree at $HUD_ROOT. Triggers on "jot
  this down", "remember this for <project>", "add a playbook for X", "note
  this peer/colleague", "update the context for <project>", or "lint the
  hud". The consolidation rule: this is the only place standalone notes go —
  never write a note file anywhere else.
user-invocable: true
argument-hint: "[lint] | [note title / playbook slug / peer handle]"
---

# hud — authoring skill for the personal work HUD

The HUD is a folder of markdown at `$HUD_ROOT` (env var, default `~/hud`).
It is not a tool you call — it is a place you write files, in a shape a
small local server can render. This skill is the shape.

## 0. Resolve the root

Use `$HUD_ROOT` if set, else `~/hud`. If that directory does not exist,
**stop and say so** — do not create it. The HUD root is scaffolded once by
its own setup step; a skill invocation is never the place that happens.

## 1. Load the schema file first

Before writing anything, check for `$HUD_ROOT/HUD.md`. If it exists, read
it — it is the schema file, co-owned by the user and every agent that
writes here, and **it wins over this skill on any conflict** (a path,
frontmatter field, or convention it states supersedes what's written
below). If it doesn't exist, proceed on the conventions in this file.

## 2. Figure out what's being asked for

The table below is **generated** from `_hud/hud-contract.mjs`, the one file
that declares what an entry is. Don't hand-edit it — `hud docgen --check`
fails when it drifts, and the unit suite runs that check.

<!-- @generated-begin: intent-table -->
| Intent | Kind | Target |
| --- | --- | --- |
| capture a note | `note` | `$HUD_ROOT/projects/<project>/notes/<date>-<slug>.md` |
| capture an unfiled note | `inbox` | `$HUD_ROOT/inbox/<date>-<slug>.md` |
| capture a playbook | `playbook` | `$HUD_ROOT/playbooks/<slug>.md` |
| create a project brief | `context` | `$HUD_ROOT/projects/<project>/CONTEXT.md` |
| add a peer | `peer` | `$HUD_ROOT/peers/<handle>.md` |
| drop raw material in | `raw` | `$HUD_ROOT/projects/<project>/raw/<date>-<slug>.<ext>` |
<!-- @generated-end: intent-table -->

Invoked with `lint`, run the lint workflow (§8) instead — no file writes
without approval.

Infer `<project>` from the current working directory's repo name, the
conversation's stated project, or an existing `$HUD_ROOT/projects/<x>/`
directory that matches. If none of those resolve it confidently, don't
guess — file under `inbox/` instead. A note in the wrong project is worse
than a note that's unsorted; `inbox/` is designed for exactly this.

<!-- @generated-begin: slug-rule -->
A slug is the title, lowercased, reduced to `a-z0-9-`, with `a`, `an`, `the` dropped and the result bounded at 60 characters on a word boundary. The dropped article is not a liberty — it is what SKILL.md's own worked example does.
<!-- @generated-end: slug-rule -->

## 2a. Write it with `hud new`, not by hand

Everything in §2 above — and the date prefix, the slug, the frontmatter keys
legal for each kind, and the two bookkeeping appends in §7 — is derived for
you by one call:

```bash
hud new note --project widget-service --title "debugging the flaky retry test"
hud new inbox --title "a thought with no home"
hud new playbook --title "Rotating the signing key" --set applies_to=widget-service
hud new context --project widget-service
hud new peer --handle octo-cat
hud new raw --project widget-service --title "session transcript" --ext txt --body-file -
```

Add `--set KEY=VALUE` (repeatable) for the optional frontmatter in §3–§6,
`--summary` for the `index.md` line, `--body`/`--body-file` for the content
(`-` reads stdin), `--date` to backdate, `--dry-run` to see the whole plan
without writing, and `--json` for one machine-readable document — emitted for
refusals as well as successes.

**Prefer it over writing the files yourself.** Not for convenience: it
*refuses* the mistakes this file can only warn about. A frontmatter key that is
not legal for the kind is refused rather than written; a project it cannot
resolve routes a note to `inbox/` rather than guessing at one; a destination
that already exists is refused rather than overwritten. A refusal is the only
non-zero exit, and nothing is written when one happens.

`hud new` is the *mechanism*; §3 below is the *contract* it implements, and is
what you need when you are reading an existing entry, editing one in place, or
writing into a tree whose `HUD.md` (§1) overrides something here.

## 3. The frontmatter contract

Every field is optional; a bare markdown file is a valid entry. These tables
are **generated** — the schema is declared in `_hud/hud-contract.mjs` and
nothing here is a second copy of it.

<!-- @generated-begin: fields-table -->
| key | type | reader assumes when absent | what it is |
| --- | --- | --- | --- |
| `title` | scalar | — | The entry's name. The reader falls back to the first heading, then the filename. |
| `date` | date | — | The day the entry is about. The reader falls back to the filename prefix, then mtime. |
| `type` | scalar | — | What kind of entry this is. The reader infers it from the path when absent. |
| `status` | scalar | `active` | Lifecycle. On a project brief it drives group rank and fold state; elsewhere it means what the author meant. |
| `tags` | list | — | Free-form labels. |
| `repos` | list | — | owner/name — lights up the PR panels for this entry. |
| `tickets` | list | — | Ticket ids, rendered as links through hud.toml's ticket_url_template. |
| `gh` | scalar | — | A GitHub handle. Drives the peers panel's PR lookup. |
| `applies_to` | list | — | What a playbook is for — repos, or situations. |
| `last_verified` | date | — | When a playbook was last known to still work. |
| `links` | list | — | Related material. |
<!-- @generated-end: fields-table -->

The third column is what the **reader substitutes when the key is absent** —
not a value the file carries. An entry with no `status` reads as `active` and
says nothing. What each kind actually writes:

<!-- @generated-begin: frontmatter-table -->
| kind | written for you | `--set` may add | refused |
| --- | --- | --- | --- |
| `note` | `title`, `date`, `type` | `status`, `tags`, `repos`, `tickets` | `title`, `date`, `type` (derived) |
| `inbox` | `title`, `date`, `type` | `status`, `tags`, `repos`, `tickets` | `title`, `date`, `type` (derived) |
| `playbook` | `title`, `type`, `last_verified` | `status`, `tags`, `repos`, `tickets`, `applies_to`, `last_verified` | `title`, `type` (derived) |
| `context` | `title` (if given), `status` | `status`, `repos`, `tickets`, `links` | `title`, `type` (derived) |
| `peer` | `gh` | `gh`, `repos` | `type` (derived) |
| `raw` | — | — (nothing) | everything |
<!-- @generated-end: frontmatter-table -->

<!-- @generated-begin: serialization-rules -->
- **Dates are normalized on READ**, not on write: `YYYY-MM-DD` and `ISO-8601 timestamp` are both accepted and both become `YYYY-MM-DD`. A writer-side convention would depend on a per-machine setting that is not committed; a reader that accepts both depends on nothing.
- **Empty collections are omitted**, never emitted as `[]`. YAML has no block spelling of an empty sequence, so `[]` is the one form with a second representation to drift into — and a key whose value you do not know is a key you were told not to write.
- Lists are **block sequences**, indent is **2 spaces**, scalars are quoted only where bare would be misread, and there are **no comments** inside the block.
- **Key order is not significant** and neither is formatting. Read keys and values; anything that depends on their arrangement is depending on something no serializer preserves.
<!-- @generated-end: serialization-rules -->

## 4. What goes in the body

The frontmatter is derived; the body is the part only you can write.

- **note** — what you'd want to read back in three months, not a transcript.
- **playbook** — the reusable process: steps, gotchas, verification. A playbook
  earns its place by being something you'd otherwise re-derive next time; if
  it's a one-off, it's a note.
- **peer** — how it goes working with this person. Context, not a log of every
  interaction.
- **raw** — nothing of yours. It *is* the artifact: a transcript, an export, a
  patch. Read it, never rewrite it.

## 5. Update project context

Edit `$HUD_ROOT/projects/<project>/CONTEXT.md` **in place**. If it doesn't
exist yet, `hud new context --project <project>` creates it with the Goal /
State / Key paths / Decisions skeleton rather than a shape you invented — and
refuses if the file is already there, because updating the brief is an edit,
not a second file.

Edit the relevant section in place — don't append a duplicate "## State"
block; keep the file as the single current brief for the project, not a
log. (Notes are the log; CONTEXT.md is the summary.)

## 6. Sections, groups and containers

<!-- @generated-begin: sections-table -->
| section | what it is |
| --- | --- |
| `projects` | Live work — one directory per project, its CONTEXT.md the brief, notes beneath it. |
| `playbooks` | Procedures worth not re-deriving: how a thing is done, written down once. |
| `peers` | One file per colleague, naming the GitHub handle whose PRs light up the peers panel. |
| `inbox` | Unfiled capture — notes taken before there was a project to put them in. |
| `archive` | Finished or parked work, kept for reference. Nothing here needs you. |

Sections are an **open set**: the five above fix the left-column order, and any
other top-level directory becomes a section too, appended alphabetically with a
fallback sentence. Kinds are the opposite — closed, and there are exactly 6.
<!-- @generated-end: sections-table -->

<!-- @generated-begin: containers-table -->
| directory | within | known to the reader? | matched at | what it means |
| --- | --- | --- | --- | --- |
| `notes/` | `projects` | **no — never heard of it** | exactly its declared depth | Where dated project entries go. Writer-side only: `projects/p/notes/x.md` and `projects/p/x.md` are the same entry to the reader. |
| `raw/` | `projects` | yes, and renders differently | any depth | Immutable drops — transcripts, exports, patches. Listed and never opened; the meta slot shows a size instead of a date. |

Those two columns are not the same claim. `notes/` means nothing to the
reader because the reader has **never heard of it** — the name appears
nowhere in `server.mjs`, so an entry inside it and one beside it are
indistinguishable. `raw/` is the opposite: the reader knows it, matches it
at any depth, and changes how its entries display. One is an accident and
one is a decision, and a table that called both "inert" would hide that.

A path deeper than its kind's declared shape is currently **absorb**ed — and that is the known-wrong answer: the
intermediate directories vanish into the group, so the tree looks like you did
nothing. It is named here rather than left implicit so that changing it is a
declaration edit.
<!-- @generated-end: containers-table -->

One asymmetry worth carrying: the HUD renders a `warn` chip for frontmatter it
cannot read, and **nothing at all** for a path that has moved. The path decides
an entry's type, section, group and date, so relocating a file silently
re-types it and renaming one silently changes its date.

## 7. After any write: update the catalog

Every write above is followed by exactly two more edits, no exceptions:

<!-- @generated-begin: bookkeeping-table -->
| file | semantics | line |
| --- | --- | --- |
| `index.md` | catalog | `- [{title}]({path}) — {summary}` |
| `log.md` | append-only | `- {date}: added {kind} {path}` |

A link plus a one-line summary. One line per path; a path already listed is kept, not listed twice. Append-only history of what happened. A second ingest of the same path is a second line, by design.
<!-- @generated-end: bookkeeping-table -->

These two files are the bookkeeping that makes the HUD navigable without
re-reading the whole tree; skipping them is how the HUD rots into the
scattered-files problem it replaced.

**`hud new` (§2a) makes both appends for you**, which is most of why it is
worth using: two follow-up edits after every single write is exactly the kind
of discipline that gets dropped when the write itself felt done. `--summary`
supplies the one-line summary; without it the catalog line still names the
kind, which is true but not worth much to read back. The two files are treated
differently on purpose — `index.md` is a catalog, so a path already listed is
left alone rather than listed twice, while `log.md` is append-only history and
always records the write.

You still make both appends by hand when you edited a file in place (a
`CONTEXT.md` update, §5) or wrote one some other way — the rule is about every
write, not about every invocation of the verb.

## THE RULE

**Never write a standalone note file outside `$HUD_ROOT`.** If a task
elsewhere says "jot this down somewhere" or "make a note of this" with no
other destination specified — this is the somewhere. Don't create a
scratch file next to unrelated code, in a home-directory scratch folder,
or as a loose file in the current repo. Route it through §2–§3 instead,
even (especially) when the fastest-looking option is to just drop a file
where you already are.

## 8. `lint` argument

When invoked with `lint`, do not write anything yet. Walk `$HUD_ROOT` and
report:

- **Stale actives** — any entry with `status: active` in its frontmatter
  (typically `CONTEXT.md` files) with no note under its project's
  `notes/` dated within the last 30 days.
- **Contradictions** — a `CONTEXT.md`'s "State" section that looks out of
  date against what its project's most recent notes actually say.
- **Orphans** — files under `$HUD_ROOT` that have no corresponding line
  in `index.md`.
- **Index drift** — lines in `index.md` that point at files that no
  longer exist.

Present the report first. Only make fixes (updating `index.md`, flagging
`CONTEXT.md`, archiving stale entries) if the user approves them after
seeing the report — this mirrors the review-then-write posture of every
other write path in this skill, just applied to cleanup instead of
capture.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.