mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 07:18:03 +01:00
* proactive core: the CLAUDE.md offer and descriptions follow core; core skills carry their trigger phrases (#3446, #2118) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (core routing line keeps in-skill handoffs) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep document-release's pre-merge cue and the ship catalog-mode pin after the description edits Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: plain sentence break in the fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
72 lines
2.9 KiB
TypeScript
72 lines
2.9 KiB
TypeScript
/** Catalog mode is a CLI contract: default/explicit trim and both full flag
|
|
* forms must produce the intended frontmatter, with no worktree writes. */
|
|
import { beforeAll, describe, expect, test } from 'bun:test';
|
|
import { spawnSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const SHIP_SKILL = path.join(ROOT, 'ship', 'SKILL.md');
|
|
|
|
function render(args: string[]) {
|
|
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-catalog-mode-'));
|
|
const before = fs.readFileSync(SHIP_SKILL, 'utf-8');
|
|
try {
|
|
const result = spawnSync('bun', ['run', 'gen:skill-docs', ...args, '--out-dir', outDir], {
|
|
cwd: ROOT, encoding: 'utf-8', timeout: 60_000,
|
|
});
|
|
const output = path.join(outDir, 'ship', 'SKILL.md');
|
|
const content = fs.existsSync(output) ? fs.readFileSync(output, 'utf-8') : '';
|
|
expect(fs.readFileSync(SHIP_SKILL, 'utf-8')).toBe(before);
|
|
return { status: result.status, stderr: result.stderr, content, files: fs.readdirSync(outDir) };
|
|
} finally {
|
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function frontmatter(content: string): string {
|
|
return content.slice(0, content.indexOf('\n---', 4));
|
|
}
|
|
|
|
describe('catalog mode CLI behavior', () => {
|
|
let defaultContent: string;
|
|
beforeAll(() => {
|
|
const result = render([]);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
defaultContent = result.content;
|
|
});
|
|
|
|
test('omitting the flag defaults to trim', () => {
|
|
expect(frontmatter(defaultContent)).toMatch(/^description: "?Ship it:[^\n]*\(gstack\)"?$/m);
|
|
expect(frontmatter(defaultContent)).not.toMatch(/Use when asked to/i);
|
|
expect(defaultContent).not.toContain('## When to invoke this skill');
|
|
});
|
|
|
|
for (const form of ['equals', 'separate'] as const) {
|
|
const args = (value: string) => form === 'equals' ? [`--catalog-mode=${value}`] : ['--catalog-mode', value];
|
|
|
|
test(`${form} flag form accepts full and preserves routing prose in frontmatter`, () => {
|
|
const result = render(args('full'));
|
|
expect(result.status, result.stderr).toBe(0);
|
|
const fm = frontmatter(result.content);
|
|
expect(fm).toMatch(/^description: \|\s*$/m);
|
|
expect(fm).toMatch(/Use when asked to/i);
|
|
expect(fm).not.toBe(frontmatter(defaultContent));
|
|
});
|
|
|
|
test(`${form} flag form accepts explicit trim and matches the default catalog`, () => {
|
|
const result = render(args('trim'));
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(frontmatter(result.content)).toBe(frontmatter(defaultContent));
|
|
});
|
|
|
|
test(`${form} flag form rejects invalid modes before writing output`, () => {
|
|
const result = render(args('invalid'));
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain('Unknown catalog mode: invalid');
|
|
expect(result.files).toEqual([]);
|
|
});
|
|
}
|
|
});
|