#!/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. `/notes-for-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