muse-bridge-deploy.sh
bash
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
18 hours ago
| 1 | #!/usr/bin/env bash |
| 2 | # muse-bridge-deploy.sh |
| 3 | # |
| 4 | # Complete MuseHub → external Git deployment pipeline. |
| 5 | # |
| 6 | # Usage: |
| 7 | # ./scripts/muse-bridge-deploy.sh "mirror: brief description of what changed" |
| 8 | # |
| 9 | # What this does (in order): |
| 10 | # 1. Security audit — npm audit fix (blocks the bridge if high vulns remain) |
| 11 | # 2. Commit audit fix — if audit changed package-lock.json, commits it to Muse |
| 12 | # 3. Bridge export — muse bridge git-export → isolated .muse/mirror checkout |
| 13 | # 4. GitHub PR — opens a PR from muse-mirror to main (skips if one already exists) |
| 14 | # |
| 15 | # Requirements: |
| 16 | # - muse CLI in PATH (from ~/.local/share/muse/venv/bin) |
| 17 | # - gh CLI in PATH and authenticated (gh auth status) |
| 18 | # - git remote "origin" points to your GitHub/GitLab repo |
| 19 | # - MUSE_BRIDGE_GIT_BRANCH env var (default: muse-mirror) |
| 20 | # - MUSE_BRIDGE_BASE_BRANCH env var (default: main) |
| 21 | # - MUSE_BRIDGE_MIRROR_DIR env var (default: .muse/mirror) |
| 22 | |
| 23 | set -euo pipefail |
| 24 | |
| 25 | # ── Config (override via env) ────────────────────────────────────────────────── |
| 26 | MIRROR_BRANCH="${MUSE_BRIDGE_GIT_BRANCH:-muse-mirror}" |
| 27 | BASE_BRANCH="${MUSE_BRIDGE_BASE_BRANCH:-main}" |
| 28 | PR_TITLE="${1:-mirror: deploy from MuseHub $(date '+%Y-%m-%d')}" |
| 29 | REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" |
| 30 | MIRROR_DIR="${MUSE_BRIDGE_MIRROR_DIR:-.muse/mirror}" |
| 31 | |
| 32 | # ── Colors ───────────────────────────────────────────────────────────────────── |
| 33 | RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; NC='\033[0m' |
| 34 | info() { echo -e "${BLUE}[bridge]${NC} $*"; } |
| 35 | success() { echo -e "${GREEN}[bridge]${NC} $*"; } |
| 36 | warn() { echo -e "${YELLOW}[bridge]${NC} $*"; } |
| 37 | fail() { echo -e "${RED}[bridge] ERROR:${NC} $*" >&2; exit 1; } |
| 38 | |
| 39 | cd "$REPO_ROOT" |
| 40 | |
| 41 | if [[ "$MIRROR_DIR" = /* ]]; then |
| 42 | MIRROR_DIR_ABS="$MIRROR_DIR" |
| 43 | else |
| 44 | MIRROR_DIR_ABS="${REPO_ROOT}/${MIRROR_DIR}" |
| 45 | fi |
| 46 | |
| 47 | if [[ "$MIRROR_DIR_ABS" == "$REPO_ROOT" ]]; then |
| 48 | fail "MUSE_BRIDGE_MIRROR_DIR must not be the repository root. Refusing unsafe --git-dir . workflow." |
| 49 | fi |
| 50 | |
| 51 | cleanup_sentinels() { |
| 52 | [[ -n "${ENV_SENTINEL:-}" && -f "$ENV_SENTINEL" ]] && rm -f "$ENV_SENTINEL" |
| 53 | [[ -n "${CONFIG_SENTINEL:-}" && -f "$CONFIG_SENTINEL" ]] && rm -f "$CONFIG_SENTINEL" |
| 54 | [[ -n "${DATA_SENTINEL:-}" && -f "$DATA_SENTINEL" ]] && rm -f "$DATA_SENTINEL" |
| 55 | [[ "${CREATED_DATA_DIR:-0}" == "1" && -d "${REPO_ROOT}/data" ]] && rmdir "${REPO_ROOT}/data" 2>/dev/null || true |
| 56 | } |
| 57 | trap cleanup_sentinels EXIT |
| 58 | |
| 59 | ensure_mirror_checkout() { |
| 60 | local remote_url |
| 61 | remote_url=$(git config --get remote.origin.url 2>/dev/null || true) |
| 62 | [[ -n "$remote_url" ]] || fail "Git remote 'origin' is not configured." |
| 63 | |
| 64 | if [[ -e "$MIRROR_DIR_ABS" && ! -d "${MIRROR_DIR_ABS}/.git" ]]; then |
| 65 | fail "Mirror path exists but is not a git repository: ${MIRROR_DIR_ABS}" |
| 66 | fi |
| 67 | |
| 68 | if [[ ! -d "${MIRROR_DIR_ABS}/.git" ]]; then |
| 69 | info "Provisioning isolated mirror checkout at ${MIRROR_DIR}..." |
| 70 | mkdir -p "$(dirname "$MIRROR_DIR_ABS")" |
| 71 | git clone --single-branch --branch "$MIRROR_BRANCH" "$remote_url" "$MIRROR_DIR_ABS" |
| 72 | fi |
| 73 | |
| 74 | git -C "$MIRROR_DIR_ABS" remote set-url origin "$remote_url" |
| 75 | git -C "$MIRROR_DIR_ABS" fetch origin "$MIRROR_BRANCH" |
| 76 | git -C "$MIRROR_DIR_ABS" checkout -B "$MIRROR_BRANCH" "origin/${MIRROR_BRANCH}" |
| 77 | git -C "$MIRROR_DIR_ABS" reset --hard "origin/${MIRROR_BRANCH}" |
| 78 | } |
| 79 | |
| 80 | prepare_sentinels() { |
| 81 | ENV_SENTINEL="${REPO_ROOT}/.env.bridge-sentinel.$$" |
| 82 | CONFIG_SENTINEL="${REPO_ROOT}/config/bridge-sentinel-local.$$.yaml" |
| 83 | CREATED_DATA_DIR=0 |
| 84 | |
| 85 | if [[ ! -d "${REPO_ROOT}/data" ]]; then |
| 86 | mkdir -p "${REPO_ROOT}/data" |
| 87 | CREATED_DATA_DIR=1 |
| 88 | fi |
| 89 | DATA_SENTINEL="${REPO_ROOT}/data/bridge-sentinel.$$.db" |
| 90 | |
| 91 | printf 'bridge sentinel: no secrets\n' > "$ENV_SENTINEL" |
| 92 | printf 'bridge sentinel: no secrets\n' > "$CONFIG_SENTINEL" |
| 93 | printf 'bridge sentinel: no secrets\n' > "$DATA_SENTINEL" |
| 94 | } |
| 95 | |
| 96 | verify_sentinels() { |
| 97 | [[ -f "$ENV_SENTINEL" ]] || fail "Bridge safety sentinel disappeared: ${ENV_SENTINEL}. The bridge touched the dev tree." |
| 98 | [[ -f "$CONFIG_SENTINEL" ]] || fail "Bridge safety sentinel disappeared: ${CONFIG_SENTINEL}. The bridge touched the dev tree." |
| 99 | [[ -f "$DATA_SENTINEL" ]] || fail "Bridge safety sentinel disappeared: ${DATA_SENTINEL}. The bridge touched the dev tree." |
| 100 | } |
| 101 | |
| 102 | # ── Step 1: Security audit ───────────────────────────────────────────────────── |
| 103 | info "Step 1/4 — Running security audit (npm audit fix)..." |
| 104 | |
| 105 | if [[ ! -f package.json ]]; then |
| 106 | warn "No package.json found — skipping npm audit. Add audits for your stack manually." |
| 107 | else |
| 108 | AUDIT_FIX_OUTPUT=$(npm audit fix --audit-level=moderate 2>&1 || true) |
| 109 | printf '%s\n' "$AUDIT_FIX_OUTPUT" | tail -5 |
| 110 | |
| 111 | # Check if high/critical vulns remain after auto-fix |
| 112 | HIGH_COUNT=$(npm audit --json 2>/dev/null \ |
| 113 | | python3 -c "import json,sys; d=json.load(sys.stdin); \ |
| 114 | v=d.get('metadata',{}).get('vulnerabilities',{}); \ |
| 115 | print(v.get('high',0)+v.get('critical',0))" 2>/dev/null || echo "0") |
| 116 | |
| 117 | if [[ "$HIGH_COUNT" -gt 0 ]]; then |
| 118 | fail "npm audit found $HIGH_COUNT high/critical vulnerabilities that could not be auto-fixed.\n\ |
| 119 | Run 'npm audit' to review them, fix manually, then re-run this script.\n\ |
| 120 | DO NOT bridge with known high/critical vulnerabilities." |
| 121 | fi |
| 122 | success "Audit clean — 0 high/critical vulnerabilities." |
| 123 | fi |
| 124 | |
| 125 | # ── Step 2: Commit audit changes if any ─────────────────────────────────────── |
| 126 | info "Step 2/4 — Checking for audit-generated changes..." |
| 127 | |
| 128 | AUDIT_CHANGED=$(muse status --short 2>/dev/null | grep -E "package-lock\.json|package\.json" || true) |
| 129 | |
| 130 | if [[ -n "$AUDIT_CHANGED" ]]; then |
| 131 | warn "Audit changed package files — committing to Muse before bridging." |
| 132 | muse code add package-lock.json package.json 2>/dev/null || true |
| 133 | muse commit -m "security: npm audit fix pre-bridge $(date '+%Y-%m-%d')" |
| 134 | muse push staging "${BASE_BRANCH}" |
| 135 | success "Audit commit pushed to Muse main." |
| 136 | else |
| 137 | success "No audit changes to commit." |
| 138 | fi |
| 139 | |
| 140 | # ── Step 3: Bridge export ────────────────────────────────────────────────────── |
| 141 | info "Step 3/4 — Bridging Muse main → ${MIRROR_BRANCH} via isolated mirror checkout..." |
| 142 | |
| 143 | ensure_mirror_checkout |
| 144 | prepare_sentinels |
| 145 | |
| 146 | muse bridge git-export \ |
| 147 | --git-dir "${MIRROR_DIR_ABS}" \ |
| 148 | --git-branch "${MIRROR_BRANCH}" \ |
| 149 | --git-remote origin \ |
| 150 | --force-push \ |
| 151 | --exclude '.env' \ |
| 152 | --exclude '.env.local' \ |
| 153 | --exclude '.env.*.local' \ |
| 154 | --exclude 'config/local.yaml' \ |
| 155 | --exclude 'config/*-local.*' \ |
| 156 | --exclude 'data/*' \ |
| 157 | --exclude 'data/**' \ |
| 158 | --exclude '*.db' \ |
| 159 | --exclude '*.sqlite' |
| 160 | |
| 161 | verify_sentinels |
| 162 | |
| 163 | success "Bridge complete. ${MIRROR_BRANCH} is up to date on origin." |
| 164 | |
| 165 | # ── Step 4: GitHub/GitLab PR ────────────────────────────────────────────────── |
| 166 | info "Step 4/4 — Opening or updating PR: ${MIRROR_BRANCH} → ${BASE_BRANCH}..." |
| 167 | |
| 168 | if ! command -v gh &>/dev/null; then |
| 169 | warn "gh CLI not found — skipping PR creation." |
| 170 | warn "Manually open a PR from '${MIRROR_BRANCH}' to '${BASE_BRANCH}' on your Git host." |
| 171 | exit 0 |
| 172 | fi |
| 173 | |
| 174 | # Check if a PR already exists for this branch |
| 175 | EXISTING_PR=$(gh pr list --head "${MIRROR_BRANCH}" --base "${BASE_BRANCH}" --json number,url \ |
| 176 | --jq '.[0].url' 2>/dev/null || true) |
| 177 | |
| 178 | if [[ -n "$EXISTING_PR" ]]; then |
| 179 | warn "PR already exists: $EXISTING_PR" |
| 180 | warn "Review and merge it at the link above. (The bridge already updated the branch.)" |
| 181 | else |
| 182 | PR_URL=$(gh pr create \ |
| 183 | --base "${BASE_BRANCH}" \ |
| 184 | --head "${MIRROR_BRANCH}" \ |
| 185 | --title "${PR_TITLE}" \ |
| 186 | --body "Automated mirror from MuseHub. All development and review happened there. |
| 187 | |
| 188 | - Security audit passed (0 high/critical vulnerabilities) |
| 189 | - Bridged via: \`muse bridge git-export\` |
| 190 | - Source: \`muse status\` on Muse main at time of bridge |
| 191 | |
| 192 | Merge this PR to trigger your deployment platform." \ |
| 193 | 2>&1) |
| 194 | success "PR created: $PR_URL" |
| 195 | fi |
| 196 | |
| 197 | echo "" |
| 198 | success "Deploy pipeline complete." |
| 199 | echo -e " Next step: merge the PR on GitHub/GitLab and your deployment platform will pick it up." |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
18 hours ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
1 day ago