codetraxis 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupDebugHub = setupDebugHub;
4
+ const ws_1 = require("ws");
5
+ /**
6
+ * Два типа подключений:
7
+ * "agent" — target-проект слёт события (path: /agent)
8
+ * "viewer" — codetraxis client слушает (path: /debug)
9
+ */
10
+ const viewerClients = new Set();
11
+ function setupDebugHub(httpServer) {
12
+ // ── WebSocket для агента (/agent) ─────────────────────────────────────────
13
+ const agentWss = new ws_1.WebSocketServer({ noServer: true });
14
+ agentWss.on("connection", (socket) => {
15
+ socket.on("message", (raw) => {
16
+ const data = raw.toString();
17
+ // Ретранслируем всем viewer-клиентам
18
+ for (const viewer of viewerClients) {
19
+ if (viewer.readyState === ws_1.WebSocket.OPEN) {
20
+ viewer.send(data);
21
+ }
22
+ }
23
+ });
24
+ socket.on("close", () => { });
25
+ });
26
+ // ── WebSocket для viewer (/debug) ─────────────────────────────────────────
27
+ const viewerWss = new ws_1.WebSocketServer({ noServer: true });
28
+ viewerWss.on("connection", (socket) => {
29
+ viewerClients.add(socket);
30
+ socket.on("close", () => {
31
+ viewerClients.delete(socket);
32
+ });
33
+ });
34
+ // ── HTTP upgrade routing ──────────────────────────────────────────────────
35
+ httpServer.on("upgrade", (req, socket, head) => {
36
+ const url = req.url ?? "";
37
+ if (url === "/agent") {
38
+ agentWss.handleUpgrade(req, socket, head, (ws) => {
39
+ agentWss.emit("connection", ws, req);
40
+ });
41
+ }
42
+ else if (url === "/debug") {
43
+ viewerWss.handleUpgrade(req, socket, head, (ws) => {
44
+ viewerWss.emit("connection", ws, req);
45
+ });
46
+ }
47
+ // Все остальные пути (/ws и т.д.) — не трогаем,
48
+ // watcherService зарегистрирован с { path: "/ws" } и обработает сам.
49
+ });
50
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildTree = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const gitignoreLoader_1 = require("./gitignoreLoader");
10
+ const getGitStatus_1 = require("../git/getGitStatus");
11
+ // Агрегируем git-статус папки по дочерним элементам:
12
+ // если внутри есть изменения — папка тоже подсвечивается
13
+ const FOLDER_STATUS_PRIORITY = [
14
+ "conflicted",
15
+ "deleted",
16
+ "modified",
17
+ "added",
18
+ "renamed",
19
+ "untracked",
20
+ ];
21
+ const aggregateFolderStatus = (children) => {
22
+ for (const status of FOLDER_STATUS_PRIORITY) {
23
+ if (hasStatus(children, status))
24
+ return status;
25
+ }
26
+ return undefined;
27
+ };
28
+ const hasStatus = (nodes, status) => {
29
+ return nodes.some((n) => n.gitStatus === status ||
30
+ (n.children && hasStatus(n.children, status)));
31
+ };
32
+ const buildNodes = (dir, rootDir, ig, statusMap) => {
33
+ const items = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
34
+ return items
35
+ .filter((item) => {
36
+ const fullPath = node_path_1.default.join(dir, item.name);
37
+ const relativePath = node_path_1.default
38
+ .relative(rootDir, fullPath)
39
+ .replace(/\\/g, "/");
40
+ return !ig.ignores(relativePath);
41
+ })
42
+ .map((item) => {
43
+ const fullPath = node_path_1.default.join(dir, item.name);
44
+ const relativePath = node_path_1.default
45
+ .relative(rootDir, fullPath)
46
+ .replace(/\\/g, "/");
47
+ if (item.isDirectory()) {
48
+ const children = buildNodes(fullPath, rootDir, ig, statusMap);
49
+ const gitStatus = aggregateFolderStatus(children);
50
+ return {
51
+ name: item.name,
52
+ path: relativePath,
53
+ type: "folder",
54
+ ...(gitStatus ? { gitStatus } : {}),
55
+ children,
56
+ };
57
+ }
58
+ const gitStatus = statusMap[relativePath];
59
+ return {
60
+ name: item.name,
61
+ path: relativePath,
62
+ type: "file",
63
+ ...(gitStatus ? { gitStatus } : {}),
64
+ };
65
+ });
66
+ };
67
+ const buildTree = (targetDir) => {
68
+ const workingDir = node_path_1.default.resolve(targetDir || process.cwd());
69
+ const ig = (0, gitignoreLoader_1.loadGitignore)(workingDir);
70
+ const statusMap = (0, getGitStatus_1.getGitStatusMap)(workingDir);
71
+ return {
72
+ app: {
73
+ name: node_path_1.default.basename(workingDir),
74
+ rootPath: workingDir,
75
+ },
76
+ tree: buildNodes(workingDir, workingDir, ig, statusMap),
77
+ };
78
+ };
79
+ exports.buildTree = buildTree;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadGitignore = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const ignore_1 = __importDefault(require("ignore"));
10
+ // Минимальный hardcode — скрывается всегда даже без .gitignore
11
+ const ALWAYS_IGNORE = [
12
+ "node_modules",
13
+ ".git",
14
+ "**/*.sock",
15
+ "**/*.socket",
16
+ "**/com.apple.launchd.*",
17
+ "**/.DS_Store",
18
+ ];
19
+ const loadGitignore = (rootDir) => {
20
+ const ig = (0, ignore_1.default)();
21
+ // Всегда скрываем node_modules и .git
22
+ ig.add(ALWAYS_IGNORE);
23
+ // Читаем .gitignore из корня тестового проекта
24
+ const gitignorePath = node_path_1.default.join(rootDir, ".gitignore");
25
+ if (node_fs_1.default.existsSync(gitignorePath)) {
26
+ const raw = node_fs_1.default.readFileSync(gitignorePath, "utf-8");
27
+ // Нормализуем паттерны: убираем trailing slash (node_modules/ → node_modules),
28
+ // пропускаем комментарии и пустые строки, убираем отрицания (!) — они не нужны для скрытия
29
+ const lines = raw
30
+ .split("\n")
31
+ .map((line) => line.trim())
32
+ .filter((line) => line && !line.startsWith("#"))
33
+ .map((line) => line.replace(/\/$/, "")); // убираем trailing slash
34
+ ig.add(lines);
35
+ }
36
+ return ig;
37
+ };
38
+ exports.loadGitignore = loadGitignore;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getGitStatusMap = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ // Разбор одной строки porcelain v1: "XY path"
6
+ const parseCode = (xy) => {
7
+ const x = xy[0]; // staged
8
+ const y = xy[1]; // unstaged
9
+ if (x === "?" && y === "?")
10
+ return "untracked";
11
+ if (x === "U" || y === "U" || (x === "A" && y === "A") || (x === "D" && y === "D"))
12
+ return "conflicted";
13
+ if (x === "R" || y === "R")
14
+ return "renamed";
15
+ if (x === "A")
16
+ return "added";
17
+ if (x === "D" || y === "D")
18
+ return "deleted";
19
+ if (x === "M" || y === "M")
20
+ return "modified";
21
+ return "clean";
22
+ };
23
+ const getGitStatusMap = (rootDir) => {
24
+ try {
25
+ // Проверяем что это git-репозиторий
26
+ (0, node_child_process_1.execSync)("git rev-parse --is-inside-work-tree", {
27
+ cwd: rootDir,
28
+ stdio: "ignore",
29
+ });
30
+ const output = (0, node_child_process_1.execSync)("git status --porcelain -u", {
31
+ cwd: rootDir,
32
+ encoding: "utf-8",
33
+ });
34
+ const map = {};
35
+ output
36
+ .split("\n")
37
+ .filter(Boolean)
38
+ .forEach((line) => {
39
+ const xy = line.slice(0, 2);
40
+ // Порcelain: "XY path" или "XY oldpath -> newpath"
41
+ const rawPath = line.slice(3).trim();
42
+ // Обрабатываем rename: "old -> new"
43
+ const filePath = rawPath.includes(" -> ")
44
+ ? rawPath.split(" -> ")[1]
45
+ : rawPath;
46
+ const status = parseCode(xy);
47
+ if (status !== "clean") {
48
+ map[filePath.replace(/\\/g, "/")] = status;
49
+ }
50
+ });
51
+ return map;
52
+ }
53
+ catch {
54
+ // Не git-репозиторий или git не установлен — возвращаем пустой map
55
+ return {};
56
+ }
57
+ };
58
+ exports.getGitStatusMap = getGitStatusMap;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makeGitignoreWatcher = makeGitignoreWatcher;
7
+ const gitignoreLoader_1 = require("../file/gitignoreLoader");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ /**
10
+ * Возвращает функцию-фильтр для chokidar, учитывающую .gitignore и ALWAYS_IGNORE.
11
+ * @param rootDir Корень проекта
12
+ */
13
+ function makeGitignoreWatcher(rootDir) {
14
+ const ig = (0, gitignoreLoader_1.loadGitignore)(rootDir);
15
+ return (filePath) => {
16
+ // относительный путь от корня проекта
17
+ const relative = node_path_1.default.relative(rootDir, filePath).replace(/\\/g, "/");
18
+ if (!relative)
19
+ return false; // Корень проекта не игнорируем!
20
+ return ig.ignores(relative);
21
+ };
22
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startWatcher = void 0;
7
+ const chokidar_1 = __importDefault(require("chokidar"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const ws_1 = require("ws");
10
+ const gitignoreWatcher_1 = require("./gitignoreWatcher");
11
+ // ─── Debounce + batch ────────────────────────────────────────────────────────
12
+ const DEBOUNCE_MS = 200;
13
+ let debounceTimer = null;
14
+ let pendingTreeChanged = false;
15
+ const pendingFileChanges = new Set();
16
+ // ─── WebSocket broadcast ─────────────────────────────────────────────────────
17
+ let wss = null;
18
+ const broadcast = (event) => {
19
+ if (!wss)
20
+ return;
21
+ const message = JSON.stringify(event);
22
+ wss.clients.forEach((client) => {
23
+ if (client.readyState === ws_1.WebSocket.OPEN) {
24
+ client.send(message);
25
+ }
26
+ });
27
+ };
28
+ // ─── Flush batch после debounce ──────────────────────────────────────────────
29
+ const scheduleBatch = () => {
30
+ if (debounceTimer)
31
+ clearTimeout(debounceTimer);
32
+ debounceTimer = setTimeout(() => {
33
+ if (pendingTreeChanged) {
34
+ broadcast({ type: "tree_changed" });
35
+ pendingTreeChanged = false;
36
+ }
37
+ pendingFileChanges.forEach((filePath) => {
38
+ broadcast({ type: "file_changed", path: filePath });
39
+ });
40
+ pendingFileChanges.clear();
41
+ debounceTimer = null;
42
+ }, DEBOUNCE_MS);
43
+ };
44
+ // ─── Запуск watcher ──────────────────────────────────────────────────────────
45
+ const startWatcher = (targetDir, httpServer) => {
46
+ // WebSocket сервер без привязки к серверу — routing через upgrade event
47
+ wss = new ws_1.WebSocketServer({ noServer: true });
48
+ // Регистрируем upgrade-обработчик для пути /ws
49
+ httpServer.on("upgrade", (req, socket, head) => {
50
+ if ((req.url ?? "") === "/ws") {
51
+ wss.handleUpgrade(req, socket, head, (ws) => {
52
+ wss.emit("connection", ws, req);
53
+ });
54
+ }
55
+ });
56
+ wss.on("connection", (socket) => {
57
+ console.log("🔌 WebSocket client connected");
58
+ // Сразу сообщаем клиенту что подключились
59
+ socket.send(JSON.stringify({ type: "connected" }));
60
+ socket.on("close", () => {
61
+ console.log("🔌 WebSocket client disconnected");
62
+ });
63
+ });
64
+ const watcher = chokidar_1.default.watch(targetDir, {
65
+ ignored: (filePath) => {
66
+ // Всегда игнорируем Unix-сокеты и системные файлы — chokidar не умеет их watchить
67
+ if (filePath.endsWith(".sock") ||
68
+ filePath.endsWith(".socket") ||
69
+ /com\.apple\.launchd\./i.test(filePath) ||
70
+ filePath.includes("Visual Studio Code-")) {
71
+ return true;
72
+ }
73
+ return (0, gitignoreWatcher_1.makeGitignoreWatcher)(targetDir)(filePath);
74
+ },
75
+ ignoreInitial: true,
76
+ persistent: true,
77
+ ignorePermissionErrors: true,
78
+ usePolling: false,
79
+ depth: 10,
80
+ awaitWriteFinish: {
81
+ stabilityThreshold: 150,
82
+ pollInterval: 50,
83
+ },
84
+ });
85
+ // Структурные изменения → tree_changed
86
+ const onStructure = (filePath) => {
87
+ const relative = node_path_1.default.relative(targetDir, filePath).replace(/\\/g, "/");
88
+ console.log(`📁 Structure change: ${relative}`);
89
+ pendingTreeChanged = true;
90
+ scheduleBatch();
91
+ };
92
+ // Изменение содержимого → file_changed + tree_changed (git-статусы обновились)
93
+ const onChange = (filePath) => {
94
+ const relative = node_path_1.default.relative(targetDir, filePath).replace(/\\/g, "/");
95
+ console.log(`📝 File changed: ${relative}`);
96
+ pendingFileChanges.add(relative);
97
+ // git-статус файла мог измениться → нужно пересобрать дерево
98
+ pendingTreeChanged = true;
99
+ scheduleBatch();
100
+ };
101
+ watcher
102
+ .on("add", onStructure)
103
+ .on("addDir", onStructure)
104
+ .on("unlink", onStructure)
105
+ .on("unlinkDir", onStructure)
106
+ .on("change", onChange)
107
+ .on("error", (err) => {
108
+ // UNKNOWN — это системные сокеты / .sock файлы — chokidar не умеет их watchить, тихо игнорируем
109
+ const e = err;
110
+ if (e.code === "UNKNOWN" || e.code === "ENOENT")
111
+ return;
112
+ console.error("❌ Watcher error:", err);
113
+ })
114
+ .on("ready", () => console.log(`👀 Watching: ${targetDir}`));
115
+ return watcher;
116
+ };
117
+ exports.startWatcher = startWatcher;