v1.13.0.0 feat: add Claude outside-voice skill (#1212)

* Add Claude outside-voice skill

* Fix gbrain config isolation test

* Restore Opus fanout overlay nudge

* Warn on oversized tracked files

* Release v1.13.0.0

* Fix Claude diff temp file handling

* Remove Opus fanout overlay nudge
This commit is contained in:
Garry Tan
2026-04-25 11:52:48 -07:00
committed by GitHub
parent 6209163900
commit 23c4d7b228
10 changed files with 450 additions and 31 deletions

View File

@@ -1468,12 +1468,16 @@ describe('Codex skill validation', () => {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
});
// Discover all Claude skills with templates (except /codex which is Claude-only)
// Discover all shared skills with templates.
// Host-exclusive outside-voice skills are intentionally omitted here:
// - /codex is Claude-only
// - /claude is external-host-only
const CLAUDE_SKILLS_WITH_TEMPLATES = (() => {
const skills: string[] = [];
for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
if (entry.name === 'codex') continue; // Claude-only skill
if (entry.name === 'claude') continue; // External-host-only skill
if (fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'))) {
skills.push(entry.name);
}
@@ -1504,6 +1508,13 @@ describe('Codex skill validation', () => {
expect(fs.existsSync(path.join(AGENTS_DIR, 'gstack-codex', 'SKILL.md'))).toBe(false);
});
test('/claude skill is external-host-only — no Claude-host variant', () => {
// Claude host should not get an outside-voice skill that shells into Claude.
expect(fs.existsSync(path.join(ROOT, 'claude', 'SKILL.md'))).toBe(false);
// Codex/external hosts should get the generated wrapper.
expect(fs.existsSync(path.join(AGENTS_DIR, 'gstack-claude', 'SKILL.md'))).toBe(true);
});
test('Codex skill names follow gstack-{name} convention', () => {
const codexDirs = fs.readdirSync(AGENTS_DIR);
for (const dir of codexDirs) {
@@ -1631,18 +1642,31 @@ describe('no compiled binaries in git', () => {
expect(binaries).toEqual([]);
});
test('git tracks no files larger than 2MB', () => {
// Pure fs.statSync — no shell spawn per file.
test('warns about tracked files larger than 2MB', () => {
// Large fixtures can be legitimate test infrastructure. Keep visibility on
// repository size without blocking those fixtures from living in git.
const MAX_BYTES = 2 * 1024 * 1024;
const oversized = trackedFiles.filter((f: string) => {
const oversized = trackedFiles.flatMap((f: string) => {
const full = path.join(ROOT, f);
try {
return fs.statSync(full).size > MAX_BYTES;
const size = fs.statSync(full).size;
return size > MAX_BYTES ? [{ file: f, size }] : [];
} catch {
return false;
return [];
}
});
expect(oversized).toEqual([]);
if (oversized.length > 0) {
const formatted = oversized
.map(({ file, size }: { file: string; size: number }) => {
const mib = (size / (1024 * 1024)).toFixed(1);
return `${file} (${mib} MiB)`;
})
.join(', ');
console.warn(`[size-warning] tracked files over 2 MiB: ${formatted}`);
}
expect(Array.isArray(oversized)).toBe(true);
});
});