skills

Hooks and safety

Git hooks

Hooks install automatically on pnpm install. Manage them with:

pnpm hooks:status
pnpm hooks:disable-all

Bypass a single commit with git commit --no-verify. Set GIT_HOOKS=0 to skip hook setup entirely (CI/Docker).

Where each gate runs

The checks are six independent triggers, not a pipeline: no CI job declares needs, the pull-request-only jobs run in parallel with validate, and the docs deployment and live-provider workflows are separate.

Mermaid updated 2026-11-01

Scope of each trigger:

  • These are independent triggers, not a pipeline. No job declares needs: in the Validate workflow, so its PR-only static job runs in parallel with validate. The static checks share one checkout, toolchain setup and dependency install; named steps report each failure and continue independent checks.
  • The validate job runs for ready PRs and pushes to main. Draft PRs skip both Validate jobs; marking a PR ready, or changing the base branch of a ready PR, schedules them without needing another commit. Title/body-only edits skip them.
  • The pre-push hook runs validate, build:check, type-check and the two gates, and deliberately skips the test suite and smoke to stay fast.
  • oat status --scope project --hook in pre-commit is non-blocking.
  • Docs CI is PR-only and path-scoped.
  • Deploy Docs is an independent workflow, also dispatchable on top of its push-to-main trigger. Live Provider E2E is the dispatch-only workflow, and nothing invokes it.
  • The Release workflow fires on consensus-v* / session-v* tags. It builds, asserts generated outputs are committed, reruns the static suite, and verifies the tag against the already-written manifests.

Pre-commit: lint-staged

The pre-commit hook runs lint-staged over staged files only. JavaScript, TypeScript, JSON, and Markdown are formatted with oxfmt; JavaScript and TypeScript are also linted with oxlint. Config: .oxlintrc.json, .oxfmtrc.json, and .lintstagedrc.mjs.

Adoption is incremental: the pre-commit hook runs lint-staged over staged files only, and CI lints/format-checks only the files a PR changes. The repo has not yet been formatted wholesale; a one-time repo-wide oxfmt is a planned follow-up. Until then, do not run pnpm format across the whole tree in unrelated PRs.

Pre-push: skill version-bump enforcement

The pre-push hook runs validate, build:check, type-check, skill-version validation, and internal-flag validation. Tests and smoke are intentionally left to the full local check set and CI, not duplicated in this fast hook.

Changed skills must bump their version. Any change under a canonical skill directory (src/skills/<name>/) requires that skill's quoted metadata.version to increase. This is enforced by scripts/validate-skill-versions.ts:

pnpm run validate:skill-versions -- --base-ref <ref>

It is wired into the PR-only static CI job and the local pre-push hook. Shared roots, generated outputs, plugin-shared areas, and rename history can affect more than the directly edited path; see the version-impact table.

The same check also requires a changelog entry: for every canonical skill or plugin release whose version changes, CHANGELOG.md must gain a new line inside its ## [Unreleased] section that names that skill or plugin together with its new version, so a version bump can never ship without release notes and an unrelated entry cannot stand in for the missing one. A release PR that moves the Unreleased entries under a new ## [x.y.z] - date heading satisfies the check for the plugin at that version through the heading itself.

Pre-push: OAT tooling internal-flag enforcement

The OAT tooling skills mirrored under .agents/skills/** must stay hidden from npx skills discovery — the CLI honors metadata.internal: true in frontmatter to drop a skill from normal discovery (it reappears only under INSTALL_INTERNAL_SKILLS=1). Because those files are regenerated by oat tools update / oat sync, the flag is re-applied by an idempotent script and guarded by a gate rather than hand-edited:

  • After refreshing tooling, re-stamp the flag: pnpm exec tsx scripts/apply-internal-flags.ts, then oat sync. The script is idempotent and skips the symlinked session-observer mirror (a canonical standalone skill that must stay publicly discoverable).

  • The detector scripts/validate-internal-flags.ts:

    pnpm run validate:internal-flags

    is wired into the PR-only static CI job and the local pre-push hook, so a missing flag cannot merge to main. If it fails, run the apply script and re-commit the stamped files.

Do not add this flag to canonical or generated product skill payloads under src/skills/, skills/, or plugins/*/skills/; those are intended public entries. See DR-260627 for the rationale.

Lint/format exclusions

Never lint/format generated, OAT-synced, or agent-instruction files: generated installation outputs from scripts/build-generated.ts, .agents/**, .claude/rules/**, .cursor/rules/**, and AGENTS.md at every level.

Format exclusions must stay in sync across .oxfmtrc.json, .lintstagedrc.mjs, and the CI oxfmt --check step in .github/workflows/validate.yml; generated .mjs lint exclusions must also stay in sync across .oxlintrc.json, .lintstagedrc.mjs, and the CI oxlint step.

Because JSON config files cannot import TypeScript, the two ignorePatterns lists cannot read the build's distribution catalog the way .lintstagedrc.mjs does. They instead use collapsed globs: every directory under skills/ and plugins/*/skills/ is a generated installation or provider view, so skills/** and plugins/*/skills/** cover them all, and the two generated plugins/consensus/scripts/*.mjs bundles are listed by path. Only src/skills/ is authored.

The covers generated roots in static lint and format configs case in tests/tooling/generated-output-sync.test.ts guards the collapse by matching semantics rather than literal listing: every generated output root must be matched by some pattern in both configs, and those patterns must not match authored paths such as src/skills/**. A new generated root that escapes the globs and an over-broad glob that swallows authored source both fail the test.

A single shared ignore file passed to both tools with --ignore-path is not an option: oxlint ignores the leading-slash anchor in such a file, so a /skills/ entry would also exclude authored src/skills/ and .claude/skills/.

oxlint/oxfmt are dev tooling — they do not touch what shipped skills run.

On this page