Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Agent Notes

## Documentation

- Keep useful TSDoc and inline comments in source. They are part of the
codebase's readability and AI-navigation surface.
- Add or improve TSDoc for exported types, functions, classes, and domain
contracts when it helps callers understand invariants, error modes, or
configuration semantics.
- Use `docs/` for durable architecture, domain, ADR, and reference material.
The docs directory complements TSDoc; it does not replace comments that belong
next to code.
8 changes: 8 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ _Avoid_: error, failure
A finding that is shown to the developer without blocking the push.
_Avoid_: advisory, notice

**Review Output Contract**:
The schema-versioned finding shape that every local AI review provider must satisfy before Pushgate consumes review results.
_Avoid_: provider response format, model output

**Guardrail**:
A local limit that skips or constrains a Pushgate phase before expensive or unreliable work begins.
_Avoid_: validation rule, safety check
Expand All @@ -96,6 +100,10 @@ _Avoid_: validation rule, safety check
A one-push instruction that bypasses all Pushgate work or only local AI review.
_Avoid_: bypass flag, disable switch

**Warning Confirmation**:
The explicit developer acknowledgement required before Pushgate allows a push to continue after warning results.
_Avoid_: prompt, approval, confirmation dialog

**Transcript**:
The developer-facing Pushgate output that explains what ran, what was skipped, and why a push passed or failed.
_Avoid_: logs, console output
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ ignore_paths:
- "coverage/**"
```

V2 configs must declare `version: 2`. Core config sections are strict, provider-specific config belongs below `ai.providers.<provider>`, and tool commands are argv arrays rather than shell strings. `{changed_files}` expands to individual argv entries without shell interpolation, so filenames with spaces stay one argument. Built-in policies are opt-in deterministic checks and share the same `blocking`/`warning` behavior as command tools. Path policy resolves changed files once, then exposes a review range for local AI diff context and a scan range for commit scanners. `plugins.gitleaks` delegates secret scanning to the Gitleaks CLI using that scan range (`<merge-base>..HEAD`) plus a temporary JSON report, while preserving Gitleaks' own config, baseline, and ignore-file mechanisms. Local AI blocks the push when changed text lines exceed `ai.max_changed_lines`, and skips only the AI phase when the approximate prompt-token budget is exceeded; deterministic checks still run first. If deterministic checks or local AI produce warnings, Pushgate asks whether to continue; declining, or running without an interactive terminal for the prompt, blocks the push. Reviewer focus and default finding-category instructions live with the built-in review prompt rather than the v2 config surface. Provider adapters return one normalized JSON review result, including per-finding confidence plus provider source metadata that Pushgate uses for provider-neutral rendering. Pushgate currently supports `claude` and `copilot` provider IDs. See `docs/v2-config-schema.md` for the schema boundary, changed-file policy, and migration behavior for `.push-review.yml`.
V2 configs must declare `version: 2`. Core config sections are strict,
provider-specific config belongs below `ai.providers.<provider>`, and tool
commands are argv arrays rather than shell strings. See
[`docs/reference/configuration.md`](docs/reference/configuration.md) for the
full schema boundary, defaults, changed-file policy, and migration behavior for
`.push-review.yml`.

AI review output is provider-independent. Pushgate validates every provider response against the same local schema before consuming findings. Providers that support native JSON Schema, strict tool calls, or JSON mode can use stronger generation-time constraints in future adapters; current Claude and Copilot CLI adapters are text fallback providers, so Pushgate prompts them for the schema, safely repairs a small set of low-risk formatting damage, and rejects output that still does not match the contract.

Expand Down Expand Up @@ -287,3 +292,8 @@ To add a new template:
3. Open a pull request

Templates should include sensible `ignore_paths` defaults and pre-configured `tools` for the common tools in that stack. The `base.yml` template is the reference for all available config options, including opt-in built-in policies.

## Docs

The maintained docs index is [`docs/README.md`](docs/README.md). It links to
the domain model, architecture overview, ADRs, and reference contracts.
66 changes: 66 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Pushgate Docs

These docs capture durable Pushgate knowledge: domain language, architecture,
reference contracts, and decisions that explain why the system has its current
shape.

They complement source comments and TSDoc. Do not remove useful code comments
just because related reference material exists here.

The docs are intentionally not an issue tracker. Temporary implementation
plans, refactor prompts, demo notes, and ticket briefs should live outside this
directory unless they are distilled into one of the maintained doc types below.

## Start Here

- [Domain Model](./domain/model.md) explains what Pushgate is in product terms.
- [Domain Glossary](../CONTEXT.md) defines the canonical language used in code,
docs, and tickets.
- [Architecture Overview](./architecture/overview.md) gives the system map.
- [Runtime Flow](./architecture/runtime-flow.md) follows one push from Git hook
to final local verdict.
- [Module Map](./architecture/modules.md) lists the main modules and stable
interfaces.

## Decisions

Architecture decisions live in [ADR](./adr/):

- [0001 - Local Hook Delegates To Managed Runner](./adr/0001-local-hook-delegates-to-managed-runner.md)
- [0002 - Local Gate Is Not Final Enforcement](./adr/0002-local-gate-is-not-final-enforcement.md)
- [0003 - Strict V2 Config With Normalized Defaults](./adr/0003-strict-v2-config-with-normalized-defaults.md)
- [0004 - Centralized Changed-File Resolution](./adr/0004-centralized-changed-file-resolution.md)
- [0005 - Provider-Neutral Local AI Review Contract](./adr/0005-provider-neutral-local-ai-review-contract.md)
- [0006 - Checked-In Generated Runner](./adr/0006-checked-in-generated-runner.md)

## Reference

- [Configuration Reference](./reference/configuration.md) documents
`.pushgate.yml`.
- [Changed-File Policy](./reference/changed-file-policy.md) documents Git range,
ignore, and live-path semantics.
- [Local AI Review](./reference/local-ai-review.md) documents provider behavior,
guardrails, and the structured output contract.
- [Distribution Runner](./architecture/distribution.md) documents
`bin/pushgate.mjs` and regeneration.

## What To Keep Tracking

Keep these docs current as the product changes:

- **Domain model**: update when user-facing terms or push-gate outcomes change.
- **ADRs**: add one only when a decision is hard to reverse, non-obvious without
context, and the result of a real trade-off.
- **Architecture docs**: update when module responsibility or runtime order
changes.
- **Reference docs**: update alongside schema, provider, path-policy, or
distribution changes.
- **Support diagnostics**: add a focused reference doc when helper commands,
debug artifacts, or support workflows become part of the product.
- **CI/PR parity**: add a focused doc when Pushgate starts emitting or checking
remote enforcement guidance.

Do not keep one-off implementation plans, refactor wishlists, delegated-agent
briefs, generated analysis exports, or ticket drafts in this directory. If they
contain a lasting decision, promote that decision into an ADR or reference doc
and delete the draft.
19 changes: 19 additions & 0 deletions docs/adr/0001-local-hook-delegates-to-managed-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Local Hook Delegates To Managed Runner

Pushgate keeps the repository `pre-push` hook as a small delegator and puts
runtime behavior behind a managed `pushgate` runner. This lets policy, config
validation, path handling, deterministic checks, and local AI evolve in
TypeScript without repeatedly rewriting shell hook logic in every installed
repository.

## Considered Options

- Keep all behavior in the shell hook. This is simple to install but makes
config parsing, timeouts, provider execution, and testing much harder.
- Delegate from the hook to a managed runner. This adds runner installation and
protocol compatibility, but gives the codebase a real implementation surface.

## Consequences

The hook must check runner existence, executability, and hook protocol before
delegating. The installer must keep the managed runner and hook compatible.
11 changes: 11 additions & 0 deletions docs/adr/0002-local-gate-is-not-final-enforcement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Local Gate Is Not Final Enforcement

Pushgate treats local push-gate results as fast developer feedback, not final
policy enforcement. Git hooks can be bypassed with `git push --no-verify`, so CI
and PR policy remain the final enforcement layer for repository rules.

## Consequences

Pushgate docs must be honest about bypasses. Local `blocking` results improve
the default workflow, but security or compliance guarantees must be mirrored in
remote enforcement.
18 changes: 18 additions & 0 deletions docs/adr/0003-strict-v2-config-with-normalized-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Strict V2 Config With Normalized Defaults

Pushgate uses `.pushgate.yml` as the v2 public config surface. The loader
validates it against a strict schema, rejects unknown core keys, normalizes
defaults before downstream modules read it, and treats `.push-review.yml` as
legacy migration input rather than an alternate runtime format.

## Considered Options

- Accept loose YAML and let each module interpret missing or unknown fields.
- Validate once at the config boundary and hand every module a normalized
`PushgateConfig`.

## Consequences

Downstream modules can assume defaults are present and active AI provider
selection has already been validated. Adding public config requires schema,
normalization, docs, and tests to change together.
17 changes: 17 additions & 0 deletions docs/adr/0004-centralized-changed-file-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Centralized Changed-File Resolution

Pushgate resolves changed files once from the configured local target ref and
shares that normalized result with deterministic checks and local AI. The path
policy module owns target-ref resolution, merge-base selection, diff parsing,
ignore filtering, and named review and scan ranges.

## Considered Options

- Let each phase run its own Git commands and choose its own range.
- Resolve once and pass domain-level changed-file facts to phases.

## Consequences

Pushgate fails explicitly when the target ref or merge base is unavailable. It
does not fetch, guess a remote, or silently switch ranges. Consumers use
`reviewRange` and `scanRange` instead of rebuilding Git syntax themselves.
17 changes: 17 additions & 0 deletions docs/adr/0005-provider-neutral-local-ai-review-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Provider-Neutral Local AI Review Contract

Pushgate normalizes Claude, Copilot, and future provider output into one local
AI review contract before building a verdict. Provider adapters may use
different command arguments and transports, but the rest of Pushgate consumes
the same schema-versioned findings and provider-neutral failure codes.

## Considered Options

- Let each provider define its own output shape and transcript behavior.
- Require every provider to satisfy one Pushgate-owned output contract.

## Consequences

The review contract, prompt, local validation, and provider adapters must stay
aligned. Providers with native schema support can enforce the contract earlier,
but Pushgate still validates responses locally before consuming findings.
12 changes: 12 additions & 0 deletions docs/adr/0006-checked-in-generated-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Checked-In Generated Runner

Pushgate checks in `bin/pushgate.mjs` because the installer needs a single
runner artifact that can be downloaded and executed without project-local
dependencies. The TypeScript source under `src/` remains the implementation
truth, and the generated runner is rebuilt from that source.

## Consequences

Generated runner diffs are expected when source, dependencies, schemas, or
build tooling change. Do not edit the generated runner by hand; update source
or schemas and regenerate it.
11 changes: 11 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Architecture Decision Records

ADRs capture decisions that are hard to reverse, non-obvious without context,
and the result of a real trade-off.

- [0001 - Local Hook Delegates To Managed Runner](./0001-local-hook-delegates-to-managed-runner.md)
- [0002 - Local Gate Is Not Final Enforcement](./0002-local-gate-is-not-final-enforcement.md)
- [0003 - Strict V2 Config With Normalized Defaults](./0003-strict-v2-config-with-normalized-defaults.md)
- [0004 - Centralized Changed-File Resolution](./0004-centralized-changed-file-resolution.md)
- [0005 - Provider-Neutral Local AI Review Contract](./0005-provider-neutral-local-ai-review-contract.md)
- [0006 - Checked-In Generated Runner](./0006-checked-in-generated-runner.md)
51 changes: 51 additions & 0 deletions docs/architecture/distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Distribution Runner

`bin/pushgate.mjs` is the installer-facing Pushgate runner. It is checked in so
`install.sh` can install a single managed command for Git hooks without a
project-local build step or installed Node dependencies.

The source of truth is TypeScript under `src/`, with `src/cli.ts` as the bundle
entry point. `scripts/build-runner.mjs` uses esbuild to produce the single-file
runner.

## Regenerating

```sh
pnpm run bundle
```

The generated file keeps its shebang first so it remains directly executable.
Do not edit `bin/pushgate.mjs` by hand. Update `src/` or schemas, then rebuild.

## Inspecting Bundle Composition

```sh
pnpm run bundle:analyze
```

The analysis command rebuilds the runner with esbuild metafile output, then
writes ignored artifacts:

- `dist/bundle-analysis/pushgate-metafile.json`
- `dist/bundle-analysis/pushgate-analysis.txt`

Use the text report for a quick size scan and the JSON metafile for custom
tooling.

## Generated Files

Two generated surfaces are intentionally checked in:

- `bin/pushgate.mjs` is the installer-facing runner artifact.
- `src/generated/*-validator.ts` contains schema validators generated from
`schemas/*.schema.json`.

Architecture graphs and docs should treat generated files as distribution
artifacts, not as the implementation source of truth.

## Freshness

`pnpm test` runs `pnpm run typecheck`, then `pnpm run bundle`, then the Node
test suite. `test/runner.test.ts` executes the generated runner directly, so the
installed artifact remains inside the tested surface while source changes stay
localized under `src/`.
61 changes: 61 additions & 0 deletions docs/architecture/modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Pushgate Module Map

This map uses the codebase-design vocabulary: a module exposes an interface and
hides an implementation. The useful question is what callers do not need to
know.

## Runtime Modules

| Module | Main interface | Implementation files | Notes |
|---|---|---|---|
| Hook delegator | Executable Git `pre-push` hook plus hook protocol check | `hook/pre-push` | Small by design. It should stay a delegator. |
| Installer | `install.sh [--template name]` | `install.sh`, `templates/*.yml` | Owns runner placement, hook backup, template install, and validation. |
| CLI | `main(argv, io)` and `pushgate` subcommands | `src/cli.ts`, `src/cli/*` | Public command surface for hook and wrapper use. |
| Pre-push workflow | `runPrePushWorkflow(io)` | `src/workflows/pre-push.ts`, `src/workflows/run-decisions.ts` | Owns phase order and warning confirmation. |
| Config | `loadConfig`, `parseConfigYaml`, `PushgateConfig` | `src/config/*`, `schemas/pushgate-config-v2.schema.json` | Converts user YAML into one normalized internal shape. |
| Path policy | `resolveChangedFiles`, `selectToolChangedFilePaths` | `src/path-policy/*`, `src/git/*` | Owns Git range, diff parsing, ignore, and live-path semantics. |
| Process execution | `runCommand`, `runTimedCommand`, `runProcessOutcome`, `runInheritedCommand` | `src/process/*` | Shared child-process mechanics and outcome formatting. |
| Deterministic runner | `runDeterministicChecks` | `src/runner/*` | Runs built-in policies, plugin checks, configured tools, transcript, and summary. |
| Local AI review | `runLocalAiReview` | `src/ai/*` | Applies guardrails, builds payload, calls provider runtime, builds verdict. |
| Provider adapters | `LocalAiProviderAdapter.runReview` | `src/ai/providers/*` | Isolates Claude and Copilot command and transport details. |
| Generated runner | Executable `bin/pushgate.mjs` | `bin/pushgate.mjs`, `scripts/build-runner.mjs` | Installer-facing artifact; source of truth remains `src/`. |

## Data Contracts

| Contract | Producer | Consumers | Important invariants |
|---|---|---|---|
| `PushgateConfig` | Config module | Workflow, runner, AI, path policy | Defaults are normalized; active AI modes require a selected provider block. |
| `ChangedFileResolution` | Path policy | Deterministic runner, local AI | Contains target ref, target commit, merge base, filtered files, review range, and scan range. |
| `ChangedFile` | Path policy | Policies, tools, AI payload builder | Includes status, optional previous path, binary marker, additions, and deletions. |
| `ToolResult` | Deterministic runner | Transcript and summary | Status is `passed`, `skipped`, `warning`, or `blocked`. |
| `LocalAiReviewPayload` | AI review context | Provider adapters | Contains changed files, rendered diff, optional full-file context, and final prompt. |
| `RawAiReviewOutput` | Provider output parser | AI verdict and transcript | Must match schema version `1` and strict finding fields. |
| `AiFinding` | Review-output normalization | Verdict and transcript | Adds provider/model source metadata and normalized severity/category. |
| `LocalAiVerdict` | Verdict module | AI gate | Contains final exit code plus transcript events. |

## Dependency Shape

The high fan-in files are useful because they hide internal layout:

| File | Why modules depend on it |
|---|---|
| `src/config/index.ts` | Public config barrel for loader, constants, errors, and config types. |
| `src/path-policy/index.ts` | Public changed-file policy barrel for resolver, filters, errors, and types. |
| `src/ai/types.ts` | Shared local AI review types and provider contracts. |
| `src/runner/deterministic.ts` | Main deterministic-check interface and changed-file token helpers. |
| `src/git/command.ts` | Shared Git command execution and checked-error behavior. |

Keep barrels deep. They should expose module interfaces, not every internal
helper.

## Test Coverage Map

| Behavior | Primary tests |
|---|---|
| Config schema, defaults, provider selection, legacy config errors | `test/config.test.ts` |
| Changed-file parsing, ignored paths, target-ref errors, deleted files | `test/path-policy.test.ts` |
| Deterministic policies, plugin checks, tool commands, fail-fast behavior | `test/deterministic-runner.test.ts`, `test/runner.test.ts` |
| Hook protocol, pre-push runner behavior, skip controls, provider stubs | `test/runner.test.ts`, `test/hook.test.ts` |
| Installer behavior and installed hook assets | `test/install.test.ts` |
| Process outcome behavior | `test/process.test.ts` |
| Local AI prompt context, guardrails, provider adapters, output repair, verdicts | `test/ai.test.ts` |
Loading
Loading