Files
lemana-vpn/.understand-anything/tmp/build-batches.cjs

207 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require("fs");
const path = require("path");
const root = process.cwd();
const inter = path.join(root, ".understand-anything", "intermediate");
const tmp = path.join(root, ".understand-anything", "tmp");
const scan = JSON.parse(fs.readFileSync(path.join(inter, "scan-result.json"), "utf8"));
const batches = JSON.parse(fs.readFileSync(path.join(inter, "batches.json"), "utf8")).batches;
const fileInfo = new Map(scan.files.map((f) => [f.path, f]));
const fileNodeId = (file) => {
if (file.fileCategory === "docs") return `document:${file.path}`;
if (file.fileCategory === "config") return `config:${file.path}`;
return `file:${file.path}`;
};
const edge = (source, target, type, weight) => ({ source, target, type, direction: "forward", weight });
const complexity = (lines) => (lines > 200 ? "complex" : lines > 50 ? "moderate" : "simple");
const nameOf = (p) => p.split("/").pop();
const fileMeta = {
".agents/skills/lemana-vpn-operations/SKILL.md": ["Lemana VPN Operations skill", "Инструкции для операционных задач: установка, обновление, статусы, безопасные ответы пользователю и live-connection правила.", ["skill", "operations", "documentation"]],
".agents/skills/lemana-vpn-sso-autofill/SKILL.md": ["Lemana VPN SSO skill", "Инструкции по Bitwarden, Keychain, TOTP, Keycloak SSO, autofill и runtime-патчам openconnect-lite.", ["skill", "sso", "credentials"]],
".agents/skills/lemana-vpn-testing/SKILL.md": ["Lemana VPN Testing skill", "Инструкции по non-live проверкам, status checks, live VPN safety и финальному отчету для изменений в репозитории.", ["skill", "testing", "verification"]],
"AGENTS.md": ["AGENTS.md", "Репозиторные правила для агентов: модель системы, live VPN safety, тестовая лестница и контракт финального ответа.", ["agent-rules", "documentation", "safety"]],
"README.md": ["README.md", "Основная пользовательская документация Lemana VPN: установка, credential sources, модули, использование, диагностика и удаление.", ["documentation", "overview", "user-guide"]],
"docs/goals/manual-first-auth-flow/GOAL.md": ["Manual-first auth goal", "Краткое описание цели manual-first authentication flow и ожидаемого пользовательского результата.", ["goal", "auth-flow", "planning"]],
"docs/goals/manual-first-auth-flow/PLAN.md": ["Manual-first auth plan", "Подробный план изменения installer UX, CLI auth profiles, Swift menu-bar surface и smoke coverage для manual-first сценария.", ["plan", "auth-flow", "implementation"]],
"docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": ["Keychain TOTP provider plan", "Исторический implementation plan для разделения credential sources, нормализации TOTP и расширения smoke-проверок.", ["plan", "keychain", "totp"]],
"app/Package.swift": ["Package.swift", "Swift Package manifest для сборки executable target LemanaVPN под macOS 13+.", ["swift", "build-system", "menu-bar-app"]],
"app/Sources/LemanaVPN/LemanaVPNApp.swift": ["LemanaVPNApp.swift", "SwiftUI/AppKit menu bar surface: отображает состояние VPN, кнопки подключения, логи и действия пользователя.", ["swiftui", "menu-bar", "ui"]],
"app/Sources/LemanaVPN/VPNManager.swift": ["VPNManager.swift", "Основной state manager Swift-приложения: запускает CLI в JSON mode, декодирует события, отслеживает health и управляет reconnect/disconnect.", ["swift", "state-manager", "cli-bridge"]],
"bin/vpn-lemanapro.sh": ["vpn-lemanapro.sh", "Главный runtime CLI: status JSON, sync credentials, Keychain/TOTP, runtime patches, SSO запуск, reconnect loop, DNS cleanup и фильтрация openconnect-lite output.", ["cli", "runtime", "sso"]],
"install.sh": ["install.sh", "Installer wizard для Homebrew/pipx/openconnect-lite, CLI, config, DNS wrapper, sudoers, Touch ID helper, Swift app, launch agent и shell aliases.", ["installer", "wizard", "macos"]],
"uninstall.sh": ["uninstall.sh", "Uninstaller для восстановления runtime patch backups, удаления CLI/app/config/sudoers/aliases и опциональной очистки Keychain/Touch ID state.", ["uninstaller", "cleanup", "macos"]],
"libexec/lemana-vpn-dns-cleanup": ["lemana-vpn-dns-cleanup", "Узкий root-owned wrapper, который сбрасывает только корпоративные DNS и обновляет macOS resolver cache.", ["dns", "sudo-wrapper", "cleanup"]],
"templates/openconnect-lite-config.toml": ["openconnect-lite-config.toml", "Шаблон профиля openconnect-lite: gateway, credentials mode и Keycloak auto-fill selectors для username, password и TOTP.", ["configuration", "openconnect-lite", "autofill"]],
"tests/smoke.sh": ["smoke.sh", "Non-live smoke suite: shell syntax, installer/uninstaller checks, Swift wiring, SSO patch snippets, config selectors и regression assertions.", ["test", "smoke", "verification"]],
};
const functionSummaries = {
choose_credential_source_interactive: "Показывает интерактивный выбор credential source и нормализует ответ пользователя.",
choose_modules: "Определяет набор устанавливаемых модулей с учетом интерактивного режима и флагов installer.",
install_openconnect_lite: "Устанавливает или проверяет openconnect-lite runtime, необходимый для SAML SSO.",
install_cli: "Копирует основной CLI и uninstall helper в пользовательский bin.",
install_config: "Устанавливает шаблон openconnect-lite config и сохраняет локальную конфигурацию модулей.",
install_dns_cleanup: "Устанавливает root-owned DNS cleanup wrapper для безопасного сброса корпоративных DNS.",
install_sudoers: "Пишет ограниченные sudoers правила для openconnect и DNS cleanup wrapper.",
install_touchid_helper: "Собирает и устанавливает Touch ID helper для Bitwarden master password.",
install_menu_bar_app: "Собирает release-версию Swift menu bar app и устанавливает LemanaVPN.app.",
install_launch_agent: "Создает LaunchAgent для автозапуска menu bar app при логине.",
restart_running_menu_bar_app: "Перезапускает уже открытое menu bar приложение после обновления bundle.",
install_shell_aliases: "Добавляет idempotent zsh aliases для CLI режимов подключения и диагностики.",
maybe_login_bitwarden: "Проверяет Bitwarden session и при необходимости запускает login/unlock flow.",
restore_openconnect_lite_patch: "Возвращает backup исходника openconnect-lite, если runtime patch был установлен.",
remove_zshrc_block: "Удаляет idempotent блок Lemana VPN aliases из zshrc.",
remove_keychain_entries: "Опционально удаляет password, TOTP seed и username из macOS Keychain.",
_module_status_json: "Собирает машинно-читаемый JSON со статусом модулей, credentials, app, DNS и patches.",
_module_status_human: "Печатает человекочитаемую строку статуса модулей перед подключением.",
_check_status: "Проверяет PID/status file, running process, DNS target и возвращает disconnected/connected JSON.",
_patch_oc: "Применяет runtime patches к openconnect-lite для Keycloak autofill/manual/debug сценариев.",
_normalize_totp_secret: "Нормализует TOTP secret из raw secret или otpauth URI перед сохранением.",
_configure_keychain: "Интерактивно сохраняет LDAP password и TOTP seed в macOS Keychain.",
_ensure_keychain_credentials: "Проверяет наличие обязательных Keychain credentials перед auto/manual подключением.",
_bw_unlock: "Открывает Bitwarden session с учетом Touch ID helper и fallback prompts.",
_sync_bitwarden: "Синхронизирует LDAP password и TOTP seed из Bitwarden в macOS Keychain.",
_sync_credentials: "Выбирает credential source и запускает соответствующий sync/preflight путь.",
_dns_cleanup: "Вызывает narrow sudo wrapper для очистки VPN DNS при завершении сессии.",
_filter_output: "Фильтрует поток openconnect-lite, извлекает IP/expiry, пишет status JSON и события для app.",
refreshStatus: "Запускает CLI status JSON и обновляет module summary в menu bar app.",
connect: "Запускает CLI в JSON mode с выбранным режимом подключения и подписывается на stdout/stderr.",
disconnect: "Отправляет SIGINT текущему CLI process и останавливает reconnect state.",
parseEvent: "Декодирует JSON events из CLI и переводит их в Swift state updates.",
handleTermination: "Обрабатывает завершение CLI process и решает, нужен ли автоматический reconnect.",
startHealthCheck: "Периодически проверяет tunnel health и запускает reconnect при повторных failures.",
};
const swiftTypeSummaries = {
LemanaVPNApp: "Точка входа SwiftUI menu bar app, связывает VPNManager с MenuBarExtra.",
StatusBarLabel: "Компактный status bar label с иконкой и оставшимся временем сессии.",
VPNMenuView: "Основной menu UI с командами auto/manual/manual-full connect, disconnect, logs и quit.",
VPNEvent: "Decodable-модель JSON events, которые runtime CLI отправляет в Swift app.",
VPNStatusResponse: "Decodable envelope для ответа `vpn --status --json`.",
ModuleStatus: "Модель статуса модулей и вычисление summary/systemImage для menu bar app.",
VPNState: "Finite state model для disconnected/connecting/connected/reconnecting/error состояний.",
VPNLaunchMode: "Маппинг app launch modes на CLI flags `--auto`, `--manual`, `--manual-full`.",
VPNManager: "ObservableObject, который мостит Swift menu bar app с shell CLI, JSON events и health checks.",
};
function makeFileNode(file) {
const meta = fileMeta[file.path] || [nameOf(file.path), `Файл проекта ${file.path}.`, ["project-file", file.language, file.fileCategory]];
const type = file.fileCategory === "docs" ? "document" : file.fileCategory === "config" ? "config" : "file";
return {
id: fileNodeId(file),
type,
name: meta[0],
filePath: file.path,
summary: meta[1],
tags: meta[2],
complexity: complexity(file.sizeLines),
};
}
function shouldIncludeFunction(filePath, fn) {
if (fn.name === "autoFill" || fn.name === "vpn") return false;
const span = fn.endLine - fn.startLine + 1;
if (functionSummaries[fn.name]) return true;
if (fn.name === "usage" || fn.name === "main") return true;
if (/^(install_|remove_|restore_|choose_|_sync|_ensure|_configure|_patch|_check|_filter|_module|_dns)/.test(fn.name)) return true;
return span >= 18 && !/^log|color_line|die|run|need_cmd|has_tty|bool_word|_emit|_json_get|_can_prompt|_prompt_read/.test(fn.name);
}
function makeFunctionNode(filePath, fn) {
const id = `function:${filePath}:${fn.name}`;
return {
id,
type: "function",
name: fn.name,
filePath,
lineRange: [fn.startLine, fn.endLine],
summary: functionSummaries[fn.name] || `Значимая функция ${fn.name} в ${filePath}.`,
tags: ["function", filePath.includes("test") ? "verification" : "runtime", fn.name.replace(/^_+/, "").replaceAll("_", "-")],
complexity: complexity(fn.endLine - fn.startLine + 1),
};
}
function swiftDecls(filePath) {
if (!filePath.endsWith(".swift")) return [];
const text = fs.readFileSync(path.join(root, filePath), "utf8");
const lines = text.split(/\n/);
const decls = [];
lines.forEach((line, index) => {
const m = line.match(/^\s*(?:@main\s+)?(?:struct|class|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/);
if (m && swiftTypeSummaries[m[1]]) {
decls.push({ name: m[1], startLine: index + 1 });
}
});
for (let i = 0; i < decls.length; i += 1) {
decls[i].endLine = (decls[i + 1]?.startLine || lines.length + 1) - 1;
}
return decls;
}
function swiftMethods(filePath) {
if (!filePath.endsWith("VPNManager.swift")) return [];
const text = fs.readFileSync(path.join(root, filePath), "utf8");
const lines = text.split(/\n/);
const wanted = new Set(["refreshStatus", "connect", "disconnect", "parseEvent", "handleTermination", "startHealthCheck"]);
const methods = [];
lines.forEach((line, index) => {
const m = line.match(/^\s*(?:private\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
if (m && wanted.has(m[1])) methods.push({ name: m[1], startLine: index + 1 });
});
for (let i = 0; i < methods.length; i += 1) {
methods[i].endLine = (methods[i + 1]?.startLine || Math.min(lines.length + 1, methods[i].startLine + 90)) - 1;
}
return methods;
}
function graphForBatch(batch) {
const extract = JSON.parse(fs.readFileSync(path.join(tmp, `ua-file-extract-results-${batch.batchIndex}.json`), "utf8"));
const nodes = [];
const edges = [];
const resultByPath = new Map(extract.results.map((r) => [r.path, r]));
for (const file of batch.files) {
const fileNode = makeFileNode(file);
nodes.push(fileNode);
const result = resultByPath.get(file.path);
for (const fn of result?.functions || []) {
if (!shouldIncludeFunction(file.path, fn)) continue;
const node = makeFunctionNode(file.path, fn);
nodes.push(node);
edges.push(edge(fileNode.id, node.id, "contains", 1.0));
}
for (const decl of swiftDecls(file.path)) {
const node = {
id: `class:${file.path}:${decl.name}`,
type: "class",
name: decl.name,
filePath: file.path,
lineRange: [decl.startLine, decl.endLine],
summary: swiftTypeSummaries[decl.name],
tags: ["swift", "type", decl.name === "VPNManager" ? "state-manager" : "menu-bar-app"],
complexity: complexity(decl.endLine - decl.startLine + 1),
};
nodes.push(node);
edges.push(edge(fileNode.id, node.id, "contains", 1.0));
}
for (const fn of swiftMethods(file.path)) {
const node = makeFunctionNode(file.path, fn);
nodes.push(node);
edges.push(edge(fileNode.id, node.id, "contains", 1.0));
}
}
return { nodes, edges };
}
for (const batch of batches) {
const graph = graphForBatch(batch);
fs.writeFileSync(path.join(inter, `batch-${batch.batchIndex}.json`), JSON.stringify(graph, null, 2));
}