diff --git a/AGENTS.md b/AGENTS.md index 901353f..58998b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,8 @@ # Universal Engineering Rules + + ## Primary bias to correct diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f27da..0516087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ releases begin. ## [Unreleased] ### Added +- `VERSION` (semver) + `stack-version` header in `AGENTS.md` — version source of truth. +- `scripts/stack-update-check.sh` — read-only upstream-update detection (fetch + compare). +- `scripts/stack-upgrade.sh` — non-destructive fast-forward-only upgrade; aborts on a + dirty tree, touches only the shared repo, reports changed `*.example` templates. +- `skills/stack-upgrade/SKILL.md` — `/stack-upgrade` command (gstack-style). +- `UPDATING.md` — the non-destructive update model ("reference, don't copy") + the + managed-block convention for the rare inlined case. - `tools/ai_docs/source_config.py` — replaces `source_exts.py`; now also exports `EXCLUDE_DIRS`, the unified directory exclusion set shared by all tools. - `tools/ai_docs/module_discovery.py` — shared `find_module()` function, eliminating diff --git a/README.md b/README.md index 59f3bd4..03220ab 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,13 @@ The single source of the engineering method is [`AGENTS.md`](AGENTS.md) — ever tool config references it instead of re-stating the rules, so the configs never diverge. Full guide: **[PORTABILITY.md](PORTABILITY.md)**. +**Staying up to date** (gstack-style, non-destructive): `bash scripts/stack-update-check.sh` +detects upstream changes (read-only); `/stack-upgrade` (or `bash scripts/stack-upgrade.sh`) +fast-forwards the shared clone without ever touching your personalized configs. +Because configs *reference* `AGENTS.md` rather than copy it, a `git pull` updates +the method for everyone while each user keeps their customizations. Full model: +**[UPDATING.md](UPDATING.md)**. + --- ## Quality Standards — AI Optimization and Human Readability diff --git a/UPDATING.md b/UPDATING.md new file mode 100644 index 0000000..5fe2661 --- /dev/null +++ b/UPDATING.md @@ -0,0 +1,84 @@ +# Updating the stack — constant, non-destructive optimization + +How the AI-Native Dev Stack evolves without ever destroying a user's +personalization. Modelled on Garry Tan's **gstack**: the shared layer is a git +clone you `pull`; your personal layer only *references* it. + +## The one principle: reference, don't copy + +Every problem with "shared config that users customize" comes from **copying** +the shared content into a personal file. The copy forks on the first edit, and +the next update either clobbers the user's edits or is silently ignored. + +The stack avoids this entirely: + +| Layer | Owner | How a user consumes it | What an update does | +|---|---|---|---| +| **Shared method** (`AGENTS.md`, skills, hooks, anti-debt) | the repo | references it (`@AGENTS.md`), links it (`setup-agents.sh`) | `git pull` updates it in place — references see the new version instantly | +| **Personal** (`~/.claude/CLAUDE.md`, Mavis `agent.md`) | the user | owns the file; it *includes* the shared method | **nothing** — the updater never opens these files | +| **Machine-local** (`config.sh`) | the user | copies from `*.example`, git-ignored | the updater reports new `*.example` keys; never overwrites the copy | + +Because personalization lives in files the updater never touches, two users with +completely different `CLAUDE.md` files both get the same method update from one +`git pull`, and neither loses a single customization. + +## Detecting updates (simple for every user) + +A read-only check — it fetches and compares, never modifies anything: + +```bash +bash scripts/stack-update-check.sh +# → UP_TO_DATE 1.0.0 +# → UPGRADE_AVAILABLE 1.0.0 -> 1.1.0 (4 commits) +# → OFFLINE | NOT_A_CLONE +``` + +Wire it wherever you want a passive notice: +- **SessionStart hook** — print the one-liner at the top of each session. +- **`/stack-upgrade` skill** — runs it as Step 1 and offers to upgrade. + +Version source of truth: the `VERSION` file (semver) + the `stack-version` header +in `AGENTS.md`. The `CHANGELOG.md` describes each change. + +## Applying updates (non-destructive by construction) + +```bash +bash scripts/stack-upgrade.sh # or the /stack-upgrade skill +``` + +Guarantees: +1. **Aborts on a dirty working tree** — a local fork is never silently clobbered. +2. **Fast-forward only** (`git pull --ff-only`) — never a history-rewriting merge. +3. **Touches only the shared repo** — referenced configs pick up the new version + automatically; no personal file is opened. +4. **Reports changed `*.example` templates** instead of overwriting your derived + machine-local copies. + +## When content MUST be inlined: the managed-block convention + +A few files can't be pure references — e.g. a project-root `AGENTS.md` that a +team wants to extend, or a `CLAUDE.md` that prefers inlining over `@include`. +For those, wrap the stack-managed region in markers and edit only outside them: + +```markdown + +... canonical content, replaced wholesale on update ... + + +## My project-specific additions ← outside the block, never touched by updates +- ... +``` + +An updater replaces only the bytes between `STACK:BEGIN`/`STACK:END`; everything +outside survives. This is the fallback for the rare inlined case — **prefer the +`@AGENTS.md` reference**, which needs no markers and no merge at all. + +## For maintainers: cutting a release + +1. Land changes via PRs (squash merge, CI green). +2. Bump `VERSION` (semver) and the `stack-version` header in `AGENTS.md`. +3. Move `CHANGELOG.md` `[Unreleased]` items under `## [x.y.z] - YYYY-MM-DD`. +4. Tag: `git tag vX.Y.Z && git push --tags`. + +Users then see `UPGRADE_AVAILABLE` and upgrade with one command — no personal +config is ever at risk. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/scripts/stack-update-check.sh b/scripts/stack-update-check.sh new file mode 100644 index 0000000..e62c952 --- /dev/null +++ b/scripts/stack-update-check.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# stack-update-check.sh — Detect whether the stack repo has upstream updates. +# +# READ-ONLY by design: it fetches and compares, but never modifies your working +# tree, never merges, never touches any personalized config. Safe to run from a +# SessionStart hook on every session. +# +# Output (one line, machine-parseable): +# UP_TO_DATE +# UPGRADE_AVAILABLE -> ( commits) +# OFFLINE # fetch failed (no network / no remote) +# NOT_A_CLONE # not run from inside the stack git repo +# +# Usage: bash scripts/stack-update-check.sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +STACK_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +git -C "$STACK_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "NOT_A_CLONE"; exit 0; } + +local_ver="$(cat "$STACK_ROOT/VERSION" 2>/dev/null || echo "0.0.0")" + +# Fetch quietly; if it fails (offline / no remote), report and stop — never block. +if ! git -C "$STACK_ROOT" fetch --quiet origin 2>/dev/null; then + echo "OFFLINE" + exit 0 +fi + +# Compare the current branch against its upstream (fallback: origin/main). +upstream="$(git -C "$STACK_ROOT" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "origin/main")" +behind="$(git -C "$STACK_ROOT" rev-list --count "HEAD..$upstream" 2>/dev/null || echo 0)" + +if [ "$behind" -eq 0 ]; then + echo "UP_TO_DATE $local_ver" + exit 0 +fi + +remote_ver="$(git -C "$STACK_ROOT" show "$upstream:VERSION" 2>/dev/null | tr -d '[:space:]' || echo "$local_ver")" +echo "UPGRADE_AVAILABLE $local_ver -> $remote_ver ($behind commits)" diff --git a/scripts/stack-upgrade.sh b/scripts/stack-upgrade.sh new file mode 100644 index 0000000..e7a7e2c --- /dev/null +++ b/scripts/stack-upgrade.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# stack-upgrade.sh — Pull the latest stack, non-destructively. +# +# Non-destructive guarantees: +# - Refuses to run if your working tree has uncommitted changes (so a local +# fork is never silently clobbered). +# - Only ever fast-forwards (`git pull --ff-only`) — never a merge that could +# rewrite your history. +# - Touches ONLY the shared repo. Your personalized configs (~/.claude/CLAUDE.md, +# Mavis agent.md, per-project config.sh) only *reference* this repo, so they +# are never modified by an upgrade. +# - Reports new keys in tracked *.example files instead of overwriting the +# machine-local copies you derived from them. +# +# Usage: +# bash scripts/stack-upgrade.sh # show changelog, then ff-only pull +# bash scripts/stack-upgrade.sh --dry-run # show what would change, pull nothing +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +STACK_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DRY_RUN="" +[ "$1" = "--dry-run" ] && DRY_RUN="yes" + +cd "$STACK_ROOT" +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "ERROR: not a git clone."; exit 1; } + +old_ver="$(cat VERSION 2>/dev/null || echo "0.0.0")" + +# 1. Refuse to clobber local work. +if [ -n "$(git status --porcelain)" ]; then + echo "⚠️ Working tree has uncommitted changes — aborting to protect your edits." + echo " Commit/stash them first, then re-run. (Your changes are untouched.)" + exit 1 +fi + +git fetch --quiet origin || { echo "OFFLINE — cannot reach origin. Nothing changed."; exit 0; } + +upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "origin/main")" +behind="$(git rev-list --count "HEAD..$upstream" 2>/dev/null || echo 0)" + +if [ "$behind" -eq 0 ]; then + echo "✅ Already up to date (v$old_ver)." + exit 0 +fi + +new_ver="$(git show "$upstream:VERSION" 2>/dev/null | tr -d '[:space:]' || echo "$old_ver")" +echo "⬆️ Update available: v$old_ver -> v$new_ver ($behind commits)" +echo "" +echo "What's new:" +git log --no-merges --format=' - %s' "HEAD..$upstream" +echo "" + +# 2. Surface new keys in *.example files (machine-local copies are never overwritten). +changed_examples="$(git diff --name-only "HEAD..$upstream" -- '*.example' 2>/dev/null || true)" +if [ -n "$changed_examples" ]; then + echo "ℹ️ These template files changed — review them for new keys to copy into your" + echo " machine-local copies (config.sh, etc.). Your local copies are NOT touched:" + echo "$changed_examples" | sed 's/^/ /' + echo "" +fi + +if [ -n "$DRY_RUN" ]; then + echo "(dry-run — nothing pulled)" + exit 0 +fi + +# 3. Fast-forward only. +if git pull --ff-only --quiet origin "${upstream#origin/}"; then + echo "✅ Upgraded to v$(cat VERSION 2>/dev/null || echo "$new_ver")." + echo " Referenced configs (@AGENTS.md) pick up the new version automatically." +else + echo "❌ Fast-forward failed (history diverged). Resolve manually:" + echo " cd $STACK_ROOT && git status" + exit 1 +fi diff --git a/skills/stack-upgrade/SKILL.md b/skills/stack-upgrade/SKILL.md new file mode 100644 index 0000000..d6bd081 --- /dev/null +++ b/skills/stack-upgrade/SKILL.md @@ -0,0 +1,67 @@ +--- +name: stack-upgrade +description: | + Check for and apply AI-Native Dev Stack updates, non-destructively. + Detects whether the stack repo is behind upstream, shows what changed, and + fast-forwards the shared clone — without ever touching your personalized + configs (CLAUDE.md, Mavis agent.md, config.sh). + Use when: "upgrade the stack", "update ai-native-dev-stack", "is the stack + up to date?", "get the latest rules/method". + Proactively suggest at the start of a session if an update is available. +origin: generic +--- + +# /stack-upgrade — Non-destructive stack update + +The stack follows the gstack model: **reference, don't copy**. The shared layer +is this git repo; your personal layer only *references* it (`@AGENTS.md`). So an +update is just a `git pull` on the shared clone — your personalization is never +overwritten. See [UPDATING.md](../../UPDATING.md) for the full model. + +Use real tool calls. Never assume the result of a step. + +## Step 1 — Detect + +Run the read-only check (fetches and compares; changes nothing): + +```bash +bash scripts/stack-update-check.sh +``` + +Interpret the single-line output: +- `UP_TO_DATE ` → tell the user they're current. **Stop.** +- `UPGRADE_AVAILABLE -> ( commits)` → continue to Step 2. +- `OFFLINE` → no network/remote; tell the user and stop. +- `NOT_A_CLONE` → the stack isn't a git clone here; point to PORTABILITY.md. + +## Step 2 — Ask the user + +The stack version `` is available (you're on ``). Use AskUserQuestion: +- Question: "AI-Native Dev Stack v{new} is available (you're on v{old}). Upgrade now?" +- Options: ["Yes, upgrade now", "Show me what changed first", "Not now"] + +**"Show me what changed first":** run `bash scripts/stack-upgrade.sh --dry-run` +(shows the changelog + any changed `*.example` templates, pulls nothing), then +ask again. + +**"Not now":** stop, do not nag again this session. + +## Step 3 — Upgrade (non-destructive) + +```bash +bash scripts/stack-upgrade.sh +``` + +The script: +1. **Aborts if your working tree is dirty** — your local edits are protected. +2. **Fast-forwards only** (`git pull --ff-only`) — never a history-rewriting merge. +3. **Touches only the shared repo** — referenced configs (`@AGENTS.md`) pick up + the new version automatically; nothing personal is modified. +4. **Reports new keys in `*.example` files** so you can copy them into your + machine-local copies (`config.sh`) yourself — it never overwrites them. + +## Step 4 — Report + +State the new version and summarize what changed (from the changelog the script +printed). If `*.example` files changed, remind the user to review them for new +keys. Do not edit any machine-local file on the user's behalf without asking.