fix(scripts): compute next ADR as max+1 not count+1 in scaffold (#182)
* fix(scripts): compute next ADR as max+1 not count+1 in scaffold * docs(handoff): add handoff and ADR-079 for ADR numbering fix * docs(handoff): set PR number --------- Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
parent
e954284ec3
commit
0bf97cf1c5
3 changed files with 56 additions and 3 deletions
|
|
@ -49,9 +49,25 @@ if [ -n "$EXISTING_ADR" ]; then
|
|||
ADR="$EXISTING_ADR"
|
||||
echo "ADR already exists: $ADR"
|
||||
else
|
||||
NEXT_N="$(ls "$ADR_DIR" 2>/dev/null | grep -E '^[0-9]{3}-' | wc -l | awk '{print $1+1}')"
|
||||
# Next ADR number = max(existing) + 1, NOT count + 1.
|
||||
# count+1 collides when files are added out of order or in parallel
|
||||
# (e.g. two PRs both computed wc -l = 72 → both wrote ADR-073, see issue #176).
|
||||
# max+1 is race-resistant: each run reads the current highest number.
|
||||
# Guard loop handles the residual race: if max+1 file already exists
|
||||
# (created between the max-read and our write), increment to a free slot.
|
||||
# 10# forces base-10 (leading zeros like 078 are invalid octal in bash).
|
||||
# `|| true` neutralizes grep's exit 1 under set -euo pipefail when the
|
||||
# directory is empty (no ADRs yet → max=0 → next=001).
|
||||
MAX_N="$(ls "$ADR_DIR" 2>/dev/null | grep -E '^[0-9]{3}-' | cut -c1-3 | sort -n | tail -n1 || true)"
|
||||
NEXT_N=$((10#${MAX_N:-0} + 1))
|
||||
while :; do
|
||||
NN="$(printf "%03d" "$NEXT_N")"
|
||||
ADR="$ADR_DIR/${NN}-pr-${PR}-${SLUG}.md"
|
||||
if ! ls "$ADR_DIR"/${NN}-pr-*.md >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
NEXT_N=$((NEXT_N + 1))
|
||||
done
|
||||
cat > "$ADR" <<EOF
|
||||
# ADR-${NN}: <title>
|
||||
|
||||
|
|
|
|||
18
docs/decisions/079-pr-182-adr-numbering-collision.md
Normal file
18
docs/decisions/079-pr-182-adr-numbering-collision.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# ADR-079: ADR numbering = max(existing)+1, not count+1
|
||||
|
||||
## Статус
|
||||
Accepted (2026-07-31, PR#182)
|
||||
|
||||
## Контекст
|
||||
`scaffold-handoff.sh` вычислял номер следующего ADR как `ls docs/decisions | grep -E '^[0-9]{3}-' | wc -l + 1`. Это привело к коллизии: PR#174 и PR#175 оба создали `ADR-073` (два файла `073-pr-174-*.md` и `073-pr-175-*.md`), так как оба запуска посчитали одинаковое количество файлов. Алгоритм `count+1` неустойчив к гонкам, ручному добавлению ADR и «дырам» в нумерации (удалённые файлы оставляют пропуски, счётчик отстаёт от max).
|
||||
|
||||
## Решение
|
||||
Заменить `count+1` на `max(existing)+1`: `ls | grep -E '^[0-9]{3}-' | cut -c1-3 | sort -n | tail -n1` берёт максимальный существующий номер, +1 даёт следующий. Дополнительно:
|
||||
- `10#${MAX_N:-0}` — base-10 forcing (номера вида `078` невалидны в bash octal arithmetic).
|
||||
- `|| true` на grep pipeline — нейтрализует exit 1 при пустой директории (`set -euo pipefail`).
|
||||
- Guard loop — если вычисленный `NN` уже занят (residual race: файл создан между max-read и write), инкремент до свободного слота.
|
||||
|
||||
## Альтернативы
|
||||
- Atomic file lock (`flock`) — отвергнут: избыточно для git-репозитория, где commits серийные; guard loop покрывает residual window.
|
||||
- Проверка только существования файла перед записью (без max+1) — отвергнута: не решает «дыры» в нумерации и не дает детерминированный следующий номер.
|
||||
- Переименование существующих ADR-073 — вне scope (ломает ссылки в memory/handoff/AGENTS.md, issue #176 явно исключает).
|
||||
19
docs/handoff/pr-182-adr-numbering-collision.md
Normal file
19
docs/handoff/pr-182-adr-numbering-collision.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
pr: 182
|
||||
title: fix(scripts): ADR numbering collision in scaffold-handoff.sh
|
||||
---
|
||||
|
||||
## Что сделано
|
||||
`scaffold-handoff.sh` вычислял next ADR как `ls | wc -l + 1`. Заменён на `max(existing) + 1` с guard loop для residual race и base-10 forcing (`10#`) для leading-zero номеров. Добавлены комментарии, объясняющие почему max+1, а не count+1.
|
||||
|
||||
## Почему
|
||||
`count+1` не гарантирует уникальность: при параллельных pipeline или ручном добавлении ADR счётчик отстаёт → коллизия (два файла `073-pr-174-*.md` и `073-pr-175-*.md`). `max+1` race-resistant: каждый запуск читает текущий max. Guard loop покрывает residual race (файл создан между max-read и write).
|
||||
|
||||
## Pending
|
||||
— (после merge: memory-syncer обновит ADR Index — добавит ADR-079, оставит ADR-073 collision note как есть)
|
||||
|
||||
## Watch out
|
||||
- Существующие ADR-073 (оба) НЕ переименовывались — вне scope, ссылки в memory/handoff уже расставлены.
|
||||
- `10#` обязателен: номера `078`/`079` с leading zero невалидны в bash octal arithmetic.
|
||||
- `|| true` на grep обязателен: empty dir → grep exit 1 → `set -euo pipefail` убивает скрипт.
|
||||
- Тестового слоя для scaffold нет — критерий «если есть test-слой» не применим.
|
||||
Loading…
Add table
Reference in a new issue