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
11 changes: 10 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test-template:

# 1. Verify root developer tooling files are symlinks to template/
failed=0
for symlink in .agentignore .envrc .githooks/commit-msg .githooks/pre-commit scripts/sync-agent-ignore.sh .claude/settings.json; do
for symlink in .agentignore .envrc .githooks/commit-msg .githooks/pre-commit .claude/settings.json; do
if [[ ! -L "$symlink" ]]; then
echo "error: root file '$symlink' is not a symlink to template/" >&2
failed=1
Expand All @@ -41,13 +41,21 @@ test-template:
fi
diff -u .gitignore template/.gitignore

# Verify sync-agent-ignore.sh is tracked as an executable regular file (not a symlink)
test -f scripts/sync-agent-ignore.sh
test ! -L scripts/sync-agent-ignore.sh
test -x scripts/sync-agent-ignore.sh

# 2. Test default template rendering (enable_pr_reviews=true)
copier copy --defaults --trust . "$TMPDIR/rendered-default"
test -f "$TMPDIR/rendered-default/README.md"
test -f "$TMPDIR/rendered-default/shell.nix"
test -f "$TMPDIR/rendered-default/.claude/settings.json"
test -f "$TMPDIR/rendered-default/.github/workflows/pr-review.yml"
test ! -f "$TMPDIR/rendered-default/copier.yml"
test ! -f "$TMPDIR/rendered-default/scripts/create-project.sh"
test ! -f "$TMPDIR/rendered-default/scripts/sync-agent-ignore.sh"
test ! -d "$TMPDIR/rendered-default/scripts"
test ! -d "$TMPDIR/rendered-default/.beads"

# Verify rendered project passes its own validation suite
Expand All @@ -61,6 +69,7 @@ test-template:
# 4. Test scaffolding script dry run
./scripts/create-project.sh --dry-run --defaults --template . "$TMPDIR/dry-run-app"
test -f "$TMPDIR/dry-run-app/README.md"
test ! -d "$TMPDIR/dry-run-app/scripts"

# 5. Verify scaffolding script rejects target paths inside template directory (both shallow and multi-level)
err_output=$(./scripts/create-project.sh --dry-run --defaults --template . "./internal-target-error" 2>&1 || true)
Expand Down
1 change: 0 additions & 1 deletion scripts/sync-agent-ignore.sh

This file was deleted.

71 changes: 71 additions & 0 deletions scripts/sync-agent-ignore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Template-maintenance script: regenerates .claude/settings.json's Read-deny
# rules from .agentignore so Claude Code enforces the same patterns as the
# gitignore-syntax .agentignore without hand-duplicating them.
# Writes through root .claude/settings.json symlink to update template/.claude/settings.json.
#
# Usage:
# scripts/sync-agent-ignore.sh # regenerate .claude/settings.json
# scripts/sync-agent-ignore.sh --check # verify it's already in sync (no write)
set -euo pipefail

CDPATH='' cd -- "$(dirname "${BASH_SOURCE[0]}")/.."

if ! command -v jq >/dev/null 2>&1; then
echo "error: required command 'jq' is not installed or not in PATH" >&2
echo "tip: run inside 'nix-shell' or install jq via your system package manager" >&2
exit 1
fi

if [[ ! -f .agentignore ]]; then
echo "error: .agentignore not found in $(pwd)" >&2
exit 1
fi

deny=()
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
# Skip empty lines, lines with only whitespace, and comments (including indented)
[[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue
# Strip leading and trailing whitespace
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
if [[ "$line" == */ ]]; then
deny+=("Read(./**/${line%/}/**)")
else
deny+=("Read(./**/${line})")
fi
done < .agentignore

# Handle empty pattern list defensively to generate valid JSON ({deny: []})
# without triggering Bash 3.2 unbound variable errors under set -u
if [[ ${#deny[@]} -eq 0 ]]; then
generated='{"permissions":{"deny":[]}}'
else
generated="$(printf '%s\n' "${deny[@]}" | jq -R . | jq -s '{permissions: {deny: .}}')"
fi

case "${1:-}" in
"")
mkdir -p .claude
printf '%s\n' "$generated" > .claude/settings.json
echo "Regenerated .claude/settings.json from .agentignore"
;;
--check)
current="$(tr -d '\r' < .claude/settings.json 2>/dev/null || true)"
if [[ "$current" != "$generated" ]]; then
echo "error: .claude/settings.json is stale — run 'just sync-agent-ignore' to regenerate" >&2
exit 1
fi
echo ".claude/settings.json is in sync with .agentignore"
;;
-h|--help)
echo "Usage: $(basename "${BASH_SOURCE[0]}") [--check]"
exit 0
;;
*)
echo "error: unrecognized argument '$1'" >&2
echo "Usage: $(basename "${BASH_SOURCE[0]}") [--check]" >&2
exit 1
;;
esac
12 changes: 2 additions & 10 deletions template/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,12 @@ default:
@just --list

# Format code and configuration files
format: sync-agent-ignore
format:
@echo "No formatter configured yet. Customize this recipe in the Justfile!"

# Run code and markdown linting checks
lint:
@echo "No linter configured yet. Customize this recipe in the Justfile!"

# Regenerate .claude/settings.json's Read-deny rules from .agentignore
sync-agent-ignore:
@scripts/sync-agent-ignore.sh

# Check that .claude/settings.json is in sync with .agentignore
check-agent-ignore-sync:
@scripts/sync-agent-ignore.sh --check

# Run all local checks (tests, format checks, lints)
validate: check-agent-ignore-sync lint
validate: lint
8 changes: 3 additions & 5 deletions template/README.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ nix-shell
### Task Runner (`Justfile`)
The following tasks are available via `just`:
- `just`: List all available tasks.
- `just format`: Format code and configuration files (also regenerates `.claude/settings.json` from `.agentignore`).
- `just format`: Format code and configuration files.
- `just lint`: Run code and markdown linters.
- `just sync-agent-ignore`: Regenerate `.claude/settings.json`'s `Read` deny rules from `.agentignore`.
- `just check-agent-ignore-sync`: Verify `.claude/settings.json` is in sync with `.agentignore` (no write).
- `just validate`: Execute all formatting, linting, and verification checks.

### Git Hook Checks
Expand All @@ -62,9 +60,9 @@ These hooks only run locally and can be skipped (`git commit --no-verify`) or si

- **Google Antigravity**: `.antigravityignore` → `.agentignore`. Note there are [open reports](https://github.com/google-antigravity/antigravity-cli/issues/309) that the Antigravity CLI doesn't always fully respect this file in practice.
- **OpenCode**: has no native ignore-file support yet. The closest option is the community [`opencode-ignore`](https://github.com/lgladysz/opencode-ignore) plugin, which you install via `opencode.json` and which reads a `.ignore` file. If you adopt it, symlink `.ignore` to `.agentignore` the same way.
- **Claude Code**: has **no** `.claudeignore` (or any other external ignore-file) mechanism — a symlink here would be inert. It automatically respects `.gitignore`. For checked-in paths it can't reach that way (e.g. lock files, vendored code), Claude Code supports `Read` deny rules in `.claude/settings.json` — see the [large-codebases guide](https://code.claude.com/docs/en/large-codebases.md#block-reads-of-generated-and-vendored-code). This repo ships a generated `.claude/settings.json` (tracked in git, like Claude Code's own convention for shared project settings — only `.claude/settings.local.json` is gitignored) so it's enforced out of the box.
- **Claude Code**: has **no** `.claudeignore` (or any other external ignore-file) mechanism — a symlink here would be inert. It automatically respects `.gitignore`. For checked-in paths it can't reach that way (e.g. lock files, vendored code), Claude Code supports `Read` deny rules in `.claude/settings.json` — see the [large-codebases guide](https://code.claude.com/docs/en/large-codebases.md#block-reads-of-generated-and-vendored-code). This repo ships a pre-generated `.claude/settings.json` (tracked in git, like Claude Code's own convention for shared project settings — only `.claude/settings.local.json` is gitignored) so it's enforced out of the box.

To update the pattern list, edit `.agentignore` — the symlinked files pick up the change automatically, and `just format` (or `just sync-agent-ignore` directly) regenerates `.claude/settings.json`'s deny rules from it via `scripts/sync-agent-ignore.sh`, so the two never drift. `just validate` fails if `.claude/settings.json` is stale.
To update the pattern list, edit `.agentignore` — the symlinked files pick up the change automatically. For Claude Code, add corresponding `Read(...)` deny rules directly to `.claude/settings.json` (or pull template updates via `copier update`).

### Upstream Template Updates
This project was scaffolded with [Copier](https://copier.readthedocs.io/). To pull in upstream updates (tooling, hooks, CI improvements) from `project-template`:
Expand Down
43 changes: 0 additions & 43 deletions template/scripts/sync-agent-ignore.sh

This file was deleted.

Loading