Skip to content

Add /dld-reindex skill for resolving decision ID collisions#21

Merged
jimutt merged 8 commits into
mainfrom
add-dld-reindex-skill
May 18, 2026
Merged

Add /dld-reindex skill for resolving decision ID collisions#21
jimutt merged 8 commits into
mainfrom
add-dld-reindex-skill

Conversation

@jimutt
Copy link
Copy Markdown
Owner

@jimutt jimutt commented May 18, 2026

Summary

Adds a /dld-reindex skill that resolves decision ID collisions between a local branch and the base branch (and open PRs) before rebasing — handling renames, cross-references, annotations, and the history rewrite that a clean rebase requires.

Why a history rewrite

When two developers independently draft DL-NNN decisions under the same ID and one of them merges first, the simple "rename + commit on top" approach doesn't work: git rebase replays commits one at a time and hits an add/add conflict on the original add commit before it ever sees a later rename. The colliding paths must never appear in the branch's history. The skill addresses this by squashing all branch commits since the merge-base into a single reindex commit containing the renamed files. The original commit subjects are preserved in the new commit's body. AskUserQuestion gates the rewrite — the user explicitly chooses Rewrite and force-push / Rewrite only / Cancel.

What the skill does

  • plan-renames.sh — single planning step that combines collision detection, the gh-aware "taken IDs" scan (scoped to decisions/records/ so unrelated PRs can't poison it), and free-ID assignment. Emits a deterministic plan: <path>\t<DL-OLD>\t<DL-NEW>.
  • rename-decision.shgit mv preserves rename history; patches the file's id: frontmatter, body self-references, cross-refs in other local decisions (supersedes / amends / references and body), and @decision(DL-OLD) annotations in locally-changed code. Digit-aware substitution (renaming DL-100 won't touch DL-1000). Diffs against the merge-base + working tree so multi-rename runs see uncommitted state from prior calls.
  • find-stale-mentions.sh — surfaces bare DL-OLD mentions in code comments / log strings / prose that rename-decision.sh deliberately doesn't auto-rewrite (false-positive risk). The SKILL has the agent review each match in context and decide whether to update it via Edit.
  • commit-reindex.sh — mixed-resets to the merge-base, stages an explicit path list (no git add -A, so untracked unrelated paths like .claude/worktrees can never be swept in), and commits. INDEX.md is intentionally excluded from the reindex commit and restored to merge-base state in the working tree — including it caused content conflicts during rebase because git's 3-way merge can't align both sides' top-of-file inserts. An EXIT trap rolls HEAD back if anything fails between the reset and the commit.
  • resolve-base.sh — prefers the branch's upstream, falls back to origin/main when the upstream tracks the same branch name (i.e., it's just the remote copy of the local branch, not a useful collision base).
  • Post-rebase step: SKILL.md tells the user to run regenerate-index.sh once after git rebase — a small, conflict-free repopulation of INDEX.md with the renamed rows.

gh integration

Opportunistic. If gh isn't installed, the origin isn't a GitHub remote, or gh auth status fails, the PR scan is skipped and a stderr note explains why — surfaced through to the user so they know renamed IDs were chosen against the base branch only.

Files

  • skills/dld-reindex/ + .claude/skills/dld-reindex/ — SKILL.md + 7 scripts (resolve-base.sh, plan-renames.sh, find-collisions.sh, list-taken-ids.sh, rename-decision.sh, find-stale-mentions.sh, commit-reindex.sh).
  • tile.json — registers the skill, bumps version to 0.7.0.
  • rules/dld-workflow.md — adds a one-line bullet pointing at /dld-reindex.
  • README.md — Skills table row + "Working in a team" subsection.
  • tests/test_reindex.bats — covers collision detection, multi-rename cross-refs, namespaced projects, the recovery trap, the digit-aware edge case, and an end-to-end rebase + post-rebase regenerate.

Test plan

  • tests/bats/bin/bats tests/ — 165/165 pass.
  • tessl tile lint — clean.
  • Real-world dry run with three colliding decisions, body cross-references, base-decision references, @decision annotations across multiple Java files, and a DL-2050-style digit-collision case.

🤖 Generated with Claude Code

jimutt and others added 8 commits May 18, 2026 13:33
Renames locally-added decisions whose IDs clash with the base branch (and,
when gh is available, open PRs) before rebasing. Uses git mv to preserve
history, patches frontmatter, updates cross-references in other local
decisions, and rewrites @Decision annotations in local code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
regenerate-index.sh gains an --include-base <ref> flag that merges in
decision rows from a base ref for any DL-*.md the local branch doesn't
have. The reindex SKILL now passes --include-base so the pre-rebase
INDEX.md contains both renamed-local rows and base-branch rows that
landed while the branch was diverged — making the subsequent rebase
auto-merge instead of conflicting on INDEX.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… files

Adds plan-renames.sh which folds collision detection, taken-ID scan, and
free-ID assignment into one deterministic output. The SKILL now calls
that single script instead of having the agent stitch together three
helpers and recompute the max — which is what was driving the
/tmp/taken-ids.txt and /tmp/stderr.txt detours. Also adds an explicit
"do not redirect to /tmp" guard in the SKILL prelude.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r base

The previous flow renamed colliding decisions in a final commit on top of the
branch, but `git rebase` replays commits one at a time and chokes on the
original add commit's colliding path (e.g. decisions/records/DL-205.md)
before it ever sees the rename. The only fix is to ensure the colliding path
never appears in the branch's history.

This change replaces step 6 of the SKILL with a squash flow: the renames are
applied to the working tree, then commit-reindex.sh mixed-resets HEAD to the
merge-base and creates one new reindex commit. The original branch commits'
subjects are preserved in the new commit body. An explicit AskUserQuestion
gate makes the history rewrite consensual.

INDEX.md is intentionally excluded from the reindex commit. Including it
caused content conflicts during rebase whenever the base also modified
INDEX.md (git's 3-way merge can't align both sides' top-of-file inserts even
when row content overlaps). commit-reindex.sh restores INDEX.md to merge-base
state in the working tree so the user has no uncommitted changes after the
flow; the post-rebase step is a one-line regenerate.

Also:

- commit-reindex.sh stages an EXPLICIT path list (the rename plan's old/new
  paths plus every file touched by the original branch). `git add -A` is no
  longer used anywhere in the flow, so untracked unrelated paths (e.g.
  .claude/worktrees, scratch files) can never be swept in.
- resolve-base.sh resolves the base ref via @{upstream}, but falls back to
  origin/main when the upstream tracks a branch with the same name as the
  current branch (i.e. it's just the remote copy of this same branch, not a
  useful collision base).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Skills table row, a "Working in a team" subsection describing when to
reach for the skill, and drops the stale roadmap line that referenced a
differently-scoped /dld-reindex (sync decision references after refactors,
issue #8) — the name now belongs to collision resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… step

Two fixes from real-world testing:

1. rename-decision.sh computed CHANGED_FILES from "$BASE"...HEAD (committed
   state only). On a multi-rename run, the second invocation saw the OLD path
   from the first rename (now gone from disk) instead of the NEW path with
   its uncommitted state, so cross-references in the first-renamed file's
   body to the second-renamed ID were never updated. Compute against $BASE
   alone so working-tree state participates, and add R to the diff filter +
   --find-renames so post-mv detection is robust.

2. Plain-text DL-OLD mentions in code comments / log strings / prose were
   silently left alone. Blanket substitution risks false positives, so add a
   new find-stale-mentions.sh that surfaces those mentions with file, line,
   and content; the SKILL gains a Step 5 where the agent reviews each one in
   context and decides whether to update it via Edit (not a blanket replace).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- list-taken-ids.sh: scope the open-PR scrape to paths under decisions/records/
  so unrelated PRs touching e.g. notes/DL-007-meeting.md don't poison the
  taken-IDs set.
- rename-decision.sh / find-stale-mentions.sh: diff against the merge-base
  instead of $BASE's tip so we don't conflate main's post-branch-point
  changes with feature's local work. Working-tree state is still included.
- Add a namespaced end-to-end test. find-collisions.sh already worked for
  namespaces (git's pathspec recurses); the test pins that behavior down
  alongside the rename + rebase flow with auth/billing namespaces.
- commit-reindex.sh: trap EXIT to roll HEAD back if anything between the
  mixed reset and the commit fails (previously the branch was left at
  merge-base with renames floating in the working tree, no recovery hint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jimutt jimutt marked this pull request as ready for review May 18, 2026 14:49
@jimutt jimutt merged commit 3f5d8a0 into main May 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant