diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..39becb3 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +git diff --cached --check +./scripts/check-repository.sh --staged diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 116a8e8..e5565c2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,12 +21,23 @@ assignees: "" ## Actual Behavior +## Evidence + +- Logs, screenshots, traces, or failing command: +- First known bad version or commit: +- Workaround, if any: + ## Environment - OS: - Runtime: - Version: -## Additional Context +## Impact +- User-visible impact: +- Data, security, or compatibility risk: +- Release or rollback urgency: + +## Additional Context diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cc48808..7d5f808 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -12,6 +12,18 @@ assignees: "" ## Proposed Solution +## Acceptance Criteria + +- + +## Validation Plan + +- Format: +- Lint: +- Test: +- Build: +- Package or release dry-run: + ## Alternatives Considered @@ -21,4 +33,5 @@ assignees: "" - Configuration: - Compatibility: - Documentation: - +- Security or privacy: +- Rollout and rollback: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 65047de..169efd6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,46 @@ +Title format: `type(scope): summary` + +Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. + ## Summary - +## Motivation + +- + +## Implementation Notes + +- + ## Validation +- [ ] Repository checks: `./scripts/check-repository.sh` +- [ ] PR title: `./scripts/check-pr-title.sh ""` - [ ] Format: - [ ] Lint: - [ ] Test: - [ ] Build: - [ ] Package or release dry-run: -## Follow-up Risks +Skipped gates and reasons: - +## Evidence + +- Linked issue/task/RFC: +- Logs, screenshots, package output, or deployed artifact: +- Reviewer notes that changed the final scope: + +## Safety Checklist + +- [ ] No credentials, tokens, private hostnames, personal filesystem paths, or generated logs are included. +- [ ] Local config, generated output, build artifacts, and temporary workspaces are not staged. +- [ ] User-facing behavior, docs, changelog, migrations, or rollback notes are updated when relevant. +- [ ] The branch is current enough for review, and the remote head matches the intended commit. + +## Follow-up Risks + +- diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 0000000..b956721 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,25 @@ +name: Review + +on: + pull_request: + types: [opened, edited, reopened, synchronize, ready_for_review] + push: + branches: [main] + +jobs: + repository-checks: + name: repository checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ./scripts/check-repository.sh + + pr-title: + name: conventional PR title + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ./scripts/check-pr-title.sh "$PR_TITLE" + env: + PR_TITLE: ${{ github.event.pull_request.title }} diff --git a/.gitignore b/.gitignore index 19e51a3..8597e00 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,21 @@ .env.* *.log .omx/ +.codex/ +.claude/settings.local.json +# Dependency and tool caches +node_modules/ +.cache/ +.turbo/ +.next/ +.vite/ +.pytest_cache/ +__pycache__/ + +# Generated output +dist/ +build/ +coverage/ +tmp/ +temp/ diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 0000000..28c7cfc --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,46 @@ +Title format: `type(scope): summary` + +Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. + +## Summary + +- + +## Motivation + +- + +## Implementation Notes + +- + +## Validation + +- [ ] Repository checks: `./scripts/check-repository.sh` +- [ ] MR title: `./scripts/check-pr-title.sh "<title>"` +- [ ] Format: +- [ ] Lint: +- [ ] Test: +- [ ] Build: +- [ ] Package or release dry-run: + +Skipped gates and reasons: + +- + +## Evidence + +- Linked issue/task/RFC: +- Logs, screenshots, package output, or deployed artifact: +- Reviewer notes that changed the final scope: + +## Safety Checklist + +- [ ] No credentials, tokens, private hostnames, personal filesystem paths, or generated logs are included. +- [ ] Local config, generated output, build artifacts, and temporary workspaces are not staged. +- [ ] User-facing behavior, docs, changelog, migrations, or rollback notes are updated when relevant. +- [ ] The branch is current enough for review, and the remote head matches the intended commit. + +## Follow-up Risks + +- diff --git a/AGENTS.md b/AGENTS.md index e206af5..ae7451d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,9 +13,21 @@ This repository is intended to become a maintainable, publishable project. Treat ## Project-Specific Commands -Replace these placeholders after choosing the project stack: +Keep the generic review commands active, and replace the remaining placeholders after choosing the project stack: ```bash +# Install local Git hooks: +./scripts/install-git-hooks.sh + +# Repository checks: +./scripts/check-repository.sh + +# PR/MR title check: +./scripts/check-pr-title.sh "docs: update project template" + +# GitHub repository setup dry run: +./scripts/configure-github-repository.sh --repo OWNER/REPO + # Format: # Lint: @@ -26,17 +38,40 @@ Replace these placeholders after choosing the project stack: # Package or release dry-run: -# Security or hygiene scan: +# Security or package-specific hygiene scan: ``` Do not claim implementation work is complete until the relevant commands pass, or until skipped commands are explained with concrete blockers. +## Development Workflow + +For non-trivial changes: + +1. Understand the requested behavior, affected domain concepts, ownership boundaries, and data flow. +2. Decide whether the change needs an RFC before implementation. +3. Keep the implementation scoped to the task and nearby code. +4. Update tests and documentation when behavior, public contracts, or workflow expectations change. +5. Ensure local Git hooks are installed for the checkout when practical. +6. Run repository checks, title checks, and project-specific validation gates. +7. For a newly created GitHub repository, configure branch protection with `scripts/configure-github-repository.sh --repo OWNER/REPO --apply` using an admin-authorized account. +8. Open or update the PR/MR with motivation, implementation notes, exact validation, skipped gates, evidence, and risks. + +## Repository Architecture + +- Organize code by domain boundaries, layer boundaries, and test boundaries before mechanical one-file-per-export preferences. +- Keep domain rules, application services, infrastructure adapters, UI/CLI entrypoints, persistence, and test fixtures separated when those responsibilities exist. +- Do not introduce a shared abstraction unless it removes real duplication, clarifies a boundary, or matches an existing project pattern. +- When a file starts mixing multiple responsibilities or layers, split by responsibility rather than by arbitrary size. +- Substantial changes to public behavior, configuration shape, trust boundaries, release process, or repository structure should be proposed in `rfcs/` first. + ## Documentation - Keep `README.md` focused on orientation, quick start, and current user-facing behavior. - Use `CONTRIBUTING.md` for contribution workflow. - Use `rfcs/` for substantial design proposals. +- Use `docs/` for durable current-state knowledge such as architecture, development guides, operational runbooks, references, and onboarding tutorials. - Update `CHANGELOG.md` for user-facing changes unless the change is docs-only or repository-only. +- When behavior, configuration, commands, APIs, deployment, architecture, or operations change, update the relevant docs in the same PR/MR or explain why no docs changed. ## AI Delivery Workflow @@ -50,6 +85,14 @@ When an AI agent completes implementation work: 6. Create or reuse a GitHub Pull Request when the task is not landing directly on `main`. 7. Include a delivery summary with motivation, implementation notes, validation, and follow-up risks. +## Review Evidence + +- PR/MR titles must follow `type(scope): summary`; use `scripts/check-pr-title.sh` to verify them. +- PR/MR descriptions must include motivation, implementation notes, exact validation commands, skipped gates with reasons, and follow-up risks. +- If a claim depends on logs, screenshots, package output, deployed behavior, or generated artifacts, attach or link the evidence in the PR/MR. +- Update the PR/MR description after substantial code changes, review-driven revisions, rebases that change behavior, or validation reruns. +- Keep GitHub PR and GitLab MR templates in sync if the project uses both hosting styles. + ## Git - Branch names should be short and descriptive, such as `feat/release-source`. @@ -69,4 +112,3 @@ rg --hidden --no-ignore -n "private-token|secret|internal-domain.example|HOME_PA --glob '!CONTRIBUTING.md' \ --glob '!SECURITY.md' ``` - diff --git a/CHANGELOG.md b/CHANGELOG.md index f47bd6b..315140d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,8 @@ ## Unreleased - Initial project template. - +- Added a generic review workflow for repository checks and conventional PR titles. +- Added local and CI-friendly repository checks for required template files, tracked local artifacts, obvious secrets, private paths, and review-template drift. +- Added installable local pre-commit hooks for staged whitespace and staged repository checks. +- Added a post-create GitHub repository setup script for default branch protection. +- Added documentation standards, contribution and agent guidance for review evidence, validation gates, and repository architecture maintenance. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5e7af6..3f59783 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,18 @@ Replace this section with project-specific setup instructions. ```bash +# install local Git hooks +./scripts/install-git-hooks.sh + +# run repository checks +./scripts/check-repository.sh + +# check a PR or MR title +./scripts/check-pr-title.sh "docs: update project template" + +# preview GitHub repository protection setup +./scripts/configure-github-repository.sh --repo OWNER/REPO + # install dependencies # run tests # run local app or CLI @@ -13,12 +25,17 @@ Replace this section with project-specific setup instructions. ## Contribution Flow 1. Open an issue or discussion for ambiguous work. -2. Write an RFC for substantial changes. -3. Create a focused branch. -4. Add or update tests for behavior changes. -5. Update `CHANGELOG.md` for user-facing changes. -6. Run format, lint, test, and build checks. -7. Open a pull request with motivation, implementation notes, validation, and follow-up risks. +2. Write an RFC before substantial changes to public behavior, configuration, trust boundaries, release flow, or repository structure. +3. Create a focused branch with a short descriptive name. +4. Install local Git hooks with `./scripts/install-git-hooks.sh` if this checkout has not already done so. +5. Identify the affected domain concepts, layer boundaries, data flow, and tests before changing code. +6. Implement the change, keeping responsibilities separated and using existing project patterns. +7. Add or update tests for behavior changes. +8. Update `README.md`, `CHANGELOG.md`, `docs/`, `AGENTS.md`, `CONTRIBUTING.md`, or RFCs when user-facing behavior, architecture, development workflow, operations, or project policy changes. +9. Run repository checks, title checks, and project-specific format, lint, test, build, and package checks. +10. For a newly created GitHub repository, run the repository setup script with an admin-authorized account. +11. Open a pull request or merge request with a conventional title, motivation, implementation notes, validation, evidence, skipped gates, and follow-up risks. +12. Keep the PR/MR description current after review feedback, rebases, validation reruns, or scope changes. Small typo corrections, narrow documentation fixes, and repository metadata updates do not need an RFC. @@ -27,6 +44,18 @@ Small typo corrections, narrow documentation fixes, and repository metadata upda Replace these placeholders after choosing the project stack: ```bash +# Local Git hooks: +./scripts/install-git-hooks.sh + +# Repository checks: +./scripts/check-repository.sh + +# PR/MR title: +./scripts/check-pr-title.sh "docs: update project template" + +# GitHub repository setup dry run: +./scripts/configure-github-repository.sh --repo OWNER/REPO + # Format: # Lint: @@ -49,25 +78,85 @@ Use `rfcs/` when a change affects: - release process - repository structure - long-term integration strategy +- durable domain boundaries or data flow + +RFCs should describe the problem, decision drivers, goals, non-goals, proposed design, architecture impact, validation plan, rollout plan, rollback plan, alternatives, risks, and open questions. -RFCs should describe the problem, goals, non-goals, proposed design, alternatives, rollout plan, and risks. +## Repository Architecture + +Maintain the repository around real responsibilities: + +- Domain rules describe business or product concepts and should not depend on UI, CLI, persistence, network, or framework adapters. +- Application services coordinate use cases and data flow without owning infrastructure details. +- Infrastructure adapters isolate external systems such as filesystems, databases, HTTP clients, queues, build tools, and hosted services. +- UI, CLI, or API entrypoints translate user or protocol input into application calls. +- Test fixtures and helpers belong near the tests or in clearly named test-support areas. + +Avoid splitting code only to satisfy a mechanical one-export rule. Split when a file mixes responsibilities, a component or service needs independent testing, a boundary becomes reusable, or a change would otherwise make review harder. When adding a top-level directory or durable module boundary, document the reason in the PR/MR and use an RFC when the structure affects long-term integration. + +## Documentation Standards + +Keep each documentation surface focused: + +- Use `README.md` for orientation, quick start, and current user-facing behavior. +- Use `CONTRIBUTING.md` for contribution workflow, review expectations, and repository policy. +- Use `AGENTS.md` for AI-agent instructions. +- Use `rfcs/` for substantial proposals, decision records, and long-term design changes before they become current project facts. +- Use `docs/` for durable current-state knowledge: architecture, development guides, operational runbooks, references, and onboarding tutorials. + +Follow `docs/README.md` when adding or reorganizing project documentation. Update docs in the same change as behavior, configuration, command, API, deployment, architecture, or operational changes. Keep examples runnable when possible; otherwise, label them as illustrative and explain the validation gap. ## Pull Request Expectations -Every PR should answer: +Every PR or MR should answer: - What changed? - Why is this change needed? - How was this tested? - Are there follow-up tasks or risks? +- What evidence proves the behavior, packaging, or deployment claim? +- Which validation gates were skipped, and why? + +Use a conventional title: + +```text +type(scope): summary +``` + +Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. + +Update the description when review feedback, rebases, or follow-up commits change the scope or validation result. Reviewers should be able to understand the final state from the PR/MR without reconstructing it from comments. -## Repository Hygiene +## Repository Checks Do not commit private tokens, local config, generated workspaces, internal hostnames, or personal filesystem paths. Keep package or deploy contents intentional. If a file should ship, verify it appears in the package or deployment dry-run. +Run `./scripts/check-repository.sh` locally before opening review. This generic check does not replace stack-specific tests, but it catches missing template files, tracked local artifacts, obvious secrets, private paths, and drift in review templates. + +## Local Git Hooks + +Install local hooks after cloning or creating a repository from this template: + +```bash +./scripts/install-git-hooks.sh +``` + +The pre-commit hook runs `git diff --cached --check` and `./scripts/check-repository.sh` before a commit is created. Hooks are a local guardrail; CI and branch protection remain the authoritative enforcement because hooks can be missing or bypassed. + +If `core.hooksPath` is already set to another path, `scripts/install-git-hooks.sh` fails instead of overwriting it. Re-run with `--force` only after confirming the existing hooks can be replaced or moved into `.githooks`. + +## Repository Setup + +Template files do not carry GitHub branch protection settings into every new repository. After creating a GitHub repository from this template, run: + +```bash +./scripts/configure-github-repository.sh --repo OWNER/REPO --apply +``` + +The setup script requires a GitHub account or token with permission to edit repository settings. It protects the default branch by requiring pull requests, one approving review, fresh reviews after new pushes, linear history, resolved conversations, and the `Review` workflow checks named `repository checks` and `conventional PR title`. + ## Security Reports Use `SECURITY.md` for vulnerability reporting guidance. Do not include secrets, exploit details, or private infrastructure in public issues or pull requests. - diff --git a/README.md b/README.md index 8547e57..8ca0eee 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,22 @@ Use this template when creating a new project that should have consistent contri 2. Replace this README with the new project's name, purpose, and quick start. 3. Fill in the project-specific validation commands in `AGENTS.md` and `CONTRIBUTING.md`. 4. Choose the actual implementation stack and add the source layout. -5. Update `CHANGELOG.md` for the first release. -6. Keep or replace `LICENSE` according to the project needs. +5. Install local Git hooks: + + ```bash + ./scripts/install-git-hooks.sh + ``` + +6. Enable or replace the stack-specific CI example in `.github/workflows/ci.yml.example`. +7. Keep `.github/workflows/review.yml` enabled for generic review checks. +8. Configure GitHub repository protection after the new repository is created: + + ```bash + ./scripts/configure-github-repository.sh --repo OWNER/REPO --apply + ``` + +9. Update `CHANGELOG.md` for the first release. +10. Keep or replace `LICENSE` according to the project needs. ## Included @@ -19,12 +33,21 @@ Use this template when creating a new project that should have consistent contri - `CLAUDE.md` for Claude Code entrypoint instructions. - `CONTRIBUTING.md` for human contribution flow. - `SECURITY.md` for vulnerability and sensitive data reporting. +- `docs/README.md` for architecture, development, operations, and reference documentation standards. - `.github/pull_request_template.md` for PR summaries and validation. - `.github/ISSUE_TEMPLATE/` for bug and feature reports. +- `.github/workflows/review.yml` for generic repository and PR title checks. +- `.githooks/pre-commit` for local commit-time repository checks. +- `.gitlab/merge_request_templates/default.md` for GitLab-style MR summaries. - `rfcs/0000-template.md` for substantial design changes. +- `scripts/check-repository.sh` for local and CI repository checks. +- `scripts/check-pr-title.sh` for conventional PR or MR title checks. +- `scripts/install-git-hooks.sh` for installing local Git hooks. +- `scripts/configure-github-repository.sh` for post-create GitHub branch protection setup. - `.editorconfig` for consistent text formatting. ## Template Maintenance Keep this repository generic. Do not add language-specific package files, framework defaults, generated output, or project-specific business logic. +Keep collaboration policy in `AGENTS.md` and `CONTRIBUTING.md`, design proposals and decision history in `rfcs/`, and current product or engineering knowledge in `docs/`. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..81cbb9a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +# Documentation Standards + +Use `docs/` for durable project knowledge that a maintainer should be able to read without replaying pull requests or chat history. This includes current architecture, development guides, operational runbooks, and factual references. + +Keep collaboration policy in `CONTRIBUTING.md`, AI-agent instructions in `AGENTS.md`, issue and review evidence requirements in templates, and automated enforcement in scripts or CI workflows. + +## What Belongs Here + +- `architecture/` for the current system shape: domain boundaries, module boundaries, data flow, trust boundaries, storage, integrations, deployment topology, and major tradeoffs. +- `development/` for local setup, common development tasks, debugging workflows, test strategy, fixtures, release dry-runs, and stack-specific commands. +- `reference/` for stable factual material such as APIs, CLI commands, configuration, protocols, schemas, error codes, and generated interfaces. +- `operations/` for runbooks, incident response, monitoring, backups, migrations, and rollback procedures when the project has operational responsibilities. +- `tutorials/` for guided onboarding exercises when a new contributor needs a start-to-finish learning path. + +Do not create empty directories just to match this list. Add a section when the project has real documentation for that reader need. + +## RFC Boundary + +Use `rfcs/` for proposals, decision records, and major changes before they become current project facts. After an RFC is accepted and implemented, update the relevant `docs/` page so architecture and development documentation describe the current state. + +RFCs explain why a decision was made. `docs/` explains how the project works now. + +## Writing Standards + +- Give every durable document one clear audience, purpose, and owner area. +- Prefer current-state language over historical narration in `docs/`; link to RFCs for decision history. +- Keep examples runnable when practical; otherwise label them as illustrative and explain the validation gap. +- Link to source files, commands, schemas, dashboards, or RFCs when they are the real source of truth. +- Update docs in the same change as behavior, configuration, command, API, deployment, architecture, or operational changes. +- Keep private tokens, internal hostnames, personal filesystem paths, generated logs, and environment-specific secrets out of documentation. diff --git a/rfcs/0000-template.md b/rfcs/0000-template.md index 6fb2329..3faad88 100644 --- a/rfcs/0000-template.md +++ b/rfcs/0000-template.md @@ -8,27 +8,57 @@ Draft What problem does this solve? +## Decision Drivers + +- + ## Goals -- +- ## Non-Goals -- +- ## Proposed Design Describe the design. +## Architecture Impact + +- Domain boundaries: +- Application or service boundaries: +- Infrastructure adapters: +- Public API, CLI, or UI: +- Configuration: +- Data flow: +- Test boundaries: + +## Validation Plan + +- Repository checks: +- Format: +- Lint: +- Test: +- Build: +- Package or release dry-run: + ## Alternatives Considered -- +- ## Rollout Plan -- +- + +## Rollback Plan + +- ## Risks -- +- + +## Open Questions +- diff --git a/scripts/check-pr-title.sh b/scripts/check-pr-title.sh new file mode 100755 index 0000000..6dbed04 --- /dev/null +++ b/scripts/check-pr-title.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +title="${1:-}" +allowed_types="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert" +pattern="^(${allowed_types})(\\([a-z0-9._-]+\\))?!?: [^[:space:]].*" + +if [[ -z "${title//[[:space:]]/}" ]]; then + printf 'check-pr-title: missing PR title\n' >&2 + exit 1 +fi + +if [[ ! "$title" =~ $pattern ]]; then + printf 'check-pr-title: title must match type(scope): summary\n' >&2 + printf 'check-pr-title: allowed types: %s\n' "$allowed_types" >&2 + printf 'check-pr-title: got: %s\n' "$title" >&2 + exit 1 +fi + +printf 'check-pr-title: ok\n' diff --git a/scripts/check-repository.sh b/scripts/check-repository.sh new file mode 100755 index 0000000..8407123 --- /dev/null +++ b/scripts/check-repository.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +set -euo pipefail + +staged=false + +usage() { + cat <<'USAGE' +Usage: + scripts/check-repository.sh [--staged] + +Checks repository template files, tracked local artifacts, obvious secrets, +private paths, and review template sections. + +Options: + --staged Check the Git index. Use this from pre-commit hooks. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --staged) + staged=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'check-repository: unknown argument: %s\n' "$1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +cd "$repo_root" + +fail() { + printf 'check-repository: %s\n' "$*" >&2 + exit 1 +} + +required_files=( + "AGENTS.md" + "CLAUDE.md" + "CONTRIBUTING.md" + "README.md" + "SECURITY.md" + "CHANGELOG.md" + "docs/README.md" + ".editorconfig" + ".gitignore" + ".github/pull_request_template.md" + ".github/ISSUE_TEMPLATE/bug_report.md" + ".github/ISSUE_TEMPLATE/feature_request.md" + ".github/workflows/review.yml" + ".github/workflows/ci.yml.example" + ".gitlab/merge_request_templates/default.md" + ".githooks/pre-commit" + "scripts/check-pr-title.sh" + "scripts/check-repository.sh" + "scripts/install-git-hooks.sh" + "scripts/configure-github-repository.sh" + "rfcs/0000-template.md" +) + +missing=() +for file in "${required_files[@]}"; do + if [[ "$staged" == true ]]; then + if ! git cat-file -e ":$file" 2>/dev/null; then + missing+=("$file") + fi + elif [[ ! -f "$file" ]]; then + missing+=("$file") + fi +done + +if (( ${#missing[@]} > 0 )); then + printf 'Missing required template files:\n' >&2 + printf ' - %s\n' "${missing[@]}" >&2 + exit 1 +fi + +tracked_artifact_pattern='(^|/)(node_modules|dist|build|coverage|tmp|temp|\.cache|\.turbo|\.next|\.vite|\.pytest_cache|__pycache__|\.DS_Store)(/|$)|(^|/)\.env($|\.)|\.log$|(^|/)\.omx(/|$)|(^|/)\.codex(/|$)|(^|/)\.claude/settings\.local\.json$' +if [[ "$staged" == true ]]; then + tracked_artifacts="$(git diff --cached --name-only --diff-filter=ACMR | grep -E "$tracked_artifact_pattern" || true)" +else + tracked_artifacts="$(git ls-files | grep -E "$tracked_artifact_pattern" || true)" +fi +if [[ -n "$tracked_artifacts" ]]; then + printf 'Tracked local, generated, or machine-specific artifacts found:\n%s\n' "$tracked_artifacts" >&2 + exit 1 +fi + +secret_pattern='AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9_]{36,}|xox[baprs]-[A-Za-z0-9-]{10,}|-----BEGIN ([A-Z]+ )?PRIVATE KEY-----' +private_path_pattern='(/Users/[^[:space:]`"'"'"'<>]+|/home/[^[:space:]`"'"'"'<>]+|C:\\Users\\)' +placeholder_pattern='private-token|internal-domain\.example|HOME_PATH_PLACEHOLDER' +findings_file="$(mktemp "${TMPDIR:-/tmp}/check-repository-findings.XXXXXX")" +trap 'rm -f "$findings_file"' EXIT + +grep_args=(-n -I -E "$secret_pattern|$private_path_pattern|$placeholder_pattern") +if [[ "$staged" == true ]]; then + grep_args=(--cached "${grep_args[@]}") +fi + +if git grep "${grep_args[@]}" -- . \ + ':!AGENTS.md' \ + ':!CONTRIBUTING.md' \ + ':!SECURITY.md' \ + ':!scripts/check-repository.sh' >"$findings_file"; then + printf 'Potential secret, private path, or private placeholder found:\n' >&2 + cat "$findings_file" >&2 + exit 1 +fi + +required_review_sections=( + "Summary" + "Motivation" + "Implementation Notes" + "Validation" + "Evidence" + "Safety Checklist" + "Follow-up Risks" +) + +for template in ".github/pull_request_template.md" ".gitlab/merge_request_templates/default.md"; do + for section in "${required_review_sections[@]}"; do + if [[ "$staged" == true ]]; then + if ! git show ":$template" | grep -qE "^## ${section}$"; then + fail "$template is missing required section: $section" + fi + elif ! grep -qE "^## ${section}$" "$template"; then + fail "$template is missing required section: $section" + fi + done +done + +if [[ "$staged" == true ]]; then + printf 'check-repository: ok (staged)\n' +else + printf 'check-repository: ok\n' +fi diff --git a/scripts/configure-github-repository.sh b/scripts/configure-github-repository.sh new file mode 100755 index 0000000..46f2f33 --- /dev/null +++ b/scripts/configure-github-repository.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -euo pipefail + +apply=false +repo="" +branch="" + +usage() { + cat <<'USAGE' +Usage: + scripts/configure-github-repository.sh [--repo OWNER/REPO] [--branch BRANCH] [--apply] + +Configures GitHub repository defaults that cannot be inherited from template files. + +Defaults: + --repo inferred from gh repo view + --branch inferred from the GitHub default branch when gh is available + +Without --apply, this script prints the branch protection payload and does not call GitHub. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) + apply=true + shift + ;; + --repo) + repo="${2:-}" + shift 2 + ;; + --branch) + branch="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'configure-github-repository: unknown argument: %s\n' "$1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$repo" ]]; then + if ! command -v gh >/dev/null 2>&1; then + printf 'configure-github-repository: gh is required when --repo is omitted\n' >&2 + exit 1 + fi + repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner')" +fi + +if [[ ! "$repo" =~ ^[^/]+/[^/]+$ ]]; then + printf 'configure-github-repository: --repo must use OWNER/REPO format\n' >&2 + exit 1 +fi + +if [[ -z "$branch" ]]; then + if command -v gh >/dev/null 2>&1; then + branch="$(gh repo view "$repo" --json defaultBranchRef --jq '.defaultBranchRef.name')" + else + printf 'configure-github-repository: cannot infer default branch without gh; pass --branch explicitly\n' >&2 + exit 1 + fi +fi + +payload="$(cat <<'JSON' +{ + "required_status_checks": { + "strict": true, + "contexts": [ + "repository checks", + "conventional PR title" + ] + }, + "enforce_admins": true, + "required_pull_request_reviews": { + "dismiss_stale_reviews": true, + "require_code_owner_reviews": false, + "require_last_push_approval": true, + "required_approving_review_count": 1 + }, + "restrictions": null, + "required_linear_history": true, + "allow_force_pushes": false, + "allow_deletions": false, + "required_conversation_resolution": true +} +JSON +)" + +printf 'Repository: %s\n' "$repo" +printf 'Branch: %s\n' "$branch" +printf 'Protection payload:\n%s\n' "$payload" + +if [[ "$apply" != true ]]; then + printf 'Dry run only. Re-run with --apply to update GitHub branch protection.\n' + exit 0 +fi + +if ! command -v gh >/dev/null 2>&1; then + printf 'configure-github-repository: gh is required for --apply\n' >&2 + exit 1 +fi + +gh api \ + --method PUT \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "repos/${repo}/branches/${branch}/protection" \ + --input - <<<"$payload" >/dev/null + +printf 'Branch protection updated for %s:%s\n' "$repo" "$branch" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100755 index 0000000..49cc07a --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +force=false + +usage() { + cat <<'USAGE' +Usage: + scripts/install-git-hooks.sh [--force] + +Installs this template's local Git hooks by setting core.hooksPath to .githooks. +If another hooks path is already configured, the script fails unless --force is used. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) + force=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'install-git-hooks: unknown argument: %s\n' "$1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +cd "$repo_root" + +if [[ ! -x ".githooks/pre-commit" ]]; then + printf 'install-git-hooks: .githooks/pre-commit is missing or not executable\n' >&2 + exit 1 +fi + +current_hooks_path="$(git config --get core.hooksPath || true)" +if [[ -n "$current_hooks_path" && "$current_hooks_path" != ".githooks" && "$force" != true ]]; then + printf 'install-git-hooks: core.hooksPath is already set to %s\n' "$current_hooks_path" >&2 + printf 'install-git-hooks: re-run with --force to replace it with .githooks\n' >&2 + exit 1 +fi + +git config core.hooksPath .githooks +printf 'install-git-hooks: core.hooksPath set to .githooks\n'