From 06017776b10d9d35214ca5db0f948d15c326356c Mon Sep 17 00:00:00 2001 From: Nikita Muntian Date: Sat, 30 May 2026 04:41:15 +0300 Subject: [PATCH 1/3] =?UTF-8?q?governance:=20add=20collapse-archived.sh=20?= =?UTF-8?q?=E2=80=94=20fold=2024=20archived=20repos=20into=20canonical=20t?= =?UTF-8?q?argets=20(subtree)=20+=20guarded=20delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/operations/collapse-archived.sh | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 governance/operations/collapse-archived.sh diff --git a/governance/operations/collapse-archived.sh b/governance/operations/collapse-archived.sh new file mode 100644 index 0000000..f0b413e --- /dev/null +++ b/governance/operations/collapse-archived.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# Fold the 24 ARCHIVED repos into their canonical targets via git subtree +# (history-preserved), then optionally DELETE the sources. +# +# Runs entirely with gh + git in YOUR shell (the MCP integration cannot +# unarchive, subtree-merge, or delete repos). Dry-run by default. +# +# Phases (each gated): +# 1. unarchive sources (gh repo unarchive) --apply +# 2. subtree-merge into targets (git subtree add) --apply +# 3. push target branches + PRs (git push + gh pr create) --apply +# 4. DELETE sources (IRREVERSIBLE) CONFIRM_DELETE=yes + --apply +# +# Usage: +# ./collapse-archived.sh # dry-run plan +# ./collapse-archived.sh --apply # phases 1-3 +# CONFIRM_DELETE=yes ./collapse-archived.sh --apply --delete +set -euo pipefail +OWNER="${OWNER:-muntianus}" +APPLY=false; DO_DELETE=false +for a in "$@"; do case "$a" in --apply) APPLY=true;; --delete) DO_DELETE=true;; esac; done +GH="https://github.com/$OWNER" +WORKDIR="${WORKDIR:-$PWD/.collapse-archived}" + +log(){ printf '\033[0;36m[arch]\033[0m %s\n' "$*"; } +warn(){ printf '\033[0;33m[arch:warn]\033[0m %s\n' "$*" >&2; } +die(){ printf '\033[0;31m[arch:err]\033[0m %s\n' "$*" >&2; exit 1; } +command -v gh >/dev/null || die "gh not found" +command -v git >/dev/null || die "git not found" +gh auth status >/dev/null 2>&1 || die "run: gh auth login" +$APPLY || log "DRY-RUN (default). Add --apply to execute." + +# source-repo -> "targetRepo:subdir". Canonical mapping (see +# governance/reports/duplications.md). Sources whose content already lives in +# the perfops monorepo as live dirs are routed under archive/ to avoid +# subtree path conflicts; junk/no-canonical repos also go under archive/. +SEED="${SEED:-perfops}" # the renamed monorepo (formerly perfops-platform) +declare -A MAP=( + # --- already-in-monorepo originals -> archive/ (avoid live-dir conflicts) --- + [platform]="$SEED:archive/platform" + [perfops-gateway]="$SEED:archive/perfops-gateway" + [perfops-ingest]="$SEED:archive/perfops-ingest" + [reportkit]="$SEED:archive/reportkit" + [perfops-sdk]="$SEED:archive/perfops-sdk" + # --- testing / load PoCs --- + [krave-loadtest]="$SEED:testing/legacy/krave-loadtest" + [load-krave]="$SEED:testing/legacy/load-krave" + [k6-nagibator2000]="$SEED:testing/legacy/k6-nagibator2000" + [ragus-loader]="$SEED:testing/legacy/ragus-loader" + [perfops-templates]="$SEED:templates" + [iac-monitoring-agent]="$SEED:observability/iac-monitoring" + # --- agents --- + [claude-agents]="$SEED:agents/claude-agents" + [agent-control-office]="$SEED:agents/legacy/agent-control-office" + # --- office / misc / junk -> archive/ --- + [office]="$SEED:archive/office" + [office-workspace]="$SEED:archive/office-workspace" + [awesome-performance-engineering]="$SEED:archive/awesome-performance-engineering" + [pybot]="$SEED:archive/pybot" + [tools]="$SEED:archive/tools" + [automation-stack]="$SEED:archive/automation-stack" + [ig-unfollow]="$SEED:archive/ig-unfollow" + # --- separate canonical repos (not the monorepo) --- + [old-telegram-reminder]="telegram-reminder-simple:legacy/old-telegram-reminder" + [telegram-reminder]="telegram-reminder-simple:legacy/telegram-reminder" + [old-globex]="globex:legacy/old-globex" + [portfolio]="muntianus-portfolio:legacy/portfolio" +) + +rm -rf "$WORKDIR"; mkdir -p "$WORKDIR" +declare -A CLONED=() + +clone_target(){ # $1 target repo + local t="$1" + [ -n "${CLONED[$t]:-}" ] && return 0 + if $APPLY; then + log "clone target $OWNER/$t" + git clone -q "$GH/$t.git" "$WORKDIR/$t" + ( cd "$WORKDIR/$t" && git checkout -q -b "collapse/absorb-archived-$(date +%Y%m%d)" ) + fi + CLONED[$t]=1 +} + +# ---- Phase 1: unarchive (optional; archived repos are still readable) ---- +for src in "${!MAP[@]}"; do + if $APPLY; then + log "unarchive $OWNER/$src" + gh repo unarchive "$OWNER/$src" --yes 2>/dev/null || warn "unarchive $src skipped" + else + printf ' PLAN unarchive %s\n' "$src" + fi +done + +# ---- Phase 2: subtree-merge each source into its target ---- +for src in "${!MAP[@]}"; do + IFS=: read -r trepo tdir <<<"${MAP[$src]}" + db="$(gh api "/repos/$OWNER/$src" --jq '.default_branch' 2>/dev/null || echo main)" + if $APPLY; then + clone_target "$trepo" + ( cd "$WORKDIR/$trepo" + if git cat-file -e "HEAD:$tdir" 2>/dev/null; then + warn "$trepo:$tdir already exists — skipping $src (resolve manually)" + else + log "subtree $src ($db) -> $trepo/$tdir" + git remote add "s-$src" "$GH/$src.git" 2>/dev/null || true + git fetch -q "s-$src" "$db" + git subtree add --prefix="$tdir" "s-$src" "$db" \ + -m "absorb archived $src into $tdir (history-preserved)" + git remote remove "s-$src" + fi ) + else + printf ' PLAN subtree %-32s -> %s/%s\n' "$src" "$trepo" "$tdir" + fi +done + +# ---- Phase 3: push branches + open draft PRs ---- +if $APPLY; then + for t in "${!CLONED[@]}"; do + ( cd "$WORKDIR/$t" + br="$(git rev-parse --abbrev-ref HEAD)" + git push -q -u origin "$br" + gh pr create --repo "$OWNER/$t" \ + --base "$(gh repo view "$OWNER/$t" --json defaultBranchRef --jq .defaultBranchRef.name)" \ + --head "$br" --draft \ + --title "absorb archived repos (history-preserved)" \ + --body "git-subtree merge of archived sources into this repo. History preserved. Review, then merge and run the delete phase." \ + || warn "PR for $t skipped (may exist)" ) + done + log "Pushed branches + opened draft PRs. REVIEW AND MERGE THEM before deleting." +fi + +# ---- Phase 4: DELETE sources (IRREVERSIBLE) ---- +if $DO_DELETE; then + [ "${CONFIRM_DELETE:-no}" = "yes" ] || die "delete requested but CONFIRM_DELETE!=yes" + $APPLY || die "delete requested without --apply" + warn "DELETING ${#MAP[@]} source repos. This also destroys their issues/releases/stars." + warn "Their CODE history is preserved inside the targets only if the PRs above are MERGED." + for src in "${!MAP[@]}"; do + log "delete $OWNER/$src (IRREVERSIBLE)" + gh repo delete "$OWNER/$src" --yes || warn "delete $src failed" + done +else + log "Delete phase NOT run. Re-run with --delete and CONFIRM_DELETE=yes AFTER merging the PRs." +fi +log "Done." From e1b12d9ea6b9ad5cfddeff74065130e6cbad640e Mon Sep 17 00:00:00 2001 From: Nikita Muntian Date: Sat, 30 May 2026 04:43:00 +0300 Subject: [PATCH 2/3] =?UTF-8?q?governance:=20collapse-archived.sh=20?= =?UTF-8?q?=E2=80=94=20fold=20all=20archived=20repos=20into=20perfops/arch?= =?UTF-8?q?ive//,=20leave=20sources=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/operations/collapse-archived.sh | 170 ++++++++------------- 1 file changed, 62 insertions(+), 108 deletions(-) diff --git a/governance/operations/collapse-archived.sh b/governance/operations/collapse-archived.sh index f0b413e..733b69e 100644 --- a/governance/operations/collapse-archived.sh +++ b/governance/operations/collapse-archived.sh @@ -1,24 +1,25 @@ #!/usr/bin/env bash -# Fold the 24 ARCHIVED repos into their canonical targets via git subtree -# (history-preserved), then optionally DELETE the sources. +# Unarchive all 24 archived repos and fold each into the perfops monorepo under +# archive// via git subtree (history-preserved). Sources are LEFT ACTIVE +# (unarchived) afterwards — no re-archive, no delete. # # Runs entirely with gh + git in YOUR shell (the MCP integration cannot -# unarchive, subtree-merge, or delete repos). Dry-run by default. +# unarchive or subtree-merge repos). Dry-run by default. # -# Phases (each gated): -# 1. unarchive sources (gh repo unarchive) --apply -# 2. subtree-merge into targets (git subtree add) --apply -# 3. push target branches + PRs (git push + gh pr create) --apply -# 4. DELETE sources (IRREVERSIBLE) CONFIRM_DELETE=yes + --apply +# Phases: +# 1. unarchive every source (gh repo unarchive) +# 2. git subtree add -> perfops/archive// +# 3. push branch + open one draft PR on perfops # # Usage: -# ./collapse-archived.sh # dry-run plan -# ./collapse-archived.sh --apply # phases 1-3 -# CONFIRM_DELETE=yes ./collapse-archived.sh --apply --delete +# ./collapse-archived.sh # dry-run plan +# ./collapse-archived.sh --apply # execute +# SEED=perfops-platform ./collapse-archived.sh --apply # if not renamed yet set -euo pipefail OWNER="${OWNER:-muntianus}" -APPLY=false; DO_DELETE=false -for a in "$@"; do case "$a" in --apply) APPLY=true;; --delete) DO_DELETE=true;; esac; done +SEED="${SEED:-perfops}" # the monorepo (formerly perfops-platform) +APPLY=false +for a in "$@"; do [ "$a" = "--apply" ] && APPLY=true; done GH="https://github.com/$OWNER" WORKDIR="${WORKDIR:-$PWD/.collapse-archived}" @@ -30,59 +31,18 @@ command -v git >/dev/null || die "git not found" gh auth status >/dev/null 2>&1 || die "run: gh auth login" $APPLY || log "DRY-RUN (default). Add --apply to execute." -# source-repo -> "targetRepo:subdir". Canonical mapping (see -# governance/reports/duplications.md). Sources whose content already lives in -# the perfops monorepo as live dirs are routed under archive/ to avoid -# subtree path conflicts; junk/no-canonical repos also go under archive/. -SEED="${SEED:-perfops}" # the renamed monorepo (formerly perfops-platform) -declare -A MAP=( - # --- already-in-monorepo originals -> archive/ (avoid live-dir conflicts) --- - [platform]="$SEED:archive/platform" - [perfops-gateway]="$SEED:archive/perfops-gateway" - [perfops-ingest]="$SEED:archive/perfops-ingest" - [reportkit]="$SEED:archive/reportkit" - [perfops-sdk]="$SEED:archive/perfops-sdk" - # --- testing / load PoCs --- - [krave-loadtest]="$SEED:testing/legacy/krave-loadtest" - [load-krave]="$SEED:testing/legacy/load-krave" - [k6-nagibator2000]="$SEED:testing/legacy/k6-nagibator2000" - [ragus-loader]="$SEED:testing/legacy/ragus-loader" - [perfops-templates]="$SEED:templates" - [iac-monitoring-agent]="$SEED:observability/iac-monitoring" - # --- agents --- - [claude-agents]="$SEED:agents/claude-agents" - [agent-control-office]="$SEED:agents/legacy/agent-control-office" - # --- office / misc / junk -> archive/ --- - [office]="$SEED:archive/office" - [office-workspace]="$SEED:archive/office-workspace" - [awesome-performance-engineering]="$SEED:archive/awesome-performance-engineering" - [pybot]="$SEED:archive/pybot" - [tools]="$SEED:archive/tools" - [automation-stack]="$SEED:archive/automation-stack" - [ig-unfollow]="$SEED:archive/ig-unfollow" - # --- separate canonical repos (not the monorepo) --- - [old-telegram-reminder]="telegram-reminder-simple:legacy/old-telegram-reminder" - [telegram-reminder]="telegram-reminder-simple:legacy/telegram-reminder" - [old-globex]="globex:legacy/old-globex" - [portfolio]="muntianus-portfolio:legacy/portfolio" +# All 24 archived repos. Each is folded into perfops/archive//. +ARCHIVED=( + platform perfops-gateway perfops-ingest reportkit perfops-sdk + perfops-templates iac-monitoring-agent awesome-performance-engineering + telegram-reminder old-telegram-reminder old-globex + office office-workspace agent-control-office portfolio + krave-loadtest load-krave k6-nagibator2000 ragus-loader + tools pybot automation-stack ig-unfollow claude-agents ) -rm -rf "$WORKDIR"; mkdir -p "$WORKDIR" -declare -A CLONED=() - -clone_target(){ # $1 target repo - local t="$1" - [ -n "${CLONED[$t]:-}" ] && return 0 - if $APPLY; then - log "clone target $OWNER/$t" - git clone -q "$GH/$t.git" "$WORKDIR/$t" - ( cd "$WORKDIR/$t" && git checkout -q -b "collapse/absorb-archived-$(date +%Y%m%d)" ) - fi - CLONED[$t]=1 -} - -# ---- Phase 1: unarchive (optional; archived repos are still readable) ---- -for src in "${!MAP[@]}"; do +# ---- Phase 1: unarchive (leave them active afterwards) ---- +for src in "${ARCHIVED[@]}"; do if $APPLY; then log "unarchive $OWNER/$src" gh repo unarchive "$OWNER/$src" --yes 2>/dev/null || warn "unarchive $src skipped" @@ -91,55 +51,49 @@ for src in "${!MAP[@]}"; do fi done -# ---- Phase 2: subtree-merge each source into its target ---- -for src in "${!MAP[@]}"; do - IFS=: read -r trepo tdir <<<"${MAP[$src]}" +# ---- Phase 2: subtree-merge each into perfops/archive// ---- +branch="collapse/absorb-archived-$(date +%Y%m%d)" +if $APPLY; then + rm -rf "$WORKDIR"; mkdir -p "$WORKDIR" + log "clone monorepo $OWNER/$SEED" + git clone -q "$GH/$SEED.git" "$WORKDIR/$SEED" + cd "$WORKDIR/$SEED" + git checkout -q -b "$branch" +fi + +for src in "${ARCHIVED[@]}"; do + dest="archive/$src" db="$(gh api "/repos/$OWNER/$src" --jq '.default_branch' 2>/dev/null || echo main)" - if $APPLY; then - clone_target "$trepo" - ( cd "$WORKDIR/$trepo" - if git cat-file -e "HEAD:$tdir" 2>/dev/null; then - warn "$trepo:$tdir already exists — skipping $src (resolve manually)" - else - log "subtree $src ($db) -> $trepo/$tdir" - git remote add "s-$src" "$GH/$src.git" 2>/dev/null || true - git fetch -q "s-$src" "$db" - git subtree add --prefix="$tdir" "s-$src" "$db" \ - -m "absorb archived $src into $tdir (history-preserved)" - git remote remove "s-$src" - fi ) + if ! $APPLY; then + printf ' PLAN subtree %-32s -> %s/%s (branch %s)\n' "$src" "$SEED" "$dest" "$db" + continue + fi + if git cat-file -e "HEAD:$dest" 2>/dev/null; then + warn "$dest already exists — skipping $src" + continue + fi + log "subtree $src ($db) -> $SEED/$dest" + git remote add "s-$src" "$GH/$src.git" 2>/dev/null || true + if git fetch -q "s-$src" "$db" 2>/dev/null; then + git subtree add --prefix="$dest" "s-$src" "$db" \ + -m "absorb archived $src into $dest (history-preserved)" else - printf ' PLAN subtree %-32s -> %s/%s\n' "$src" "$trepo" "$tdir" + warn "fetch $src@$db failed — skipping" fi + git remote remove "s-$src" 2>/dev/null || true done -# ---- Phase 3: push branches + open draft PRs ---- +# ---- Phase 3: push + open one draft PR ---- if $APPLY; then - for t in "${!CLONED[@]}"; do - ( cd "$WORKDIR/$t" - br="$(git rev-parse --abbrev-ref HEAD)" - git push -q -u origin "$br" - gh pr create --repo "$OWNER/$t" \ - --base "$(gh repo view "$OWNER/$t" --json defaultBranchRef --jq .defaultBranchRef.name)" \ - --head "$br" --draft \ - --title "absorb archived repos (history-preserved)" \ - --body "git-subtree merge of archived sources into this repo. History preserved. Review, then merge and run the delete phase." \ - || warn "PR for $t skipped (may exist)" ) - done - log "Pushed branches + opened draft PRs. REVIEW AND MERGE THEM before deleting." -fi - -# ---- Phase 4: DELETE sources (IRREVERSIBLE) ---- -if $DO_DELETE; then - [ "${CONFIRM_DELETE:-no}" = "yes" ] || die "delete requested but CONFIRM_DELETE!=yes" - $APPLY || die "delete requested without --apply" - warn "DELETING ${#MAP[@]} source repos. This also destroys their issues/releases/stars." - warn "Their CODE history is preserved inside the targets only if the PRs above are MERGED." - for src in "${!MAP[@]}"; do - log "delete $OWNER/$src (IRREVERSIBLE)" - gh repo delete "$OWNER/$src" --yes || warn "delete $src failed" - done + git push -q -u origin "$branch" + gh pr create --repo "$OWNER/$SEED" \ + --base "$(gh repo view "$OWNER/$SEED" --json defaultBranchRef --jq .defaultBranchRef.name)" \ + --head "$branch" --draft \ + --title "absorb all archived repos into archive/ (history-preserved)" \ + --body "git-subtree merge of all 24 archived repos into archive//. Full history preserved. Sources were unarchived and left active per request." \ + || warn "PR create skipped (may already exist)" + log "Done. Sources were unarchived and LEFT ACTIVE (no re-archive, no delete)." + log "Review and merge the draft PR on $OWNER/$SEED." else - log "Delete phase NOT run. Re-run with --delete and CONFIRM_DELETE=yes AFTER merging the PRs." + log "DRY-RUN complete. Re-run with --apply to execute." fi -log "Done." From 3c3515d8ddf66784b4a035b38d9a3a2f26649523 Mon Sep 17 00:00:00 2001 From: Nikita Muntian Date: Sat, 30 May 2026 12:28:14 +0300 Subject: [PATCH 3/3] =?UTF-8?q?governance:=20harden=20collapse-archived.sh?= =?UTF-8?q?=20=E2=80=94=20per-repo=20failure=20isolation=20+=20local=20bas?= =?UTF-8?q?e-branch=20resolve=20(review)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- governance/operations/collapse-archived.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/governance/operations/collapse-archived.sh b/governance/operations/collapse-archived.sh index 733b69e..d3bc05c 100644 --- a/governance/operations/collapse-archived.sh +++ b/governance/operations/collapse-archived.sh @@ -74,11 +74,13 @@ for src in "${ARCHIVED[@]}"; do fi log "subtree $src ($db) -> $SEED/$dest" git remote add "s-$src" "$GH/$src.git" 2>/dev/null || true - if git fetch -q "s-$src" "$db" 2>/dev/null; then - git subtree add --prefix="$dest" "s-$src" "$db" \ - -m "absorb archived $src into $dest (history-preserved)" + # One failed source must not abort the whole run; record and continue. + if git fetch -q "s-$src" "$db" 2>/dev/null && + git subtree add --prefix="$dest" "s-$src" "$db" \ + -m "absorb archived $src into $dest (history-preserved)"; then + : else - warn "fetch $src@$db failed — skipping" + warn "subtree-merge $src@$db failed — skipping (other repos continue)" fi git remote remove "s-$src" 2>/dev/null || true done @@ -86,8 +88,10 @@ done # ---- Phase 3: push + open one draft PR ---- if $APPLY; then git push -q -u origin "$branch" + # Resolve base branch locally (no extra API round-trip). + base_branch="$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | cut -d/ -f2-)" gh pr create --repo "$OWNER/$SEED" \ - --base "$(gh repo view "$OWNER/$SEED" --json defaultBranchRef --jq .defaultBranchRef.name)" \ + --base "${base_branch:-main}" \ --head "$branch" --draft \ --title "absorb all archived repos into archive/ (history-preserved)" \ --body "git-subtree merge of all 24 archived repos into archive//. Full history preserved. Sources were unarchived and left active per request." \