Lefthook Pre-Commit Catalogue Integration Research
Lefthook Pre-Commit Catalogue Integration Research
Scope and method
Four readonly lanes inspected the CLI lifecycle, catalogue and desired-state model, existing hook implementations, and prior research/runbooks. The coordinator also inspected the published tool metadata and the upstream Lefthook 2.1.10 source checkout. This report records facts and bounded architectural implications. It does not settle product choices.
Trusted facts
Package identity and upstream behavior
- The npm name
left-hookis not published:npm view left-hookreturns a registry 404. The likely intended tool islefthook, currently published as 2.1.10 with theevilmartians/lefthookrepository. This naming must be confirmed before adding a dependency or catalog entry. - Lefthook's upstream README documents npm installation, a repository config
file,
lefthook install, andlefthook run pre-commit: README.md#L20-L69; upstream usage also documentsLEFTHOOK=0 git commit: docs/usage.md#L5-L24. - Lefthook accepts YAML, TOML, JSON, and JSONC config names and merges a
lefthook-localfile. Its docs state thatlefthook installwrites hook scripts under.git/hooks/: docs/configuration.md#L5-L23; docs/index.md#L14-L25. - Lefthook supports staged-file placeholders, glob filters, parallel jobs, and
stage_fixedfor formatters that rewrite files: README.md#L88-L129; docs/index.md#L27-L48; docs/examples/stage_fixed.md#L1-L15.
HI lifecycle and settings
hi initis interactive and delegates to repository scaffolding;hi scaffoldis the setup/materialization command:apps/cli/src/cli/scaffold-command.ts:210-296.- Init computes required tools, writes project settings, and materializes the
selected desired scaffold state:
apps/cli/src/scaffold/stage.ts:954-968,1022-1029. hi ensurecurrently only reconfigures provider, repository, and required tool settings. It reads.devpunks/settings.jsonand writes aReconfigurationChange; it does not materialize scaffold files:apps/cli/src/cli/ensure-command.ts:17-74.- Project settings have no hook-policy field today:
apps/cli/src/features/project-settings/model.ts:12-31. The settings service preserves unknown JSON keys when it writes a reconfiguration, but typed reads and changes do not expose a hook choice:apps/cli/src/features/project-settings/service.ts:151-243,292-340.
Catalogue, context plan, and managed output
- Pack entries already have
hooksstring IDs, and context plans already have alifecycle-hookcontribution kind:packages/scaffold/src/catalog.ts:12-26;packages/scaffold/src/context-plan.ts:8-55. - The current hook catalog contains only the shared
scaffold-update-checkandformat-edited-fileassets:apps/cli/src/data/catalog/hooks.ts:1-26. Root context planning always adds the update check, adds pack hooks, and gates the edit formatter on Python or detected Oxfmt:apps/cli/src/features/context-planning/compiler.ts:300-315. - Hook source files are copied from the selected baseline into
.agents/hooks/and recorded in the managed output/receipt:apps/cli/src/scaffold/output.ts:3478-3489,3954-3977. - Lint assets already describe required dependencies, versions, plugins,
config placement, and source files, while scaffold writes lint specs under
.devpunks/specs/lint/:apps/cli/src/data/catalog/lint.ts:25-43;apps/cli/src/scaffold/output.ts:3200-3266. - Existing lifecycle projection adapters are for Claude, Codex, Cursor, and
OpenCode agent events. They have no Git commit event; OpenCode deliberately
treats unsupported lifecycle hooks as omissions:
apps/cli/src/data/scripts/harness-projection/adapters/{claude,codex,cursor,opencode}.mjs:1-124.
Existing repository hooks are a separate concern
- HI itself uses
.githooks/pre-commitand.githooks/pre-push, installed byscripts/install-git-hooks.mjs, which sets localcore.hooksPathand leaves global Git configuration unchanged:.githooks/pre-commit:1-5;.githooks/pre-push:1-10;scripts/install-git-hooks.mjs:23-40. - HI's pre-commit selector is worktree-aware, parses staged renames, maps
apps/*andpackages/*owners, and runs filtered checks or a root check for global/unknown paths:apps/cli/scripts/staged-verification-selector.mjs:19-113. - This repository-owned release gate is not consumer scaffold behavior. Adding
Lefthook to HI itself would compete with the existing
.githooksandcore.hooksPathmechanism.
Alignment constraints
- HI requires a lint/format migration to update package scripts, task
pipelines, CI, editor/docs references, and hooks together:
docs/reference/dp-requirements.md:244-260;apps/wiki/content/docs/harness/validation-and-tools/hooks.mdx:27-43. - The current global tool catalog models operator binaries installed through
global Bun, pnpm, or npm commands:
apps/cli/src/data/catalog/tools.ts:1-60;apps/cli/src/integrations/tool-management.ts:435-555. That mechanism is a poor fit for a project-local commit gate whose version should resolve from the consumer's package manager and lockfile.
Evidence-backed architectural implications
These are recommendations for a later requirements/specification pass, not accepted decisions:
- Keep Git commit policy separate from the existing agent
lifecycle-hookprojection, or add an explicit Git-hook contribution subtype. This avoids teaching four AI-provider adapters about a Git event they do not own while retaining the existing catalog, context-plan, desired-state, and receipt seams. - Persist the opt-out as typed project policy in
.devpunks/settings.jsonsohi init,hi ensure,hi update, andhi checkcan derive one deterministic desired state. A command-only flag cannot explain rerun, drift, or removal behavior. The field name and shape remain open. - Prefer a consumer-local
lefthookdev dependency plus a generated config and an explicit install/reconcile action. Do not classify it as a global Harness operator tool unless the product intentionally wants a PATH-owned version. Do not addlefthook installblindly toprepare: upstream installation mutates.git/hooks, and existing consumerpreparecommands are already reconciled as structured entries. - Define the first rule as a staged pre-commit gate that runs the consumer's
canonical format and lint commands, with explicit behavior for formatter
rewrites (
stage_fixedversus check-only), mixed JS/TS and Python repos, monorepo owner selection, empty/non-source commits, and command failure. Lefthook provides staged-file and glob primitives, but it does not discover HI's nearest-workspace lint policy for us. - Treat the generated config, hook installer state, dependency declaration,
and policy as separate managed/structured entries. Preserve unrelated
project-owned Husky, lint-staged,
.githooks, andcore.hooksPathstate; fail or ask before taking ownership of an existing hook manager. Disable, uninstall, and recovery semantics must be explicit before implementation.
Conflicts and unresolved product decisions
- Identity: confirm whether “left-hook” means the published
lefthookpackage, a private/internal executable, or another tool. No implementation should infer this from the spelling alone. - Scope: decide whether every consumer receives the gate, or only repos with a discoverable lint/format command and supported language/config.
- Opt-out semantics: decide whether opt-out prevents installation,
execution, or both; whether it removes only HI-owned files; and how a later
hi ensurere-enables the gate. - Lifecycle placement: decide whether
hi ensureremains settings-only and schedules a later scaffold update, or becomes a transaction that also reconciles the dependency, config, and Git hook. - Command contract: decide the canonical format/lint command shape, staged versus workspace scope, formatter mutation/staging, package-manager choice, and version pin/range.
- Ownership/coexistence: decide behavior for an existing
core.hooksPath, Husky, lint-staged, Lefthook config, or direct.git/hooks/pre-commit. - Authority and drift: decide which files are strict baseline-managed, which settings/config keys are structured and mergeable, and how consumer edits are preserved or reported.
Synthesized conclusion
HI has the right catalog and managed-state primitives, but no current contract for consumer Git hooks. The lowest-friction architecture is a project-policy controlled, project-local Lefthook integration that is planned with scaffold desired state, installed/reconciled through an explicit lifecycle action, and kept distinct from agent edit-hook projections. The package spelling, command semantics, coexistence rules, and opt-out behavior require a requirements decision before code or baseline assets are added.
Next local action
Confirm the intended package identity and answer the unresolved policy questions through a requirements grill. If accepted, compile a spec that assigns catalog, settings, scaffold-state, package-manager, hook-reconciliation, and docs work to their owning surfaces before implementation.