Decision-Linked Development (DLD) — a workflow for recording, linking, and maintaining development decisions alongside code. Skills for planning, recording, implementing, auditing, and documenting decisions via @decision annotations.
55
69%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
You are helping the developer untangle decision ID collisions before they rebase. Two or more developers can draft DL-NNN decisions in parallel; once one of them lands on the base branch (or appears in an open PR), the others must rename their local copies to the next free ID. This skill handles that mechanically and produces a branch state that rebases cleanly.
This skill rewrites branch history. That's not optional: if a colliding path (e.g. decisions/records/DL-205.md) was added by any commit on the branch, git rebase will hit an add/add conflict on that commit before it ever sees a later rename. The only fix is to ensure the colliding path never appears in the branch's history. The skill does this by squashing all branch commits since the merge-base into one reindex commit containing the renamed files.
If the branch has already been pushed, finishing the reindex will require a --force-with-lease push. The skill asks for explicit consent before rewriting history.
Use the AskUserQuestion tool when prompting for consent and at the finish step. Everything else is deterministic.
Do not redirect any command output to /tmp files. The scripts in this skill emit only what you need to act on; piping to /tmp/*.txt, tee-ing into scratch files, or stashing stderr separately is unnecessary and creates clutter outside the repo. If a command's output is too long to read in one go, narrow it (| tail -N, | head -N, or pass a more specific flag) rather than persisting it.
Shared scripts:
../dld-common/scripts/common.sh
../dld-common/scripts/regenerate-index.shSkill-specific scripts:
scripts/resolve-base.sh
scripts/plan-renames.sh
scripts/find-collisions.sh
scripts/list-taken-ids.sh
scripts/rename-decision.sh
scripts/find-stale-mentions.sh
scripts/commit-reindex.shdld.config.yaml exists at the repo root. If not, tell the user to run /dld-init first and stop.git status --porcelain empty). If not, ask the user to commit or stash first, then stop.git fetch originBASE=$(bash scripts/resolve-base.sh)resolve-base.sh prefers the branch's upstream when it tracks a different branch (the typical "feature → main" setup). It falls back to origin/main if the upstream is unset OR if the upstream tracks the same branch name as the current branch (i.e. it's just the remote copy of this same branch, not a useful collision base).
The user may pass an explicit base when invoking the skill (e.g. /dld-reindex origin/develop) — honor it if present.
bash scripts/plan-renames.sh --base "$BASE"Output is tab-separated, one rename per line:
<relative-path>\t<DL-OLD>\t<DL-NEW>If the output is empty, exit with:
No ID collisions detected. Safe to rebase onto
$BASE.
plan-renames.sh may print a stderr note like [dld-reindex] open PRs not scanned: gh CLI not installed. Always surface this to the user so they know the renamed IDs were chosen against base-branch state only and may still collide with an open PR.
The underlying helpers (find-collisions.sh, list-taken-ids.sh) remain available for debugging, but the SKILL flow always goes through plan-renames.sh.
Show the user the rename plan and the implication. Use AskUserQuestion:
Resolving these collisions requires rewriting branch history. I will squash the N commits since
<merge-base>into a single reindex commit. The original commit subjects will be preserved in the new commit body. If the branch has already been pushed, finishing will requiregit push --force-with-lease. How should I proceed?
Options:
git push --force-with-lease.If the user cancels, exit without touching anything.
For each line in the plan, call:
bash scripts/rename-decision.sh --old DL-OLD --new DL-NEW --path <relative-path> --base "$BASE"rename-decision.sh does all of:
git mv the file from DL-OLD.md to DL-NEW.md.id: frontmatter field in the renamed file.DL-OLD mentions inside the renamed file's body.DL-OLD mentions inside OTHER locally-added/modified decision files (frontmatter supersedes / amends / references and body). Scoped to the local change set vs the base ref.`@decision`(DL-OLD) annotations to `@decision`(DL-NEW) in non-decision files that are part of the local change set.The substitution is digit-aware: renaming DL-100 will not accidentally rewrite DL-1000.
Note on plain-text DL-NNN mentions in code: In non-decision files (source code, READMEs, etc.) rename-decision.sh's rewrite is scoped to `@decision`(DL-NNN) annotations only. Bare DL-NNN references in comments, log strings, or test fixtures are left untouched here to avoid false-positive matches against unrelated identifiers. Step 5 handles them.
After all renames are applied (but before the squash), find any remaining bare DL-OLD references in non-decision changed files:
echo "$PLAN" | bash scripts/find-stale-mentions.sh --base "$BASE"Output is tab-separated, one match per line: <path>\t<line>\t<DL-OLD>\t<DL-NEW>\t<line-content>. Empty output means nothing to review.
For each match:
DL-OLD or should become DL-NEW. A code comment like // Span-driven batching (DL-207) groups resolutions sharing the same turn context (DL-202) after a DL-207 → DL-213 rename almost certainly wants DL-213 (it's prose alongside an annotation). A test fixture string that's checking historical data may need to stay as DL-OLD.Edit tool to update that specific occurrence — don't do a blanket find-and-replace.Surface the list of matches to the user with your verdicts before you finish, so they can sanity-check the judgment calls.
Pipe the rename plan into commit-reindex.sh:
echo "$PLAN" | bash scripts/commit-reindex.sh --base "$BASE"This:
$BASE.INDEX.md in the working tree to its merge-base state so the working tree is consistent with what we're about to commit. INDEX.md is intentionally excluded from the reindex commit — including it would cause a content conflict during rebase whenever the base branch also modified INDEX.md (git's 3-way merge fails to align both sides' top-of-file inserts even when row content overlaps). INDEX.md gets regenerated post-rebase instead..claude/worktrees, scratch files, in-progress edits to unrelated files) are deliberately NOT swept in.Do not use git add -A or git commit -a anywhere in this flow. Use only commit-reindex.sh to commit. Targeting paths explicitly is the whole point of this step.
If step 3's answer was "Rewrite and force-push":
if git rev-parse --verify --quiet "@{upstream}" >/dev/null; then
git push --force-with-lease
else
git push -u origin HEAD
fi--force-with-lease is mandatory over --force here — it refuses to push if the remote moved since the last fetch, which protects against overwriting concurrent collaborator pushes.
Print:
gh was skipped.
git rebase $BASEbash ../dld-common/scripts/regenerate-index.sh(to repopulate INDEX.md with the renamed locals — the reindex commit intentionally leaves INDEX.md alone to keep the rebase conflict-free; INDEX.md is missing the renamed rows until you regenerate)- Commit the INDEX.md update
The skill never rebases or merges — that is always the user's call.
git rebase --abort first and re-run this skill.--preserve-history flag could perform a cherry-pick walk that rewrites each commit individually, but the edge cases (commits modifying an already-renamed file, merge commits, partial reruns) make it materially more complex than the squash.next-id.sh.