From ae077886bac07d322a1ccad47b7ca0642334ad99 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Sun, 8 Feb 2026 16:43:43 -0500 Subject: [PATCH] Add diff-upstream.sh and document framework change workflow New script extracts a diff of framework files against the upstream ref in .diachron-version. Both DIACHRON.md and diachron/AGENTS.md now explain how to use it and recommend keeping framework changes in discrete, well-described commits to ease upstreaming. Co-Authored-By: Claude Opus 4.6 --- DIACHRON.md | 20 +++++++- diachron/AGENTS.md | 8 ++++ diff-upstream.sh | 116 +++++++++++++++++++++++++++++++++++++++++++++ file-list | 1 + 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100755 diff-upstream.sh diff --git a/DIACHRON.md b/DIACHRON.md index dc40a8f..00c5b54 100644 --- a/DIACHRON.md +++ b/DIACHRON.md @@ -88,8 +88,24 @@ There are two owners of files in a diachron project: `backend/diachron/`, and the top-level scripts (`cmd`, `develop`, `mgmt`, `sync.sh`, `check.sh`). -Don't modify framework-owned files. This separation keeps framework upgrades -clean. +Don't modify framework-owned files unless you need to. This separation +keeps framework upgrades clean. If you do need to change framework files +(especially early on, there are rough edges), you can extract your changes +as a patch: + +```bash +./diff-upstream.sh # full diff against upstream +./diff-upstream.sh --stat # just list changed files +``` + +This diffs every file in `file-list` against the upstream ref recorded in +`.diachron-version`. + +When you do change framework files, make each change in its own commit with +a clear message explaining what the change is and why it's needed. Mixing +framework fixes with application work in a single commit makes it much +harder to upstream later. A clean history of discrete, well-explained +framework commits is the easiest thing to turn into contributions. ## Getting started diff --git a/diachron/AGENTS.md b/diachron/AGENTS.md index d95c1c9..4c24893 100644 --- a/diachron/AGENTS.md +++ b/diachron/AGENTS.md @@ -137,6 +137,14 @@ Do not edit: master/*, logger/*, diachron/*, backend/diachron/* ``` If a task requires framework changes, confirm with the user first. +When framework files are modified, the changes can be extracted as a +diff against upstream with `./diff-upstream.sh` (or `--stat` to list +changed files only). + +When committing framework changes, keep them in separate commits from +application code. Each framework commit should have a clear message +explaining what was changed and why. This makes it much easier to +upstream the changes later. ### Command safety tiers diff --git a/diff-upstream.sh b/diff-upstream.sh new file mode 100755 index 0000000..c07169f --- /dev/null +++ b/diff-upstream.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Generate a diff of framework files against the upstream version this +# project is based on. Useful for contributing changes back to diachron. +# +# Usage: +# ./diff-upstream.sh # diff against .diachron-version +# ./diff-upstream.sh # diff against a specific ref +# ./diff-upstream.sh --stat # show changed files only + +set -eu +set -o pipefail +IFS=$'\n\t' + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +stat_only=false +ref="" + +for arg in "$@"; do + case "$arg" in + --stat) stat_only=true ;; + *) ref="$arg" ;; + esac +done + +if [ -z "$ref" ]; then + if [ ! -f "$DIR/.diachron-version" ]; then + echo "Error: .diachron-version not found and no ref specified." >&2 + echo "Usage: $0 [--stat] []" >&2 + exit 1 + fi + ref=$(cat "$DIR/.diachron-version") +fi + +cached_repo="$HOME/.cache/diachron/v1/repositories/diachron.git" + +if [ ! -d "$cached_repo" ]; then + echo "Error: cached repository not found at $cached_repo" >&2 + echo "Run ./update-cached-repository.sh first." >&2 + exit 1 +fi + +# Update cached repo +"$DIR/update-cached-repository.sh" + +# Verify ref exists +if ! git -C "$cached_repo" rev-parse --verify "$ref^{commit}" >/dev/null 2>&1; then + echo "Error: ref '$ref' not found in cached repository." >&2 + exit 1 +fi + +# Read file-list +files=() +while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "$line" ]] && continue + files+=("$line") +done < "$DIR/file-list" + +# Check out upstream into a temp directory +tmpdir=$(mktemp -d) +cleanup() { rm -rf "$tmpdir"; } +trap cleanup EXIT + +git clone --quiet "$cached_repo" "$tmpdir/upstream" +git -C "$tmpdir/upstream" checkout --quiet "$ref" + +# Generate diff +if $stat_only; then + diff -rq "$tmpdir/upstream" "$DIR" \ + --no-dereference \ + 2>/dev/null \ + | grep -v '^\.' \ + || true + + # Simpler: just list files that differ + for f in "${files[@]}"; do + # Skip directories + [ -d "$DIR/$f" ] && continue + + upstream="$tmpdir/upstream/$f" + local="$DIR/$f" + + if [ ! -f "$upstream" ] && [ -f "$local" ]; then + echo "added: $f" + elif [ -f "$upstream" ] && [ ! -f "$local" ]; then + echo "removed: $f" + elif [ -f "$upstream" ] && [ -f "$local" ]; then + if ! diff -q "$upstream" "$local" >/dev/null 2>&1; then + echo "modified: $f" + fi + fi + done +else + for f in "${files[@]}"; do + [ -d "$DIR/$f" ] && continue + + upstream="$tmpdir/upstream/$f" + local="$DIR/$f" + + if [ ! -f "$upstream" ] && [ -f "$local" ]; then + diff -u /dev/null "$local" \ + --label "a/$f" --label "b/$f" \ + || true + elif [ -f "$upstream" ] && [ ! -f "$local" ]; then + diff -u "$upstream" /dev/null \ + --label "a/$f" --label "b/$f" \ + || true + elif [ -f "$upstream" ] && [ -f "$local" ]; then + diff -u "$upstream" "$local" \ + --label "a/$f" --label "b/$f" \ + || true + fi + done +fi diff --git a/file-list b/file-list index 486d618..123d1a9 100644 --- a/file-list +++ b/file-list @@ -26,6 +26,7 @@ backend/watch.sh bootstrap.sh cmd develop +diff-upstream.sh diachron diachron/AGENTS.md file-list