diff --git a/CLAUDE.md b/CLAUDE.md index 8a2b4f5..bc376c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,7 @@ The constitution (`.specify/memory/constitution.md`) governs all feature work: ## Active Technologies - TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite (003-remove-combatant) - In-memory React state (local-first, single-user MVP) (003-remove-combatant) +- TypeScript 5.x (project), Go binary via npm (Lefthook) + `lefthook` (npm devDependency) (006-pre-commit-gate) ## Recent Changes - 003-remove-combatant: Added TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..88cc6e5 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,4 @@ +pre-commit: + jobs: + - name: check + run: pnpm check diff --git a/package.json b/package.json index 15facff..17d1d70 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "packageManager": "pnpm@10.6.0", "devDependencies": { "@biomejs/biome": "2.0.0", + "lefthook": "^1.11.0", "typescript": "^5.8.0", "vitest": "^3.0.0" }, "scripts": { + "prepare": "lefthook install", "format": "biome format --write .", "format:check": "biome format .", "lint": "biome lint .", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce76670..10624d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@biomejs/biome': specifier: 2.0.0 version: 2.0.0 + lefthook: + specifier: ^1.11.0 + version: 1.13.6 typescript: specifier: ^5.8.0 version: 5.9.3 @@ -660,6 +663,60 @@ packages: engines: {node: '>=6'} hasBin: true + lefthook-darwin-arm64@1.13.6: + resolution: {integrity: sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A==} + cpu: [arm64] + os: [darwin] + + lefthook-darwin-x64@1.13.6: + resolution: {integrity: sha512-CoRpdzanu9RK3oXR1vbEJA5LN7iB+c7hP+sONeQJzoOXuq4PNKVtEaN84Gl1BrVtCNLHWFAvCQaZPPiiXSy8qg==} + cpu: [x64] + os: [darwin] + + lefthook-freebsd-arm64@1.13.6: + resolution: {integrity: sha512-X4A7yfvAJ68CoHTqP+XvQzdKbyd935sYy0bQT6Ajz7FL1g7hFiro8dqHSdPdkwei9hs8hXeV7feyTXbYmfjKQQ==} + cpu: [arm64] + os: [freebsd] + + lefthook-freebsd-x64@1.13.6: + resolution: {integrity: sha512-ai2m+Sj2kGdY46USfBrCqLKe9GYhzeq01nuyDYCrdGISePeZ6udOlD1k3lQKJGQCHb0bRz4St0r5nKDSh1x/2A==} + cpu: [x64] + os: [freebsd] + + lefthook-linux-arm64@1.13.6: + resolution: {integrity: sha512-cbo4Wtdq81GTABvikLORJsAWPKAJXE8Q5RXsICFUVznh5PHigS9dFW/4NXywo0+jfFPCT6SYds2zz4tCx6DA0Q==} + cpu: [arm64] + os: [linux] + + lefthook-linux-x64@1.13.6: + resolution: {integrity: sha512-uJl9vjCIIBTBvMZkemxCE+3zrZHlRO7Oc+nZJ+o9Oea3fu+W82jwX7a7clw8jqNfaeBS+8+ZEQgiMHWCloTsGw==} + cpu: [x64] + os: [linux] + + lefthook-openbsd-arm64@1.13.6: + resolution: {integrity: sha512-7r153dxrNRQ9ytRs2PmGKKkYdvZYFPre7My7XToSTiRu5jNCq++++eAKVkoyWPduk97dGIA+YWiEr5Noe0TK2A==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@1.13.6: + resolution: {integrity: sha512-Z+UhLlcg1xrXOidK3aLLpgH7KrwNyWYE3yb7ITYnzJSEV8qXnePtVu8lvMBHs/myzemjBzeIr/U/+ipjclR06g==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@1.13.6: + resolution: {integrity: sha512-Uxef6qoDxCmUNQwk8eBvddYJKSBFglfwAY9Y9+NnnmiHpWTjjYiObE9gT2mvGVpEgZRJVAatBXc+Ha5oDD/OgQ==} + cpu: [arm64] + os: [win32] + + lefthook-windows-x64@1.13.6: + resolution: {integrity: sha512-mOZoM3FQh3o08M8PQ/b3IYuL5oo36D9ehczIw1dAgp1Ly+Tr4fJ96A+4SEJrQuYeRD4mex9bR7Ps56I73sBSZA==} + cpu: [x64] + os: [win32] + + lefthook@1.13.6: + resolution: {integrity: sha512-ojj4/4IJ29Xn4drd5emqVgilegAPN3Kf0FQM2p/9+lwSTpU+SZ1v4Ig++NF+9MOa99UKY8bElmVrLhnUUNFh5g==} + hasBin: true + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -1362,6 +1419,49 @@ snapshots: json5@2.2.3: {} + lefthook-darwin-arm64@1.13.6: + optional: true + + lefthook-darwin-x64@1.13.6: + optional: true + + lefthook-freebsd-arm64@1.13.6: + optional: true + + lefthook-freebsd-x64@1.13.6: + optional: true + + lefthook-linux-arm64@1.13.6: + optional: true + + lefthook-linux-x64@1.13.6: + optional: true + + lefthook-openbsd-arm64@1.13.6: + optional: true + + lefthook-openbsd-x64@1.13.6: + optional: true + + lefthook-windows-arm64@1.13.6: + optional: true + + lefthook-windows-x64@1.13.6: + optional: true + + lefthook@1.13.6: + optionalDependencies: + lefthook-darwin-arm64: 1.13.6 + lefthook-darwin-x64: 1.13.6 + lefthook-freebsd-arm64: 1.13.6 + lefthook-freebsd-x64: 1.13.6 + lefthook-linux-arm64: 1.13.6 + lefthook-linux-x64: 1.13.6 + lefthook-openbsd-arm64: 1.13.6 + lefthook-openbsd-x64: 1.13.6 + lefthook-windows-arm64: 1.13.6 + lefthook-windows-x64: 1.13.6 + loupe@3.2.1: {} lru-cache@5.1.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0e5a073..f93f88c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ packages: - "packages/*" - "apps/*" + +onlyBuiltDependencies: + - lefthook diff --git a/specs/006-pre-commit-gate/checklists/requirements.md b/specs/006-pre-commit-gate/checklists/requirements.md new file mode 100644 index 0000000..df553d1 --- /dev/null +++ b/specs/006-pre-commit-gate/checklists/requirements.md @@ -0,0 +1,34 @@ +# Specification Quality Checklist: Pre-Commit Gate + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-03-05 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass. Spec is ready for `/speckit.clarify` or `/speckit.plan`. diff --git a/specs/006-pre-commit-gate/plan.md b/specs/006-pre-commit-gate/plan.md new file mode 100644 index 0000000..8e1df32 --- /dev/null +++ b/specs/006-pre-commit-gate/plan.md @@ -0,0 +1,67 @@ +# Implementation Plan: Pre-Commit Gate + +**Branch**: `006-pre-commit-gate` | **Date**: 2026-03-05 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `/specs/006-pre-commit-gate/spec.md` + +## Summary + +Enforce a pre-commit quality gate by adding Lefthook as a Git hooks manager. A `lefthook.yml` configuration defines a pre-commit hook that runs `pnpm check`. The hook auto-installs via a `prepare` script in `package.json`, ensuring zero manual setup for developers after `pnpm install`. + +## Technical Context + +**Language/Version**: TypeScript 5.x (project), Go binary via npm (Lefthook) +**Primary Dependencies**: `lefthook` (npm devDependency) +**Storage**: N/A +**Testing**: Manual verification (commit with passing/failing checks) +**Target Platform**: macOS, Linux (developer workstations) +**Project Type**: Monorepo (pnpm workspaces) -- web application with domain/application/web layers +**Performance Goals**: No additional overhead beyond `pnpm check` execution time +**Constraints**: Must auto-install on `pnpm install`; must not interfere with CI +**Scale/Scope**: 2 files changed (lefthook.yml created, package.json modified) + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|-----------|--------|-------| +| I. Deterministic Domain Core | N/A | No domain logic changes | +| II. Layered Architecture | PASS | Tooling-only change, no layer imports affected | +| III. Agent Boundary | N/A | No agent layer changes | +| IV. Clarification-First | PASS | User explicitly specified Lefthook; no ambiguity | +| V. Escalation Gates | PASS | Feature is within spec scope | +| VI. MVP Baseline Language | PASS | No permanent bans introduced | +| VII. No Gameplay Rules | N/A | Not a gameplay feature | +| Development Workflow (merge gate) | PASS | Directly implements the "automated checks must pass" rule | +| Layer boundary compliance | N/A | No source code layer changes | + +**Post-design re-check**: All gates still pass. No design decisions introduced new violations. + +## Project Structure + +### Documentation (this feature) + +```text +specs/006-pre-commit-gate/ +├── plan.md # This file +├── research.md # Phase 0 output +├── quickstart.md # Phase 1 output +├── spec.md # Feature specification +└── checklists/ + └── requirements.md # Spec quality checklist +``` + +### Source Code (repository root) + +```text +./ +├── lefthook.yml # NEW — Lefthook configuration (pre-commit hook) +├── package.json # MODIFIED — add lefthook devDep + prepare script +└── (all existing files unchanged) +``` + +**Structure Decision**: This feature only adds tooling configuration at the repository root. No source code directories are created or modified. The existing monorepo structure (`packages/*`, `apps/*`) is unchanged. + +## Complexity Tracking + +No constitution violations. Table not applicable. diff --git a/specs/006-pre-commit-gate/quickstart.md b/specs/006-pre-commit-gate/quickstart.md new file mode 100644 index 0000000..6037962 --- /dev/null +++ b/specs/006-pre-commit-gate/quickstart.md @@ -0,0 +1,34 @@ +# Quickstart: Pre-Commit Gate + +## What This Feature Does + +Adds a Git pre-commit hook (managed by Lefthook) that runs `pnpm check` before every commit. If any check fails, the commit is blocked. + +## Files to Create/Modify + +| File | Action | Purpose | +|------|--------|---------| +| `lefthook.yml` | Create | Lefthook configuration with pre-commit hook | +| `package.json` | Modify | Add `lefthook` devDependency + `prepare` script | + +## Setup After Implementation + +After the feature is implemented, hooks activate automatically: + +```bash +pnpm install # installs lefthook + runs `prepare` which calls `lefthook install` +``` + +## How It Works + +1. Developer runs `git commit` +2. Lefthook intercepts via the Git pre-commit hook +3. Lefthook runs `pnpm check` (format + lint + typecheck + test) +4. If `pnpm check` exits 0 → commit proceeds +5. If `pnpm check` exits non-zero → commit is blocked, output shown + +## Bypass + +```bash +git commit --no-verify # skips the pre-commit hook +``` diff --git a/specs/006-pre-commit-gate/research.md b/specs/006-pre-commit-gate/research.md new file mode 100644 index 0000000..e45ec85 --- /dev/null +++ b/specs/006-pre-commit-gate/research.md @@ -0,0 +1,45 @@ +# Research: Pre-Commit Gate + +## R1: Hook Management Tool + +**Decision**: Use [Lefthook](https://github.com/evilmartians/lefthook) (npm package) as the Git hooks manager. + +**Rationale**: +- User explicitly requested Lefthook. +- Lightweight, standalone Go binary distributed via npm -- no runtime dependencies. +- Simple YAML configuration (`lefthook.yml`). +- Auto-installs hooks via npm `postinstall` lifecycle script -- satisfies FR-005 (no manual setup). +- Well-maintained, widely adopted (used by n8n, Shopify, and others). +- Respects `--no-verify` by default (standard Git behavior) -- satisfies FR-006. + +**Alternatives considered**: +- Husky: Popular but heavier configuration, requires `.husky/` directory with shell scripts. +- `core.hooksPath`: Native Git, but requires manual setup or custom scripts for auto-install. +- Simple `prepare` script copying a shell script: Works but no parallel jobs, no structured config. + +## R2: Auto-Install Mechanism + +**Decision**: Use a `prepare` script in root `package.json` that runs `lefthook install`. + +**Rationale**: +- The `prepare` lifecycle hook runs automatically after `pnpm install`. +- This ensures every developer gets hooks installed without extra steps after cloning. +- Lefthook's npm package includes a `postinstall` script that can auto-install, but an explicit `prepare` script is more transparent and reliable across package managers. +- In CI environments, `CI=true` prevents the `prepare` script from running (standard npm/pnpm behavior), avoiding unnecessary hook installation in CI. + +**Alternatives considered**: +- Relying solely on lefthook's built-in `postinstall`: Less transparent; behavior varies with `CI` env var. +- Manual `lefthook install` step in README: Violates FR-005. + +## R3: Hook Command Strategy + +**Decision**: The pre-commit hook runs `pnpm check` as a single command. + +**Rationale**: +- `pnpm check` already orchestrates format, lint, typecheck, and test in sequence. +- Running it as one command keeps the hook configuration minimal and consistent with the existing merge-gate workflow. +- Output from `pnpm check` already identifies which specific check failed (FR-004, SC-004). + +**Alternatives considered**: +- Running each check as a separate Lefthook job with `parallel: true`: Could be faster but adds configuration complexity and the existing `pnpm check` script already handles sequencing. MVP baseline does not include parallel hook jobs. +- Using `{staged_files}` for file-scoped checks: MVP baseline does not include staged-only checking per spec assumptions. diff --git a/specs/006-pre-commit-gate/spec.md b/specs/006-pre-commit-gate/spec.md new file mode 100644 index 0000000..a5d8f76 --- /dev/null +++ b/specs/006-pre-commit-gate/spec.md @@ -0,0 +1,88 @@ +# Feature Specification: Pre-Commit Gate + +**Feature Branch**: `006-pre-commit-gate` +**Created**: 2026-03-05 +**Status**: Draft +**Input**: User description: "Enforce a pre-commit gate: block commits unless `pnpm check` passes." + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Blocked Commit on Failing Checks (Priority: P1) + +A developer attempts to commit code that does not pass `pnpm check` (format, lint, typecheck, or test failures). The commit is automatically rejected with a clear message indicating what failed, preventing broken code from entering the repository. + +**Why this priority**: This is the core purpose of the feature -- preventing commits that violate project quality standards. + +**Independent Test**: Can be tested by introducing a deliberate lint or type error, attempting to commit, and verifying the commit is blocked with an informative error message. + +**Acceptance Scenarios**: + +1. **Given** a developer has staged changes that fail formatting, **When** they run `git commit`, **Then** the commit is rejected and the output shows the formatting errors. +2. **Given** a developer has staged changes that fail linting, **When** they run `git commit`, **Then** the commit is rejected and the output shows the lint errors. +3. **Given** a developer has staged changes that fail typechecking, **When** they run `git commit`, **Then** the commit is rejected and the output shows the typecheck errors. +4. **Given** a developer has staged changes that fail tests, **When** they run `git commit`, **Then** the commit is rejected and the output shows the test failures. + +--- + +### User Story 2 - Successful Commit on Passing Checks (Priority: P1) + +A developer commits code that passes all checks. The pre-commit gate runs `pnpm check`, all checks pass, and the commit proceeds normally without extra friction. + +**Why this priority**: Equally critical -- the gate must not block valid commits. A gate that only blocks but never allows is useless. + +**Independent Test**: Can be tested by making a valid code change, committing, and verifying the commit succeeds after checks pass. + +**Acceptance Scenarios**: + +1. **Given** a developer has staged changes that pass all checks, **When** they run `git commit`, **Then** `pnpm check` runs and the commit completes successfully. + +--- + +### User Story 3 - Bypass Gate in Emergencies (Priority: P2) + +A developer needs to bypass the pre-commit gate in an emergency situation (e.g., a hotfix where the existing codebase already has a known issue). They can use the standard Git `--no-verify` flag to skip the hook. + +**Why this priority**: Important escape hatch, but not the primary use case. Standard Git behavior should be preserved. + +**Independent Test**: Can be tested by attempting `git commit --no-verify` with failing checks and verifying the commit succeeds. + +**Acceptance Scenarios**: + +1. **Given** a developer has staged changes that fail checks, **When** they run `git commit --no-verify`, **Then** the commit proceeds without running the pre-commit gate. + +--- + +### Edge Cases + +- What happens when `pnpm` is not installed or not in PATH? The hook should fail with a clear error message. +- What happens when `node_modules` are not installed? The hook should fail with a clear error message suggesting `pnpm install`. +- What happens when the hook is run outside the project root? The hook should resolve the project root correctly. +- What happens on a fresh clone? The hook must be automatically available after `pnpm install` without additional manual steps. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The repository MUST include a Git pre-commit hook that runs `pnpm check` before every commit. +- **FR-002**: The hook MUST block the commit (exit non-zero) if `pnpm check` fails. +- **FR-003**: The hook MUST allow the commit (exit zero) if `pnpm check` succeeds. +- **FR-004**: The hook MUST display the output from `pnpm check` so the developer can see what failed. +- **FR-005**: The hook MUST be automatically available to all developers after cloning and running `pnpm install` (no manual hook installation steps). +- **FR-006**: The hook MUST be bypassable using the standard `git commit --no-verify` flag. +- **FR-007**: The hook MUST provide a clear error message if `pnpm` is not available. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 100% of commits made without `--no-verify` are validated by `pnpm check` before being accepted. +- **SC-002**: Developers see check results within the normal `pnpm check` execution time -- the hook adds no meaningful overhead beyond running the checks themselves. +- **SC-003**: New contributors can clone the repository, run `pnpm install`, and have the pre-commit gate active without any additional setup steps. +- **SC-004**: Developers can identify the specific failing check (format, lint, typecheck, or test) from the hook output alone. + +## Assumptions + +- The project already has a working `pnpm check` command that runs format, lint, typecheck, and test checks. +- All developers use Git for version control. +- The hook management approach uses Lefthook, a lightweight Git hooks manager distributed as an npm package, with a `prepare` script for auto-installation. +- MVP baseline does not include partial/staged-only checking (e.g., lint-staged). The full `pnpm check` runs on the entire project. diff --git a/specs/006-pre-commit-gate/tasks.md b/specs/006-pre-commit-gate/tasks.md new file mode 100644 index 0000000..e99e2cd --- /dev/null +++ b/specs/006-pre-commit-gate/tasks.md @@ -0,0 +1,105 @@ +# Tasks: Pre-Commit Gate + +**Input**: Design documents from `/specs/006-pre-commit-gate/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, quickstart.md + +**Tests**: No test tasks included (not requested in feature specification). Verification is manual. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Install Lefthook and configure auto-install mechanism + +- [x] T001 Add `lefthook` as a devDependency and add a `prepare` script that runs `lefthook install` in `package.json` +- [x] T002 Run `pnpm install` to install lefthook and activate the prepare script + +**Checkpoint**: `lefthook` is installed and `pnpm install` triggers `lefthook install` automatically + +--- + +## Phase 2: User Story 1 & 2 - Block Failing / Allow Passing Commits (Priority: P1) MVP + +**Goal**: Create the Lefthook pre-commit hook configuration that runs `pnpm check`, blocking commits on failure and allowing commits on success. + +**Independent Test (US1)**: Introduce a deliberate lint error, run `git commit`, and verify the commit is blocked with visible error output. + +**Independent Test (US2)**: Make a valid change, run `git commit`, and verify the commit succeeds after `pnpm check` passes. + +### Implementation + +- [x] T003 [US1] [US2] Create `lefthook.yml` at repository root with a `pre-commit` hook that runs `pnpm check` + +**Checkpoint**: Commits are blocked when `pnpm check` fails (US1) and allowed when it passes (US2). Output from `pnpm check` is visible to the developer. + +--- + +## Phase 3: User Story 3 - Bypass Gate in Emergencies (Priority: P2) + +**Goal**: Ensure the standard `git commit --no-verify` flag bypasses the pre-commit hook. + +**Independent Test**: Stage a change that would fail checks, run `git commit --no-verify`, and verify the commit succeeds without running checks. + +### Implementation + +No implementation task needed -- Lefthook respects `--no-verify` by default (standard Git behavior). This phase exists for verification only. + +**Checkpoint**: `git commit --no-verify` bypasses the pre-commit gate. + +--- + +## Phase 4: Polish & Cross-Cutting Concerns + +**Purpose**: Edge case handling and validation + +- [x] T004 Verify the hook provides a clear error when `pnpm` is not in PATH (FR-007) and when `node_modules` are missing (edge case) +- [x] T005 Run quickstart.md validation: clone-install-commit workflow works end-to-end (SC-003) + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies -- start immediately +- **US1 & US2 (Phase 2)**: Depends on Phase 1 (lefthook must be installed) +- **US3 (Phase 3)**: No implementation needed -- verify after Phase 2 +- **Polish (Phase 4)**: Depends on Phase 2 completion + +### Parallel Opportunities + +- T001 and T003 touch different files (`package.json` vs `lefthook.yml`) but T003 depends on lefthook being installed, so they must be sequential. +- T004 and T005 can run in parallel after Phase 2. + +--- + +## Implementation Strategy + +### MVP First (User Stories 1 & 2) + +1. Complete Phase 1: Install lefthook + prepare script +2. Complete Phase 2: Create `lefthook.yml` with pre-commit hook +3. **STOP and VALIDATE**: Test both blocking and allowing commits +4. Verify US3 bypass works (no implementation needed) + +### Execution Summary + +Total: **5 tasks** across 4 phases +- Phase 1 (Setup): 2 tasks +- Phase 2 (US1 + US2): 1 task +- Phase 3 (US3): 0 tasks (verification only) +- Phase 4 (Polish): 2 tasks + +--- + +## Notes + +- US1 and US2 are implemented by the same single task (T003) because they are two sides of the same coin: the hook either blocks or allows based on `pnpm check` exit code. +- US3 requires no implementation -- `--no-verify` is standard Git behavior that Lefthook respects. +- This is a minimal-footprint feature: 1 new file (`lefthook.yml`), 1 modified file (`package.json`).