The session count came from the mtime prefilter, so an old date range reported every file on disk as if it had contributed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
agent-skills
Single source of truth for custom agent skills + commands. Shared across Claude Code and Codex, every machine.
Layout
skills/ # SKILL.md dirs — Claude Code AND Codex both read these (open Agent Skills standard)
commands/ # slash commands — Claude Code only (Codex ignores)
hooks/ # Claude Code hooks — see hooks/README.md, wiring is manual
claude-md/ # shared instruction fragments — imported by both entry files
entry/ # entry files: ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md
systemd/ # user timers: weekly review + hour log — one machine only, see below
bin/link.sh # bootstrap symlinks for non-Nix machines
nix/home.nix # home-manager module for NixOS machines
flake.nix # exposes homeModules.default
Skills are portable: only name+description frontmatter is required by both tools; Claude-only fields (user-invocable, args) are ignored by Codex. Cross-skill refs use root-relative paths (linear-common/COMMON.md), so they resolve under ~/.claude/skills and ~/.agents/skills alike.
Install
Non-Nix machine (e.g. dev VM)
git clone https://git.naps.pt/yolo/agent-skills.git ~/tea/yolo/agent-skills
~/tea/yolo/agent-skills/bin/link.sh
Symlinks each skill into ~/.claude/skills/ and ~/.agents/skills/, commands into ~/.claude/commands/, hooks into ~/.claude/hooks/, claude-md/ fragments into ~/.claude/. Idempotent; any pre-existing real dir is moved to ~/.agent-skills-backup/ (outside the discovery path, so it isn't picked up as a duplicate skill). Re-run after adding a skill.
Hooks still need one manual step: the settings.json snippet in hooks/README.md. Entry files are linked automatically — entry/CLAUDE.md and entry/codex-AGENTS.md hold the machine-local sections and @import the shared fragments, so both tools read the same rules with no copy and no drift.
NixOS machine (home-manager)
# flake inputs
inputs.agent-skills.url = "git+https://git.naps.pt/yolo/agent-skills.git";
# home config imports
imports = [ inputs.agent-skills.homeModules.default ];
recursive = true links files individually, so machine-local skills can coexist in the same dir. nixos-rebuild switch to apply/update.
Shared machine, many sessions
Several autonomous runs share one box. skills/linear-common/scripts/gate.sh is a machine-wide semaphore for heavy commands (full test suites, whole-project builds): bounded slots, memory + CPU cap via a systemd user scope, pinned build/test parallelism. Skills run scoped checks in the inner loop and put only the once-per-push full suite through the gate; exit 75 means it never ran and CI takes over. Policy lives in linear-common/COMMON.md under "Local verification budget".
~/.claude/skills/linear-common/scripts/gate.sh --status
AGENT_GATE_SLOTS=3 AGENT_GATE_MEM_MAX=4G ~/.claude/skills/linear-common/scripts/gate.sh -- cargo test
Weekly review timer
systemd/week-review.timer fires Fridays at 17:00 Europe/Lisbon (the zone is pinned in the unit because the machine clock is UTC). It runs bin/week-review-session.sh, which creates an Agent of Empires session in a fresh week-review/<ISO week> worktree, sends it /week-review, and pushes an ntfy notification to the homelab topic.
The session is interactive, not claude -p. /week-review asks which carry-over issues to take and which changes to apply, so an unattended run would either stall or decide on its own. It stops at the first question and waits for aoe attach.
If a previous week-review-* session is still open the run skips and says so, so weeks do not pile up. Persistent=true means a Friday spent with the machine off fires on the next boot instead of being lost.
bin/link.sh symlinks the units into ~/.config/systemd/user/ but does not enable them — enabling on every machine would spawn one session per box for the same week. On the one machine that should run it:
systemctl --user daemon-reload
systemctl --user enable --now week-review.timer
systemctl --user list-timers week-review.timer
Needs loginctl enable-linger so the timer runs while logged out. Logs are in ~/.local/state/week-review/run.log. The nix module deliberately omits the timer for the same one-machine reason.
Hour log timer
systemd/hourlog.timer fires Fridays at 18:00 Europe/Lisbon and runs
bin/hourlog-session.sh, which opens an Agent of Empires session on a scratch
dir, sends it /hourlog --week this, and pushes an ntfy notification.
Same shape as the weekly review and interactive for the same reason: the skill
proposes hours and stops for approval before writing anything to the timesheet.
An unattended run would be deciding a company record on your behalf. It skips
if a previous hourlog-* session is still open, and Persistent=true makes a
missed Friday fire on the next boot.
Enable on one machine only:
systemctl --user daemon-reload
systemctl --user enable --now hourlog.timer
Setup lives outside this repo, which is public:
~/.config/hourlog/projects.json— path prefix to project mapping, copied fromskills/hourlog/config.example.json.HOURLOG_APIandHOURLOG_TOKENin~/.env.claude— API base URL and a personal access token (profile:read,schedule:read,schedule:write).
No project, client, or host name belongs in a committed file here.
Adding a skill
Drop a new skills/<name>/SKILL.md (+ optional scripts/, references/, assets/). Commit. Non-Nix: re-run bin/link.sh. Nix: rebuild.
Skills
| skill | what |
|---|---|
work |
tracker issue → worktree → PR → hands off to land |
yolo |
quick ship; optional land handoff |
land |
drive an open PR to green + ready-to-merge; user clicks merge (canonical CI/review loop) |
blitz |
drive a whole milestone to done |
nightshift |
hours-long unattended build; architect delegating to subagents, backs off before the 5h limit |
linear-common |
shared config/setup/worktree conventions + local verification budget (dependency of work/yolo/blitz/nightshift) |
week-review |
review the past week's sessions for recurring friction; reads open issues here as carry-over |
hourlog |
hours-per-project-per-day proposal from session activity, reconciled against the timesheet; submits only what you approve |
crit, improve-codebase-architecture |
misc |
Vendored skills
humanizer and impeccable are third-party and copied in, not written here.
Each carries an UPSTREAM file recording repo, pinned tag, subdirectory, and
license. Do not edit them — a local change becomes a merge conflict on every
update, and a rule that reads wrong is a bug to report upstream.
./bin/check-vendored.sh # reports any that have moved upstream
To update: re-clone at the newer tag, copy the subdir over the skill dir, and
bump ref + vendored in UPSTREAM.
| skill | upstream | license |
|---|---|---|
humanizer |
blader/humanizer | MIT |
impeccable |
pbakaus/impeccable | Apache-2.0 |
Writing contract
One contract, claude-md/writing.md. Both entry files @import it, so Claude
Code and Codex read the same bytes on every machine.
The rule it encodes is selection, not compression: keep output short by cutting whole ideas that don't change what the reader does next, then write what survives as plain sentences. Not by dropping articles, abbreviating, or writing fragments — that is shorter and worse. Anthropic's Fable 5 and Opus 5 prompting guides both say this explicitly.
Two enforcers in hooks/:
| enforcer | scope |
|---|---|
comms-lint.py |
GitHub issues/PRs/review comments — 150 words (hard cap 300), evidence in <details>, no reviewer-addressing opener, plain diction |
comment-lint.py |
code comments — 1-3 lines, volume and purpose, not wording. The rule is path-scoped (~/.claude/rules/), so it loads only when Claude reads a source file. |
Written in the voice it asks for, and leads with a worked example of each.
Prompt style leaks into output style, and examples steer harder than
prohibitions, so the file is short and shows rather than forbids. Prose alone
drifts; the linters make it binding. Details in hooks/README.md.