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:
@@ -0,0 +1,589 @@
|
||||
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 { discoverClaudeCodePlugins, getPluginSearchPaths } from './discover-plugins.js';
|
||||
import { getDextoGlobalPath } from '../utils/path.js';
|
||||
|
||||
describe('discoverClaudeCodePlugins', () => {
|
||||
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('plugin discovery from project directories', () => {
|
||||
it('should discover plugins from <cwd>/.dexto/plugins/', () => {
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'test-plugin',
|
||||
description: 'A test plugin',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/my-plugin/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('my-plugin', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(manifestContent);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchObject({
|
||||
path: '/test/project/.dexto/plugins/my-plugin',
|
||||
source: 'project',
|
||||
manifest: {
|
||||
name: 'test-plugin',
|
||||
description: 'A test plugin',
|
||||
version: '1.0.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('plugin discovery from user directories', () => {
|
||||
it('should discover plugins from ~/.dexto/plugins/', () => {
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'user-plugin',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins') return true;
|
||||
if (p === '/home/user/.dexto/plugins/global-plugin/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/home/user/.dexto/plugins') {
|
||||
return [createDirent('global-plugin', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(manifestContent);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchObject({
|
||||
path: '/home/user/.dexto/plugins/global-plugin',
|
||||
source: 'user',
|
||||
manifest: {
|
||||
name: 'user-plugin',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplication by plugin name', () => {
|
||||
it('should deduplicate by plugin name (first found wins)', () => {
|
||||
const projectManifest = JSON.stringify({
|
||||
name: 'duplicate-plugin',
|
||||
description: 'Project version',
|
||||
});
|
||||
const userManifest = JSON.stringify({
|
||||
name: 'duplicate-plugin',
|
||||
description: 'User version',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/home/user/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/plugin-a/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
if (p === '/home/user/.dexto/plugins/plugin-b/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('plugin-a', true)] as any;
|
||||
}
|
||||
if (dir === '/home/user/.dexto/plugins') {
|
||||
return [createDirent('plugin-b', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (String(p).includes('plugin-a')) return projectManifest;
|
||||
if (String(p).includes('plugin-b')) return userManifest;
|
||||
return '';
|
||||
});
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
// Should only have one plugin - the project version (first found)
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.manifest.description).toBe('Project version');
|
||||
expect(result[0]!.source).toBe('project');
|
||||
});
|
||||
|
||||
it('should be case-insensitive when deduplicating', () => {
|
||||
const manifest1 = JSON.stringify({ name: 'My-Plugin' });
|
||||
const manifest2 = JSON.stringify({ name: 'my-plugin' }); // Different case
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/home/user/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/plugin1/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
if (p === '/home/user/.dexto/plugins/plugin2/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('plugin1', true)] as any;
|
||||
}
|
||||
if (dir === '/home/user/.dexto/plugins') {
|
||||
return [createDirent('plugin2', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (String(p).includes('plugin1')) return manifest1;
|
||||
if (String(p).includes('plugin2')) return manifest2;
|
||||
return '';
|
||||
});
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
// Should only have one plugin - case-insensitive dedup
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.manifest.name).toBe('My-Plugin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid manifests', () => {
|
||||
it('should skip plugins without plugin.json', () => {
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
// No plugin.json exists
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('incomplete-plugin', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should skip plugins with invalid JSON in manifest', () => {
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/bad-json/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('bad-json', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('{ invalid json }');
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should skip manifests missing required name field silently', () => {
|
||||
// Note: Invalid manifests cause tryLoadManifest to throw PluginError,
|
||||
// but the error is caught in the scanPluginsDir try/catch and silently skipped.
|
||||
// This is intentional - we don't want one invalid plugin to prevent others from loading.
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/no-name/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('no-name', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ description: 'No name' }));
|
||||
|
||||
// Invalid manifests are silently skipped - result is empty
|
||||
const result = discoverClaudeCodePlugins();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return empty array when no plugin directories exist', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should skip non-directory entries in plugins folder', () => {
|
||||
const manifestContent = JSON.stringify({ name: 'valid-plugin' });
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/valid-plugin/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [
|
||||
createDirent('valid-plugin', true),
|
||||
createDirent('some-file.txt', false), // File, not directory
|
||||
] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(manifestContent);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.manifest.name).toBe('valid-plugin');
|
||||
});
|
||||
|
||||
it('should handle missing HOME environment variable', () => {
|
||||
delete process.env.HOME;
|
||||
delete process.env.USERPROFILE;
|
||||
|
||||
const manifestContent = JSON.stringify({ name: 'local-only' });
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/test/project/.dexto/plugins') return true;
|
||||
if (p === '/test/project/.dexto/plugins/local/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/test/project/.dexto/plugins') {
|
||||
return [createDirent('local', true)] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(manifestContent);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
// Should still work for local plugins
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('installed_plugins.json reading', () => {
|
||||
it('should discover plugins from installed_plugins.json', () => {
|
||||
const installedPluginsJson = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'code-review@claude-code-plugins': [
|
||||
{
|
||||
scope: 'user',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/claude-code-plugins/code-review/1.0.0',
|
||||
version: '1.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'code-review',
|
||||
version: '1.0.0',
|
||||
description: 'Code review plugin',
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/claude-code-plugins/code-review/1.0.0')
|
||||
return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/claude-code-plugins/code-review/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') {
|
||||
return installedPluginsJson;
|
||||
}
|
||||
return manifestContent;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockReturnValue([]);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchObject({
|
||||
path: '/home/user/.dexto/plugins/cache/claude-code-plugins/code-review/1.0.0',
|
||||
manifest: {
|
||||
name: 'code-review',
|
||||
version: '1.0.0',
|
||||
},
|
||||
source: 'user',
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter project-scoped plugins by current project path', () => {
|
||||
const installedPluginsJson = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'my-plugin@marketplace': [
|
||||
{
|
||||
scope: 'project',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/marketplace/my-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
projectPath: '/test/project', // Matches current project
|
||||
},
|
||||
{
|
||||
scope: 'project',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/marketplace/my-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
projectPath: '/other/project', // Different project
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const manifestContent = JSON.stringify({
|
||||
name: 'my-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/marketplace/my-plugin/1.0.0')
|
||||
return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/marketplace/my-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') {
|
||||
return installedPluginsJson;
|
||||
}
|
||||
return manifestContent;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockReturnValue([]);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
// Should only include the plugin for the current project
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.source).toBe('project');
|
||||
});
|
||||
|
||||
it('should filter local-scoped plugins by current project path', () => {
|
||||
const installedPluginsJson = JSON.stringify({
|
||||
version: 2,
|
||||
plugins: {
|
||||
'local-plugin@marketplace': [
|
||||
{
|
||||
scope: 'local',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/marketplace/local-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
projectPath: '/other/project', // Different project - should be filtered out
|
||||
},
|
||||
],
|
||||
'user-plugin@marketplace': [
|
||||
{
|
||||
scope: 'user',
|
||||
installPath:
|
||||
'/home/user/.dexto/plugins/cache/marketplace/user-plugin/1.0.0',
|
||||
version: '1.0.0',
|
||||
// No projectPath - user scope applies everywhere
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/marketplace/local-plugin/1.0.0')
|
||||
return true;
|
||||
if (p === '/home/user/.dexto/plugins/cache/marketplace/user-plugin/1.0.0')
|
||||
return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/marketplace/local-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
p ===
|
||||
'/home/user/.dexto/plugins/cache/marketplace/user-plugin/1.0.0/.claude-plugin/plugin.json'
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins/installed_plugins.json') {
|
||||
return installedPluginsJson;
|
||||
}
|
||||
if (String(p).includes('local-plugin')) {
|
||||
return JSON.stringify({ name: 'local-plugin', version: '1.0.0' });
|
||||
}
|
||||
return JSON.stringify({ name: 'user-plugin', version: '1.0.0' });
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockReturnValue([]);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
// Should only include the user-scoped plugin, not the local-scoped one for a different project
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.manifest.name).toBe('user-plugin');
|
||||
expect(result[0]!.source).toBe('user');
|
||||
});
|
||||
|
||||
it('should skip cache and marketplaces directories in directory scan', () => {
|
||||
const manifestContent = JSON.stringify({ name: 'direct-plugin' });
|
||||
|
||||
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
||||
if (p === '/home/user/.dexto/plugins') return true;
|
||||
if (p === '/home/user/.dexto/plugins/direct-plugin/.claude-plugin/plugin.json')
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
vi.mocked(fs.readdirSync).mockImplementation((dir) => {
|
||||
if (dir === '/home/user/.dexto/plugins') {
|
||||
return [
|
||||
createDirent('cache', true), // Should be skipped
|
||||
createDirent('marketplaces', true), // Should be skipped
|
||||
createDirent('direct-plugin', true), // Should be scanned
|
||||
] as any;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(manifestContent);
|
||||
|
||||
const result = discoverClaudeCodePlugins();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.manifest.name).toBe('direct-plugin');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPluginSearchPaths', () => {
|
||||
const originalCwd = process.cwd;
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
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}`
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.cwd = originalCwd;
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
it('should return all search paths in priority order', () => {
|
||||
const paths = getPluginSearchPaths();
|
||||
|
||||
expect(paths).toEqual([
|
||||
// Dexto's installed_plugins.json (highest priority)
|
||||
'/home/user/.dexto/plugins/installed_plugins.json',
|
||||
// Directory scan locations
|
||||
'/test/project/.dexto/plugins',
|
||||
'/home/user/.dexto/plugins',
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user