Skip to content

kit_doctor's dependency graph does not read shell source, so lib/repo_root.sh has no dependents #228

Description

@topij

kit_doctor.derive_dependencies builds its dependency graph from Python imports
only
. Shell source is not scanned, so this real, unconditional dependency is
absent from the graph:

scripts/dev_session.sh:63        source "$SCRIPT_DIR/lib/repo_root.sh"
scripts/reconcile_sessions.sh:52 source "$SCRIPT_DIR/lib/repo_root.sh"

A tree missing lib/repo_root.sh therefore reports it as an ordinary missing
— "sized-down adoption, or incomplete" — while bash scripts/dev_session.sh
dies on its source line. That is a live instance of #41's own bug class,
knowingly left open. It is pinned by
test_the_shell_source_dependency_is_a_KNOWN_GAP_not_an_oversight so it stays
stated rather than drifting into an assumption, and the same test asserts the
complete derived graph, so any future false edge fails in the kit's own repo.

A scanner was built and withdrawn — read this before building another

PR #225 added one, then removed it after three review rounds. Both directions
failed, and each failure was found only by execution, never by reading.

Preventing MISSED edges needs command-position detection. Anchoring source
to the first token on its line misses the guarded form:

[ -f "$LIB" ] && source "$LIB"        # arguably better bash than the bare form
if true; then source "$LIB"; fi
while read -r x; do source lib/dep.sh; done

Widening the anchor to match after a separator or block keyword catches those —
and also catches this, because a regex has no notion of quote state:

echo "run this; source lib/dep.sh to finish"     # -> a real dependency edge

dev_session.sh already prints exactly that kind of instruction to the user
(lines 420/423 tell a human to run source "$session_dir/activate") and was
safe only by phrasing.

Preventing FALSE edges needs every real heredoc opener recognised. A source
line inside a heredoc is text being printed, so bodies must be skipped — and a
body that is not recognised as one gets scanned as code. All of these are
valid bash that defeated the last version's opener regex:

cmd <<A <<B          # only the first opener on a line was seen
cat <<123            # numeric delimiter fails an identifier-shaped pattern
cat <<'MULTI WORD'   # quoted multi-word delimiter, likewise

Over-detection is the mirror image and is worse per unit: <<< herestrings and
<< inside $(( … )) arithmetic both look like <<DELIM, and a single
misparse put the scanner into a heredoc that never closed — deleting every real
edge for the rest of the file. dev_session.sh contains eight herestrings
and was safe only because each is followed by "$… rather than a bare word.
That one was bounded structurally in the end (a heredoc region counts only if
its delimiter actually closes), but containment is not parsing.

Why the gap is preferable to the scanner that existed

A missed edge degrades a file to plain missing — the pre-#41 behaviour,
unhelpful but never misleading. A false edge makes kit_doctor report
missing-required: "this install is broken, install these before refreshing
any component"
— to an adopter whose install is fine, about a file they do not
need. That is the missing / missing-required split firing in reverse, and it
is strictly worse than the hole it was meant to close.

What the real fix needs

Command-position detection and heredoc-boundary detection, which together
mean tokenising rather than pattern-matching: quote state ('…', "…",
$'…'), escapes, $( … ) and $(( … )) nesting, all heredoc opener forms,
line continuations, and multi-line strings.

  1. bashlex or a comparable shell parser. Correct, and a new third-party
    dependency in an engine that currently declares dependencies = [] — itself
    a deliberate property worth not giving up cheaply.
  2. A small hand-written tokenizer answering only "is this offset in command
    position, and is this line inside a heredoc body". Perhaps 100–150 lines,
    fully testable, no dependency. Most likely the right shape.
  3. Leave the gap and rely on the whole-graph assertion to catch regressions.
    Cheapest, and what ships today.

Acceptance bar for any attempt, all four executable today:

  • [ -f "$L" ] && source "$L" produces an edge
  • echo "run this; source lib/dep.sh" does not
  • cmd <<A <<B / cat <<123 / cat <<'MULTI WORD' bodies are not scanned
  • read -r x <<< "$out" does not swallow the rest of the file

Related: #41 — PR #225 added the required/optional axis this would feed — and
#37, #227.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions