mirror of https://github.com/garrytan/gstack.git
98 lines
4.1 KiB
TypeScript
98 lines
4.1 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
|
|
import {
|
|
generateLensEarlyCommands,
|
|
generateLensPrepare,
|
|
generateLensLayer,
|
|
generateLensDisposition,
|
|
} from '../scripts/resolvers/lens-layer';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const ctx: TemplateContext = {
|
|
skillName: 'review',
|
|
tmplPath: path.join(ROOT, 'review', 'SKILL.md.tmpl'),
|
|
host: 'claude',
|
|
paths: HOST_PATHS.claude,
|
|
};
|
|
|
|
describe('stakeholder lens resolver', () => {
|
|
test('list and describe short-circuit before preamble', () => {
|
|
const content = generateLensEarlyCommands(ctx);
|
|
expect(content).toContain('Do not run the preamble');
|
|
expect(content).toContain('--lens list');
|
|
expect(content).toContain('--lens describe');
|
|
});
|
|
|
|
test('plain review checks mandatory policy and otherwise stays unchanged', () => {
|
|
const content = generateLensPrepare(ctx);
|
|
expect(content).toContain('--mode <mode>');
|
|
expect(content).toContain('No lens flag: `mandatory`');
|
|
expect(content).toContain('LENS_MODE=off');
|
|
expect(content).toContain('--no-mandatory-lenses');
|
|
});
|
|
|
|
test('independent Stage A excludes technical and peer findings', () => {
|
|
const content = generateLensLayer(ctx);
|
|
expect(content).toContain('Stage A independent lens dispatch');
|
|
expect(content).toContain('Do not pass technical findings');
|
|
expect(content).toContain('gstack-lens-reviewer');
|
|
expect(content).toContain('empty tool allowlist');
|
|
});
|
|
|
|
test('structured reconciliation does not overclaim free-text novelty', () => {
|
|
const content = generateLensLayer(ctx);
|
|
expect(content).toContain('Stage B structured reconciliation');
|
|
expect(content).toContain('claim_key');
|
|
expect(content).toContain('AMBIGUOUS');
|
|
expect(content).toContain('NOT_MEASURED');
|
|
});
|
|
|
|
test('CTO synthesis is a separate constrained stage', () => {
|
|
const content = generateLensLayer(ctx);
|
|
expect(content).toContain('Stage C CTO synthesis');
|
|
expect(content).toContain('A CTO is not another lens');
|
|
expect(content).toContain('gstack-cto-synthesizer');
|
|
expect(content).toContain('cannot read repository content');
|
|
expect(content).toContain('gstack-lens-synthesis-validate --input <synthesis-input.json> --output <synthesis-output.json>');
|
|
expect(content).not.toContain('--synthesis <synthesis-output.json>');
|
|
});
|
|
|
|
test('lens-only skips Review Army without changing core Step 4', () => {
|
|
const content = generateLensPrepare(ctx);
|
|
expect(content).toContain('skip the complete Review Army section');
|
|
expect(content).toContain('Core Step 4 still runs');
|
|
});
|
|
|
|
test('disposition separates validity, relevance, decision, and routing feedback', () => {
|
|
const content = generateLensDisposition(ctx);
|
|
expect(content).toContain('validity');
|
|
expect(content).toContain('relevance');
|
|
expect(content).toContain('routing_feedback');
|
|
expect(content).not.toContain('Accept everything');
|
|
});
|
|
|
|
test('managed Claude agents declare an empty tool allowlist', () => {
|
|
for (const name of ['gstack-lens-reviewer', 'gstack-lens-output-validator', 'gstack-cto-synthesizer']) {
|
|
const content = fs.readFileSync(path.join(ROOT, 'hosts', 'claude', 'agents', `${name}.md`), 'utf8');
|
|
expect(content).toContain('tools: []');
|
|
expect(content).toContain('permissionMode: dontAsk');
|
|
expect(content).toContain('gstack-managed-agent');
|
|
}
|
|
});
|
|
|
|
test('setup installs the managed Claude agents', () => {
|
|
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf8');
|
|
expect(setup).toContain('install_claude_managed_agents');
|
|
expect(setup).toContain('hosts/claude/agents');
|
|
});
|
|
|
|
test('generated review skill contains lens sections and no placeholders', () => {
|
|
const content = fs.readFileSync(path.join(ROOT, 'review', 'SKILL.md'), 'utf8');
|
|
expect(content).toContain('## Step 4.7: Stakeholder Lens Layer');
|
|
expect(content).toContain('## Step 5e: Stakeholder lens disposition');
|
|
expect(content).not.toMatch(/\{\{LENS_[A-Z_]+\}\}/);
|
|
});
|
|
});
|