feat: AI Code Governance CLI v1.0.0
- Config-driven gates via .codegov.yml/.codegov.json - disclosure gate: requires AI-GENERATED/AI-ASSISTED markers on changed files - rfc gate: requires issue/RFC reference in commit message or PR description - checklist gate: requires all items checked in REQUIREMENTS.md - quality gate: runs configurable lint/test/typecheck commands - CLI with readable reports and non-zero exit on violations - 41 tests covering all gates, config loading, and integration - Zero skeletons, no fake features, deferred items honestly documented
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { loadConfig, findConfigFile } from '../src/config';
|
||||
|
||||
const TMP = resolve(__dirname, 'tmp-config');
|
||||
|
||||
function clean() {
|
||||
try {
|
||||
rmSync(TMP, { recursive: true, force: true });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
describe('config', () => {
|
||||
beforeEach(() => {
|
||||
clean();
|
||||
mkdirSync(TMP, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clean();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('finds .codegov.yml', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.yml'), 'enabled: true\n');
|
||||
expect(findConfigFile(TMP)).toBe(resolve(TMP, '.codegov.yml'));
|
||||
});
|
||||
|
||||
it('finds .codegov.json', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.json'), '{"enabled":true}');
|
||||
expect(findConfigFile(TMP)).toBe(resolve(TMP, '.codegov.json'));
|
||||
});
|
||||
|
||||
it('returns null when no config exists', () => {
|
||||
expect(findConfigFile(TMP)).toBeNull();
|
||||
});
|
||||
|
||||
it('loads JSON config', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.json'), JSON.stringify({ enabled: true, gates: { rfc: { required: false } } }));
|
||||
const { config, usingDefaults } = loadConfig(undefined, TMP);
|
||||
expect(config.enabled).toBe(true);
|
||||
expect(config.gates.rfc?.required).toBe(false);
|
||||
expect(usingDefaults).toBe(false);
|
||||
});
|
||||
|
||||
it('loads YAML config', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.yml'), 'enabled: true\ngates:\n disclosure:\n required: false\n');
|
||||
const { config, usingDefaults } = loadConfig(undefined, TMP);
|
||||
expect(config.enabled).toBe(true);
|
||||
expect(config.gates.disclosure?.required).toBe(false);
|
||||
expect(usingDefaults).toBe(false);
|
||||
});
|
||||
|
||||
it('uses defaults when no config exists', () => {
|
||||
const { config, usingDefaults } = loadConfig(undefined, TMP);
|
||||
expect(config.enabled).toBe(true);
|
||||
expect(config.gates.disclosure?.required).toBe(true);
|
||||
expect(usingDefaults).toBe(true);
|
||||
});
|
||||
|
||||
it('merges partial config with defaults', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.json'), JSON.stringify({ enabled: false }));
|
||||
const { config } = loadConfig(undefined, TMP);
|
||||
expect(config.enabled).toBe(false);
|
||||
expect(config.gates.checklist?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('throws on missing explicit config path', () => {
|
||||
expect(() => loadConfig('/nonexistent/.codegov.yml', TMP)).toThrow('Config file not found');
|
||||
});
|
||||
|
||||
it('throws on invalid config', () => {
|
||||
writeFileSync(resolve(TMP, '.codegov.json'), 'not-json');
|
||||
expect(() => loadConfig(undefined, TMP)).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user