fix(board): discover dot-prefixed repos (.profile) without dotglob's scope creep

board.sh's discovery loops (for entry in "$root"/*, and the polyrepo
container's child loop) never see dot-prefixed directories - measured with
a known-positive control: two repos in one root, one dot-prefixed, board
found only the other. This makes any dot-repo (e.g. .profile, the
Forgejo/GitHub org-profile convention) permanently invisible: not listed,
and --dispatch --repo .profile refuses outright.

A blanket `shopt -s dotglob` is not the fix: it would also route a
dot-prefixed non-repo dir (e.g. .claude) into the container-scan branch,
silently expanding what counts as a polyrepo container. Instead, add_dot_repos()
picks up only dot-entries that are themselves git repos, at both discovery
depths, leaving non-repo dot-dirs untouched. `.*` as a literal glob pattern
already matches dot-entries without dotglob, so no shopt toggle is needed.

Closes ordre 20260818T124828Z-136209036-from-.claude.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qMr8vZZFwa4dBrheNgAhj
This commit is contained in:
Kjell Tore Guttormsen 2026-08-18 15:01:00 +02:00
commit 3e5add0aee
2 changed files with 94 additions and 0 deletions

View file

@ -1973,6 +1973,71 @@ grep -v '^[[:space:]]*#' "$NRFNR_FIXTURE" | grep -q 'NR==FNR'
check "the same filter+grep DOES find a live NR==FNR in a known-positive fixture" $?
/bin/rm -f "$NRFNR_FIXTURE" 2>/dev/null
# --- 22. Dot-prefixed repo discovery (board.sh:265/271 dotglob gap) --------
# Ordre 20260818T124828Z-136209036-from-.claude, measured: `for entry in
# "$root"/*` has no dotglob, so a dot-prefixed repo directory (the Forgejo/
# GitHub `.profile` org-profile convention) is never seen by board at all -
# not listed, not dispatchable ("no repo named '.profile' in the scanned
# roots"). Known-positive control per the order: two repos in one isolated
# root, one dot-prefixed, one not - board must find BOTH, not just the dot
# one (a test that only checked the dot-repo would pass even if the fix
# broke the ordinary case).
DOT_ROOT="$(mktemp -d)"
mkrepo "$DOT_ROOT/.dotrepo"
{
echo "# STATE - .dotrepo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Dot-prefixed repo, e.g. the .profile org-profile convention."
} > "$DOT_ROOT/.dotrepo/STATE.md"
mkrepo "$DOT_ROOT/regularrepo"
{
echo "# STATE - regularrepo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Ordinary repo, no leading dot."
} > "$DOT_ROOT/regularrepo/STATE.md"
DOT_OUT="$("$BOARD" --roots "$DOT_ROOT" --plain 2>/dev/null)"
printf '%s' "$DOT_OUT" | grep -q '\.dotrepo'
check "board finds a dot-prefixed repo" $?
printf '%s' "$DOT_OUT" | grep -q 'regularrepo'
check "board still finds the ordinary repo alongside it (known-positive)" $?
printf '%s' "$DOT_OUT" | grep -q '(2 repo'
check "board reports both repos, not just one" $?
# A dot-prefixed directory that is NOT itself a repo must never become a
# polyrepo container: a naive `shopt -s dotglob` on the existing else-branch
# (board.sh:270-275) would route a dir like .claude there, silently
# expanding what counts as a container - unmeasured scope creep the order
# explicitly flagged. Its child repo must stay invisible.
mkdir -p "$DOT_ROOT/.nonrepo-dotdir"
mkrepo "$DOT_ROOT/.nonrepo-dotdir/child-repo"
DOT_OUT2="$("$BOARD" --roots "$DOT_ROOT" --plain 2>/dev/null)"
printf '%s' "$DOT_OUT2" | grep -q 'child-repo'
[ $? -ne 0 ]
check "a dot-prefixed NON-repo dir is not turned into a polyrepo container" $?
printf '%s' "$DOT_OUT2" | grep -q '(2 repo'
check "the non-repo dot-dir does not change the repo count" $?
# Same glob, second place (board.sh:271): a dot-prefixed repo nested under a
# polyrepo container (depth 2) must also be found, for the identical reason
# - the order flags this as the same defect class, unmeasured but present in
# the code.
mkdir -p "$DOT_ROOT/polyrepo-dir"
mkrepo "$DOT_ROOT/polyrepo-dir/.dotchild"
{
echo "# STATE - .dotchild"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Dot-prefixed repo nested under a polyrepo container."
} > "$DOT_ROOT/polyrepo-dir/.dotchild/STATE.md"
DOT_OUT3="$("$BOARD" --roots "$DOT_ROOT" --plain 2>/dev/null)"
printf '%s' "$DOT_OUT3" | grep -q '\.dotchild'
check "board finds a dot-prefixed repo nested under a polyrepo container (depth 2)" $?
/bin/rm -rf "$DOT_ROOT" 2>/dev/null
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -254,6 +254,31 @@ trunc() { printf '%s' "$1" | LC_ALL=en_US.UTF-8 cut -c1-"$2"; }
# can CARRY its own STATE.md - a -d test drops it silently. Kept identical in
# the rollup builder (catalog) on purpose: two readers, one name.
REPOS=""
# Picks up dot-prefixed git repos directly under $1 - the Forgejo/GitHub
# `.profile` org-profile convention, measured invisible to the loops below
# (ordre 20260818T124828Z-136209036-from-.claude: `for entry in "$root"/*`
# has no dotglob, so a dot-prefixed repo is never seen at all). A blanket
# `shopt -s dotglob` on the loops below is deliberately NOT the fix: it
# would also route a dot-prefixed NON-repo dir (e.g. .claude) into the
# else-branch container-scan, silently expanding what counts as a polyrepo
# container - unmeasured scope creep the order explicitly flagged. `.* ` as
# a literal glob pattern already matches dot-entries without dotglob (only
# a bare `*` needs dotglob to see them), so no shopt toggle is needed here.
# Only a dot-entry that IS itself a repo is ever added; one that is not is
# silently skipped, never recursed into as a container.
add_dot_repos() {
for dotentry in "$1"/.*; do
dotbase="$(basename "$dotentry")"
[ "$dotbase" = "." ] && continue
[ "$dotbase" = ".." ] && continue
[ -d "$dotentry" ] || continue
[ -e "$dotentry/.git" ] || continue
REPOS="$REPOS
$dotentry"
done
}
# Split on comma via IFS + `set --` rather than an unquoted $(...) expansion:
# unquoted word-splitting would also split roots containing spaces. Arg parsing
# is finished above, so clobbering the positional parameters is safe here.
@ -262,6 +287,7 @@ set -- $ROOTS
IFS="$OLD_IFS"
for root in "$@"; do
[ -d "$root" ] || continue
add_dot_repos "$root"
for entry in "$root"/*; do
[ -d "$entry" ] || continue
if [ -e "$entry/.git" ]; then
@ -273,6 +299,9 @@ $entry"
REPOS="$REPOS
$child"
done
# Same glob, same gap, one level down (board.sh:271 in the order's
# numbering) - a dot-prefixed repo nested under a polyrepo container.
add_dot_repos "$entry"
fi
done
done