diff --git a/.claude/settings.json b/.claude/settings.json index 13ddb94..a6e4cf6 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,18 +2,11 @@ "hooks": { "PreToolUse": [ { - "matcher": "Bash", + "matcher": "Bash(git push *)", "hooks": [ { "type": "command", - "if": "Bash(git commit *)", - "command": "input=$(cat); cmd=$(echo \"$input\" | jq -r '.tool_input.command // empty' 2>/dev/null); if ! echo \"$cmd\" | grep -qE '(^|[^a-zA-Z0-9_-])git[[:space:]]+commit($|[^a-zA-Z0-9_-])'; then exit 0; fi; if git diff --cached --name-only 2>/dev/null | grep -q '^CHANGELOG.md$'; then exit 0; fi; if echo \"$cmd\" | grep -q 'CHANGELOG'; then exit 0; fi; echo '{\"decision\":\"block\",\"reason\":\"CHANGELOG.md is not staged. Update the changelog and bump the version before committing. Stage CHANGELOG.md (and any version-file changes), then retry.\"}'", - "statusMessage": "Checking changelog..." - }, - { - "type": "command", - "if": "Bash(git push *)", - "command": "input=$(cat); cmd=$(echo \"$input\" | jq -r '.tool_input.command // empty' 2>/dev/null); if ! echo \"$cmd\" | grep -qE '(^|[^a-zA-Z0-9_-])git[[:space:]]+push($|[^a-zA-Z0-9_-])'; then exit 0; fi; branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); if [ \"$branch\" = \"main\" ] || [ \"$branch\" = \"HEAD\" ] || [ -z \"$branch\" ]; then exit 0; fi; if echo \"$branch\" | grep -qE '^(feat|fix|refactor|chore|docs|test)/[0-9]+-[a-z0-9-]+$'; then exit 0; fi; printf '{\"decision\":\"block\",\"reason\":\"Branch name %s does not follow /-. Allowed prefixes: feat/, fix/, refactor/, chore/, docs/, test/. Example: chore/4-dep-bump. Rename with git branch -m, or push to main if this is a trivial edit.\"}\\n' \"$branch\"", + "command": "branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); if [ \"$branch\" = \"main\" ] || [ \"$branch\" = \"HEAD\" ] || [ -z \"$branch\" ]; then exit 0; fi; if echo \"$branch\" | grep -qE '^(feat|fix|refactor|chore|docs|test)/([0-9]+-)?[a-z0-9-]+$'; then exit 0; fi; if echo \"$branch\" | grep -qE '^chore/release-v[0-9]+[.][0-9]+[.][0-9]+'; then exit 0; fi; printf '{\"decision\":\"block\",\"reason\":\"Branch name %s does not follow / or /-. Allowed prefixes: feat/, fix/, refactor/, chore/, docs/, test/. Examples: refactor/viz-cleanup, chore/4-dep-bump. Rename with git branch -m, or push to main if this is a trivial edit.\"}\\n' \"$branch\"", "statusMessage": "Checking branch name..." } ] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 40ea98a..4fe5028 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,14 @@ ## Test plan - + + +## Follow-ups + + Closes # diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..b99f46a --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,16 @@ +changelog: + categories: + - title: "New Features" + labels: ["type: feature"] + - title: "Bug Fixes" + labels: ["type: bug"] + - title: "Security" + labels: ["security"] + - title: "Improvements" + labels: ["type: enhancement"] + - title: "Documentation" + labels: ["type: docs"] + - title: "Maintenance" + labels: ["infra", "chore"] + exclude: + labels: ["duplicate", "invalid", "wontfix"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 148f21d..40ca558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project are documented here. Format based on [Keep a ### Added - `README.md` — quick-start, sovereignty sequence summary, file map. +- `scripts/init-labels.sh` and `scripts/protect-main.sh` from WorldRover canon. +- `.github/release.yml` — PR-label-based release note categories. + +### Changed + +- `.claude/settings.json` — retire old pre-commit changelog hook; update pre-push hook to canon v0.2.5 (issue number optional in slug, allow `chore/release-v*` branches). +- `.github/PULL_REQUEST_TEMPLATE.md` — add `## Follow-ups` section; update test plan comment to canon v0.2.5 wording. - `.markdownlint.json` and `.editorconfig` from the WorldRover canon. Initial scaffold missed these; CI was red on default markdownlint MD013 (line-length) until the configs landed. Plus a local `MD024: {siblings_only: true}` override so the standard Keep a Changelog repeated `### Added` headings under different version sections don't trigger duplicate-heading errors. Closes #1. ## [0.1.0] - 2026-04-26 diff --git a/scripts/init-labels.sh b/scripts/init-labels.sh new file mode 100755 index 0000000..55c3b6c --- /dev/null +++ b/scripts/init-labels.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Create the canonical WorldRover label set on the current repo. +# Idempotent: existing labels with the same names are left alone (gh exits non-zero +# but we ignore it). To force-update colors/descriptions, run with `--update`. + +set -euo pipefail + +REPO="${1:-}" +UPDATE_FLAG="" + +# Allow `./init-labels.sh --update` or `./init-labels.sh OWNER/REPO --update` +for arg in "$@"; do + case "$arg" in + --update) UPDATE_FLAG="--force" ;; + */*) REPO="$arg" ;; + esac +done + +if [[ -z "$REPO" ]]; then + REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner) +fi + +echo "Initializing labels on $REPO" + +create() { + local name="$1" description="$2" color="$3" + if [[ -n "$UPDATE_FLAG" ]]; then + gh label create "$name" --description "$description" --color "$color" --force --repo "$REPO" + else + gh label create "$name" --description "$description" --color "$color" --repo "$REPO" 2>/dev/null \ + || echo " · skipped (exists): $name" + fi +} + +# Domain +create "ui" "User interface, visual, layout" "9b59b6" +create "data" "Measurement, computation, baselines, scoring" "0e8a16" +create "infra" "Dependencies, tooling, CI, platform support" "1abc9c" + +# Priority +create "P1" "High priority" "b60205" +create "P2" "Medium priority" "ff9f1c" +create "P3" "Low priority / nice to have" "c2e0c6" + +# Type +create "type: bug" "Something isn't working" "d73a4a" +create "type: feature" "New functionality" "1d76db" +create "type: docs" "Documentation only" "999999" +create "type: enhancement" "Refinement of existing functionality" "7057ff" +create "type: refactor" "Behavior-preserving restructuring" "e4e669" + +# Remove the GitHub default labels that overlap with the type:- variants. +# These deletions are also non-fatal — if they don't exist, gh prints a warning. +echo "Removing redundant default labels" +gh label delete "bug" --yes --repo "$REPO" 2>/dev/null || true +gh label delete "enhancement" --yes --repo "$REPO" 2>/dev/null || true +gh label delete "documentation" --yes --repo "$REPO" 2>/dev/null || true + +echo +echo "Done. Final label set:" +gh label list --repo "$REPO" diff --git a/scripts/protect-main.sh b/scripts/protect-main.sh new file mode 100755 index 0000000..c9c3d3f --- /dev/null +++ b/scripts/protect-main.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Apply the canonical WorldRover branch-protection profile to `main` on the +# current GitHub repo. Solo-friendly: blocks force-push and deletion, requires +# a PR for any change to main, and requires a status check named `markdown-lint` +# (the one job every starter-derived repo has). Skip the review requirement — +# solo work doesn't have a reviewer, and a 0-approval PR-required rule still +# enforces the PR flow. +# +# Usage: ./scripts/protect-main.sh [extra_check_name ...] +# +# Required checks given as arguments are added on top of `markdown-lint`. For a +# Node project that runs typecheck + build under a `build` job, you would call: +# +# ./scripts/protect-main.sh build +# +# Re-running is idempotent — it overwrites the protection ruleset. + +set -euo pipefail + +repo=$(gh repo view --json nameWithOwner --jq .nameWithOwner) + +# Build the contexts list: markdown-lint plus any extra args. +contexts_json='["markdown-lint"' +for c in "$@"; do + contexts_json+=",\"$c\"" +done +contexts_json+="]" + +# shellcheck disable=SC2016 +gh api -X PUT "repos/${repo}/branches/main/protection" \ + -H "Accept: application/vnd.github+json" \ + --input - <