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
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

## Unreleased

## 0.13.0 - 2026-10-03

### Fixed
- Phase requirements share the complete run's remaining time budget. Command
output is capped before supervisor overflow, and completed Unix shells clean
up background descendants. Installed agent hooks pass an internal run timeout
below the host hook's timeout.
- Invalid explicitly requested Git baselines and unreadable historical trees
fail closed instead of falling back to another branch or an empty file set.
Unrelated histories require a usable common ancestor.
- Passing-result caches re-evaluate commands, security scanners, reviews,
instruction selection, and dependency checks. Local snapshots include ignored
gate inputs, branch/index state, and symlink file contents; malformed cache
records are ignored. Receipts cannot overwrite gate inputs, and runs that
change their snapshot cannot produce a passing receipt.
- Pre-commit hooks run on deletion-only commits. Reinstallation upgrades the
existing Skillgate hook, preserves command flags, hook stages and other hooks,
and `doctor` flags old wiring.
- Installed Claude Stop hooks use stdout JSON decisions, including a blocking
fallback if the gate cannot launch. Missing-file diagnostics remain blocking.
- Hook installation is serialized per project, and complete configuration files
are replaced atomically with their existing permissions preserved.
- Historical path matching uses the same glob syntax as disk scans, including
character classes, extglobs, and newline-containing filenames.
- Nested workspace checks read historical contents using Git-root-relative paths.
- Evidence and pattern checks reject directories and unreadable file inputs.

### Breaking
- Re-run `skillgate install <target>` to upgrade existing hook commands. For a
Claude Stop hook, use `skillgate install claude-code --stop`.
- `--cache` reports why fresh evaluation is required for gates with inputs outside
its local snapshot. Choose receipt outputs outside the policy's input paths and
globs, or exclude the output directory with the gate's `ignore` option.
- Correct invalid `--base` / `SKILLGATE_BASE` values and replace directory-shaped
evidence with non-empty regular files.

## 0.12.0 - 2026-10-01

### Added
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,22 @@ skillgate doctor claude-code # validates policy discovery and hook registr
existing agent settings, refuses to overwrite unrelated workflows, and pins
generated npm commands to the installed Skillgate version.

Pre-commit installation includes deletion-only commits and upgrades an existing
Skillgate hook when re-run. Installations are serialized per project and replace
complete config files atomically while preserving existing permissions and other
hooks. If another installation is active, retry after it finishes.

Agent hooks are installed **fail-closed**: if the gate itself cannot run (npx
missing, offline, registry error), the hook blocks instead of letting the command
through, and it gets a 10-minute budget so a slow test suite doesn't time out into
an allow. `doctor` flags a hook installed by an older version that would fail
open. Re-running `install` upgrades it in place.

Generated hooks give the evaluator a nine-minute run budget inside the ten-minute
host timeout. Claude Stop hooks use explicit stdout JSON decisions, so test output
such as a missing-file error cannot turn a block into a non-blocking hook error.
Upgrade an existing Stop hook with `skillgate install claude-code --stop`.

## Define your gates

Use `skillgate init --preset no-secrets` for credential and private infrastructure
Expand Down
1 change: 1 addition & 0 deletions contrib/pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ repos:
entry: npx @reneza/skillgate check
language: system
pass_filenames: false
always_run: true
stages: [pre-commit]
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ README for the research basis (the Compliance Gap).
| `command.ts` | Structural shell segmentation and finish-line matching across POSIX shells, PowerShell, wrappers, and Windows executable suffixes. |
| `process.ts` | Supervised command execution with bounded wall time and process-tree cleanup. |
| `receipt.ts` | Repository snapshot keys, passing-result cache storage, and versioned execution receipts. |
| `files.ts` | Atomic integration configuration writes and a per-project installation lock. |
| `integrations.ts` | Idempotent installer and health checks for Claude Code, OpenCode, GitHub Actions, and pre-commit. |
| `drift.ts` | Instruction-file drift detection (similarity of CLAUDE.md / AGENTS.md / Cursor / Copilot / …). Powers the `instruction-sync` gate and the `drift` / `diff-instructions` commands. Also exports `lineDiff()` and `formatDiff()` for showing line-level changes between instruction files. |
| `link.ts` | `runSync()` — makes one instruction file canonical and links the rest. Powers `sync`. |
Expand Down
6 changes: 6 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ These are stable; tools may rely on them.
| `1` | A gate failed / drift detected — the finish line is blocked |
| `2` | Usage error: no spec found, unknown command, spec failed to load |

Hook-specific output protocols (`gate --format cursor`, `gemini`, or
`claude-stop`) exit 0 after emitting the host's allow/block JSON. The default
text and generic `--json` gate modes retain exit 0 for allow and exit 2 for block.
`claude-stop` requires `--event stop` and emits `{}` for allow or
`{"decision":"block","reason":"..."}` for block.

## How deprecations happen

1. **Deprecate before removing.** A gate field or flag that is going away is first
Expand Down
1 change: 1 addition & 0 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Block local commits until gates pass.
entry: npx @reneza/skillgate@latest check
language: system
pass_filenames: false
always_run: true
```

## CI (GitHub Actions)
Expand Down
25 changes: 21 additions & 4 deletions docs/spec-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ unfixed CVEs.
### `evidence`

The escape hatch for steps that are not machine-observable ("research X first"): the
agent writes a named file as it works, and the gate verifies the file exists and is
non-empty.
agent writes a named file as it works, and the gate verifies it is a non-empty
regular file. Directories and unreadable paths fail the check.

```yaml
- id: research-recorded
Expand Down Expand Up @@ -163,6 +163,12 @@ These gates compare the working tree with the commit the change forked from
base they fail closed. The one exception is a repository with no commits at all: its
first commit is judged against the empty tree.

An explicit `--base` or `SKILLGATE_BASE` must resolve; Skillgate never replaces a
misspelled request with another branch. Unreadable baseline trees fail the gate,
and unrelated histories cannot supply a common ancestor. Globs use the same
syntax for on-disk and historical paths, including braces, character classes,
and extglobs. Historical reads are scoped to the policy's workspace.

```yaml
- id: no-new-skips # count must not increase
type: no-new
Expand Down Expand Up @@ -329,12 +335,23 @@ current Git worktree root. Gates run relative to the directory that owns the pol
so calling `skillgate check` from a nested package cannot accidentally use paths from
the main checkout or a parent repository.

The run budget applies across all gates. A per-command `timeout` is capped by the
The run budget applies across all gates and cumulative phase requirements. A per-command `timeout` is capped by the
remaining total budget, and a timed-out command's supervised process tree is
terminated. Any gates that could not start are returned as blocking `not-run`
results, preserving one result per configured gate.
Commands have a combined 8 MiB output cap. On Unix, completion also terminates
background descendants in the shell's process group; command gates should finish
their work before exiting. Windows timeout cleanup uses `taskkill /T` when available.

`skillgate check --receipt <file>` writes a versioned JSON receipt with the workspace
snapshot, per-gate status/reason/duration, and total budget. `--cache` stores a passing
receipt outside the working tree and reuses it only for the same parsed policy,
runtime, base commit, and repository snapshot. Failing results are never cached.
runtime, base commit, branch/index state, and repository snapshot, including
explicit ignored gate inputs and symlink file contents. Failing or malformed
results are never reused. A workspace change during evaluation blocks a passing
receipt. Receipt outputs must not overlap gate input paths or globs.

Cache reuse applies to local file, directory, pattern, diff, and phase checks.
Commands, Trivy, TruffleHog, reviews, instruction selection, and dependency checks
always execute again because their complete inputs are not represented by the
snapshot. With `--json`, `cacheDisabledReason` explains a requested cache bypass.
19 changes: 14 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reneza/skillgate",
"version": "0.12.0",
"version": "0.13.0",
"publishConfig": {
"access": "public"
},
Expand Down Expand Up @@ -66,11 +66,13 @@
},
"dependencies": {
"@mattrglobal/pairing-crypto": "^0.4.2",
"picomatch": "^4.0.7",
"tinyglobby": "^0.2.10",
"yaml": "^2.6.0"
},
"devDependencies": {
"@types/node": "^26.0.0",
"@types/picomatch": "^4.0.3",
"typescript": "^7.0.2"
}
}
Loading
Loading