Adds bin/lint.sh (everything CI runs) and bin/lint-repo.py (the checks specific to this repo, stdlib only). Two Gitea workflows: lint + nix flake check on every PR, check-vendored.sh weekly on its own.
Generic: shellcheck, ruff, compileall, jq, node --check, nix flake check. Baseline was already clean apart from two ruff findings in me-api.py, fixed here.
Repo-specific, because nothing else catches these:
Skill frontmatter — a typo'd key is ignored silently by every tool that reads it.
Installer drift — bin/link.sh and nix/home.nix install the same files by two independent mechanisms.
Entry imports — every @~/.claude/x.md is something both installers create.
Unit paths — ExecStart targets exist.
The drift check found two bugs, fixed in the same commit: link.sh never linked scripts/ into ~/.claude/scripts (hook commands in settings.json call it by absolute path), and never created ~/.claude/machine.md, which entry/CLAUDE.md@imports unconditionally — so every non-Nix machine started sessions with a failed import.
No shfmt: it rewrites the deliberate compact style in check-vendored.sh and hourlog-session.sh. No ruff format check: it would reformat 7 files.
Verification
$ bin/lint.sh
== repo lints
lint-repo: ok
== shellcheck
== ruff
All checks passed!
== python syntax
== json
ok
== js syntax
ok
== nix flake check
all checks passed!
all checks passed
Negative-tested: renaming a skill's name, adding a dead path reference, a broken relative link, and deleting the machine.md link line each produce one finding.
Adds `bin/lint.sh` (everything CI runs) and `bin/lint-repo.py` (the checks specific to this repo, stdlib only). Two Gitea workflows: lint + `nix flake check` on every PR, `check-vendored.sh` weekly on its own.
Generic: shellcheck, ruff, `compileall`, jq, `node --check`, `nix flake check`. Baseline was already clean apart from two ruff findings in `me-api.py`, fixed here.
Repo-specific, because nothing else catches these:
- **Skill frontmatter** — a typo'd key is ignored silently by every tool that reads it.
- **Internal paths** — `<skills-root>/…` refs, repo-relative paths and relative links resolve.
- **Installer drift** — `bin/link.sh` and `nix/home.nix` install the same files by two independent mechanisms.
- **Entry imports** — every `@~/.claude/x.md` is something both installers create.
- **Unit paths** — `ExecStart` targets exist.
The drift check found two bugs, fixed in the same commit: `link.sh` never linked `scripts/` into `~/.claude/scripts` (hook commands in `settings.json` call it by absolute path), and never created `~/.claude/machine.md`, which `entry/CLAUDE.md` `@imports` unconditionally — so every non-Nix machine started sessions with a failed import.
No shfmt: it rewrites the deliberate compact style in `check-vendored.sh` and `hourlog-session.sh`. No `ruff format` check: it would reformat 7 files.
<details>
<summary>Verification</summary>
```
$ bin/lint.sh
== repo lints
lint-repo: ok
== shellcheck
== ruff
All checks passed!
== python syntax
== json
ok
== js syntax
ok
== nix flake check
all checks passed!
all checks passed
```
Negative-tested: renaming a skill's `name`, adding a dead path reference, a broken relative link, and deleting the `machine.md` link line each produce one finding.
</details>
bin/lint.sh runs everything CI runs; bin/lint-repo.py holds the checks that
are specific to this repo and needs no toolchain beyond stdlib Python.
The repo-specific ones exist because nothing else catches these: a typo'd
frontmatter key makes a skill silently not load, a renamed script leaves
dead paths in the SKILL.md that references it, and bin/link.sh and
nix/home.nix install the same files by two independent mechanisms that can
drift apart without either one breaking on its own.
That last check found two: link.sh never linked scripts/ into
~/.claude/scripts, which hook commands in settings.json call by absolute
path, and never created ~/.claude/machine.md, which entry/CLAUDE.md
@imports unconditionally. Both are fixed here.
Vendored skills are excluded throughout. check-vendored.sh moves to its own
weekly workflow rather than running per-PR: it needs network, and a skill
one release behind is not a reason to block a change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds
bin/lint.sh(everything CI runs) andbin/lint-repo.py(the checks specific to this repo, stdlib only). Two Gitea workflows: lint +nix flake checkon every PR,check-vendored.shweekly on its own.Generic: shellcheck, ruff,
compileall, jq,node --check,nix flake check. Baseline was already clean apart from two ruff findings inme-api.py, fixed here.Repo-specific, because nothing else catches these:
<skills-root>/…refs, repo-relative paths and relative links resolve.bin/link.shandnix/home.nixinstall the same files by two independent mechanisms.@~/.claude/x.mdis something both installers create.ExecStarttargets exist.The drift check found two bugs, fixed in the same commit:
link.shnever linkedscripts/into~/.claude/scripts(hook commands insettings.jsoncall it by absolute path), and never created~/.claude/machine.md, whichentry/CLAUDE.md@importsunconditionally — so every non-Nix machine started sessions with a failed import.No shfmt: it rewrites the deliberate compact style in
check-vendored.shandhourlog-session.sh. Noruff formatcheck: it would reformat 7 files.Verification
Negative-tested: renaming a skill's
name, adding a dead path reference, a broken relative link, and deleting themachine.mdlink line each produce one finding.