@casomoltd/tooling

Casomo's shared dev config: ESLint, Prettier, commitlint, knip & jscpd presets and dev CLIs (git hooks, readability, screenshots)


License
MIT
Install
npm install @casomoltd/tooling@0.25.0

Documentation

@casomoltd/tooling

Shared linting, formatting, commit config, and CLI tools for Casomo Ltd's repos.

What's included

Exports

Export Description
@casomoltd/tooling/eslint createBaseConfig() — ESLint flat config with Next.js, TypeScript, and SonarJS
@casomoltd/tooling/eslint-lib ESLint config variant for library packages
@casomoltd/tooling/prettier Prettier config object
@casomoltd/tooling/commitlint commitlint config with no-ai-attribution plugin
@casomoltd/tooling/knip Knip config for unused exports/dependencies
@casomoltd/tooling/jscpd Copy-paste detection config
@casomoltd/tooling/readability scoreText() — readability scoring for page content

Bin commands

Command Description
check-version Pre-push guard — rejects push if package.json version hasn't changed vs origin/main
pre-push Husky pre-push hook — runs check, build, then version/tag guards
pre-commit Husky pre-commit hook — runs npm run check
commit-msg Husky commit-msg hook — runs commitlint
readability Measure reading difficulty of page content
screenshot Capture a dev server page via Playwright — an agent's visual feedback loop
build-report Compile a Typst client report to PDF with the house template

Install

Published to the public npm registry. Add it to a consumer's devDependencies:

npm i -D @casomoltd/tooling
"@casomoltd/tooling": "^0.20.0"

The package is public, so installs need no token or .npmrc. The files allowlist plus the verify-pack leak-gate keep the published surface to configs/CLI only.

Installing this package never installs a browser. The screenshot bin needs one and build-report needs the typst CLI, but neither is declared here — see External prerequisites.

External prerequisites

Two bins need something npm cannot sensibly deliver: screenshot needs a Chromium build, build-report needs the typst CLI and the IBM Plex fonts. Neither is declared — not as a dependency, and not as an optional peer. Both load their prerequisite lazily and fail with an install instruction when it is missing, so a consumer that never runs those bins carries nothing for them.

The rule, for anything added later: a heavyweight prerequisite only some consumers need is documented here, never declared. An optional peer looks free because npm installs nothing for it, but it resolves into a consumer's lockfile as soon as anything pulls it in, and npm will not prune a satisfied optional peer afterwards — not on npm install, not on npm install --package-lock-only, not on npm uninstall. That is how one dev-only browser put the extract-zip advisory (GHSA-jmr9-qjv8-65gv) into four repos that never took a screenshot, and why clearing it needed a release of this package rather than a fix in each of them.

Husky hooks

The package provides shared hook commands so all repos enforce the same standards. Wire them up in .husky/:

.husky/pre-commit

pre-commit

.husky/commit-msg

commit-msg $1

.husky/pre-push

pre-push

Each repo defines its own check script in package.json — the hooks call npm run check which runs whatever checks that repo needs (lint, typecheck, spell, etc.).

Note: The tooling repo itself calls scripts by path in its hooks (e.g. ./bin/check-version.sh) because it can't resolve its own bin commands via node_modules/.bin.

Markdown / skills lint

bin/skills-lint.config.mjs is a stock remark config (remark-frontmatter + remark-validate-links + remark-lint-frontmatter-schema) that validates a Claude Code skills/agents/docs tree: every SKILL.md / agents/*.md has parseable YAML frontmatter with name + description, and every relative link and #anchor resolves. tooling runs it over its own skills/ in npm run check (lint:md) — catching the breakage a rename or hand-edit leaves that Claude Code's loader silently swallows.

To gate a consumer repo's docs (link/anchor integrity) at its own pre-commit, install the remark toolchain and point it at the shipped config:

npm i -D remark-cli remark-frontmatter remark-validate-links remark-lint-frontmatter-schema
"lint:md": "remark --frail --quiet --no-stdout --rc-path node_modules/@casomoltd/tooling/bin/skills-lint.config.mjs docs"

Append && npm run lint:md to your check script. The config resolves the plugins from your repo's node_modules and the schema (shipped beside it) relative to your cwd; the frontmatter schema only matches SKILL.md/agents files, so for plain docs it acts as a link/anchor check. Requires a tooling version that ships the config.

Screenshot tool

A visual feedback loop for coding agents. Claude captures the page it just changed, reads the PNG back and checks its own work, instead of waiting for someone to look and paste a screen grab. That is what the bin is for, and why its output lands under .claude/; a person wanting a screenshot already has a browser open.

It needs a Chromium build, which this package does not install (see External prerequisites):

npm i -D playwright && npx playwright install chromium

Then capture the running dev server:

npm run ss              # 1280×800 desktop capture
npm run ss contact      # desktop capture of /contact
npm run ss -- --width 390 --height 844   # mobile capture
npm run ss contact --width 390           # mobile /contact

Options:

Flag Default Description
--width <n> 1280 Viewport width in pixels
--height <n> 800 Viewport height in pixels

Add these scripts to your package.json:

{
  "screenshot": "screenshot",
  "ss": "npm run screenshot --"
}

Screenshots are saved to .claude/screenshots/. Set SCREENSHOT_URL to override the default http://localhost:3000.

Client reports (Typst)

report/ ships the Casomo house style for client-facing PDF reports — a Typst template (casomo-template.typ, with the brand mark beside it) plus the build-report bin that compiles a report to PDF:

npx build-report path/to/report.typ                # → scratch (see below)
npx build-report report.typ -o public/report.pdf   # explicit output path
npx build-report report.typ --watch                # recompile on save
npx build-report report.typ --open                 # open the PDF when done

The compiled PDF is a build artefact, so it defaults to the same scratch resolution as the agent reports (see Report output & SCRATCH_DIR under Agents): $SCRATCH_DIR/reports/<name>.pdf, or <repo-root>/scratch/reports/ when SCRATCH_DIR is unset — add scratch/ to the consumer repo's .gitignore (this repo does). Pass --out only when the PDF is a deliverable the repo actually keeps (e.g. a site's public/).

A report imports the template and applies it as a show rule; everything after is the body. From a consumer repo the import goes through node_modules (the bin sets the Typst root to the nearest package.json/.git ancestor so the path resolves):

#import "/node_modules/@casomoltd/tooling/report/casomo-template.typ": casomo-report, band

#show: casomo-report.with(
  kicker: "Delivery Report & Findings",
  title: "The Report Title",
  subtitle: "One-line summary.",  // optional
)

= Executive summary

report/example.typ is a lorem-ipsum reference report exercising every feature the template styles — compile it to see the house style.

Two external prerequisites apply here: the typst CLI on PATH (snap install typst / cargo install typst-cli) and the IBM Plex Sans / IBM Plex Mono fonts (github.com/IBM/plex) — build-report fails loud on the former and warns on the latter.

Quality gates

Quality gates run locally via git hooks, so code is deployment-ready by the time it reaches the remote. GitHub Actions re-runs check on push and publishes on a version tag; the local hooks are the gate, CI is the backstop.

  • pre-commit: npm run check — the repo's full health gate.
  • commit-msg: commitlint (house rules + the AI-attribution ban).
  • pre-push: npm run check, then the version and tag guards.

check owns the build. A repo whose build fails is not healthy, and any assertion that reads build output has to run after the build in the same script — so build belongs in check, and appears nowhere else. Declaring it in the pre-push gate as well would give it two owners and build twice.

What every repo's check must contain

check is a hand-written, ordered chain — the repos are heterogeneous and some orderings are load-bearing — but its composition is enforced, not remembered. Run check-gates as its first step:

"check": "check-gates && npm run lint && npm run typecheck && ..."

It fails if a required gate is missing, if one is defined but never run (a dead gate), or if an exception outlives the problem it was written for. Required: lint, typecheck, test, knip, jscpd, lint:md, spell, build.

A repo opts out by declaring a reason — a blank one fails, so an opt-out is never silent, and every exception is printed on each run:

"casomo": {
  "gates": { "test": "content site — no suite yet" }
}

Usage

.prettierrc.js

module.exports = require('@casomoltd/tooling/prettier');

commitlint.config.cjs

module.exports = require('@casomoltd/tooling/commitlint');

eslint.config.mjs

import { createBaseConfig } from '@casomoltd/tooling/eslint';

The ESLint config uses a factory function that receives resolved imports from the consumer to avoid module resolution issues across package boundaries.

Local development

Changes to tooling require a push to main to publish (the workflow runs automatically). Consuming repos then pick up changes with npm update @casomoltd/tooling.

For fast iteration while editing tooling config locally, use npm link to temporarily symlink your local checkout:

# in the consuming repo
npm link ../tooling

This overrides the published version until the next npm install, which restores the registry version.

Claude Code skills (plugin)

This repo is a Claude Code plugin (.claude-plugin/) shipping the generic, public engineering standards we work to across three agent-facing surfaces — skills, hooks, and agents. Business-specific ones live in private workspace config, not here.

The tooling serves three consumers: Claude (the editing workflow) → these plugin surfaces; CI + git-hooks (automation) → the npm bin scripts; manual human CLI is no longer a design target.

Authoring standard: creating or editing a skill/agent follows docs/skill-agent-schema.md — the interface schema (three profiles — procedural skill · agent · standard/rubric — plus a frontmatter decision table) that keeps these units composing without overlap.

Skill Description
/casomoltd:commit Run checks and commit cleanly (no bump, no push)
/casomoltd:release-version Bump → push → CI publish/deploy (the release tail)
/casomoltd:frontend-design Distinctive, production-grade frontend UI
/casomoltd:python-style Python code-generation style rules
/casomoltd:typescript TypeScript data modelling and type design
/casomoltd:screenshot Capture and analyse a dev server page
/casomoltd:design-pass Map → review → refactor a package (drives design-xray + code-review)
/casomoltd:draft-design-spec Author a browser-reviewable HTML design spec from a brief and iterate on it before writing code (drives design-xray)
/casomoltd:page-design Structure a content/explainer page for trust — above-the-fold answer, disclosure, palette-only colour (rubric a page-design reviewer preloads)
/casomoltd:generate-report Scaffold a Typst client report from the house template, compile via build-report, verify the PDF

Enable the plugin by adding this repo as a marketplace and installing it:

/plugin marketplace add casomoltd/tooling
/plugin install casomoltd@casomo-tooling

Skills then load namespaced as /casomoltd:<name>.

Hooks

The plugin also ships generic PreToolUse hooks (hooks/) that guard command/shell intent no linter or commit hook can see: confirm before git push, hard-block destructive git, npm version major, global installs, and edits that weaken TypeScript strictness. (They deliberately don't duplicate what commitlint/eslint already enforce — see hooks/README.md.) The guards are advisory — they ask you to confirm when they can't read a call's input, and never hard-block on their own malfunction. They require Node ≥ 22.18 / ≥ 24 (they run as TypeScript .mts via native type-stripping — no build step).

See hooks/README.md for the full rule set, the file map, how the hooks relate to settings.json permissions and skill allowed-tools, and the TypeScript / vendored-types design rationale.

Agents

Three namespaced agents (agents/), each read-only on what it inspects and preloading the relevant house standard as its rubric — typescript for .ts/.tsx, python-style for .py, docs-style for markdown. design-xray and docs-xray persist only their own report (see Report output, below).

casomoltd:code-review enforces the judgment-level half of the standards — the design calls a linter can't make (typed identifiers, static/varying separation, swallowed exceptions; class design, polymorphism over type-codes, value objects, EAFP, framework-first, test naming). It complements, never duplicates: eslint/ruff own the mechanical rules, the built-in /code-review owns correctness bugs, this owns the house design standards. Namespaced so it doesn't collide with the bundled /code-review. Invoke with @agent-casomoltd:code-review (or let Claude auto-delegate); it reports findings and never edits.

casomoltd:design-xray takes a package or diff and returns the structural picture: a doc-ready module inventory + mermaid class-hierarchy diagram, a weight table (which modules/classes are too heavy or thin), prioritized design findings, a ranked handoff of refactor targets, and a verdict on whether a heavier pattern (e.g. a state machine) is warranted yet or premature. It feeds forward — its map lifts straight into package docs, and its targets drive code-review and a refactor pass. The /casomoltd:design-pass skill chains the three (x-ray → code-review → refactor), and /casomoltd:draft-design-spec reuses its current-state map as the "before" picture when authoring a pre-implementation design spec. Like its sibling it visualizes and judges structure only — no correctness bugs (that's /code-review), no lint (eslint/ruff).

casomoltd:docs-xray is the same idea for a documentation corpus: it walks every markdown doc (README, CLAUDE.md, docs/, skill/agent definitions) and returns a map — each doc's heading tree + outbound pointers, plus a mermaid reference-graph of how the docs link — and a coherence report: orphan docs nothing links to, stale cross-references whose summary has drifted from the target, duplicated coverage, and missing back-links. It judges structure and cross-reference coherence against docs-style — not mechanical broken links (that's a markdown link linter) or prose voice (a content reviewer).

Report output & SCRATCH_DIR. design-xray and docs-xray persist their report — the .md plus an .html rendered by bin/render-report.mjs — so it outlives the run and opens in a browser. Each writes to a gitignored <repo-root>/scratch/<agent>/ by default. Set the optional SCRATCH_DIR env var to pool every report under one shared location instead ($SCRATCH_DIR/<agent>/) — e.g. a multi-repo workspace collecting reports in one place rather than scattering them per-repo. Publishing a report to a hosted claude.ai artifact is orchestrator- only and on explicit request; the agents never do it.

Package distribution

@casomoltd/tooling, alongside the product libraries (@casomoltd/paye-calc, @casomoltd/nhs-pay), publishes to the public npm registry via OIDC trusted publishing — install with no auth or .npmrc. A version-tag push (npm version patchgit push --follow-tags) triggers publish.yml. That workflow carries no npm pin: it builds on Node 24, whose bundled npm 11.x is already the range we want — at or above the npm >= 11.5.1 trusted publishing needs, and short of npm 12, which still refuses non-registry fetches by default (EALLOWREMOTE) and so cannot regenerate a lockfile that resolves a transitive remote tarball. The other two npm 12.0.0 regressions this once guarded against are fixed: npm ci strictness (12.0.2 installs Linux-generated locks cleanly) and the provenance/sigstore crash (12.0.1). Re-test the fetch block before moving to npm 12.