- 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>
393 lines
13 KiB
TypeScript
393 lines
13 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|