orient-contextdocs
#!/bin/bash
# Finds orientation docs along the path from the project root down to a given
# location, surfacing the context that applies to where you're working.
#
# Usage: orient-contextdocs [--skip-root] [PATH]
# PATH defaults to cwd.
#
# Walks from the project root (resolved via `find-project-root`) downward
# through each ancestor directory of PATH, all the way to PATH itself.
# At each level, collects conventional orientation docs:
# README, CONTRIBUTING, HACKME, AGENTS, CLAUDE (any case, common extensions),
# and `.editorconfig`.
#
# Markdown docs are summarized as a heading-only TOC (via `skim-md-toc`)
# rather than inlined in full: context docs can be large, and the headings are
# usually enough to know whether to open them.
#
# Output ordering: outermost level first, so the most-local context lands
# last in the output -- where the reader's eye comes to rest.
#
# Silent (no output, exit 0) when:
# - PATH is not inside a project (no git work tree), or
# - no orientation docs are found at any level.
#
# Flags:
# --skip-root Omit docs at the project root itself.
# Useful when a composer is already inlining the root
# (e.g. via `orient-project`) and only wants the deeper
# ancestor context.
# Default (no flag) is to include the root: the script
# should make sense when run in isolation.
#
# FUTURE: an associated meta repo may also carry path-specific notes
# (e.g. `<meta>/notes-for-path/<repo-relative-path>/...`) so that private
# orientation can be maintained for specific subpaths of a public repo.
# The exact convention isn't pinned down yet, but the loop below is the
# natural integration point: at each per-directory step, also check the
# meta repo for a corresponding entry.
set -euo pipefail
self_dir=$(cd "$(dirname "$(readlink -f "$0")")" && pwd)
find_root="$self_dir/find-project-root"
summarize="$self_dir/skim-md-toc"
skip_root=0
while [ $# -gt 0 ]; do
case "$1" in
--skip-root) skip_root=1; shift ;;
-h|--help)
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
exit 0 ;;
--) shift; break ;;
-*) echo "orient-contextdocs: unknown option: $1" >&2; exit 2 ;;
*) break ;;
esac
done
start="${1:-$PWD}"
if [ ! -d "$start" ]; then
echo "orient-contextdocs: not a directory: $start" >&2
exit 2
fi
start=$(cd "$start" && pwd)
# Resolve the project root. Silent exit if there isn't one.
if ! root=$("$find_root" "$start"); then
exit 0
fi
# Build the list of directories to inspect, from outermost (root) inward.
# We walk up from `start` collecting parents until we hit `root`,
# then reverse the order for outermost-first output.
levels=()
current="$start"
while :; do
levels=("$current" "${levels[@]}")
if [ "$current" = "$root" ]; then
break
fi
parent=$(dirname "$current")
if [ "$parent" = "$current" ]; then
# Defensive: shouldn't happen if find-project-root succeeded,
# but means `start` somehow isn't under `root`.
break
fi
current="$parent"
done
if [ "$skip_root" -eq 1 ]; then
# Drop the first element (the root).
levels=("${levels[@]:1}")
fi
# Per-level doc finder.
# Case-insensitive name match against the conventional prose files;
# `.editorconfig` matched exactly.
find_docs_in() {
local dir="$1"
local base candidate
shopt -s nullglob nocaseglob
for base in README CONTRIBUTING HACKME AGENTS CLAUDE; do
for candidate in "$dir/$base" "$dir/$base".md "$dir/$base".rst "$dir/$base".txt; do
if [ -f "$candidate" ]; then
echo "$candidate"
fi
done
done
shopt -u nocaseglob
if [ -f "$dir/.editorconfig" ]; then
echo "$dir/.editorconfig"
fi
}
# Accumulate output; suppress everything if nothing was found.
buffer=""
for dir in "${levels[@]}"; do
mapfile -t docs < <(find_docs_in "$dir")
if [ ${#docs[@]} -eq 0 ]; then
continue
fi
# Show paths relative to the project root for readability.
if [ "$dir" = "$root" ]; then
label="./"
else
label="${dir#$root/}/"
fi
buffer+=$'\n'"#### \`${label}\`"$'\n\n'
for doc in "${docs[@]}"; do
rel=$(basename "$doc")
buffer+="- \`${rel}\`"$'\n'
# Markdown: append the TOC nested under the bullet.
case "$doc" in
*.md|*.MD|*.Md|*.mD)
while IFS= read -r line; do
buffer+=" ${line}"$'\n'
done < <("$summarize" "$doc")
;;
esac
# FUTURE: meta-repo path-keyed notes integration point.
# Resolve the meta repo (when conventions are pinned down),
# look for notes keyed by repo-relative path, and inline/summarize.
done
done
if [ -n "$buffer" ]; then
printf '%s' "$buffer"
fi
bin/orient-contextdocs · 4.4 KiB · 037b7fe1d5