feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
266
dexto/packages/agent-management/src/plugins/list-plugins.test.ts
Normal file
266
dexto/packages/agent-management/src/plugins/list-plugins.test.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Mock fs module
|
||||
vi.mock('fs', async () => {
|
||||
const actual = await vi.importActual<typeof import('fs')>('fs');
|
||||
return {
|
||||
...actual,
|
||||
readdirSync: vi.fn(),
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock path utilities
|
||||
vi.mock('../utils/path.js', () => ({
|
||||
getDextoGlobalPath: vi.fn((type: string, filename?: string) =>
|
||||
filename ? `/home/user/.dexto/${type}/${filename}` : `/home/user/.dexto/${type}`
|
||||
),
|
||||
}));
|
||||
|
||||
import { listInstalledPlugins } from './list-plugins.js';
|
||||
import { getDextoGlobalPath } from '../utils/path.js';
|
||||
|
||||
describe('listInstalledPlugins', () => {
|
||||
const originalCwd = process.cwd;
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
// Helper to create mock Dirent-like objects for testing
|
||||
const createDirent = (name: string, isDir: boolean) => ({
|
||||
name,
|
||||
isFile: () => !isDir,
|
||||
isDirectory: () => isDir,
|
||||
isBlockDevice: () => false,
|
||||
isCharacterDevice: () => false,
|
||||
isSymbolicLink: () => false,
|
||||
isFIFO: () => false,
|
||||
isSocket: () => false,
|
||||
path: '',
|
||||
parentPath: '',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(fs.readdirSync).mockReset();
|
||||
vi.mocked(fs.existsSync).mockReset();
|
||||
vi.mocked(fs.readFileSync).mockReset();
|
||||
vi.mocked(getDextoGlobalPath).mockReset();
|
||||
|
||||
// Default mocks
|
||||
process.cwd = vi.fn(() => '/test/project');
|
||||
process.env.HOME = '/home/user';
|
||||
vi.mocked(getDextoGlobalPath).mockImplementation((type: string, filename?: string) =>
|
||||
filename ? `/home/user/.dexto/${type}/${filename}` : `/home/user/.dexto/${type}`
|
||||
);
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.cwd = originalCwd;
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
describe('scope filtering', () => {
|
||||
it('should filter out project-scoped plugins from different projects', () => {
|
||||
const installedPluginsContent = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'test-plugin@test-marketplace': [
|
||||
{
|
||||
scope: 'local',
|
||||
projectPath: '/different/project',
|
||||
installPath: '/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
installedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'test-plugin',
|
||||
description: 'Test plugin',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0') return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (typeof p === 'string' && p.endsWith('installed_plugins.json')) {
|
||||
return installedPluginsContent;
|
||||
}
|
||||
if (typeof p === 'string' && p.endsWith('plugin.json')) {
|
||||
return manifestContent;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const result = listInstalledPlugins('/test/project');
|
||||
|
||||
// Should be filtered out because projectPath doesn't match
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should include user-scoped plugins regardless of project', () => {
|
||||
const installedPluginsContent = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'test-plugin@test-marketplace': [
|
||||
{
|
||||
scope: 'user',
|
||||
installPath: '/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
installedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'test-plugin',
|
||||
description: 'Test plugin',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0') return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/test/test-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (typeof p === 'string' && p.endsWith('installed_plugins.json')) {
|
||||
return installedPluginsContent;
|
||||
}
|
||||
if (typeof p === 'string' && p.endsWith('plugin.json')) {
|
||||
return manifestContent;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const result = listInstalledPlugins('/test/project');
|
||||
|
||||
// Should be included because it's user-scoped
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.name).toBe('test-plugin');
|
||||
});
|
||||
|
||||
it('should NOT re-add filtered plugins via cache scanning', () => {
|
||||
// This is the bug fix test: plugins filtered by scope from installed_plugins.json
|
||||
// should NOT reappear when scanning the cache directory
|
||||
|
||||
const installedPluginsContent = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'filtered-plugin@test-marketplace': [
|
||||
{
|
||||
scope: 'local',
|
||||
projectPath: '/different/project',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/test/filtered-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
installedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'filtered-plugin',
|
||||
description: 'Plugin that should be filtered',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
if (p === '/home/user/.claude/plugins/cache') return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/test/filtered-plugin/1.0.0') return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/test/filtered-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (typeof p === 'string' && p.endsWith('installed_plugins.json')) {
|
||||
return installedPluginsContent;
|
||||
}
|
||||
if (typeof p === 'string' && p.endsWith('plugin.json')) {
|
||||
return manifestContent;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// Mock cache directory structure scanning
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
const dirStr = typeof dir === 'string' ? dir : dir.toString();
|
||||
if (dirStr === '/home/user/.claude/plugins/cache') {
|
||||
return [createDirent('test', true)] as any;
|
||||
}
|
||||
if (dirStr === '/home/user/.dexto/plugins/cache/test') {
|
||||
return [createDirent('filtered-plugin', true)] as any;
|
||||
}
|
||||
if (dirStr === '/home/user/.dexto/plugins/cache/test/filtered-plugin') {
|
||||
return [createDirent('1.0.0', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const result = listInstalledPlugins('/test/project');
|
||||
|
||||
// Should be 0 because:
|
||||
// 1. Filtered out by scope check in readClaudeCodeInstalledPlugins
|
||||
// 2. Should NOT be re-added by scanClaudeCodeCache (this is the bug fix)
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache scanning', () => {
|
||||
it('does not list plugins in cache that are not tracked in installed_plugins.json', () => {
|
||||
// Plugins must be tracked in installed_plugins.json or placed directly in
|
||||
// ~/.dexto/plugins/ or <cwd>/.dexto/plugins/ (not in cache subdirectory)
|
||||
// The cache directory is only for versioned copies managed by the install system
|
||||
|
||||
const installedPluginsContent = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {},
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (typeof p === 'string' && p.endsWith('installed_plugins.json')) {
|
||||
return installedPluginsContent;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockReturnValue([]);
|
||||
|
||||
const result = listInstalledPlugins('/test/project');
|
||||
|
||||
// No plugins should be found because nothing is tracked in installed_plugins.json
|
||||
// and no plugins are directly in the plugins directories
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user