From 08dfc857782cc1ff102ad5a14489efa9322ecc67 Mon Sep 17 00:00:00 2001 From: Christian Wendler Date: Mon, 15 Jun 2026 21:13:59 +0200 Subject: [PATCH] chore: apply engineering-standards (code-repo profile) --- .github/engineering-standards.yml | 9 ++ .github/workflows/ci.yml | 44 +++++++++ .hooks/pre-commit | 92 +++++++++++++++++ .hooks/pre-push | 23 +++++ AGENTS.md | 58 +++++++++++ CONTRIBUTING.md | 27 +++++ script/prune-worktrees | 158 ++++++++++++++++++++++++++++++ script/setup | 15 +++ 8 files changed, 426 insertions(+) create mode 100644 .github/engineering-standards.yml create mode 100644 .github/workflows/ci.yml create mode 100755 .hooks/pre-commit create mode 100755 .hooks/pre-push create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100755 script/prune-worktrees create mode 100755 script/setup diff --git a/.github/engineering-standards.yml b/.github/engineering-standards.yml new file mode 100644 index 0000000..8a137d3 --- /dev/null +++ b/.github/engineering-standards.yml @@ -0,0 +1,9 @@ +# Engineering-Standards-Status dieses Repos. +# Single source of truth, gilt host- und agent-übergreifend. +# Schema: siehe byte5ai/engineering-standards. + +status: applied # applied | exempt +source: byte5ai/engineering-standards +decided_on: 2026-06-15 +reviewed_on: 2026-06-15 +reason: "Profil: Code-Repo (TS-Plugin-Bündel, siehe plan.md §4). Heute Docs-only; CI wächst mit Phase 0." diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c7279bf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + # Code-Repo-Profil: baut/testet, sobald TS-Pakete existieren (Phase 0). + # Bis dahin ist der Job grün und validiert nur, dass das Repo konsistent ist. + - name: Build & test + run: | + if [ -f package.json ]; then + echo "package.json found — installing, building, testing." + if [ -f package-lock.json ]; then npm ci; else npm install; fi + npm run build --if-present + npm test + else + echo "No TS packages yet (Stufe A / Phase 0 bootstrap) — nothing to build." + fi + + # Solange das Repo Docs-getragen ist, halten wir die zentralen Dokumente + # wenigstens auf balancierte Markdown-Code-Fences geprüft (deterministisch, + # dependency-frei). Verschwindet, wenn der Code-Test-Pfad oben greift. + - name: Docs sanity (balanced code fences) + run: | + status=0 + for f in plan.md research.md AGENTS.md CONTRIBUTING.md; do + [ -f "$f" ] || continue + fences=$(grep -c '^```' "$f" || true) + if [ $((fences % 2)) -ne 0 ]; then + echo "::error file=$f::unbalanced code fences ($fences)"; status=1 + fi + done + exit $status diff --git a/.hooks/pre-commit b/.hooks/pre-commit new file mode 100755 index 0000000..e1865f2 --- /dev/null +++ b/.hooks/pre-commit @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Pre-commit hook for engineering-standards repos. +# +# Two checks, both run on every commit: +# +# 1) Main-tree discipline (mandatory engineering standard, see SKILL.md §3) +# The main clone never receives commits — every change lands in a +# worktree. This is branch-agnostic: an agent whose HEAD got switched +# to main by a parallel session is caught here too, not just one that +# created a feature branch in the wrong tree. +# +# Bypass (one-off): ALLOW_MAIN_TREE_BRANCH=1 git commit ... +# Opt-out (per repo): git config engineering-standards.main-tree-discipline false +# Global opt-out: status: exempt in .github/engineering-standards.yml +# +# 2) Per-branch path-guard (optional backstop) +# Refuses commits that stage paths inconsistent with the current +# branch's scope. No-op for branches without a rule in the case +# statement below — add per-branch rules as needed. +# +# Activated automatically when the repo runs: +# git config core.hooksPath .hooks + +set -e + +# --- (1) Main-tree discipline ------------------------------------------------- + +mtd_enabled=$(git config --bool --get engineering-standards.main-tree-discipline 2>/dev/null || echo true) + +if [ "$mtd_enabled" != "false" ] && [ "${ALLOW_MAIN_TREE_BRANCH:-}" != "1" ]; then + main_tree=$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}') + current_tree=$(git rev-parse --show-toplevel 2>/dev/null) + current_branch=$(git branch --show-current 2>/dev/null) + + if [ -n "$main_tree" ] && [ -n "$current_tree" ] && \ + [ "$main_tree" = "$current_tree" ]; then + echo "pre-commit: commits from the main clone are not allowed." >&2 + echo "Convention: every change — even single-line fixes — lands in a worktree." >&2 + echo "Reason: the main clone's HEAD can be switched by parallel sessions; commits" >&2 + echo "from here have unreliable branch attribution." >&2 + echo "" >&2 + if [ -n "$current_branch" ] && [ "$current_branch" != "main" ] && [ "$current_branch" != "master" ]; then + echo " git worktree add ../$(basename "$current_tree")-${current_branch//\//-} $current_branch" >&2 + else + echo " git worktree add ../$(basename "$current_tree")- -b main" >&2 + fi + echo "" >&2 + echo "Bypass (one-off): ALLOW_MAIN_TREE_BRANCH=1 git commit ..." >&2 + echo "Opt-out per repo: git config engineering-standards.main-tree-discipline false" >&2 + exit 1 + fi +fi + +# --- (2) Per-branch path-guard ------------------------------------------------ + +branch="$(git branch --show-current)" + +case "$branch" in + # --- examples; delete and replace with your own rules --- + # + # research/foo) + # allow='^(experiments/foo/|docs/foo/)' + # ;; + # main) + # forbid='^experiments/' + # ;; + # --------------------------------------------------------- + *) + exit 0 + ;; +esac + +staged="$(git diff --cached --name-only)" + +if [ -n "${allow:-}" ]; then + bad="$(printf '%s\n' "$staged" | grep -vE "$allow" || true)" + if [ -n "$bad" ]; then + echo "pre-commit: branch '$branch' should not stage these paths:" >&2 + printf ' %s\n' $bad >&2 + echo "Use a worktree for this branch (see AGENTS.md) to avoid the drift class entirely." >&2 + exit 1 + fi +fi + +if [ -n "${forbid:-}" ]; then + bad="$(printf '%s\n' "$staged" | grep -E "$forbid" || true)" + if [ -n "$bad" ]; then + echo "pre-commit: branch '$branch' must not stage these paths:" >&2 + printf ' %s\n' $bad >&2 + exit 1 + fi +fi diff --git a/.hooks/pre-push b/.hooks/pre-push new file mode 100755 index 0000000..026418b --- /dev/null +++ b/.hooks/pre-push @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Pre-push hook: block direct pushes to main/master. +# Override: ALLOW_PUSH_TO_MAIN=1 git push origin main + +if [ "$ALLOW_PUSH_TO_MAIN" = "1" ]; then + exit 0 +fi + +while read -r _local_ref _local_sha remote_ref _remote_sha; do + remote_branch="${remote_ref##refs/heads/}" + if [[ "$remote_branch" =~ ^(main|master)$ ]]; then + echo "" + echo " BLOCKED: Direct push to '$remote_branch' is not allowed." + echo " Create a feature branch and open a pull request instead." + echo "" + echo " Override (emergencies only):" + echo " ALLOW_PUSH_TO_MAIN=1 git push origin $remote_branch" + echo "" + exit 1 + fi +done + +exit 0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..49aaefe --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# Agent Instructions + +These rules apply to all AI agents working on this repository (Claude, Codex, Copilot, etc.). + +## Git Workflow +- **Never push directly to `main`.** All changes go through feature branches and pull requests. +- **Branch naming:** `feat/`, `fix/`, `refactor/`, `docs/`, `chore/`, `test/`, `release/`, `dev/` prefixes. +- **Conventional commits:** `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`, `release:`, `dev:`. +- **No `Co-Authored-By:` trailers for Claude or other AI agents.** Commits are made under the configured git identity, with no model-attribution footer. +- **Never force-push** to any shared branch. +- **Never commit secrets** (`.env`, API keys, tokens, credentials). +- **Never skip hooks** (`--no-verify`). + +## Pull Requests +- Keep PR titles short (<70 chars), use conventional prefix. +- One logical change per PR. +- Ensure tests pass before requesting merge. + +## Working in a multi-session repo + +**Convention (enforced):** the main clone never receives commits. Every change — even a single-line typo fix — lands in a worktree. This is branch-agnostic: an agent whose HEAD got switched to `main` by a parallel session is caught here too, not just one that created a feature branch in the wrong tree. Enforced by the `.hooks/pre-commit` hook shipped via the engineering-standards skill. + +~~~bash +git worktree add ../- -b main # create with new branch +git worktree add ../- # or attach existing +# work in ../-/ +git worktree remove ../- # remove the tree +git branch -D # remove the branch ref (after merge or discard) +git worktree list # inspect +~~~ + +**After your PR is merged, clean up your worktree.** The two commands above (`git worktree remove …` + `git branch -D …`) are part of the merge workflow, not optional. For accumulated orphans across the repo, `script/prune-worktrees` (if present) reports and safely removes worktrees whose remote branches are gone or merged. + +Build artefacts (`target/`, `node_modules/`, etc.) live per worktree — first build per tree is full cost, subsequent builds are independent. + +**Bypass levels** (in increasing persistence): + +| Level | Effect | How | +|---|---|---| +| One-off | this commit only | `ALLOW_MAIN_TREE_BRANCH=1 git commit ...` | +| Per repo | persistent disable, other standards still apply | `git config engineering-standards.main-tree-discipline false` | +| Repo exempt | all engineering-standards disabled for this repo | `status: exempt` in `.github/engineering-standards.yml` | + +### Drift signals (for any unusual main-tree work that is allowed) + +- Run `git branch --show-current` before each commit and confirm it matches what you intended. +- Treat `git status` anomalies as drift signals: directories you didn't touch showing as `??`, unexpected `M` on files you didn't edit. Don't commit through that — `git reflog | head -20` first to see who moved your HEAD. +- Don't reach for `git reset --hard` reflexively; another session may have uncommitted work in the shared tree. Inspect `git stash list` and `git diff --stat` first. + +## Pre-push Hook +A `.hooks/pre-push` hook blocks direct pushes to `main`/`master`. Override only when explicitly instructed: +~~~bash +ALLOW_PUSH_TO_MAIN=1 git push origin main +~~~ + +## Engineering Standards +This repo's engineering-standards status is tracked in `.github/engineering-standards.yml`. +Source of truth: the account's `engineering-standards` repo. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c23fe11 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing to omadia Proof + +`byte5ai/omadia-proof` ist das ANP-Proof-Plugin-Bündel für omadia. Kontext: [plan.md](plan.md) (Implementierungsplan, Codex-reviewed) und [research.md](research.md) (verifizierte Faktenbasis A1–A8). Die Arbeit ist als Issues in Milestones gruppiert (Stufe A + Phase 0–5). + +## Dev-Setup + +```bash +script/setup # konfiguriert git-Hooks; installiert Dependencies, sobald Pakete existieren +``` + +## Workflow + +Dieses Repo folgt den byte5ai-Engineering-Standards (`.github/engineering-standards.yml`, Details in [AGENTS.md](AGENTS.md)). + +- **Nie im Haupt-Klon committen — immer ein Worktree:** + ```bash + git worktree add ../omadia-proof- -b feat/ main + ``` + Durchgesetzt durch `.hooks/pre-commit`. Nach Merge aufräumen: `git worktree remove …` + `git branch -D …` (oder `script/prune-worktrees`). +- **Nie direkt auf `main` pushen.** Feature-Branch + PR; CI (`ci`) muss grün sein. +- **Conventional Commits:** `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`, `release:`. +- **Branch-Präfixe:** `feat/ fix/ refactor/ docs/ chore/ test/ release/ dev/`. +- **Keine** `Co-Authored-By:`-Trailer (auch nicht für KI-Agenten). **Nie** `--no-verify`. **Nie** Secrets committen. + +## Schema-Backflow (zwingend) + +Jede Änderung, die ein ANP-Object-/VC-Schema definiert oder ändert, **MUSS** in derselben Arbeitseinheit einen PR gegen `byte5ai/anp` (Appendix A) eröffnen — siehe [plan.md §1.4](plan.md) und [#2](https://github.com/byte5ai/omadia-proof/issues/2). Der RFC ist kanonisch; bei Divergenz gewinnt die SPEC. diff --git a/script/prune-worktrees b/script/prune-worktrees new file mode 100755 index 0000000..d45595f --- /dev/null +++ b/script/prune-worktrees @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Identify and remove worktrees whose work is done. +# +# A worktree is a candidate for removal when ALL of: +# - it is not the main worktree +# - it has no uncommitted changes and no untracked files +# - it is on a named branch (not detached HEAD) +# - the branch is either (a) gone from the remote, or (b) fully merged +# into the remote's base branch (default: origin/main) +# +# Squash-merged branches whose remote ref still exists are NOT detected (their +# squash commit on main has a different SHA). The reliable signal is the remote +# branch being deleted post-merge — which is the standard squash-and-delete flow. +# +# Default mode: dry-run (report only, change nothing). +# Pass --yes to actually remove. Pass --yes --batch to skip per-item confirmation. + +set -euo pipefail +cd "$(dirname "$0")/.." + +REMOTE="origin" +BASE="${BASE_BRANCH:-main}" +YES=0 +BATCH=0 +for arg in "$@"; do + case "$arg" in + --yes) YES=1 ;; + --batch) BATCH=1 ;; + --base=*) BASE="${arg#--base=}" ;; + --remote=*) REMOTE="${arg#--remote=}" ;; + -h|--help) + cat <] [--remote=] + +Without --yes, only reports candidates (dry-run). +With --yes, prompts per candidate; add --batch to skip prompts. +EOF + exit 0 + ;; + *) + echo "unknown arg: $arg" >&2 + exit 2 + ;; + esac +done + +echo "Fetching from $REMOTE (with --prune)..." >&2 +git fetch --prune "$REMOTE" >/dev/null 2>&1 || { + echo "warn: fetch failed — using stale remote-tracking refs" >&2 +} + +main_tree=$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}') + +# Parse worktrees +wt_paths=() +wt_branches=() +while IFS= read -r line; do + case "$line" in + "worktree "*) + wt_paths+=("${line#worktree }") + wt_branches+=("__PENDING__") + ;; + "branch refs/heads/"*) + wt_branches[${#wt_branches[@]}-1]="${line#branch refs/heads/}" + ;; + "detached") + wt_branches[${#wt_branches[@]}-1]="__DETACHED__" + ;; + esac +done < <(git worktree list --porcelain) + +candidates_path=() +candidates_branch=() +candidates_reason=() +preserved=() + +n=${#wt_paths[@]} +for ((i=0; i/dev/null)" ]; then + preserved+=("$p ($b) — uncommitted changes or untracked files") + continue + fi + + if git show-ref --verify --quiet "refs/remotes/$REMOTE/$b"; then + # Remote branch still exists. Is its tip merged into the base? + if git merge-base --is-ancestor "refs/remotes/$REMOTE/$b" "refs/remotes/$REMOTE/$BASE" 2>/dev/null; then + candidates_path+=("$p") + candidates_branch+=("$b") + candidates_reason+=("merged into $REMOTE/$BASE") + else + preserved+=("$p ($b) — remote branch exists and is not merged") + fi + else + candidates_path+=("$p") + candidates_branch+=("$b") + candidates_reason+=("remote branch gone") + fi +done + +# Report preserved +if [ ${#preserved[@]} -gt 0 ]; then + echo "" + echo "Preserved (${#preserved[@]}):" + for p in "${preserved[@]}"; do echo " $p"; done +fi + +# Report candidates +if [ ${#candidates_path[@]} -eq 0 ]; then + echo "" + echo "No worktrees to prune." + exit 0 +fi + +echo "" +echo "Candidates (${#candidates_path[@]}):" +for ((i=0; i<${#candidates_path[@]}; i++)); do + printf " %s branch %s (%s)\n" "${candidates_path[$i]}" "${candidates_branch[$i]}" "${candidates_reason[$i]}" +done + +if [ "$YES" -eq 0 ]; then + echo "" + echo "Dry-run. Pass --yes to act." + exit 0 +fi + +# Act +echo "" +for ((i=0; i<${#candidates_path[@]}; i++)); do + p="${candidates_path[$i]}" + b="${candidates_branch[$i]}" + + if [ "$BATCH" -eq 0 ]; then + printf "Remove %s (branch %s)? [y/N] " "$p" "$b" + read -r ans + case "$ans" in + y|Y|yes|YES) ;; + *) echo " skipped"; continue ;; + esac + fi + + echo "Removing $p ($b)..." + git worktree remove "$p" + git branch -D "$b" 2>/dev/null || true +done + +echo "Done." diff --git a/script/setup b/script/setup new file mode 100755 index 0000000..0c6b439 --- /dev/null +++ b/script/setup @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." + +echo "Configuring git hooks..." +git config core.hooksPath .hooks + +if [ -f package.json ]; then + echo "Installing dependencies..." + npm install +else + echo "No package.json yet — skipping dependency install (Stufe A / Phase 0 bootstrap)." +fi + +echo "Done."