fix(brain-allowlist): sync project-root eng-review-test-plan artifacts (#1452)

Cherry-picked from #1465 by genisis0x and extended with the v1.40.0.0
upgrade migration that codex review #5 surfaced.

#1465 alone only patches bin/gstack-artifacts-init, which means fresh
installs and re-inits pick up the new pattern. But existing users who
already ran v1.38.1.0 have a `.migrations/v1.38.1.0.done` marker — that
migration won't re-run no matter what we change. So their installed
`.brain-allowlist`, `.brain-privacy-map.json`, and `.gitattributes` stay
without the new pattern, and `/plan-eng-review` artifacts continue to
silently drop out of their federation queue.

This commit:

- bin/gstack-artifacts-init: adds projects/*/*-eng-review-test-plan-*.md
  to the three managed blocks. v1.38.1.0 covered design + test-plan; this
  completes the set for /plan-eng-review.

- gstack-upgrade/migrations/v1.40.0.0.sh: targeted in-place repair for
  existing installs. Same idempotent jq-based shape as v1.38.1.0. Adds
  the new pattern to .brain-allowlist (before the USER ADDITIONS marker),
  .brain-privacy-map.json (as class=artifact), and .gitattributes (as
  merge=union). NEVER commits + pushes — the user controls when the
  patches ship to their federated artifacts repo.

- test/artifacts-init-migration.test.ts: 5 new tests covering the
  v1.40.0.0 migration applied on top of a post-v1.38.1.0 state, jq
  patching, gitattributes append, idempotent re-run, and done-marker
  write when files are missing entirely.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
genisis0x
2026-05-13 14:31:29 +05:30
committed by Garry Tan
parent 62ff90cfc9
commit d0463e59ed
3 changed files with 238 additions and 0 deletions
+130
View File
@@ -201,3 +201,133 @@ describe('v1.38.1.0 migration', () => {
}
});
});
// ──────────────────────────────────────────────────────────────────────────
// v1.40.0.0 — `projects/*/*-eng-review-test-plan-*.md` follow-on for #1452.
// v1.38.1.0 shipped the design + test-plan patterns but missed
// /plan-eng-review's filename. Codex review #5 flagged that
// v1.38.1.0's done-marker prevents users who already upgraded from picking
// up #1465's allowlist edit, so v1.40.0.0 needs its own migration.
// ──────────────────────────────────────────────────────────────────────────
const MIGRATION_V1_40 = join(REPO_ROOT, 'gstack-upgrade', 'migrations', 'v1.40.0.0.sh');
function runMigrationV140(fakeHome: string): { code: number; stdout: string; stderr: string } {
const proc = Bun.spawnSync({
cmd: ['bash', MIGRATION_V1_40],
env: { ...process.env, HOME: fakeHome },
stdout: 'pipe',
stderr: 'pipe',
});
return {
code: proc.exitCode ?? -1,
stdout: new TextDecoder().decode(proc.stdout),
stderr: new TextDecoder().decode(proc.stderr),
};
}
describe('v1.40.0.0 migration', () => {
test('adds eng-review-test-plan pattern to allowlist on top of an installed v1.38.1.0 state', () => {
const home = setupFakeHome();
try {
// Simulate post-v1.38.1.0 state: design + test-plan patterns present,
// done-marker set so the v1.38.1.0 migration wouldn't re-run.
mkdirSync(join(home, '.gstack', '.migrations'), { recursive: true });
writeFileSync(join(home, '.gstack', '.migrations', 'v1.38.1.0.done'), '');
writeFileSync(join(home, '.gstack', '.brain-allowlist'), [
'projects/*/learnings.jsonl',
'projects/*/designs/*.md',
'projects/*/*-design-*.md',
'projects/*/*-test-plan-*.md',
'# ---- USER ADDITIONS BELOW ---- (survives re-init; above is managed)',
'projects/*/my-custom.txt',
].join('\n') + '\n');
const r = runMigrationV140(home);
expect(r.code).toBe(0);
const content = readFileSync(join(home, '.gstack', '.brain-allowlist'), 'utf-8');
expect(content).toContain('projects/*/*-eng-review-test-plan-*.md');
// New pattern above the user marker.
const engRevIdx = content.indexOf('projects/*/*-eng-review-test-plan-*.md');
const markerIdx = content.indexOf('# ---- USER ADDITIONS BELOW');
expect(engRevIdx).toBeLessThan(markerIdx);
// User customizations below the marker preserved.
expect(content).toContain('projects/*/my-custom.txt');
// v1.40.0.0 done-marker created.
expect(existsSync(join(home, '.gstack', '.migrations', 'v1.40.0.0.done'))).toBe(true);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('adds eng-review-test-plan entry to privacy-map.json via jq', () => {
const home = setupFakeHome();
try {
writeFileSync(join(home, '.gstack', '.brain-privacy-map.json'), JSON.stringify([
{ pattern: 'projects/*/*-design-*.md', class: 'artifact' },
{ pattern: 'projects/*/*-test-plan-*.md', class: 'artifact' },
], null, 2));
const r = runMigrationV140(home);
expect(r.code).toBe(0);
const parsed = JSON.parse(readFileSync(join(home, '.gstack', '.brain-privacy-map.json'), 'utf-8'));
const patterns = parsed.map((e: any) => e.pattern);
expect(patterns).toContain('projects/*/*-eng-review-test-plan-*.md');
expect(parsed.find((e: any) => e.pattern === 'projects/*/*-eng-review-test-plan-*.md').class).toBe('artifact');
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('adds union-merge rule to gitattributes', () => {
const home = setupFakeHome();
try {
writeFileSync(join(home, '.gstack', '.gitattributes'), [
'projects/*/*-design-*.md merge=union',
'projects/*/*-test-plan-*.md merge=union',
].join('\n') + '\n');
const r = runMigrationV140(home);
expect(r.code).toBe(0);
const content = readFileSync(join(home, '.gstack', '.gitattributes'), 'utf-8');
expect(content).toContain('projects/*/*-eng-review-test-plan-*.md merge=union');
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('is idempotent: re-running is a no-op', () => {
const home = setupFakeHome();
try {
writeFileSync(join(home, '.gstack', '.brain-allowlist'),
'projects/*/*-eng-review-test-plan-*.md\n# ---- USER ADDITIONS BELOW ---- (survives re-init; above is managed)\n');
const r1 = runMigrationV140(home);
expect(r1.code).toBe(0);
const r2 = runMigrationV140(home);
expect(r2.code).toBe(0);
const content = readFileSync(join(home, '.gstack', '.brain-allowlist'), 'utf-8');
const occurrences = content.match(/projects\/\*\/\*-eng-review-test-plan-\*\.md/g) || [];
expect(occurrences.length).toBe(1);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('writes done-marker even when files are missing', () => {
const home = setupFakeHome();
try {
// No allowlist / privacy-map / gitattributes — fresh-init users with
// no federated artifacts yet. Migration should still mark itself done.
const r = runMigrationV140(home);
expect(r.code).toBe(0);
expect(existsSync(join(home, '.gstack', '.migrations', 'v1.40.0.0.done'))).toBe(true);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
});