chore(migration): per-repo gitignore/gitleaks/mailmap re-rooting

This commit is contained in:
Kjell Tore Guttormsen 2026-06-17 12:42:06 +02:00
commit b59240ad32
3 changed files with 135 additions and 0 deletions

View file

@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Step 4 — Per-repo config re-rooting (.gitignore, .gitleaks.toml, .gitleaksignore, .mailmap).
# Operates on an extracted repo in $WORK/<key>: the monorepo's root config does not travel with
# filter-repo's --path (it lives at the monorepo root, F6), so we regenerate it per-repo:
# - .gitignore : re-rooted from templates/gitignore.plugin.tmpl (plugins/*/ prefixes dropped; the
# global STATE.md/*.local.md/OS patterns kept — C3).
# - .gitleaks.toml : the root baseline, retitled per plugin (its allowlist path is already plugin-relative).
# - .gitleaksignore : the matching false-positive fingerprint(s) from the root file, re-rooted by dropping
# ONLY the `plugins/<key>/` path prefix and keeping the full `:rule-id:line` suffix (M7).
# Plugins with no fingerprint get NO .gitleaksignore.
# - .mailmap : copied verbatim (F6 — it does not travel with --path).
# For the design-system target it also copies sync-design-system.mjs + test (Step 2) and
# PLAYGROUND-MAINTENANCE.md into the repo root (playground-examples/ already travels via --path).
# Idempotent. NULL push (D8) — operates only inside $WORK/<key>.
#
# Usage: 20-rehome-config.sh <target-key>
set -euo pipefail
KEY="${1:-}"
[ -n "$KEY" ] || { echo "usage: 20-rehome-config.sh <target-key>" >&2; exit 2; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
TEMPLATE="$SCRIPT_DIR/templates/gitignore.plugin.tmpl"
WORK="${WORK:-/tmp/polyrepo-migration}"
DEST="$WORK/$KEY"
fail() { printf 'REHOME FAIL: %s\n' "$*" >&2; exit 1; }
# Self-heal: extract the target first if it is not present.
if [ ! -d "$DEST/.git" ]; then
echo " $KEY not extracted → running 10-extract.sh"
WORK="$WORK" bash "$SCRIPT_DIR/10-extract.sh" "$KEY" >/dev/null
fi
[ -d "$DEST/.git" ] || fail "extract missing after self-heal: $DEST"
[ -f "$TEMPLATE" ] || fail "gitignore template missing: $TEMPLATE"
# 1) .gitignore from the re-rooted template
cp "$TEMPLATE" "$DEST/.gitignore"
# 2) .gitleaks.toml from the root baseline, retitled for the standalone repo
if [ -f "$REPO_ROOT/.gitleaks.toml" ]; then
sed "s/^title = .*/title = \"$KEY gitleaks config\"/" "$REPO_ROOT/.gitleaks.toml" > "$DEST/.gitleaks.toml"
fi
# 3) .gitleaksignore — re-rooted fingerprints for this plugin (drop only the plugins/<key>/ prefix, M7)
rm -f "$DEST/.gitleaksignore"
if [ -f "$REPO_ROOT/.gitleaksignore" ]; then
MATCHES="$(grep "^plugins/$KEY/" "$REPO_ROOT/.gitleaksignore" || true)"
if [ -n "$MATCHES" ]; then
{
echo "# Re-rooted false-positive fingerprints (from the monorepo .gitleaksignore)"
printf '%s\n' "$MATCHES" | sed "s#^plugins/$KEY/##"
} > "$DEST/.gitleaksignore"
fi
fi
# 4) .mailmap copied verbatim
[ -f "$REPO_ROOT/.mailmap" ] && cp "$REPO_ROOT/.mailmap" "$DEST/.mailmap"
# 5) Design-system target: bring in the sync script + maintenance doc (§9 Q4)
if [ "$KEY" = "playground-design-system" ]; then
mkdir -p "$DEST/scripts"
cp "$REPO_ROOT/scripts/sync-design-system.mjs" "$DEST/scripts/sync-design-system.mjs"
cp "$REPO_ROOT/scripts/sync-design-system.test.mjs" "$DEST/scripts/sync-design-system.test.mjs"
[ -f "$REPO_ROOT/shared/PLAYGROUND-MAINTENANCE.md" ] && \
cp "$REPO_ROOT/shared/PLAYGROUND-MAINTENANCE.md" "$DEST/PLAYGROUND-MAINTENANCE.md"
fi
GLI_STATE="absent"
[ -f "$DEST/.gitleaksignore" ] && GLI_STATE="$(wc -l < "$DEST/.gitleaksignore" | tr -d ' ') line(s)"
echo "REHOME OK $KEY → .gitignore + .gitleaks.toml + .mailmap; .gitleaksignore: $GLI_STATE"

View file

@ -0,0 +1,47 @@
// Step 4 test — re-rooted .gitignore, M7 gitleaks fingerprint, .mailmap copy, no-fingerprint plugin.
// Pattern: plugins/config-audit/tests/lib/*.test.mjs. Runs against freshly extracted repos in $WORK.
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const SCRIPT = path.join(here, '20-rehome-config.sh');
const WORK = process.env.WORK || '/tmp/polyrepo-migration';
function rehome(key) {
const r = spawnSync('bash', [SCRIPT, key], { encoding: 'utf8', env: { ...process.env, WORK } });
if (r.status !== 0) throw new Error(`rehome ${key} failed (${r.status}):\n${r.stdout}\n${r.stderr}`);
return path.join(WORK, key);
}
const llm = rehome('llm-security');
const gh = rehome('graceful-handoff');
test('re-rooted .gitignore ignores .claude/ and carries no plugins/ prefix', () => {
const gi = readFileSync(path.join(llm, '.gitignore'), 'utf8');
assert.match(gi, /^\.claude\/$/m, '.gitignore should ignore a root-level .claude/');
assert.ok(!gi.includes('plugins/'), '.gitignore must not contain a plugins/ prefix');
});
test('llm-security .gitleaksignore fingerprint is re-rooted with rule-id retained (M7)', () => {
const gli = readFileSync(path.join(llm, '.gitleaksignore'), 'utf8');
assert.match(
gli,
/^examples\/malicious-skill-demo\/evil-project-health\/lib\/telemetry\.mjs:generic-api-key:18$/m,
'fingerprint must be the exact re-rooted path:rule-id:line'
);
assert.ok(!gli.includes('plugins/llm-security/'), 'the plugins/llm-security/ prefix must be dropped');
assert.ok(gli.includes(':generic-api-key:'), 'the rule-id must be retained (else the suppression breaks)');
});
test('.mailmap is copied into the extract', () => {
assert.ok(existsSync(path.join(llm, '.mailmap')), '.mailmap should exist in the extract');
});
test('a plugin with no fingerprint (graceful-handoff) gets .gitleaks.toml but no .gitleaksignore', () => {
assert.ok(existsSync(path.join(gh, '.gitleaks.toml')), 'graceful-handoff should have .gitleaks.toml');
assert.ok(!existsSync(path.join(gh, '.gitleaksignore')), 'graceful-handoff should have no .gitleaksignore');
});

View file

@ -0,0 +1,16 @@
# Session state files (local only, not tracked)
REMEMBER.md
TODO.md
ROADMAP.md
STATE.md
*.local.md
# Session directories (plans, research, execution progress)
.claude/
# Session-generated reports (not release artifacts)
reports/*-beskrivelse.*
# OS files
.DS_Store
Thumbs.db