Adds a two-dimensional address (wing, hall) to brain notes. A wing is a
topic domain (e.g. jepa-fx, hyperguild); a hall is one of a closed
vocabulary of memory types (facts, decisions, failures, hypotheses,
sources). Notes route to brain/wiki/<wing>/<hall>/<slug>.md with
wing/hall/created_at YAML frontmatter, making the directory a valid
Obsidian vault.
Changes:
- new package ingestion/internal/brain (NotePath, ValidHalls, Sanitise,
BuildWingIndex, BuildAllWingIndexes)
- api.WriteNote refactored to WriteNoteOptions; wing+hall routes to
brain/wiki/, otherwise falls back to brain/knowledge/ (legacy)
- search.Query → QueryOptions with optional Wing/Hall filtering; Result
carries wing/hall extracted from frontmatter or path segments
- MCP tools brain_write and brain_query gain optional wing/hall params
(hall enum-validated); new brain_index tool regenerates _index.md MOC
- POST /index REST endpoint mirrors brain_index
- brain_write auto-rebuilds the wing's _index.md after a wing+hall write
- scripts/migrate-brain-halls.sh migrates flat brain/wiki/{concepts,entities}/
into the new layout (dry-run by default, --commit applies)
All existing tests pass; new tests cover wing/hall write routing, scope
filtering, invalid hall rejection, _index.md generation, and migration
script paths.
Closes hyperguild#1.
136 lines
3.6 KiB
Bash
Executable File
136 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# migrate-brain-halls.sh — move flat brain/wiki/{concepts,entities}/ notes
|
|
# into the structured brain/wiki/<wing>/<hall>/ layout introduced by
|
|
# hyperguild#1.
|
|
#
|
|
# Reads each note's YAML frontmatter:
|
|
# type: maps to hall (decision, hypothesis, failure, source → eponymous;
|
|
# concept, entity, anything else → facts)
|
|
# domain: maps to wing (sanitised: lowercase, alphanumerics + hyphens);
|
|
# empty → "general"
|
|
#
|
|
# Dry-run by default. Pass --commit to actually move files. Idempotent:
|
|
# already-migrated notes (already under a Wing dir) are left alone.
|
|
#
|
|
# Usage:
|
|
# scripts/migrate-brain-halls.sh /path/to/brain # dry-run
|
|
# scripts/migrate-brain-halls.sh --commit /path/to/brain # apply
|
|
|
|
set -euo pipefail
|
|
|
|
COMMIT=0
|
|
BRAIN=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--commit) COMMIT=1 ;;
|
|
-h|--help)
|
|
sed -n '2,18p' "$0"
|
|
exit 0
|
|
;;
|
|
*) BRAIN="$arg" ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$BRAIN" ]]; then
|
|
echo "error: brain directory required" >&2
|
|
echo "usage: $0 [--commit] <brain-dir>" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! -d "$BRAIN" ]]; then
|
|
echo "error: $BRAIN is not a directory" >&2
|
|
exit 2
|
|
fi
|
|
|
|
WIKI="$BRAIN/wiki"
|
|
if [[ ! -d "$WIKI" ]]; then
|
|
echo "no $WIKI/ — nothing to migrate"
|
|
exit 0
|
|
fi
|
|
|
|
sanitise() {
|
|
# lowercase, replace non-alnum with hyphen, collapse hyphens, trim
|
|
local s
|
|
s=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
|
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g')
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
# extract_frontmatter_value <file> <key>
|
|
# Echoes the value (trimmed, unquoted) of `key:` from a leading YAML
|
|
# frontmatter block. Empty if absent or no frontmatter.
|
|
extract_frontmatter_value() {
|
|
awk -v key="$2" '
|
|
BEGIN { in_fm = 0; first = 1 }
|
|
/^---[[:space:]]*$/ {
|
|
if (first) { in_fm = 1; first = 0; next }
|
|
if (in_fm) { exit }
|
|
}
|
|
in_fm {
|
|
idx = index($0, ":")
|
|
if (idx == 0) next
|
|
k = substr($0, 1, idx-1)
|
|
v = substr($0, idx+1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", v)
|
|
gsub(/^["'\'']|["'\'']$/, "", v)
|
|
if (k == key) { print v; exit }
|
|
}
|
|
' "$1"
|
|
}
|
|
|
|
hall_for_type() {
|
|
case "$1" in
|
|
decision|decisions) echo "decisions" ;;
|
|
hypothesis|hypotheses) echo "hypotheses" ;;
|
|
failure|failures) echo "failures" ;;
|
|
source|sources) echo "sources" ;;
|
|
*) echo "facts" ;;
|
|
esac
|
|
}
|
|
|
|
declare -i moved=0 skipped=0
|
|
|
|
migrate_source_dir() {
|
|
local src="$1"
|
|
[[ -d "$src" ]] || return 0
|
|
while IFS= read -r -d '' f; do
|
|
local typ domain wing hall slug dest
|
|
typ=$(extract_frontmatter_value "$f" type)
|
|
domain=$(extract_frontmatter_value "$f" domain)
|
|
hall=$(hall_for_type "$typ")
|
|
wing=$(sanitise "${domain:-general}")
|
|
[[ -z "$wing" ]] && wing="general"
|
|
slug=$(basename "$f" .md)
|
|
dest="$WIKI/$wing/$hall/$slug.md"
|
|
|
|
if [[ "$f" == "$dest" ]]; then
|
|
skipped=$((skipped + 1))
|
|
continue
|
|
fi
|
|
|
|
if [[ -e "$dest" ]]; then
|
|
echo "skip (target exists): $f → $dest"
|
|
skipped=$((skipped + 1))
|
|
continue
|
|
fi
|
|
|
|
if [[ "$COMMIT" -eq 1 ]]; then
|
|
mkdir -p "$(dirname "$dest")"
|
|
git -C "$BRAIN" mv "$f" "$dest" 2>/dev/null || mv "$f" "$dest"
|
|
fi
|
|
echo "move: $f → $dest"
|
|
moved=$((moved + 1))
|
|
done < <(find "$src" -maxdepth 1 -type f -name '*.md' -print0)
|
|
}
|
|
|
|
migrate_source_dir "$WIKI/concepts"
|
|
migrate_source_dir "$WIKI/entities"
|
|
|
|
echo
|
|
if [[ "$COMMIT" -eq 1 ]]; then
|
|
echo "moved=$moved skipped=$skipped (committed)"
|
|
else
|
|
echo "moved=$moved skipped=$skipped (dry-run — pass --commit to apply)"
|
|
fi
|