mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-08 22:35:36 +02:00
b0e0a76dca
Three new regression tests guard the rename's blast radius (per codex Findings #1, #8, #9, #12): - test/no-stale-gstack-brain-refs.test.ts: greps bin/, scripts/, *.tmpl, test/ for forbidden identifiers (gstack-brain-init, gbrain_sync_mode); fails CI if any non-allowlisted file references them. - test/post-rename-doc-regen.test.ts: confirms gen-skill-docs output has no stale references in any */SKILL.md (the cross-product blind spot). - test/setup-gbrain-path4-structure.test.ts: structural lint over the Path 4 prose contract — STOP gates after verify failure, never-write- token rules, mode-aware CLAUDE.md block, bearer always via env-var. Two new gate-tier E2E tests (deterministic stub HTTP server, fixed inputs): - test/skill-e2e-setup-gbrain-remote.test.ts: Path 4 happy path. Stubs an HTTP MCP server, drives the skill via Agent SDK with a stubbed bearer, asserts claude.json gets the http MCP entry, CLAUDE.md gets the remote-http block, the secret token NEVER leaks to CLAUDE.md. - test/skill-e2e-setup-gbrain-bad-token.test.ts: stub server returns 401; asserts the AUTH classifier hint surfaces, no MCP registration occurs, CLAUDE.md is unchanged. Regression guard for the "verify failed → STOP" rule. touchfiles.ts: setup-gbrain-remote and setup-gbrain-bad-token added at gate-tier so CI catches Path 4 regressions on every PR. Plus a few comment refs flipped: bin/gstack-jsonl-merge, bin/gstack-timeline-log (legacy gstack-brain-init mentions in headers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
121 lines
5.0 KiB
TypeScript
121 lines
5.0 KiB
TypeScript
/**
|
|
* Regression: no stale `gstack-brain-init`, `gbrain_sync_mode`, or
|
|
* `~/.gstack-brain-remote.txt` references survive the v1.27.0.0 rename.
|
|
*
|
|
* Per codex Findings #1 + #8 + #9: the rename's blast radius is wider than
|
|
* the obvious bin/ + scripts/ surface. This test grep-scans the broader
|
|
* tree (bin, scripts, *.tmpl, generated *.md, test/, docs/) for the
|
|
* deprecated identifiers and fails CI if any callers were missed.
|
|
*
|
|
* Allowlist: the migration script (`gstack-upgrade/migrations/v1.27.0.0.sh`)
|
|
* legitimately references the old names — it's the rename actor itself.
|
|
* Old migration scripts (v1.17.0.0.sh and similar) reference the old names
|
|
* for their own historical context and are also allowlisted.
|
|
*
|
|
* The test is mechanical: if you find yourself adding a non-historical
|
|
* file to the allowlist, you probably need to actually fix the rename
|
|
* instead.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { spawnSync } from 'child_process';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
|
|
const ALLOWLIST = [
|
|
// The migration script that performs the rename. Self-references are expected.
|
|
'gstack-upgrade/migrations/v1.27.0.0.sh',
|
|
// Older migration scripts — historical references; these document past state.
|
|
'gstack-upgrade/migrations/v1.17.0.0.sh',
|
|
// The migration test itself — it asserts on the migration's behavior.
|
|
'test/migrations-v1.27.0.0.test.ts',
|
|
// The test for the v1.17.0.0 historical migration.
|
|
'test/gstack-upgrade-migration-v1_17_0_0.test.ts',
|
|
// CHANGELOG entries describe historical state by their nature.
|
|
'CHANGELOG.md',
|
|
// TODOS may reference past or future states by name.
|
|
'TODOS.md',
|
|
// The plan file for v1.27.0.0 documents why we're renaming.
|
|
'.context/plans/setup-gbrain-remote-mcp-rename-brain-artifacts.md',
|
|
// The bin/gstack-config comment explicitly preserves the rename note.
|
|
'bin/gstack-config',
|
|
// Detect script's "renamed in v1.27.0.0" comment + brain-remote-fallback path.
|
|
'bin/gstack-gbrain-detect',
|
|
// brain-restore + source-wireup keep the old file as a migration-window fallback
|
|
// (read both, prefer artifacts). brain-uninstall has the same fallback.
|
|
'bin/gstack-brain-restore',
|
|
'bin/gstack-gbrain-source-wireup',
|
|
'bin/gstack-brain-uninstall',
|
|
// The preamble resolver reads the legacy file as a fallback during the
|
|
// migration window — same pattern.
|
|
'scripts/resolvers/preamble/generate-brain-sync-block.ts',
|
|
// gstack-upgrade.test.ts may exercise old migration behavior.
|
|
'test/gstack-upgrade.test.ts',
|
|
// This test itself references the patterns to grep for.
|
|
'test/no-stale-gstack-brain-refs.test.ts',
|
|
// memory.md documents the rename context.
|
|
'setup-gbrain/memory.md',
|
|
// The new init script's header comment intentionally cites the rename.
|
|
'bin/gstack-artifacts-init',
|
|
// The replacement test mirrors the pattern of the old test (lineage note).
|
|
'test/gstack-artifacts-init.test.ts',
|
|
// The post-rename-doc-regen test references the patterns it greps for.
|
|
'test/post-rename-doc-regen.test.ts',
|
|
// The Path 4 structural lint references some legacy names in comments.
|
|
'test/setup-gbrain-path4-structure.test.ts',
|
|
// Generated docs that include the preamble bash (which has the fallback).
|
|
// We grep template sources, not generated output, by limiting scan paths.
|
|
];
|
|
|
|
const FORBIDDEN_PATTERNS = [
|
|
'gstack-brain-init',
|
|
'gbrain_sync_mode',
|
|
];
|
|
|
|
const SCAN_PATHS = [
|
|
'bin/',
|
|
'scripts/',
|
|
'setup-gbrain/SKILL.md.tmpl',
|
|
'sync-gbrain/SKILL.md.tmpl',
|
|
'health/SKILL.md.tmpl',
|
|
'plan-eng-review/SKILL.md.tmpl',
|
|
'plan-ceo-review/SKILL.md.tmpl',
|
|
'review/SKILL.md.tmpl',
|
|
'ship/SKILL.md.tmpl',
|
|
'test/',
|
|
];
|
|
|
|
function grepRefs(pattern: string): string[] {
|
|
const args = ['-rn', '--', pattern, ...SCAN_PATHS.map((p) => path.join(ROOT, p))];
|
|
const r = spawnSync('grep', args, { encoding: 'utf-8' });
|
|
// grep exits 1 when no matches — that's fine for our purposes.
|
|
const lines = (r.stdout || '').split('\n').filter((l) => l.trim().length > 0);
|
|
return lines
|
|
.map((line) => {
|
|
// Strip ROOT prefix to get repo-relative path.
|
|
const colon = line.indexOf(':');
|
|
const file = line.slice(0, colon);
|
|
return path.relative(ROOT, file);
|
|
})
|
|
.filter((file) => !ALLOWLIST.includes(file))
|
|
// Filter out any file that's inside a directory we don't actually scan.
|
|
.filter((file) => !file.startsWith('node_modules/') && !file.startsWith('.git/'));
|
|
}
|
|
|
|
describe('no stale gstack-brain refs (v1.27.0.0 rename)', () => {
|
|
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
test(`no non-allowlisted references to "${pattern}"`, () => {
|
|
const offenders = [...new Set(grepRefs(pattern))];
|
|
if (offenders.length > 0) {
|
|
console.error(`Found stale "${pattern}" references in:\n${offenders.map((f) => ` - ${f}`).join('\n')}`);
|
|
console.error(
|
|
`If a file is intentionally referencing the old name (migration, historical doc, fallback path), add it to ALLOWLIST in this test.`
|
|
);
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
}
|
|
});
|