From 6bbe38c378058d53ccf3a2c4e09498698792fb79 Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 7 Apr 2026 11:42:48 -0500 Subject: [PATCH] feat(scripts): add template_review for surfacing backport candidates template_review.sh runs Claude in read-only mode against a downstream clone, diffs the shared infrastructure files against this template, and prints a structured report classifying each diff as BACKPORT, PROJECT-SPECIFIC, DRIFT, or TEMPLATE-NEWER. template_review_all.sh parallelizes the review across every repo in downstream.txt and writes an aggregated REVIEW.md. Exposed via 'just tr' / 'just tra'. --- justfile | 14 +++++ scripts/template_review.sh | 96 +++++++++++++++++++++++++++++++++ scripts/template_review/role.md | 3 ++ scripts/template_review/task.md | 75 ++++++++++++++++++++++++++ scripts/template_review_all.sh | 83 ++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+) create mode 100755 scripts/template_review.sh create mode 100644 scripts/template_review/role.md create mode 100644 scripts/template_review/task.md create mode 100755 scripts/template_review_all.sh diff --git a/justfile b/justfile index 4772fd3..3a7a997 100644 --- a/justfile +++ b/justfile @@ -107,3 +107,17 @@ alias cu := cargo-update cargo-update-all *args: bash scripts/cargo_update_all.sh {{args}} alias cua := cargo-update-all + +# ---------------------------------------------------------------------------- # +# TEMPLATE REVIEW # +# ---------------------------------------------------------------------------- # + +# Review a downstream project for improvements to backport into the template (dry-run by default; --execute to run, optional target dir) +template-review *args: + bash scripts/template_review.sh {{args}} +alias tr := template-review + +# Review all downstream projects in parallel for backport candidates (dry-run by default; --execute to run) +template-review-all *args: + bash scripts/template_review_all.sh {{args}} +alias tra := template-review-all diff --git a/scripts/template_review.sh b/scripts/template_review.sh new file mode 100755 index 0000000..95bed2c --- /dev/null +++ b/scripts/template_review.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROMPT_DIR="${SCRIPT_DIR}/template_review" +# The template repo is the parent of scripts/ +DEFAULT_TEMPLATE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# --- Locate a JS package runner (pnpm dlx preferred, npx fallback) ---------- +find_runner() { + if command -v pnpm &>/dev/null; then + echo "pnpm dlx" + elif command -v npx &>/dev/null; then + echo "npx" + else + echo "Error: neither pnpm nor npx found. Install one of them first." >&2 + exit 1 + fi +} + +RUNNER="$(find_runner)" + +# --- Parse arguments -------------------------------------------------------- +EXECUTE=false +TARGET_DIR="" + +for arg in "$@"; do + case "$arg" in + --execute) EXECUTE=true ;; + *) TARGET_DIR="$arg" ;; + esac +done + +# Resolve target directory (default: current directory) +if [[ -n "$TARGET_DIR" ]]; then + TARGET_DIR="$(cd "$TARGET_DIR" && pwd)" +else + TARGET_DIR="$(pwd)" +fi + +# Allow caller to override the template location +TEMPLATE_DIR="${TEMPLATE_DIR:-$DEFAULT_TEMPLATE_DIR}" + +# --- Compose the prompt from markdown files --------------------------------- +compose_prompt() { + local prompt="" + + if [[ -f "${PROMPT_DIR}/role.md" ]]; then + prompt+="$(cat "${PROMPT_DIR}/role.md")" + prompt+=$'\n\n' + fi + + for f in "${PROMPT_DIR}"/*.md; do + [[ "$(basename "$f")" == "role.md" ]] && continue + prompt+="$(cat "$f")" + prompt+=$'\n\n' + done + + echo "$prompt" +} + +PROMPT="$(compose_prompt)" + +# --- Allowed tools ---------------------------------------------------------- +# Read-only review: no Edit/Write, no git mutations. +ALLOWED_TOOLS="Read Bash" + +# --- Execute or dry-run ----------------------------------------------------- +if [[ "$EXECUTE" == true ]]; then + echo "Using runner: ${RUNNER}" + echo "Target: ${TARGET_DIR}" + echo "Template: ${TEMPLATE_DIR}" + echo "Prompt length: ${#PROMPT} chars" + echo "Allowed tools: ${ALLOWED_TOOLS}" + echo "---" + cd "$TARGET_DIR" + export TEMPLATE_DIR + echo "${PROMPT}" | ${RUNNER} @anthropic-ai/claude-code --print \ + --allowed-tools ${ALLOWED_TOOLS} +else + echo "=== DRY RUN ===" + echo "" + echo "${PROMPT}" + echo "---" + echo "Runner: ${RUNNER}" + echo "Target: ${TARGET_DIR}" + echo "Template: ${TEMPLATE_DIR}" + echo "Prompt length: ${#PROMPT} chars" + echo "Allowed tools: ${ALLOWED_TOOLS}" + echo "" + echo "Would run:" + echo " cd ${TARGET_DIR}" + echo " TEMPLATE_DIR=${TEMPLATE_DIR} echo \"\${PROMPT}\" | ${RUNNER} @anthropic-ai/claude-code --print --allowed-tools ${ALLOWED_TOOLS}" + echo "" + echo "Pass --execute to run this against Claude Code." +fi diff --git a/scripts/template_review/role.md b/scripts/template_review/role.md new file mode 100644 index 0000000..218b256 --- /dev/null +++ b/scripts/template_review/role.md @@ -0,0 +1,3 @@ +You are an experienced, pragmatic software engineer reviewing a downstream project that was forked from a shared Rust template. +Your job is to identify changes in the downstream project that represent **generalizable improvements** worth pulling back into the template — not project-specific code. +You produce a concise, actionable report. You do NOT modify any files, create branches, or open PRs. diff --git a/scripts/template_review/task.md b/scripts/template_review/task.md new file mode 100644 index 0000000..475fff7 --- /dev/null +++ b/scripts/template_review/task.md @@ -0,0 +1,75 @@ +## Context + +You are in a clone of a downstream project that was originally based on a shared Rust template. +The path to a local checkout of the template is provided in the environment variable `TEMPLATE_DIR`. + +The template contains shared infrastructure files that are intended to be identical (or nearly so) across all downstream projects. Examples include: + +- `.editorconfig`, `.gitignore`, `.prettierrc.yml` +- `.github/workflows/**` +- `.vscode/settings.json` +- `CLAUDE.md`, `cliff.toml`, `justfile`, `release.toml`, `rust-toolchain.toml`, `rustfmt.toml` +- `scripts/**` (except `scripts/downstream.txt`, which is template-only) + +Files that are intentionally project-specific and should be IGNORED: +- `Cargo.toml`, `Cargo.lock` +- `README.md` +- `src/**` +- `LICENSE.md` +- `scripts/downstream.txt` + +## Your task + +Find changes in this downstream project that represent **generalizable improvements** to the shared infrastructure, which the template itself does not yet have. + +### 1. Diff the shared files + +For each shared-infrastructure file listed above (and any other non-project file you notice), compare the downstream copy against the template copy at `$TEMPLATE_DIR`. Use: + +``` +diff -u "$TEMPLATE_DIR/" "" +``` + +or walk directories with `diff -ruN "$TEMPLATE_DIR/" ""`. + +### 2. Classify each difference + +For every non-trivial difference, decide whether it is: + +- **BACKPORT** — a genuine improvement (bug fix, new capability, better default, cleaner config, new script, etc.) that would benefit every downstream project if added to the template. +- **PROJECT-SPECIFIC** — a customization that only makes sense for this project (e.g. a workflow step tailored to this repo's deploy target). Do not recommend these. +- **DRIFT** — stale local edits, accidental changes, or noise. Do not recommend these. +- **TEMPLATE-NEWER** — the template has changes the downstream doesn't. Ignore; `cargo_update_all` handles that direction. + +Be conservative: when in doubt, mark it PROJECT-SPECIFIC or DRIFT, not BACKPORT. + +### 3. Check git log for intent + +For each candidate BACKPORT, run `git log --oneline -- ` on the most recent commits touching it, to understand *why* the change was made. A commit message like "fix ci flake on macos" is a strong BACKPORT signal; "tweak for acme deployment" is PROJECT-SPECIFIC. + +### 4. Produce the report + +Print a single markdown report to stdout with this exact structure: + +``` +# Template Backport Review: + +## Summary + + +## Backport Candidates + +### +**What changed:** <1-2 sentence description> +**Why it matters:** +**Origin commit(s):** +**Suggested action:** <"copy file verbatim" | "merge hunk" | "adapt idea"> + + + +## Skipped (project-specific or drift) +- : + +``` + +If there are no backport candidates, still print the report with an empty "Backport Candidates" section and a clear summary line. Do not create any files, branches, or PRs. Do not modify the working tree. diff --git a/scripts/template_review_all.sh b/scripts/template_review_all.sh new file mode 100755 index 0000000..0fd81b5 --- /dev/null +++ b/scripts/template_review_all.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DOWNSTREAM_FILE="${SCRIPT_DIR}/downstream.txt" +TEMPLATE_REVIEW="${SCRIPT_DIR}/template_review.sh" +TEMPLATE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +if [[ ! -f "$DOWNSTREAM_FILE" ]]; then + echo "Error: ${DOWNSTREAM_FILE} not found" >&2 + exit 1 +fi + +# Forward all args (e.g. --execute) to each invocation +ARGS=("$@") + +WORK_DIR="$(mktemp -d)" +echo "Clone directory: ${WORK_DIR}" +echo "Template: ${TEMPLATE_DIR}" + +PIDS=() +REPOS=() +REPORT_FILES=() + +while IFS= read -r repo_url; do + [[ -z "$repo_url" || "$repo_url" == \#* ]] && continue + + REPO_NAME="$(basename "${repo_url%/}" .git)" + CLONE_PATH="${WORK_DIR}/${REPO_NAME}" + REPORT_FILE="${WORK_DIR}/${REPO_NAME}.report.md" + + echo "Cloning: ${repo_url} -> ${CLONE_PATH}" + ( + git clone --quiet "$repo_url" "$CLONE_PATH" + TEMPLATE_DIR="$TEMPLATE_DIR" \ + bash "$TEMPLATE_REVIEW" ${ARGS[@]+"${ARGS[@]}"} "$CLONE_PATH" \ + >"$REPORT_FILE" 2>&1 + ) & + PIDS+=($!) + REPOS+=("$repo_url") + REPORT_FILES+=("$REPORT_FILE") +done < "$DOWNSTREAM_FILE" + +# Wait for all jobs and print each report sequentially +FAILED=0 +for i in "${!PIDS[@]}"; do + if wait "${PIDS[$i]}"; then + echo + echo "════════════════════════════════════════════════════════════════" + echo " ${REPOS[$i]}" + echo "════════════════════════════════════════════════════════════════" + cat "${REPORT_FILES[$i]}" + else + echo + echo "════════════════════════════════════════════════════════════════" + echo " FAILED: ${REPOS[$i]}" + echo "════════════════════════════════════════════════════════════════" >&2 + cat "${REPORT_FILES[$i]}" >&2 || true + FAILED=$((FAILED + 1)) + fi +done + +# Aggregate reports into a single file for convenience +AGGREGATE="${WORK_DIR}/REVIEW.md" +{ + echo "# Template Backport Review — $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo + for i in "${!REPORT_FILES[@]}"; do + echo "---" + echo "## ${REPOS[$i]}" + echo + cat "${REPORT_FILES[$i]}" + echo + done +} >"$AGGREGATE" + +echo +echo "---" +echo "Finished: $((${#PIDS[@]} - FAILED))/${#PIDS[@]} succeeded" +echo "Aggregate report: ${AGGREGATE}" +echo "(clones kept at ${WORK_DIR} so you can inspect — delete when done)" + +exit $FAILED