New extensions shipping with Agent JAE: - jae-rules: Per-project .jae/rules.md injection - cost-tracker: Token/cost tracking with budget limits - project-dna: Auto-analyze project fingerprint - teach-mode: Pedagogical explanation mode - bookmarks: File bookmark management - replay: Session recording and asciicast export - checkpoints: Git-based undo/time-travel - pr-review: Autonomous PR diff review - auto-docs: Auto-generate README and docs - screenshot-context: Vision analysis for screenshots - deploy: One-command deploy (Docker/Vercel/rsync/static) - pair-programming: Real-time file watching with feedback - dashboard: Terminal HUD with live stats - swarm: Multi-agent parallel task execution - skill-marketplace: Search/install/publish skills - widget-api-demo: TUI widget API demonstration
112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
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<string>();
|
|
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);
|
|
}
|
|
},
|
|
});
|
|
}
|