add orient-project: root files, inlined docs, meta repo detection
commit 51870a0624b192a14c8a8db0fc22212cd291131d · parent 2385489487
· browse this tree
Ximianth, 2026-06-24 13:07:25 UTC
Combines project-root orientation and meta-repo detection in one probe, since the two share resolution logic and naturally want to be reported together. Behaviour: - Resolves the project root from an optional path arg, else `git rev-parse --show-toplevel`, else cwd. - Inlines conventional orientation files (README, CONTRIBUTING, HACKME, AGENTS, CLAUDE -- case-insensitive, common extensions -- plus `.editorconfig` exactly). Each wrapped in a `<file path="...">` tag per the project-wide convention for whole-file inclusion. - Lists remaining root entries (depth 1) as a bullet list, capped at 50 for sanity. - Detects an associated meta repo by either an in-repo `.meta` symlink or non-empty directory (the documented convention dominates), or a sibling `<basename>.meta/` directory. Reports the resolved path, a shallow dir layout (dotdirs pruned -- otherwise `.git/objects/` etc leaks in), and up to 5 most-recent worklogs by ISO date prefix.
diff --git a/orient-project b/orient-project new file mode 100755 index 0000000..93d7292 --- /dev/null +++ b/orient-project @@ -0,0 +1,163 @@ +#!/bin/bash +# Describes a project root: lists notable files, inlines orientation docs, +# and detects an associated "meta" repo (private notes companion). +# +# Usage: orient-project [PATH] +# PATH defaults to the git toplevel of cwd, or cwd itself if not in a repo. +# +# Emits a markdown fragment intended to sit under an h3 or smaller. +# Inlined file contents are wrapped in <file path="..."> XML tags so they +# carry their own framing and don't fight the surrounding markdown. +# +# Never fully silent (a project root always exists), but individual +# subsections (meta repo, etc.) are omitted when there's nothing to say. + +set -euo pipefail + +# Resolve the project root. +# Explicit arg wins; otherwise prefer git toplevel; fall back to cwd. +if [ $# -ge 1 ]; then + root=$(cd "$1" && pwd) +elif root=$(git rev-parse --show-toplevel 2>/dev/null); then + : +else + root="$PWD" +fi + +echo "- project root: \`${root}\`" + +# Orientation docs to inline if present. +# Case-insensitive name match against a small set of conventional filenames, +# with a few common extensions. `.editorconfig` is included by exact name. +# We deliberately don't recurse: this is a root-orientation probe. +shopt -s nullglob nocaseglob +orient_files=() +for base in README CONTRIBUTING HACKME AGENTS CLAUDE; do + for candidate in "$root/$base" "$root/$base".md "$root/$base".rst "$root/$base".txt; do + if [ -f "$candidate" ]; then + orient_files+=("$candidate") + fi + done +done +shopt -u nocaseglob +if [ -f "$root/.editorconfig" ]; then + orient_files+=("$root/.editorconfig") +fi + +# Inline the orientation docs, each wrapped in its own <file> tag. +# The path attribute is repo-relative for readability. +if [ ${#orient_files[@]} -gt 0 ]; then + echo + echo "#### Orientation files" + echo + for f in "${orient_files[@]}"; do + rel="${f#$root/}" + echo "<file path=\"${rel}\">" + cat "$f" + echo "</file>" + echo + done +fi + +# List remaining root entries (depth 1, no dotfiles, excluding files we inlined). +# This is a sanity probe, not an inventory tool, so cap at 50 entries. +# (If a project root has more than 50 visible entries at depth 1, +# something else is going on and a fuller listing isn't the right tool anyway.) +mapfile -t root_entries < <( + find "$root" -mindepth 1 -maxdepth 1 -not -name '.*' -printf '%f\n' | sort +) +# Build a lookup of inlined basenames so we can skip them in the listing. +declare -A inlined=() +for f in "${orient_files[@]}"; do + inlined["$(basename "$f")"]=1 +done +remaining=() +for entry in "${root_entries[@]}"; do + if [ -z "${inlined[$entry]:-}" ]; then + remaining+=("$entry") + fi +done +if [ ${#remaining[@]} -gt 0 ]; then + echo + echo "#### Other root entries" + echo + count=0 + for entry in "${remaining[@]}"; do + if [ $count -ge 50 ]; then + echo "- ... ($((${#remaining[@]} - 50)) more not shown)" + break + fi + if [ -d "$root/$entry" ]; then + echo "- \`${entry}/\`" + else + echo "- \`${entry}\`" + fi + count=$((count + 1)) + done +fi + +# Meta repo detection. +# Two conventions, checked in order: +# 1. `<root>/.meta` -- if it's a symlink, or a non-empty directory. +# (An empty real directory falls through; it's probably stale state.) +# 2. `<root>/../<basename>.meta/` -- sibling directory next to the repo. +# The first hit wins; we don't try to merge. +meta="" +# Resolve symlinks (`pwd -P`) so the reported path points to where files live. +if [ -L "$root/.meta" ] && [ -d "$root/.meta" ]; then + meta=$(cd "$root/.meta" && pwd -P) +elif [ -d "$root/.meta" ] && [ -n "$(ls -A "$root/.meta" 2>/dev/null)" ]; then + meta=$(cd "$root/.meta" && pwd -P) +fi +if [ -z "$meta" ]; then + parent=$(dirname "$root") + base=$(basename "$root") + sibling="$parent/$base.meta" + if [ -d "$sibling" ]; then + meta=$(cd "$sibling" && pwd) + fi +fi + +if [ -n "$meta" ]; then + echo + echo "#### Meta repo" + echo + echo "- path: \`${meta}\`" + + # Directory layout: shallow listing of subdirs to convey structure. + # Prune dotdirs entirely (don't descend into them) so noise like `.git/objects/` + # doesn't leak in via the maxdepth-2 walk. + mapfile -t meta_dirs < <( + find "$meta" -mindepth 1 -maxdepth 2 \ + \( -name '.*' -prune \) -o \ + \( -type d -printf '%P\n' \) \ + | sort + ) + if [ ${#meta_dirs[@]} -gt 0 ]; then + echo "- directory layout:" + for d in "${meta_dirs[@]}"; do + echo " - \`${d}/\`" + done + fi + + # Recent worklogs: filenames with a YYYY-MM-DD prefix. + # Look first under `worklogs/`, then fall back to anywhere in the meta repo. + # Sort by the date prefix (lexicographic = chronological for ISO dates), + # show the 5 most recent. + if [ -d "$meta/worklogs" ]; then + worklog_search_root="$meta/worklogs" + else + worklog_search_root="$meta" + fi + mapfile -t worklogs < <( + find "$worklog_search_root" -type f -name '????-??-??*' -printf '%P\n' \ + | sort -r \ + | head -n 5 + ) + if [ ${#worklogs[@]} -gt 0 ]; then + echo "- recent worklogs (under \`${worklog_search_root#$meta/}\`):" + for w in "${worklogs[@]}"; do + echo " - \`${w}\`" + done + fi +fi