File size: 4,658 Bytes
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
import * as path from 'path';
import { logger } from '../../utils/logger.js';
import { eventBus } from '../../mcp/EventBus.js';

const execAsync = promisify(exec);

export interface DevToolsStatus {
    lastScan: string | null;
    repoCount: number;
    brainPath: string;
    isScanning: boolean;
    lastError: string | null;
}

export class DevToolsService {
    private static instance: DevToolsService;
    private isScanning = false;
    private devToolsPath: string;
    private brainPath: string | null = null;

    private constructor() {
        // Assuming DevTools-Monorepo is parallel to WidgeTDC root
        // WidgeTDC/apps/backend -> ../../../DevTools-Monorepo
        this.devToolsPath = path.resolve(process.cwd(), '../../../DevTools-Monorepo');
        this.findBrainPath();
    }

    public static getInstance(): DevToolsService {
        if (!DevToolsService.instance) {
            DevToolsService.instance = new DevToolsService();
        }
        return DevToolsService.instance;
    }

    private async findBrainPath() {
        // Logic mirrors PowerShell script to find active brain
        const potentialPaths = [
            'G:\\My Drive\\WidgeTDC-Config',
            path.join(process.env.USERPROFILE || '', 'Google Drive', 'WidgeTDC-Config'),
            path.join(process.env.OneDrive || '', 'WidgeTDC-Config'),
            path.join(process.env.HOME || process.env.USERPROFILE || '', '.widget-tdc')
        ];

        for (const p of potentialPaths) {
            try {
                await fs.access(p);
                this.brainPath = path.join(p, 'brain.json');
                break;
            } catch {
                continue;
            }
        }
    }

    public async getStatus(): Promise<DevToolsStatus> {
        let repoCount = 0;
        let lastScan = null;

        if (this.brainPath) {
            try {
                const data = await fs.readFile(this.brainPath, 'utf-8');
                const brain = JSON.parse(data);
                if (brain.templates) {
                    repoCount = Object.keys(brain.templates).length;
                    // Find most recent scan time
                    const dates = Object.values(brain.templates)
                        .map((t: any) => t.last_scanned ? new Date(t.last_scanned).getTime() : 0);
                    if (dates.length > 0) {
                        lastScan = new Date(Math.max(...dates)).toISOString();
                    }
                }
            } catch (error) {
                logger.error('Failed to read brain.json', error);
            }
        }

        return {
            lastScan,
            repoCount,
            brainPath: this.brainPath || 'Not Found',
            isScanning: this.isScanning,
            lastError: null
        };
    }

    public async runScan(): Promise<void> {
        if (this.isScanning) throw new Error('Scan already in progress');

        this.isScanning = true;
        eventBus.emit('devtools:scan:started', {});

        try {
            const script = path.join(this.devToolsPath, 'widget-agent.ps1');
            const command = `pwsh -NoProfile -ExecutionPolicy Bypass -File "${script}" scan-github`;

            logger.info(`Running DevTools scan: ${command}`);
            const { stdout, stderr } = await execAsync(command);

            logger.info('DevTools scan completed', { stdout });
            if (stderr) logger.warn('DevTools scan stderr', { stderr });

            eventBus.emit('devtools:scan:completed', { output: stdout });
        } catch (error) {
            logger.error('DevTools scan failed', error);
            eventBus.emit('devtools:scan:failed', { error: String(error) });
            throw error;
        } finally {
            this.isScanning = false;
        }
    }

    public async validateRepo(repoPath: string): Promise<string> {
        const script = path.join(this.devToolsPath, 'widget-agent.ps1');
        // We need to run validate in the context of the target repo
        const command = `pwsh -NoProfile -ExecutionPolicy Bypass -File "${script}" validate`;

        try {
            const { stdout } = await execAsync(command, { cwd: repoPath });
            return stdout;
        } catch (error: any) {
            // PowerShell script returns exit code 1 on validation failure, which throws here
            if (error.stdout) return error.stdout; // Return the validation output even if it failed
            throw error;
        }
    }
}

export const devToolsService = DevToolsService.getInstance();