import type { ExtensionAPI } from "@jaeswift/jae-coding-agent"; import { watch, type FSWatcher, readFileSync } from "node:fs"; import { join, relative, extname } from "node:path"; export default function (pi: ExtensionAPI) { let watcher: FSWatcher | null = null; let pairMode = false; let watchedFiles = new Set(); let lastChangeTime = 0; const DEBOUNCE_MS = 2000; const CODE_EXTENSIONS = new Set([ ".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".go", ".java", ".c", ".cpp", ".h", ".css", ".scss", ".html", ".vue", ".svelte", ".rb", ".php", ".json", ".yaml", ".yml", ".toml", ".md", ]); const IGNORE_PATTERNS = [ "node_modules", ".git", "dist", "build", ".jae", ".next", ".cache", "__pycache__", "target", ]; function shouldWatch(filepath: string): boolean { if (IGNORE_PATTERNS.some((p) => filepath.includes(p))) return false; return CODE_EXTENSIONS.has(extname(filepath)); } function startWatching(cwd: string, ctx: any): void { if (watcher) { watcher.close(); } try { watcher = watch(cwd, { recursive: true }, (eventType, filename) => { if (!filename || !pairMode) return; const filepath = join(cwd, filename); if (!shouldWatch(filepath)) return; const now = Date.now(); if (now - lastChangeTime < DEBOUNCE_MS) return; lastChangeTime = now; const relPath = relative(cwd, filepath); watchedFiles.add(relPath); try { const content = readFileSync(filepath, "utf-8"); const lines = content.split("\n").length; ctx.ui.notify( `\u{1F4DD} File changed: ${relPath} (${lines} lines, ${eventType})\nJAE is watching in pair mode.`, "info", ); } catch { ctx.ui.notify(`\u{1F4DD} File ${eventType}: ${relPath}`, "info"); } }); ctx.ui.notify( `\u{1F91D} Pair programming mode started!\nWatching: ${cwd}\nJAE will observe your changes and provide feedback.`, "info", ); } catch (err) { ctx.ui.notify(`Failed to start file watcher: ${err}`, "error"); } } function stopWatching(ctx: any): void { if (watcher) { watcher.close(); watcher = null; } const filesCount = watchedFiles.size; watchedFiles.clear(); ctx.ui.notify( `\u{1F91D} Pair programming mode stopped. ${filesCount} files were modified during session.`, "info", ); } pi.on("session_end", async (_event, _ctx) => { if (watcher) { watcher.close(); watcher = null; pairMode = false; } }); pi.on("before_agent_start", async (_event, _ctx) => { if (pairMode && watchedFiles.size > 0) { const filesList = Array.from(watchedFiles).slice(0, 10).join(", "); return { systemPrompt: `\n\n# Pair Programming Mode Active\nRecently modified files: ${filesList}\nProvide constructive feedback on changes. Suggest improvements, catch bugs, and explain patterns.\n`, }; } }); pi.registerCommand("pair", { description: "Toggle pair programming mode - JAE watches and comments on your file changes", handler: async (_args, ctx) => { pairMode = !pairMode; if (pairMode) { startWatching(ctx.cwd, ctx); ctx.ui.setFooter("pair", "\u{1F91D} Pair Mode"); } else { stopWatching(ctx); ctx.ui.setFooter("pair", undefined); } }, }); }