agentleFS
madeordinarymadeordinary/serel-memory/.claude/skills/sync-upstream/SKILL.md

sync-upstream

Skill1 starsChanged 17 days agoMITView on GitHub

Check the upstream Serel Memory repo for framework updates (skills, commands, agent instructions) and selectively pull changes. Use when the user asks to update Serel Memory, update Basecamp, check for upstream changes, sync the framework, or pull latest skills.

  • Deletes or force-pushes

15 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use start-mission and tdd.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • parkchuSkill

    start-mission

    넥스트스텝(NextStep) 미션을 처음 시작할 때, 미션 저장소를 fork·clone하고 최초 작업 브랜치(step1 등)까지 만들어 준다. gh CLI로 fork하고 clone까지 이어서 처리하며, gh가 없거나 인증이 안 돼 있으면 브라우저 Fork 안내로 폴백한다. 이미 fork/clone돼 있으면 재사용하고 이어서 진행한다(재실행 안전). 사용자가 "미션 시작하려는데 저장소 세팅해줘", "새 미션 시작할래", "fork부터 해줘", "/nextstep:start-mission" 등으로 요청할 때 사용한다.

    Also in 3 projects that use this

  • GoogleSkill

    tdd

    Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. 0 are common, 13 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 11 edited versions we read keep these.

  • 13

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    Check the upstream Serel Memory repo for new framework updates (skills, commands, agent instructions) and help the user decide what to pull …

    line 8
  2. Distinctive:

    Only framework files should be synced.

    line 25
  3. Very specific:

    Never create a .serel-memory.json inside a project folder.

    line 67
  4. Distinctive:

    Only these paths are eligible for sync.

    line 76
  5. Distinctive:

    If compatible, show the deletion diff and offer retirement separately from "pull all safe changes." Remove only after the user selects it, …

    line 110
  6. Distinctive:

    Only after the user selects it, run the apply snippet below with that line's blob.

    line 175
  7. Distinctive:

    If the apply snippet refuses, stop for that workflow, run the inventory again, and show the user what changed.

    line 242
  8. Distinctive:

    If any framework files have uncommitted changes, warn the user and ask them to commit or stash before syncing.

    line 273
  9. Very specific:

    If missing, add it using the anchor's upstream value (fall back to madeordinary/serel-memory when there's no anchor):

    line 287
  10. Distinctive:

    If the remote and the anchor disagree about the upstream slug, surface the mismatch and ask the user which is correct before proceeding.

    line 293
  11. Distinctive:

    If a merge base exists: fork mode — use three-dot diffs and conflict detection.

    line 335
  12. Very specific:

    If no merge base (unrelated histories, e.g.

    line 336
  13. Distinctive:

    Template mode with an anchor: if the anchor resolved in step 3, also show what actually changed upstream since it — this is the precise …

    line 352
  14. Distinctive:

    Keep the skill of every RETIRE workflow, and every file of a HELD workflow, out of SAFE TO PULL and NEW FILES: a RETIRE skill arrives with …

    line 416
  15. Distinctive:

    Do not offer cherry-pick by commit — upstream commits may touch both framework and project files.

    line 429
  16. Distinctive:

    Never advance for discovery alone or a failed operation.

    line 458
  17. Very specific:

    If upstream points to a repo that doesn't exist or can't be reached, report the error clearly, suggest the user verify it with git remote …

    line 483

“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Sync Upstream

Check the upstream Serel Memory repo for new framework updates (skills, commands, agent instructions) and help the user decide what to pull into their project.

Scope: --scope does not apply here. Sync is repo-root anchored even when the repo configures scopes — framework files and the anchor live at the root only (see "Resolving scope" in docs/workflow-contract.md).

Preconditions

  • Git must be available.
  • The repo must have started from Serel Memory (formerly Basecamp) — copied in via degit, cloned, or forked. (Template mode in step 4 handles the degit case, where there's no shared git history.)
  • The framework files must be tracked. A local install (install.sh --local, see docs/serel-setup.md) keeps them Git-excluded, so Git cannot show local edits and a restore would overwrite them without a trace. The preflight's local-install guard stops when Memory's own files or the anchor are Git-excluded — removing the anchor's exclusion does not make a local install syncable — and the restore guard in step 3 stops on any untracked or Git-excluded file that upstream ships. Updating a local install is not supported yet. Another tool's local files in the shared folders (such as Serel Kit packs installed with --local) do not stop sync: upstream does not ship them, so no restore touches them.
  • Run every command below in the project being updated. When this file is read from a fresh clone of Serel Memory (the README's update prompt), the clone only supplies these instructions: never run a step inside it. Everything else upstream ships is read through Git from upstream/main.

Reaching 0.8.0

Serel Memory 0.8.0 moved each Claude Code workflow from .claude/commands/<n>.md to .claude/skills/<n>/SKILL.md. An installed Claude sync from 0.7.0 or earlier (.claude/commands/sync-upstream.md) cannot reach 0.8.0: its allowlist lacks .claude/skills/, and running it again loads the same old instructions. Use the README's update prompt instead, which follows this file from a fresh clone. An installed Codex sync from 0.6.0 or 0.7.0 takes two runs: the first updates this file; commit, reload it, and run it again to add the Claude skills and retire the old commands. From 0.5.0 or earlier, use the README's update prompt.

Framework vs Project Files

Only framework files should be synced. Never auto-merge project-specific files.

Framework files (safe to sync):

  • .agents/skills/ — workflow sources, which Codex runs as skills
  • .claude/commands/ — Claude Code commands up to 0.7.0; upstream ships none now, and the command retirement below retires Serel Memory's own
  • .claude/skills/ — Claude Code skills, each a copy of its source
  • AGENTS.md — agent instructions
  • docs/workflow-contract.md — workflow design contract
  • docs/cross-agent-review.md — second-opinion loop policy
  • docs/serel-setup.md — guided setup and the local install
  • hooks/ — optional automation scripts
  • bin/serel-memory — the read-only drift checker

Project files (never sync):

  • memory-bank/ — user's actual project context
  • .rules — project-specific patterns
  • .serel-memory.json — this project's provenance anchor (per-project state, see below)
  • docs/decisions/ — project-specific ADRs
  • docs/ files not in the framework list above (PRDs, specs, project docs)
  • Any application code, configs, or project-specific docs

The provenance anchor (.serel-memory.json)

A small file at the project root recording which upstream version this project was scaffolded from or last synced to:

{ "upstream": "madeordinary/serel-memory", "ref": "v0.8.0", "linked": false }
  • ref — the upstream tag or commit this project is anchored to.
  • linked: true — the anchor was reconstructed after install (exact starting version unknown), so diffs against ref may include changes the project already has. Treat them as candidates to review, not guaranteed-new.

The anchor makes template-mode reports precise: instead of diffing every file blindly, you can show exactly what changed upstream since ref.

Scoped banks ("scopes" in the anchor, see docs/workflow-contract.md "Resolving scope") do not change sync: everything here is repo-root relative. Never create a .serel-memory.json inside a project folder.

.serel-memory.json is the single provenance-anchor filename as of Serel Memory 0.3.0. Never create a second anchor file; two mutable anchors could disagree. A legacy .basecamp.json left over from a v0.x install is never read — it triggers the fail-fast guard in step 1 until the user renames it.

Framework file allowlist

Only these paths are eligible for sync. Use this exact list in all git commands:

.agents/skills/ .claude/commands/ .claude/skills/ AGENTS.md docs/workflow-contract.md docs/cross-agent-review.md docs/serel-setup.md hooks/ bin/serel-memory

Legacy Claude import shim

CLAUDE.md is project-owned and outside the allowlist. Never restore it from an old anchor or upstream version. Before the sync summary, check only whether it matches the original shipped shim:

# Legacy shim check: discovery only; never deletes a file.
if [ -f CLAUDE.md ] && [ ! -L CLAUDE.md ] &&
   [ ! -e .claude/CLAUDE.md ] && [ ! -L .claude/CLAUDE.md ] &&
   [ ! -e CLAUDE.local.md ] && [ ! -L CLAUDE.local.md ] &&
   cmp -s CLAUDE.md <(printf '# Claude Code instructions\n\n@AGENTS.md\n'); then
  echo "LEGACY SHIM: unchanged; compatibility confirmation required before removal"
else
  echo "LEGACY SHIM: preserve (absent, customized, symlinked, or fallback suppressed)"
fi

An unchanged shim is only a candidate. Confirm that every Claude setup used on the project supports AGENTS.md loading: Claude Code v2.1.277 or later on a supported provider, with the AGENTS fallback/both mode enabled in Project instructions. The v2.1.277 release excludes Bedrock, Vertex, and Foundry. A local CLI version alone does not establish provider or project-wide compatibility; if it cannot be checked, require the user's explicit confirmation or keep the file. See release support and loading modes.

If compatible, show the deletion diff and offer retirement separately from "pull all safe changes." Remove only after the user selects it, then re-check the fingerprint and suppression files immediately before removal. A customized, symlinked, or suppressed shim is preserved; do not strip imports or merge its instructions automatically. An approved retirement stays uncommitted, like the restored framework files.

Command retirement

Claude Code runs a same-name skill instead of a command, so a command left beside its new skill no longer runs, and the skill would silently shadow any customization in it. Retirement works from a fixed migration inventory: Serel Memory's 18 workflows, each pairing its old command .claude/commands/<n>.md with its new skill .claude/skills/<n>/SKILL.md (and its Codex skill .agents/skills/<n>/SKILL.md). It is checked on every run, independent of the anchor diff, so a project whose anchor already moved past 0.8.0 with commands left behind still gets the offer. It never touches anything outside the inventory: the project's own commands, and Serel Kit's (Kit retires its own), stay as they are.

A file counts as a version upstream shipped only when its blob is one that path had in upstream's history, or has at the anchor. Anything else is unknown bytes, treated as customized, and so is everything when Git cannot read that history: a shallow clone, or no upstream/main.

# Retirement inventory: every run, from this fixed list, independent of the anchor diff; discovery only, never deletes.
anchor="${ANCHOR:-refs/serel-memory/anchor}" unread="" nomain=""
git rev-parse --verify -q 'upstream/main^{commit}' >/dev/null || { nomain=1; unread="upstream/main does not resolve"; }
[ -n "$unread" ] || [ "$(git rev-parse --is-shallow-repository)" = false ] || unread="shallow clone: run git fetch --unshallow, then sync again"
known() {  # <path>: 0 when the file is tracked and holds bytes upstream shipped at that path
  [ -z "$unread" ] && [ -f "$1" ] && [ ! -L "$1" ] && [ -n "$(git ls-files -- "$1")" ] || return 1
  b="$(git hash-object -- "$1")" || return 1
  [ "$(git rev-parse -q --verify "$anchor:$1" 2>/dev/null)" = "$b" ] && return 0
  ids="$(git log upstream/main --no-renames -m --format= --raw --no-abbrev -- "$1")" || return 1
  printf '%s\n' "$ids" | awk -v b="$b" '/^:/ && ($3 == b || $4 == b) { f = 1 } END { exit !f }'
}
left=""
for n in analyze ask-codex breakdown decision-log discover from-prd handoff init-memory retro review risk-review runbook security-check ship start sync-upstream update-memory weekly-update; do
  old=".claude/commands/$n.md" new=".claude/skills/$n/SKILL.md" src=".agents/skills/$n/SKILL.md"
  [ -e "$old" ] || [ -L "$old" ] || continue
  left=1
  if [ -z "$nomain" ] && { git cat-file -e "upstream/main:$old" 2>/dev/null || ! git cat-file -e "upstream/main:$new" 2>/dev/null; }; then
    echo "SKIP $n: upstream does not ship $new in place of $old"
    continue
  fi
  held=""
  for p in "$old" "$src" "$new"; do
    { [ "$p" = "$old" ] || [ -e "$p" ] || [ -L "$p" ]; } && ! known "$p" && held="$held $p"
  done
  blob="$(git hash-object -- "$old" 2>/dev/null || echo none)"
  if [ -n "$held" ]; then
    echo "HOLD $n (blob $blob): unknown bytes in$held${unread:+ ($unread)}; hold $new, $src and the retirement of $old together"
  else
    echo "RETIRE $n (blob $blob): $old is a version upstream shipped; add $new, then retire $old"
  fi
done
[ -n "$left" ] || echo "RETIREMENT: no inventory command left in .claude/commands/"
[ -z "$nomain" ] || exit 1
  • RETIRE <n> — every adapter of the workflow holds bytes upstream shipped. Offer to add the skill and retire the command together, separately from "pull all safe changes". Show the command against its replacement (git diff HEAD:.claude/commands/<n>.md upstream/main:.claude/skills/<n>/SKILL.md). Only after the user selects it, run the apply snippet below with that line's blob. It writes nothing until every adapter of the workflow still holds the bytes it had when shown; then it adds the skill from upstream (when absent) and deletes the command.
  • HOLD <n> — some adapter holds unknown bytes (customized). Keep this workflow's Claude skill, Codex skill and command retirement out of "pull all safe changes", and resolve the customizations on the Claude and Codex sides together, before either replacement is written. Show each customization diff: against the anchor's version of that path when the anchor has one (git diff "$ANCHOR" -- <path>), otherwise against upstream's last version (git diff upstream/main -- <path> for a path upstream still ships; git diff "$(git rev-list -1 upstream/main -- <path>)^" -- <path> for a removed command). Then offer:

    • port the customizations into the new skill copies (they may stay different per CLI): show the proposed copies against upstream's, and only after approval write them to temporary files outside the project; then run the apply snippet with that line's blob, claude_copy naming the Claude copy and codex_copy the Codex copy (leave codex_copy unset to keep the Codex skill as it is). The snippet installs them only after its recheck;
    • or keep the command and skip the skill for now. That is a skipped change, so the anchor rule in step 10 applies, and the next sync offers it again.

    An untracked, Git-excluded or symlinked command is never deleted, because Git would keep no copy: the user can commit it and sync again, or keep it. For a shallow clone, suggest git fetch --unshallow and a fresh sync before resolving anything by hand.

  • SKIP <n> — upstream does not ship that workflow's skill in place of its command yet; nothing to retire.
# Retirement apply: only after the user selects this workflow. Set n and blob from its inventory line (and, for a ported held workflow, claude_copy and codex_copy).
old=".claude/commands/$n.md" new=".claude/skills/$n/SKILL.md" src=".agents/skills/$n/SKILL.md"
cmd_ok() { [ -f "$old" ] && [ ! -L "$old" ] && [ "$(git hash-object -- "$old")" = "$blob" ] && [ "$(git rev-parse -q --verify "HEAD:$old")" = "$blob" ]; }
unchanged() {  # <path>: absent as in HEAD, or HEAD's bytes, or upstream's (this sync's own pull)
  if [ ! -e "$1" ] && [ ! -L "$1" ]; then ! git cat-file -e "HEAD:$1" 2>/dev/null; return; fi
  [ -f "$1" ] && [ ! -L "$1" ] && b="$(git hash-object -- "$1")" || return 1
  [ "$b" = "$(git rev-parse -q --verify "HEAD:$1")" ] || [ "$b" = "$(git rev-parse -q --verify "upstream/main:$1")" ]
}
if ! cmd_ok || ! unchanged "$new" || ! unchanged "$src"; then
  echo "NOT RETIRED $n: $old, $new or $src changed since it was shown (or the command is not committed); nothing written"
  exit 1
fi
keep="$(mktemp -d)" || exit 1
save() {  # <path> <slot>: before this attempt writes the path, keep its bytes, or the fact that it is absent
  if [ -e "$1" ]; then cp -p -- "$1" "$keep/$2"; else : > "$keep/$2.absent"; fi ||
    { echo "NOT RETIRED $n: could not keep a copy of $1; nothing written"; rm -rf "$keep"; exit 1; }
}
put_back() {  # <path> <slot>: only a path this attempt wrote, back to exactly what it was before
  if [ -e "$keep/$2.absent" ]; then rm -f -- "$1"; elif [ -e "$keep/$2" ]; then cp -p -- "$keep/$2" "$1"; fi
}
[ -d ".claude/skills/$n" ] || : > "$keep/claude-dir.absent"
[ -d ".agents/skills/$n" ] || : > "$keep/codex-dir.absent"
if [ -n "${claude_copy:-}" ] || [ ! -e "$new" ]; then save "$new" claude; fi
if [ -n "${codex_copy:-}" ]; then save "$src" codex; fi
mkdir -p ".claude/skills/$n" &&
  if [ -n "${claude_copy:-}" ]; then cp -- "$claude_copy" "$new"; elif [ ! -e "$new" ]; then git restore --source=upstream/main -- "$new"; fi &&
  if [ -n "${codex_copy:-}" ]; then mkdir -p ".agents/skills/$n" && cp -- "$codex_copy" "$src"; fi &&
  cmd_ok && rm -- "$old" && { echo "RETIRED $n: deleted $old (uncommitted; HEAD keeps it); $new is in place"; rm -rf "$keep"; exit 0; }
put_back "$new" claude; put_back "$src" codex   # undo this attempt's writes only, so the command stays the active one
[ ! -e "$keep/claude-dir.absent" ] || rmdir ".claude/skills/$n" 2>/dev/null
[ ! -e "$keep/codex-dir.absent" ] || rmdir ".agents/skills/$n" 2>/dev/null
rm -rf "$keep"
echo "NOT RETIRED $n: the retirement failed after writing; what it wrote is back as it was before this attempt, and $old stays"
exit 1

If the apply snippet refuses, stop for that workflow, run the inventory again, and show the user what changed; nothing it wrote stays behind to shadow the command, and files it did not write (such as a Codex skill this sync already pulled) stay as they are. Retired commands are deleted from the worktree only: an approved retirement stays uncommitted, like the restored framework files, and HEAD keeps the old bytes. Inventory commands are offered here only, never again under REMOVED UPSTREAM.

Workflow

  1. Preflight: require a clean worktree, and read the anchor.

    cd "$(git rev-parse --show-toplevel)"  # sync is repo-root anchored: framework files and the anchor live at the root, never in a scope folder
    # Local-install guard: sync reads and restores through Git, so Memory's own files and the anchor may not be Git-excluded.
    excluded="$(git ls-files --others --ignored --exclude-standard -- bin/serel-memory docs/workflow-contract.md hooks/lib/resolve-scope.sh)"
    if [ -n "$excluded" ] || git check-ignore -q .serel-memory.json; then
      echo "LOCAL INSTALL: Git-excluded Serel Memory files or anchor; updating a local install is not supported yet."
      printf '%s\n' "$excluded"
      exit 1
    fi
    git status --porcelain -- .agents/skills/ .claude/commands/ .claude/skills/ AGENTS.md docs/workflow-contract.md docs/cross-agent-review.md docs/serel-setup.md hooks/ bin/serel-memory
    # Legacy-anchor guard: fail fast, never treat as unanchored, never reconstruct.
    if [ ! -f .serel-memory.json ] && [ -f .basecamp.json ]; then  # legacy anchor present
      echo "MIGRATION REQUIRED: this project still has a legacy .basecamp.json anchor."
      echo "Rename it to .serel-memory.json (same JSON schema) before syncing — Serel Memory 0.3.0 ended the v0.x anchor name."
      exit 1
    fi
    cat .serel-memory.json 2>/dev/null

    If any framework files have uncommitted changes, warn the user and ask them to commit or stash before syncing. git restore from upstream would silently overwrite their edits.

    Stop on a local install. If the local-install guard fired, STOP before any other step and show the files it listed. Say that sync compares and restores through Git, which cannot see edits to Git-excluded files, so updating a local install is not supported yet. Never untrack, force-add, or un-exclude files to get past it. It checks only files Serel Memory alone installs, so another tool's local files in the shared folders (such as Serel Kit packs installed with --local) do not fire it.

    Fail fast on a legacy-only anchor. If the guard above fired, STOP: the project is still anchored to a pre-0.3.0 install. Tell the user to migrate by renaming the legacy .basecamp.json to .serel-memory.json (same schema; advance ref on the next successful sync). Never treat the project as unanchored and never reconstruct a baseline while a legacy .basecamp.json is present — and never create a second anchor alongside it.

    Otherwise note the anchor's ref and linked values if the file exists; if it doesn't, you'll offer to reconstruct it in step 5.

  2. Check for upstream remote.

    git remote get-url upstream 2>/dev/null

    If missing, add it using the anchor's upstream value (fall back to madeordinary/serel-memory when there's no anchor):

    git remote add upstream "https://github.com/<anchor-upstream>.git"

    If the remote and the anchor disagree about the upstream slug, surface the mismatch and ask the user which is correct before proceeding. If the user forked from a different origin, ask them for the correct URL.

  3. Fetch upstream without merging, then resolve the anchor.

    git fetch upstream main
    # Anchor resolve: `git fetch upstream main` does NOT fetch tags, so resolve the anchor explicitly and privately.
    REF="$(jq -r .ref .serel-memory.json 2>/dev/null || true)"
    if [ -n "$REF" ] && git ls-remote --exit-code --tags upstream "refs/tags/$REF" >/dev/null 2>&1; then
      git fetch --no-tags upstream "+refs/tags/$REF:refs/serel-memory/anchor" && ANCHOR="refs/serel-memory/anchor"
    elif [ -n "$REF" ]; then
      git fetch upstream "$REF" >/dev/null 2>&1 && ANCHOR="$REF"
    fi
    git rev-parse --verify --quiet "${ANCHOR:-no-anchor}^{commit}" >/dev/null && echo "anchor resolves: $ANCHOR" || echo "anchor does not resolve"

    The tag (or commit) is fetched into a private ref, never into the project's own tags — a downstream v0.3.0 must not collide with upstream's. Use $ANCHOR wherever the steps below say <ref>. If it does not resolve, say so and fall back to the direct HEAD-vs-upstream/main diff only.

    Then check that no restore would replace a file Git has no copy of:

    # Restore guard: a file upstream ships must not be untracked or Git-excluded here; a restore would replace it without a trace.
    untracked="$(git ls-files --others -- .agents/skills/ .claude/commands/ .claude/skills/ AGENTS.md docs/workflow-contract.md docs/cross-agent-review.md docs/serel-setup.md hooks/ bin/serel-memory)" || exit 1
    shipped="$(git ls-tree -r --name-only upstream/main -- .agents/skills/ .claude/commands/ .claude/skills/ AGENTS.md docs/workflow-contract.md docs/cross-agent-review.md docs/serel-setup.md hooks/ bin/serel-memory)" || exit 1
    replaced="$(comm -12 <(printf '%s\n' "$untracked" | sort) <(printf '%s\n' "$shipped" | sort))"
    if [ -n "$replaced" ]; then
      echo "UNTRACKED FRAMEWORK FILES: upstream ships these paths, and a restore would replace the local files without a trace."
      printf '%s\n' "$replaced"
      exit 1
    fi

    Stop before replacing untracked files. If the restore guard fired (or Git could not list the files), STOP before any other step and show what it printed. Upstream ships those paths, but here Git does not track them, so a restore would replace them and Git keeps no copy. If they belong to a local install, updating it is not supported yet; a file of the user's own can be committed or moved first. Never force-add, untrack, or un-exclude files to get past it. Local files upstream does not ship — a downstream custom command or skill, or a Serel Kit pack installed with --local — never fire it.

  4. Detect sync mode.

    git merge-base HEAD upstream/main 2>/dev/null
    • If a merge base exists: fork mode — use three-dot diffs and conflict detection.
    • If no merge base (unrelated histories, e.g. degit installs): template mode — compare files directly. Every changed file requires the user to review its diff before applying.
  5. Show what changed since last sync. Diff only framework files, using the allowlist. Every add/delete discovery below passes --no-renames: a workflow that moved to a new path must show as one deletion plus one addition, never as a rename that both lists miss.

    Fork mode:

    git diff --no-renames HEAD...upstream/main --stat -- <allowlist>

    Template mode:

    git diff --no-renames HEAD upstream/main --stat -- <allowlist>

    Template mode with an anchor: if the anchor resolved in step 3, also show what actually changed upstream since it — this is the precise report:

    git log --oneline "$ANCHOR"..upstream/main -- <allowlist>
    git diff --no-renames "$ANCHOR" upstream/main --stat -- <allowlist>

    Keep using the direct HEAD-vs-upstream/main file diff for conflict detection (the project's local edits aren't in upstream history) and for files the project no longer has: an allowlisted file deleted downstream that did not change upstream never appears in the anchor diff, so list it from git diff --no-renames --name-only --diff-filter=A HEAD upstream/main -- <allowlist> and offer it back under NEW FILES. If the anchor has "linked": true, remind the user that anchor-based diffs may include changes their copy already has.

    Separate upstream deletions (git diff --no-renames --name-only --diff-filter=D "$ANCHOR" upstream/main -- <allowlist>) from restorable changes (--diff-filter=d, lowercase excludes deletions). Report removed paths for review; never pass a path absent from upstream/main to git restore, and never infer that a downstream-only custom file should be deleted. A removed upstream file is preserved unless the user explicitly reviews and selects its deletion. The legacy shim and the inventory commands are handled separately above.

    Then run the retirement inventory ("Command retirement" above) and keep its RETIRE, HOLD and SKIP lines for the summary.

    No anchor? Offer to reconstruct one now, marked as linked. Derive upstream from the actual remote — don't hardcode it:

    UP="$(git remote get-url upstream | sed -E 's#^(git@github\.com:|https://github\.com/)##; s#\.git$##')"
    printf '{ "upstream": "%s", "ref": "%s", "linked": true }\n' "$UP" "$(git rev-parse upstream/main)" > .serel-memory.json

    Explain what linked: true means: the exact version this project started from is unknown, so this anchor only establishes a baseline from today forward.

  6. Flag conflict risks (fork mode: files changed on both sides).

    BASE=$(git merge-base HEAD upstream/main)
    comm -12 \
      <(git diff --no-renames --name-only "$BASE" HEAD -- <allowlist> | sort) \
      <(git diff --no-renames --name-only "$BASE" upstream/main -- <allowlist> | sort)

    In template mode, treat every locally modified file as a conflict risk.

  7. Present a summary.

    UPSTREAM SYNC CHECK
    Mode: [fork / template (no shared history)]
    
    New upstream commits: [count]
    Framework files changed: [list with +/- line counts]
    
    CONFLICT RISK:
    - [files changed on both sides, if any]
    
    SAFE TO PULL:
    - [files only changed upstream]
    
    NEW FILES:
    - [files that don't exist locally yet]
    REMOVED UPSTREAM (review only): [paths, excluding downstream-only files and inventory commands]
    LEGACY SHIM: [preserve / unchanged candidate; compatibility and choice pending]
    RETIRE COMMANDS: [none / workflow: .claude/commands/<n>.md → .claude/skills/<n>/SKILL.md; choice pending]
    HELD: [none / workflow: customized paths; Claude skill, Codex skill and retirement held together; choice pending]
    OVERLAPS: [none found / upstream workflow ↔ project workflow: rules to preserve; choice pending]

    Keep the skill of every RETIRE workflow, and every file of a HELD workflow, out of SAFE TO PULL and NEW FILES: a RETIRE skill arrives with its command's retirement, and a HELD workflow waits until its customizations are resolved.

    Review new and changed workflows by purpose. Before offering options, compare each new or changed upstream workflow in .agents/skills/, .claude/skills/ or .claude/commands/ with the existing workflows each CLI discovers in the project (.claude/commands/ and .claude/skills/ for Claude Code, .agents/skills/ for Codex), plus relevant global entries already surfaced by that CLI, by purpose, trigger and behavior, whatever their names. Do not search unrelated private/global folders or modify global workflows. The rules are "Overlapping workflows" in upstream's setup guide (git show upstream/main:docs/serel-setup.md; an older installed copy may lack it). A resembling name, the Claude/Codex pair of one workflow, the inventory command a workflow's new skill replaces (that is the command retirement), or a specialized workflow with a different job is not a finding, and a coexistence recorded in the project's docs is respected while it still applies. For each finding, list both paths and the project workflow's rules to preserve under OVERLAPS, and keep that upstream workflow, both copies together, out of "Pull all safe changes" until the user picks: skip it for now; add it beside the project's, with the reason recorded in the project's workflow or instruction docs; or add it and plan the project workflow's retirement as a separate migration with its own shown diffs and approval. Sync never deletes, renames or disables a project's own workflow. A skipped workflow counts as a skipped change for the anchor choice in step 10. This review is agent-guided; the allowlist and restore guard still protect only paths.

  8. Offer options. Let the user choose:

    • Pull all safe changes — apply only files with no local modifications
    • Retire matched commands — for each RETIRE workflow the user selects, add its skill and retire its command together
    • Resolve held workflows — for each HELD workflow, port the customizations into the new skill copies and then retire, or keep the command and skip the skill for now
    • View specific diffs — show full diff for files the user wants to inspect
    • Apply file-by-file — show diff for each changed file, let user accept or skip
    • Skip — do nothing, just note the available updates

    Do not offer cherry-pick by commit — upstream commits may touch both framework and project files.

  9. Execute the chosen strategy. Restore individual files, one file at a time, from the upstream-changed list (anchor diff plus the absent-locally list from step 5) — never a whole allowlisted directory (e.g. git restore --source=upstream/main -- .claude/skills/), which would also delete any custom commands or skills the downstream project added. The directory allowlist is for diff discovery, not for restore.

    Exclude upstream deletions from the restore list (--diff-filter=d); handle reviewed deletions, command retirement and shim retirement separately.

    For safe files, use restore from upstream:

    git restore --source=upstream/main -- <file-path>

    For conflicting files, show the diff and let the user decide per-file.

    For new files (skills that don't exist locally), restore them in:

    git restore --source=upstream/main -- <new-file-path>

    For each selected retirement, run the apply snippet in "Command retirement". It rechecks every adapter of the workflow before writing the replacement, deletes the command only once the replacement is in place, and if the deletion still fails, puts back only what that attempt wrote, exactly as it was before.

  10. After syncing, update the anchor and summarize. Advance .serel-memory.json to the upstream commit you just synced from, so the next sync reports only what's newer. Only if at least one file was restored, or the user explicitly chose to skip everything — an empty restore (an unresolved anchor, a mis-parsed list) must never move the anchor, or the next sync silently loses those changes. Update the two provenance keys in place — the anchor may carry other keys (such as scopes) that must survive — then drop the private ref (git update-ref -d refs/serel-memory/anchor):

    An explicitly approved deletion, command retirement or shim retirement also counts as an applied change, but does not waive review of skipped framework changes below. A held workflow left as it is counts as skipped. Never advance for discovery alone or a failed operation.

    # Anchor update: keep every other key (e.g. "scopes"); never rewrite the file blind.
    if command -v jq >/dev/null 2>&1; then
      tmp="$(mktemp)" && jq --arg ref "$(git rev-parse upstream/main)" '.ref = $ref | .linked = false' .serel-memory.json > "$tmp" && mv "$tmp" .serel-memory.json
    else
      echo "jq not found: edit .serel-memory.json by hand — set \"ref\" to $(git rev-parse upstream/main) and \"linked\" to false; leave every other key as it is."
    fi

    (After a reviewed sync the baseline is now known, so linked becomes false even if the anchor was originally reconstructed.) If jq is missing, stop and give the user that one-line manual edit — never regenerate the file.

    If the user skipped some upstream changes, tell them before advancing: once the anchor moves, skipped changes stop appearing in the "new since last sync" report (they still show up in the file-level diff against upstream/main). Let them choose: advance the anchor anyway (skip means "no thanks"), or keep the old anchor (skip means "not yet"). Command retirement does not depend on the anchor, so a held or skipped retirement is offered again either way.

    Then summarize what was pulled and suggest the user run update-memory if significant changes were made to framework behavior.

If no upstream changes

Report that the project is up to date and show when the last sync check was done (from the upstream fetch timestamp).

Fallback

If upstream points to a repo that doesn't exist or can't be reached, report the error clearly, suggest the user verify it with git remote -v, and mention the canonical URL https://github.com/madeordinary/serel-memory.git.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.