compass

orient-project

#!/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

orient-project · 4.8 KiB · 9b576736e6