mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-01 19:25:10 +02:00
a4a181ca92
* feat: extend gstack-diff-scope with SCOPE_MIGRATIONS, SCOPE_API, SCOPE_AUTH
Three new scope signals for Review Army specialist activation:
- SCOPE_MIGRATIONS: db/migrate/, prisma/migrations/, alembic/, *.sql
- SCOPE_API: *controller*, *route*, *endpoint*, *.graphql, openapi.*
- SCOPE_AUTH: *auth*, *session*, *jwt*, *oauth*, *permission*, *role*
* feat: add 7 specialist checklist files for Review Army
- testing.md (always-on): coverage gaps, flaky patterns, security enforcement
- maintainability.md (always-on): dead code, DRY, stale comments
- security.md (conditional): OWASP deep analysis, auth bypass, injection
- performance.md (conditional): N+1 queries, bundle impact, complexity
- data-migration.md (conditional): reversibility, lock duration, backfill
- api-contract.md (conditional): breaking changes, versioning, error format
- red-team.md (conditional): adversarial analysis, cross-cutting concerns
All use standard header with JSON output schema and NO FINDINGS fallback.
* feat: Review Army resolver — parallel specialist dispatch + merge
New resolver in review-army.ts generates template prose for:
- Stack detection and specialist selection
- Parallel Agent tool dispatch with learning-informed prompts
- JSON finding collection, fingerprint dedup, consensus highlighting
- PR quality score computation
- Red Team conditional dispatch
Registered as REVIEW_ARMY in resolvers/index.ts.
* refactor: restructure /review template for Review Army
- Replace Steps 4-4.75 with CRITICAL pass + {{REVIEW_ARMY}}
- Remove {{DESIGN_REVIEW_LITE}} and {{TEST_COVERAGE_AUDIT_REVIEW}}
(subsumed into Design and Testing specialists respectively)
- Extract specialist-covered categories from checklist.md
- Keep CRITICAL + uncovered INFORMATIONAL in main agent pass
* test: Review Army — 14 diff-scope tests + 7 E2E tests
- test/diff-scope.test.ts: 14 tests for all 9 scope signals
- test/skill-e2e-review-army.test.ts: 7 E2E tests
Gate: migration safety, N+1 detection, delivery audit,
quality score, JSON findings
Periodic: red team, consensus
- Updated gen-skill-docs tests for new review structure
- Added touchfile entries and tier classifications
* docs: update SELF_LEARNING_V0.md with Release 2 status + Release 2.5
Mark Release 2 (Review Army) as in-progress. Add Release 2.5 for
deferred expansions (E1 adaptive gating, E3 test stubs, E5 cross-review
dedup, E7 specialist tracking).
* chore: bump version and changelog (v0.14.3.0)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
/**
|
|
* Tests for bin/gstack-diff-scope — verifies scope signal detection.
|
|
*
|
|
* Creates temp git repos with specific file patterns and verifies
|
|
* the correct SCOPE_* variables are output.
|
|
*/
|
|
import { describe, test, expect, afterAll } from 'bun:test';
|
|
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { tmpdir } from 'os';
|
|
import { spawnSync } from 'child_process';
|
|
|
|
const SCRIPT = join(import.meta.dir, '..', 'bin', 'gstack-diff-scope');
|
|
|
|
const dirs: string[] = [];
|
|
|
|
function createRepo(files: string[]): string {
|
|
const dir = mkdtempSync(join(tmpdir(), 'diff-scope-test-'));
|
|
dirs.push(dir);
|
|
|
|
const run = (cmd: string, args: string[]) =>
|
|
spawnSync(cmd, args, { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
|
|
run('git', ['init', '-b', 'main']);
|
|
run('git', ['config', 'user.email', 'test@test.com']);
|
|
run('git', ['config', 'user.name', 'Test']);
|
|
|
|
// Base commit
|
|
writeFileSync(join(dir, 'README.md'), '# test\n');
|
|
run('git', ['add', '.']);
|
|
run('git', ['commit', '-m', 'initial']);
|
|
|
|
// Feature branch with specified files
|
|
run('git', ['checkout', '-b', 'feature/test']);
|
|
for (const f of files) {
|
|
const fullPath = join(dir, f);
|
|
const dirPath = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
|
if (dirPath !== dir) mkdirSync(dirPath, { recursive: true });
|
|
writeFileSync(fullPath, '# test content\n');
|
|
}
|
|
run('git', ['add', '.']);
|
|
run('git', ['commit', '-m', 'add files']);
|
|
|
|
return dir;
|
|
}
|
|
|
|
function runScope(dir: string): Record<string, string> {
|
|
const result = spawnSync('bash', [SCRIPT, 'main'], {
|
|
cwd: dir, stdio: 'pipe', timeout: 5000,
|
|
});
|
|
const output = result.stdout.toString().trim();
|
|
const vars: Record<string, string> = {};
|
|
for (const line of output.split('\n')) {
|
|
const [key, val] = line.split('=');
|
|
if (key && val) vars[key] = val;
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
afterAll(() => {
|
|
for (const d of dirs) {
|
|
try { rmSync(d, { recursive: true, force: true }); } catch {}
|
|
}
|
|
});
|
|
|
|
describe('gstack-diff-scope', () => {
|
|
// --- Existing scope signals ---
|
|
|
|
test('detects frontend files', () => {
|
|
const dir = createRepo(['styles.css', 'component.tsx']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_FRONTEND).toBe('true');
|
|
});
|
|
|
|
test('detects backend files', () => {
|
|
const dir = createRepo(['app.rb', 'service.py']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_BACKEND).toBe('true');
|
|
});
|
|
|
|
test('detects test files', () => {
|
|
const dir = createRepo(['test/app.test.ts']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_TESTS).toBe('true');
|
|
});
|
|
|
|
// --- New scope signals (Review Army) ---
|
|
|
|
test('detects migrations via db/migrate/', () => {
|
|
const dir = createRepo(['db/migrate/20260330_create_users.rb']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_MIGRATIONS).toBe('true');
|
|
});
|
|
|
|
test('detects migrations via generic migrations/', () => {
|
|
const dir = createRepo(['app/migrations/0001_initial.py']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_MIGRATIONS).toBe('true');
|
|
});
|
|
|
|
test('detects migrations via prisma', () => {
|
|
const dir = createRepo(['prisma/migrations/20260330/migration.sql']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_MIGRATIONS).toBe('true');
|
|
});
|
|
|
|
test('detects API via controller files', () => {
|
|
const dir = createRepo(['app/controllers/users_controller.rb']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_API).toBe('true');
|
|
});
|
|
|
|
test('detects API via route files', () => {
|
|
const dir = createRepo(['src/routes/api.ts']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_API).toBe('true');
|
|
});
|
|
|
|
test('detects API via GraphQL schemas', () => {
|
|
const dir = createRepo(['schema.graphql']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_API).toBe('true');
|
|
});
|
|
|
|
test('detects auth files', () => {
|
|
const dir = createRepo(['app/services/auth_service.rb']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_AUTH).toBe('true');
|
|
});
|
|
|
|
test('detects session files', () => {
|
|
const dir = createRepo(['lib/session_manager.ts']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_AUTH).toBe('true');
|
|
});
|
|
|
|
test('detects JWT files', () => {
|
|
const dir = createRepo(['utils/jwt_helper.py']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_AUTH).toBe('true');
|
|
});
|
|
|
|
test('returns false for all new signals when no matching files', () => {
|
|
const dir = createRepo(['docs/readme.md', 'config.yml']);
|
|
const scope = runScope(dir);
|
|
expect(scope.SCOPE_MIGRATIONS).toBe('false');
|
|
expect(scope.SCOPE_API).toBe('false');
|
|
expect(scope.SCOPE_AUTH).toBe('false');
|
|
});
|
|
|
|
test('outputs all 9 scope variables', () => {
|
|
const dir = createRepo(['app.ts']);
|
|
const scope = runScope(dir);
|
|
expect(Object.keys(scope)).toHaveLength(9);
|
|
expect(scope).toHaveProperty('SCOPE_FRONTEND');
|
|
expect(scope).toHaveProperty('SCOPE_BACKEND');
|
|
expect(scope).toHaveProperty('SCOPE_PROMPTS');
|
|
expect(scope).toHaveProperty('SCOPE_TESTS');
|
|
expect(scope).toHaveProperty('SCOPE_DOCS');
|
|
expect(scope).toHaveProperty('SCOPE_CONFIG');
|
|
expect(scope).toHaveProperty('SCOPE_MIGRATIONS');
|
|
expect(scope).toHaveProperty('SCOPE_API');
|
|
expect(scope).toHaveProperty('SCOPE_AUTH');
|
|
});
|
|
});
|