Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Universal Engineering Rules
<!-- stack-version: 1.0.0 -->
<!-- Cross-tool: Claude Code (@AGENTS.md in CLAUDE.md), Cursor (reads AGENTS.md natively at root + nested dirs), Codex (auto-loaded AGENTS.md) -->
<!-- Keep this file at the project root. Customize per project as needed. -->
<!-- Canonical source — reference this file (@AGENTS.md), do not copy it. Updates flow via `git pull` + /stack-upgrade. See UPDATING.md. -->
<!-- Source: https://github.com/Rwanbt/ai-native-dev-stack -->

## Primary bias to correct
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
84 changes: 84 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
@@ -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
<!-- STACK:BEGIN v1.0.0 — managed by ai-native-dev-stack, do not edit inside -->
... canonical content, replaced wholesale on update ...
<!-- STACK:END -->

## 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.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
40 changes: 40 additions & 0 deletions scripts/stack-update-check.sh
Original file line number Diff line number Diff line change
@@ -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 <version>
# UPGRADE_AVAILABLE <local-version> -> <remote-version> (<N> 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)"
76 changes: 76 additions & 0 deletions scripts/stack-upgrade.sh
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions skills/stack-upgrade/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 <version>` → tell the user they're current. **Stop.**
- `UPGRADE_AVAILABLE <old> -> <new> (<N> 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 `<new>` is available (you're on `<old>`). 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.
Loading