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
1 change: 1 addition & 0 deletions .agents/skills
76 changes: 56 additions & 20 deletions .claude/hooks/block-dangerous-git.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,61 @@
#!/bin/bash
# Block genuinely destructive git operations.
#
# Design notes:
# - Matches the *command*, not the raw tool input. The previous version
# grepped the whole string, so prose in a heredoc that merely named a
# command was blocked (including this file's own replacement), while
# `git push` with two spaces or `git -C . push` slipped straight through.
# - Plain `git push` is not destructive and is no longer blocked. The
# repository's own pre-push hook is the policy-backed control; this hook
# only refuses operations that destroy history or uncommitted work.
# - Degrades safely when jq is unavailable.

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

DANGEROUS_PATTERNS=(
"git push"
"git reset --hard"
"git clean -fd"
"git clean -f"
"git branch -D"
"git checkout \."
"git restore \."
"push --force"
"reset --hard"
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qE "$pattern"; then
echo "BLOCKED: '$COMMAND' matches dangerous pattern '$pattern'. The user has prevented you from doing this." >&2
exit 2
fi
done

if command -v jq >/dev/null 2>&1; then
COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
else
COMMAND=$(printf '%s' "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p')
fi

[ -z "$COMMAND" ] && exit 0

# Drop heredoc bodies, so documentation or test data that names a command is
# not mistaken for an invocation of it. A heredoc body spans lines, so this
# keeps the line that opens it and discards everything after.
#
# Exception: if the heredoc feeds an interpreter, the body IS executed, and
# dropping it would be a bypass (`bash <<EOF` / `git reset --hard` / `EOF`).
# In that case scan the whole thing.
HEREDOC_LINE=$(printf '%s' "$COMMAND" | grep -m1 '<<')
if [ -n "$HEREDOC_LINE" ] && printf '%s' "$HEREDOC_LINE" |
grep -qE '(^|[;&|[:space:]])(bash|sh|zsh|dash|ksh|python3?|perl|ruby|eval|xargs|source)([[:space:]]|$)'; then
SCRUBBED="$COMMAND"
else
SCRUBBED=$(printf '%s' "$COMMAND" | awk '/<</ { print; exit } { print }')
fi

# Only inspect commands that actually invoke git.
printf '%s' "$SCRUBBED" | grep -qE '(^|[;&|[:space:]])git([[:space:]]|$)' || exit 0

deny() {
echo "BLOCKED: '$COMMAND' looks like $1. The user has prevented you from doing this." >&2
exit 2
}

# --force rewrites remote history unconditionally; --force-with-lease is guarded.
# Checked as two independent conditions rather than one ordered regex: the flag
# may precede or follow the remote, and `push -f` leaves no space for a pattern
# that expects one before the flag.
if printf '%s' "$SCRUBBED" | grep -qE '(^|[;&|[:space:]])push([[:space:]]|$)' &&
printf '%s' "$SCRUBBED" | grep -qE '(^|[[:space:]])(--force|-f)([[:space:]]|$)'; then
printf '%s' "$SCRUBBED" | grep -q -- '--force-with-lease' || deny "an unguarded force-push"
fi

printf '%s' "$SCRUBBED" | grep -qE 'reset[[:space:]]+--hard' && deny "a hard reset (discards working-tree changes)"
printf '%s' "$SCRUBBED" | grep -qE 'clean[[:space:]]+-[a-zA-Z]*f' && deny "a forced clean (deletes untracked files)"
printf '%s' "$SCRUBBED" | grep -qE 'branch[[:space:]]+-D([[:space:]]|$)' && deny "a forced branch delete"
printf '%s' "$SCRUBBED" | grep -qE '(checkout|restore)[[:space:]]+(--[[:space:]]+)?\.([[:space:]]|$)' && deny "a bulk discard of working-tree changes"

exit 0
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copilot instructions

See [`AGENTS.md`](../AGENTS.md) at the repository root. It is the canonical
guide for every coding agent working in this repository and applies in full:
build and verify commands, the CLI-first agent workflow, architecture, and the
commit and pull-request rules.
4 changes: 2 additions & 2 deletions .github/workflows/cargo-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
draft: ${{ steps.release_type.outputs.draft }}
changelog: ${{ steps.changelog.outputs.changelog }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0

Expand Down Expand Up @@ -84,7 +84,7 @@ jobs:
asset_suffix: macos-arm64

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Get binary name and version
shell: bash
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ on:
push:
branches: [main, develop]
pull_request:
branches: ["*"]
# `**` rather than `*`: in Actions branch filters `*` does not match `/`,
# so a pull request targeting any branch with a slash in its name -- every
# stacked pull request, and anything under a `feat/` or `fix/` prefix --
# ran no checks at all.
branches: ["**"]

env:
CARGO_TERM_COLOR: always
Expand All @@ -13,7 +17,7 @@ jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust
uses: actions-rs/toolchain@v1.0.7
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/native-git-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
native-git-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Run native git Docker verification
run: ./scripts/verify_native_git_e2e.sh
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Committy is a Rust CLI for conventional commits, branch policy, grouped commits,

- Discover active policy with `cargo run -- schema --output json`.
- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`.
- Set `COMMITTY_NONINTERACTIVE=1` in the environment instead of relying on flag placement. `--non-interactive` and `-q`/`--quiet` are global and work on either side of the subcommand; every other flag (`--repo-path`, `--output`, `--dry-run`, and `--verbose`) must follow it.
- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it.
- Use the `committy-branch`, `committy-commit`, `committy-release`, and `committy-enforce` plugin skills when available.
- Never infer permission for a push, publish, destructive Git action, or unrelated staging.
Expand All @@ -30,4 +31,4 @@ Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repo
- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects
- `plugins/committy/`: shared Codex and Claude skills

PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`.
PRs target `develop`. Commits follow Conventional Commits. Never add AI attribution trailers — no `Co-Authored-By:`, no `Claude-Session:`, no equivalent — to commit messages or PR descriptions, even when tooling suggests them. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`.
34 changes: 4 additions & 30 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,7 @@
# Committy Claude guide
@AGENTS.md

Committy is a Rust CLI for conventional commits, branch policy, grouped commits, releases, and multi-package version updates. The checkout is CLI-first; do not assume a TUI exists.
## Claude Code specifics

## Work safely
`AGENTS.md` above is the canonical guide and applies in full. Only this section is Claude-specific.

- Discover active policy with `cargo run -- schema --output json`.
- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`.
- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it.
- Use `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed.
- Never infer permission for a push, publish, destructive Git action, or unrelated staging.

## Build and verify

```bash
cargo check
cargo test
cargo fmt --all -- --check
cargo clippy --all-targets --all-features
```

Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repositories. Add public-seam tests for machine output, exit codes, Git behavior, hook protocol, and plugin manifests.

## Architecture

- `src/cli/commands/`: command implementations and JSON payloads
- `src/config/`, `src/convention/`: hierarchical typed policy
- `src/git/`: repository and Git operations
- `src/linter/`: message/history validation; exit code 3 means lint findings
- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects
- `plugins/committy/`: shared Codex and Claude skills

PRs target `develop`. Commits follow Conventional Commits. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`.
- Invoke the bundled skills as `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. Other runtimes resolve them by bare name.
5 changes: 5 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Gemini instructions

See [`AGENTS.md`](./AGENTS.md). It is the canonical guide for every coding agent
working in this repository and applies in full: build and verify commands, the
CLI-first agent workflow, architecture, and the commit and pull-request rules.
Loading
Loading