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:
392
dexto/packages/core/src/utils/path.test.ts
Normal file
392
dexto/packages/core/src/utils/path.test.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { tmpdir, homedir } from 'os';
|
||||
import { getDextoPath, getDextoGlobalPath, getDextoEnvPath, findPackageRoot } from './path.js';
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
function createTempDir() {
|
||||
return fs.mkdtempSync(path.join(tmpdir(), 'dexto-test-'));
|
||||
}
|
||||
|
||||
function createTempDirStructure(structure: Record<string, any>, baseDir?: string): string {
|
||||
const tempDir = baseDir || createTempDir();
|
||||
|
||||
for (const [filePath, content] of Object.entries(structure)) {
|
||||
const fullPath = path.join(tempDir, filePath);
|
||||
const dir = path.dirname(fullPath);
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
if (typeof content === 'string') {
|
||||
fs.writeFileSync(fullPath, content);
|
||||
} else if (typeof content === 'object') {
|
||||
fs.writeFileSync(fullPath, JSON.stringify(content, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
describe('getDextoPath', () => {
|
||||
let tempDir: string;
|
||||
|
||||
afterEach(() => {
|
||||
if (tempDir) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('in dexto project', () => {
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'test-project',
|
||||
dependencies: { dexto: '^1.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns project-local path for logs', () => {
|
||||
const result = getDextoPath('logs', 'test.log', tempDir);
|
||||
expect(result).toBe(path.join(tempDir, '.dexto', 'logs', 'test.log'));
|
||||
});
|
||||
|
||||
it('returns project-local path for database', () => {
|
||||
const result = getDextoPath('database', 'dexto.db', tempDir);
|
||||
expect(result).toBe(path.join(tempDir, '.dexto', 'database', 'dexto.db'));
|
||||
});
|
||||
|
||||
it('returns directory path when no filename provided', () => {
|
||||
const result = getDextoPath('config', undefined, tempDir);
|
||||
expect(result).toBe(path.join(tempDir, '.dexto', 'config'));
|
||||
});
|
||||
|
||||
it('works from nested directories', () => {
|
||||
const nestedDir = path.join(tempDir, 'src', 'app');
|
||||
fs.mkdirSync(nestedDir, { recursive: true });
|
||||
|
||||
const result = getDextoPath('logs', 'app.log', nestedDir);
|
||||
expect(result).toBe(path.join(tempDir, '.dexto', 'logs', 'app.log'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('outside dexto project (global)', () => {
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'regular-project',
|
||||
dependencies: { express: '^4.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns global path when not in dexto project', () => {
|
||||
const originalCwd = process.cwd();
|
||||
try {
|
||||
process.chdir(tempDir);
|
||||
const result = getDextoPath('logs', 'global.log');
|
||||
expect(result).toContain('.dexto');
|
||||
expect(result).toContain('logs');
|
||||
expect(result).toContain('global.log');
|
||||
expect(result).not.toContain(tempDir);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDextoGlobalPath', () => {
|
||||
let tempDir: string;
|
||||
|
||||
afterEach(() => {
|
||||
if (tempDir) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('basic functionality', () => {
|
||||
it('returns global agents directory', () => {
|
||||
const result = getDextoGlobalPath('agents');
|
||||
expect(result).toContain('.dexto');
|
||||
expect(result).toContain('agents');
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns global path with filename', () => {
|
||||
const result = getDextoGlobalPath('agents', 'database-agent');
|
||||
expect(result).toContain('.dexto');
|
||||
expect(result).toContain('agents');
|
||||
expect(result).toContain('database-agent');
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
});
|
||||
|
||||
it('handles different types correctly', () => {
|
||||
const agents = getDextoGlobalPath('agents');
|
||||
const logs = getDextoGlobalPath('logs');
|
||||
const cache = getDextoGlobalPath('cache');
|
||||
|
||||
expect(agents).toContain('agents');
|
||||
expect(logs).toContain('logs');
|
||||
expect(cache).toContain('cache');
|
||||
});
|
||||
});
|
||||
|
||||
describe('in dexto project context', () => {
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'test-project',
|
||||
dependencies: { dexto: '^1.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('always returns global path, never project-relative', () => {
|
||||
// getDextoPath returns project-relative
|
||||
const projectPath = getDextoPath('agents', 'test-agent', tempDir);
|
||||
expect(projectPath).toBe(path.join(tempDir, '.dexto', 'agents', 'test-agent'));
|
||||
|
||||
// getDextoGlobalPath should ALWAYS return global, never project-relative
|
||||
const globalPath = getDextoGlobalPath('agents', 'test-agent');
|
||||
expect(globalPath).toContain('.dexto');
|
||||
expect(globalPath).toContain('agents');
|
||||
expect(globalPath).toContain('test-agent');
|
||||
expect(globalPath).not.toContain(tempDir); // Key difference!
|
||||
expect(path.isAbsolute(globalPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('outside dexto project context', () => {
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'regular-project',
|
||||
dependencies: { express: '^4.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns global path (same as in project context)', () => {
|
||||
const globalPath = getDextoGlobalPath('agents', 'test-agent');
|
||||
expect(globalPath).toContain('.dexto');
|
||||
expect(globalPath).toContain('agents');
|
||||
expect(globalPath).toContain('test-agent');
|
||||
expect(globalPath).not.toContain(tempDir);
|
||||
expect(path.isAbsolute(globalPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPackageRoot', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDir();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('returns null if no package.json found', () => {
|
||||
const result = findPackageRoot(tempDir);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns the directory containing package.json', () => {
|
||||
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'test-pkg' }));
|
||||
const result = findPackageRoot(tempDir);
|
||||
expect(result).toBe(tempDir);
|
||||
});
|
||||
|
||||
it('finds package.json by walking up directories', () => {
|
||||
const nestedDir = path.join(tempDir, 'nested', 'deep');
|
||||
fs.mkdirSync(nestedDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'test-pkg' }));
|
||||
|
||||
const result = findPackageRoot(nestedDir);
|
||||
expect(result).toBe(tempDir);
|
||||
});
|
||||
});
|
||||
|
||||
// resolveBundledScript tests have been moved to @dexto/agent-management
|
||||
|
||||
describe('getDextoEnvPath', () => {
|
||||
describe('in dexto project', () => {
|
||||
let tempDir: string;
|
||||
let originalCwd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
originalCwd = process.cwd();
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'test-project',
|
||||
dependencies: { dexto: '^1.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(originalCwd);
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('returns project root .env path', () => {
|
||||
process.chdir(tempDir);
|
||||
const result = getDextoEnvPath(tempDir);
|
||||
expect(result).toBe(path.join(tempDir, '.env'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('in dexto source', () => {
|
||||
let tempDir: string;
|
||||
let originalCwd: string;
|
||||
const originalDevMode = process.env.DEXTO_DEV_MODE;
|
||||
|
||||
beforeEach(() => {
|
||||
originalCwd = process.cwd();
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'dexto-monorepo',
|
||||
version: '1.0.0',
|
||||
},
|
||||
'agents/default-agent.yml': 'mcpServers: {}',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(originalCwd);
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
// Restore original env
|
||||
if (originalDevMode === undefined) {
|
||||
delete process.env.DEXTO_DEV_MODE;
|
||||
} else {
|
||||
process.env.DEXTO_DEV_MODE = originalDevMode;
|
||||
}
|
||||
});
|
||||
|
||||
it('returns repo .env when DEXTO_DEV_MODE=true', () => {
|
||||
process.chdir(tempDir);
|
||||
process.env.DEXTO_DEV_MODE = 'true';
|
||||
const result = getDextoEnvPath(tempDir);
|
||||
expect(result).toBe(path.join(tempDir, '.env'));
|
||||
});
|
||||
|
||||
it('returns global ~/.dexto/.env when DEXTO_DEV_MODE is not set', () => {
|
||||
process.chdir(tempDir);
|
||||
delete process.env.DEXTO_DEV_MODE;
|
||||
const result = getDextoEnvPath(tempDir);
|
||||
expect(result).toBe(path.join(homedir(), '.dexto', '.env'));
|
||||
});
|
||||
|
||||
it('returns global ~/.dexto/.env when DEXTO_DEV_MODE=false', () => {
|
||||
process.chdir(tempDir);
|
||||
process.env.DEXTO_DEV_MODE = 'false';
|
||||
const result = getDextoEnvPath(tempDir);
|
||||
expect(result).toBe(path.join(homedir(), '.dexto', '.env'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('in global-cli context', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'regular-project',
|
||||
dependencies: { express: '^4.0.0' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('returns global ~/.dexto/.env path', () => {
|
||||
const result = getDextoEnvPath(tempDir);
|
||||
expect(result).toBe(path.join(homedir(), '.dexto', '.env'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('real-world execution contexts', () => {
|
||||
describe('SDK usage in project', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'my-app',
|
||||
dependencies: { dexto: '^1.0.0' },
|
||||
},
|
||||
'src/dexto/agents/default-agent.yml': 'mcpServers: {}',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('uses project-local storage', () => {
|
||||
const logPath = getDextoPath('logs', 'dexto.log', tempDir);
|
||||
const dbPath = getDextoPath('database', 'dexto.db', tempDir);
|
||||
|
||||
expect(logPath).toBe(path.join(tempDir, '.dexto', 'logs', 'dexto.log'));
|
||||
expect(dbPath).toBe(path.join(tempDir, '.dexto', 'database', 'dexto.db'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLI in dexto source', () => {
|
||||
let tempDir: string;
|
||||
const originalDevMode = process.env.DEXTO_DEV_MODE;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = createTempDirStructure({
|
||||
'package.json': {
|
||||
name: 'dexto-monorepo',
|
||||
version: '1.0.0',
|
||||
},
|
||||
'agents/default-agent.yml': 'mcpServers: {}',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
// Restore original env
|
||||
if (originalDevMode === undefined) {
|
||||
delete process.env.DEXTO_DEV_MODE;
|
||||
} else {
|
||||
process.env.DEXTO_DEV_MODE = originalDevMode;
|
||||
}
|
||||
});
|
||||
|
||||
it('uses local repo storage when DEXTO_DEV_MODE=true', () => {
|
||||
process.env.DEXTO_DEV_MODE = 'true';
|
||||
const logPath = getDextoPath('logs', 'dexto.log', tempDir);
|
||||
expect(logPath).toBe(path.join(tempDir, '.dexto', 'logs', 'dexto.log'));
|
||||
});
|
||||
|
||||
it('uses global storage when DEXTO_DEV_MODE is not set', () => {
|
||||
delete process.env.DEXTO_DEV_MODE;
|
||||
const logPath = getDextoPath('logs', 'dexto.log', tempDir);
|
||||
expect(logPath).toContain('.dexto');
|
||||
expect(logPath).toContain('logs');
|
||||
expect(logPath).toContain('dexto.log');
|
||||
expect(logPath).not.toContain(tempDir); // Should be global, not local
|
||||
});
|
||||
|
||||
it('uses global storage when DEXTO_DEV_MODE=false', () => {
|
||||
process.env.DEXTO_DEV_MODE = 'false';
|
||||
const logPath = getDextoPath('logs', 'dexto.log', tempDir);
|
||||
expect(logPath).toContain('.dexto');
|
||||
expect(logPath).toContain('logs');
|
||||
expect(logPath).toContain('dexto.log');
|
||||
expect(logPath).not.toContain(tempDir); // Should be global, not local
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user