You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vibed/scripts/migrate-nbd-to-beans.sh

207 lines
7.5 KiB
Bash

#!/usr/bin/env bash
# migrate-nbd-to-beans.sh — Migrate nbd tickets to beans
#
# Usage: bash scripts/migrate-nbd-to-beans.sh [project-dir]
# Defaults to current directory if no arg given.
#
# Requirements: beans, jq
set -euo pipefail
PROJECT_DIR="${1:-.}"
PROJECT_DIR="$(cd "$PROJECT_DIR" && pwd)"
NBD_DIR="$PROJECT_DIR/.nbd/tickets"
MAP_FILE="/tmp/nbd_id_map_$(echo "$PROJECT_DIR" | tr '/' '_').tsv"
# ── Preflight ────────────────────────────────────────────────────────────────
if [[ ! -d "$NBD_DIR" ]]; then
echo "ERROR: No .nbd/tickets directory found in $PROJECT_DIR" >&2
exit 1
fi
command -v beans >/dev/null 2>&1 || { echo "ERROR: beans not found in PATH" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq not found in PATH" >&2; exit 1; }
# ── Field mappers ─────────────────────────────────────────────────────────────
map_priority() {
local p="${1:-5}"
if [[ "$p" -ge 8 ]]; then echo "critical"
elif [[ "$p" -eq 7 ]]; then echo "high"
elif [[ "$p" -ge 5 ]]; then echo "normal"
elif [[ "$p" -ge 3 ]]; then echo "low"
else echo "deferred"
fi
}
map_status() {
case "$1" in
todo) echo "todo" ;;
in_progress) echo "in-progress" ;;
done) echo "completed" ;;
closed) echo "scrapped" ;;
archived) echo "completed" ;;
backlog) echo "draft" ;;
*) echo "todo" ;;
esac
}
map_type() {
case "$1" in
project) echo "epic" ;;
feature) echo "feature" ;;
task) echo "task" ;;
bug) echo "bug" ;;
*) echo "task" ;;
esac
}
# ── Parse TOML frontmatter field (string value, strips surrounding quotes) ───
parse_field() {
local field="$1"
local file="$2"
awk -v field="$field" '
/^\+\+\+$/ { fm_count++; if (fm_count == 2) exit; next }
fm_count == 1 && $0 ~ "^" field " = " {
line = $0
sub("^" field " = \"?", "", line)
sub("\"?$", "", line)
print line
exit
}
' "$file"
}
# ── Extract body (everything after second +++) ────────────────────────────────
extract_body() {
local file="$1"
awk '/^\+\+\+$/ { count++; if (count == 2) { body=1; next } } body { print }' "$file"
}
# ── Extract dep IDs from a TOML array string ─────────────────────────────────
# Input: dependencies = ["abc123", "def456"]
# Output: abc123\ndef456 (one per line)
extract_dep_ids() {
echo "$1" | grep -oE '[0-9a-f]{6}' || true
}
# ── Init ──────────────────────────────────────────────────────────────────────
echo "==> Initializing beans in $PROJECT_DIR"
(cd "$PROJECT_DIR" && beans init)
# Clear map file: columns are old_id <TAB> new_id <TAB> deps_raw
> "$MAP_FILE"
tickets_created=0
tickets_failed=0
deps_wired=0
warnings=()
# ── Pass 1: Create beans ──────────────────────────────────────────────────────
echo ""
echo "==> Pass 1: Creating beans from nbd tickets"
body_tmp="$(mktemp)"
trap 'rm -f "$body_tmp"' EXIT
for ticket_file in "$NBD_DIR"/*.md; do
[[ -f "$ticket_file" ]] || continue
old_id="$(basename "$ticket_file" .md)"
# Parse frontmatter fields
title="$(parse_field title "$ticket_file")"
priority_num="$(parse_field priority "$ticket_file")"
status_raw="$(parse_field status "$ticket_file")"
type_raw="$(parse_field ticket_type "$ticket_file")"
deps_line="$(awk '/^\+\+\+$/ { fm_count++; if (fm_count == 2) exit; next } fm_count == 1 && /^dependencies = / { print; exit }' "$ticket_file")"
# Map to beans values
beans_priority="$(map_priority "${priority_num:-5}")"
beans_status="$(map_status "${status_raw:-todo}")"
beans_type="$(map_type "${type_raw:-task}")"
# Write body to temp file to avoid quoting issues
extract_body "$ticket_file" > "$body_tmp"
# Create the bean
result="$(cd "$PROJECT_DIR" && beans create \
--json \
--type "$beans_type" \
--status "$beans_status" \
--priority "$beans_priority" \
--body-file "$body_tmp" \
"$title" 2>&1)" || true
if new_id="$(echo "$result" | jq -r '.bean.id // .id' 2>/dev/null)" && [[ -n "$new_id" && "$new_id" != "null" ]]; then
printf '%s\t%s\t%s\n' "$old_id" "$new_id" "$deps_line" >> "$MAP_FILE"
(( tickets_created++ )) || true
echo "$old_id -> $new_id [$beans_status/$beans_priority/$beans_type] $title"
else
(( tickets_failed++ )) || true
warnings+=("WARN: failed to create ticket for nbd/$old_id: $title")
echo "$old_id FAILED: $title"
echo " Response: $result"
fi
done
# ── Pass 2: Wire dependencies ─────────────────────────────────────────────────
echo ""
echo "==> Pass 2: Wiring dependencies"
while IFS=$'\t' read -r old_id new_id deps_line; do
dep_ids="$(extract_dep_ids "$deps_line")"
[[ -z "$dep_ids" ]] && continue
while IFS= read -r dep_old_id; do
[[ -z "$dep_old_id" ]] && continue
# Look up the new beans ID for this dependency
dep_new_id="$(awk -F'\t' -v id="$dep_old_id" '$1 == id { print $2 }' "$MAP_FILE")"
if [[ -z "$dep_new_id" ]]; then
warnings+=("WARN: dep $dep_old_id (for $old_id -> $new_id) not found in map — skipped")
echo " ! dep $dep_old_id not found in map (for $new_id)"
continue
fi
result="$(cd "$PROJECT_DIR" && beans update "$new_id" --blocked-by "$dep_new_id" --json 2>&1)" || true
if echo "$result" | jq -e '.bean.id // .id' > /dev/null 2>&1; then
(( deps_wired++ )) || true
echo "$new_id blocked-by $dep_new_id (was $old_id blocked-by $dep_old_id)"
else
warnings+=("WARN: failed to wire dep $dep_new_id -> $new_id: $result")
echo " ✗ failed to wire $dep_new_id -> $new_id"
fi
done <<< "$dep_ids"
done < "$MAP_FILE"
# ── Cleanup ───────────────────────────────────────────────────────────────────
echo ""
echo "==> Removing .nbd directory"
rm -rf "$PROJECT_DIR/.nbd"
echo " Removed $PROJECT_DIR/.nbd"
# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
echo "=========================================="
echo " Migration complete for: $PROJECT_DIR"
echo " Tickets created : $tickets_created"
echo " Tickets failed : $tickets_failed"
echo " Deps wired : $deps_wired"
if [[ ${#warnings[@]} -gt 0 ]]; then
echo " Warnings:"
for w in "${warnings[@]}"; do
echo " - $w"
done
fi
echo "=========================================="